commit ca1b675b9ab2b1164b590d82556d67b3c8952ecc Author: Brandon <46827438+disclearing@users.noreply.github.com> Date: Thu May 25 01:02:33 2023 +0100 sexy diff --git a/Network/eHub/build.gradle b/Network/eHub/build.gradle new file mode 100644 index 0000000..527b8c2 --- /dev/null +++ b/Network/eHub/build.gradle @@ -0,0 +1,40 @@ +plugins { + id 'java' +} + +group 'com.elevatemc' +version '1.0' + +sourceSets { + main.java.srcDirs = ['src/main/java'] + main.resources.srcDirs = ['src/main/resources'] +} + +compileJava.options.encoding = 'UTF-8' + +repositories { + mavenCentral() + maven { url 'https://jitpack.io' } +} + + +dependencies { + compileOnly files('../lib/espigot.jar') + compileOnly files('../lib/primespigot.jar') + compileOnly files('../lib/universespigot.jar') + compileOnly project(':eLib') + + compileOnly 'com.github.koca2000:NoteBlockAPI:1.6.1' + + compileOnly 'org.projectlombok:lombok:1.18.22' + annotationProcessor 'org.projectlombok:lombok:1.18.22' +} + +processResources { + def props = [version: 'git rev-parse --verify --short HEAD'.execute().text.trim()] + inputs.properties props + filteringCharset 'UTF-8' + filesMatching('plugin.yml') { + expand props + } +} diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/command/MusicCommands.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/command/MusicCommands.class new file mode 100644 index 0000000..7fb695a Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/command/MusicCommands.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/command/SpawnCommands.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/command/SpawnCommands.class new file mode 100644 index 0000000..29bc050 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/command/SpawnCommands.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/database/RedisManager.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/database/RedisManager.class new file mode 100644 index 0000000..c10aa78 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/database/RedisManager.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/eHub.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/eHub.class new file mode 100644 index 0000000..ccb6113 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/eHub.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/listener/FunListener.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/listener/FunListener.class new file mode 100644 index 0000000..a7f1cb1 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/listener/FunListener.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/listener/HubListener.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/listener/HubListener.class new file mode 100644 index 0000000..3da7229 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/listener/HubListener.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/listener/MusicListener.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/listener/MusicListener.class new file mode 100644 index 0000000..ab865df Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/listener/MusicListener.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/listener/PlayerListener.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/listener/PlayerListener.class new file mode 100644 index 0000000..d932ecd Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/listener/PlayerListener.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/listener/PreventionListener.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/listener/PreventionListener.class new file mode 100644 index 0000000..911a06d Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/listener/PreventionListener.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/CosmeticsMenu$1.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/CosmeticsMenu$1.class new file mode 100644 index 0000000..a0f6216 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/CosmeticsMenu$1.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/CosmeticsMenu$2.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/CosmeticsMenu$2.class new file mode 100644 index 0000000..9774e00 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/CosmeticsMenu$2.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/CosmeticsMenu.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/CosmeticsMenu.class new file mode 100644 index 0000000..97b4ea6 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/CosmeticsMenu.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu$1.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu$1.class new file mode 100644 index 0000000..3219a26 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu$1.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu$2.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu$2.class new file mode 100644 index 0000000..4be38f2 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu$2.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu$3.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu$3.class new file mode 100644 index 0000000..35d753d Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu$3.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu$4.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu$4.class new file mode 100644 index 0000000..d9a6157 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu$4.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu$5.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu$5.class new file mode 100644 index 0000000..3baa976 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu$5.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu$6.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu$6.class new file mode 100644 index 0000000..f1e0ba7 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu$6.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu$7.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu$7.class new file mode 100644 index 0000000..218b6c0 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu$7.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu$8.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu$8.class new file mode 100644 index 0000000..8ea8e60 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu$8.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu$9.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu$9.class new file mode 100644 index 0000000..9f7c69a Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu$9.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu.class new file mode 100644 index 0000000..51ddae2 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/EditorMenu.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/ParticlesMenu$1.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/ParticlesMenu$1.class new file mode 100644 index 0000000..319f735 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/ParticlesMenu$1.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/ParticlesMenu$2.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/ParticlesMenu$2.class new file mode 100644 index 0000000..e9ed458 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/ParticlesMenu$2.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/ParticlesMenu$3.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/ParticlesMenu$3.class new file mode 100644 index 0000000..da253fb Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/ParticlesMenu$3.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/ParticlesMenu.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/ParticlesMenu.class new file mode 100644 index 0000000..2c939fc Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/ParticlesMenu.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/RanksMenu$1.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/RanksMenu$1.class new file mode 100644 index 0000000..5b06215 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/RanksMenu$1.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/RanksMenu$2.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/RanksMenu$2.class new file mode 100644 index 0000000..ca58305 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/RanksMenu$2.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/RanksMenu$3.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/RanksMenu$3.class new file mode 100644 index 0000000..2aa3e65 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/RanksMenu$3.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/RanksMenu.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/RanksMenu.class new file mode 100644 index 0000000..1ff2d19 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/cosmetics/RanksMenu.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/selector/ServerButton.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/selector/ServerButton.class new file mode 100644 index 0000000..73fcdf2 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/selector/ServerButton.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/selector/ServerSelector.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/selector/ServerSelector.class new file mode 100644 index 0000000..df7ae7e Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/menu/selector/ServerSelector.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/profile/Profile.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/profile/Profile.class new file mode 100644 index 0000000..abfb52b Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/profile/Profile.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/profile/manager/ProfileManager.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/profile/manager/ProfileManager.class new file mode 100644 index 0000000..1b27935 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/profile/manager/ProfileManager.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/queue/QueueHandler.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/queue/QueueHandler.class new file mode 100644 index 0000000..d896eab Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/queue/QueueHandler.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/queue/QueuePosition.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/queue/QueuePosition.class new file mode 100644 index 0000000..145b9fd Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/queue/QueuePosition.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/queue/listener/MessageListener.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/queue/listener/MessageListener.class new file mode 100644 index 0000000..84dddd4 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/queue/listener/MessageListener.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/scoreboard/eHubScoreGetter.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/scoreboard/eHubScoreGetter.class new file mode 100644 index 0000000..a1eb876 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/scoreboard/eHubScoreGetter.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/scoreboard/eHubScoreboardConfiguration$1.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/scoreboard/eHubScoreboardConfiguration$1.class new file mode 100644 index 0000000..7a10f2f Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/scoreboard/eHubScoreboardConfiguration$1.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/scoreboard/eHubScoreboardConfiguration.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/scoreboard/eHubScoreboardConfiguration.class new file mode 100644 index 0000000..e627ef6 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/scoreboard/eHubScoreboardConfiguration.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/tab/eHubTabProvider.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/tab/eHubTabProvider.class new file mode 100644 index 0000000..30c0357 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/tab/eHubTabProvider.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/type/armor/ArmorType.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/type/armor/ArmorType.class new file mode 100644 index 0000000..bf5d429 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/type/armor/ArmorType.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/type/armor/task/ArmorTask.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/type/armor/task/ArmorTask.class new file mode 100644 index 0000000..b2646a6 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/type/armor/task/ArmorTask.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/type/particle/ParticleType.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/type/particle/ParticleType.class new file mode 100644 index 0000000..1143fab Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/type/particle/ParticleType.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/type/particle/impl/ParticleCallable.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/type/particle/impl/ParticleCallable.class new file mode 100644 index 0000000..7ed5421 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/type/particle/impl/ParticleCallable.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/type/particle/impl/ParticleMeta.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/type/particle/impl/ParticleMeta.class new file mode 100644 index 0000000..1db602f Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/type/particle/impl/ParticleMeta.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/type/particle/task/ParticleTask.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/type/particle/task/ParticleTask.class new file mode 100644 index 0000000..c0e6e4d Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/type/particle/task/ParticleTask.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/utils/CC.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/utils/CC.class new file mode 100644 index 0000000..258a356 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/utils/CC.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/utils/HubItems.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/utils/HubItems.class new file mode 100644 index 0000000..144d392 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/utils/HubItems.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/utils/ItemBuilder.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/utils/ItemBuilder.class new file mode 100644 index 0000000..e0c2336 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/utils/ItemBuilder.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/utils/ParticleUtil.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/utils/ParticleUtil.class new file mode 100644 index 0000000..0915375 Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/utils/ParticleUtil.class differ diff --git a/Network/eHub/build/classes/java/main/com/elevatemc/ehub/utils/PlayerCountTask.class b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/utils/PlayerCountTask.class new file mode 100644 index 0000000..af3f10d Binary files /dev/null and b/Network/eHub/build/classes/java/main/com/elevatemc/ehub/utils/PlayerCountTask.class differ diff --git a/Network/eHub/build/libs/eHub-1.0.jar b/Network/eHub/build/libs/eHub-1.0.jar new file mode 100644 index 0000000..bd55838 Binary files /dev/null and b/Network/eHub/build/libs/eHub-1.0.jar differ diff --git a/Network/eHub/build/resources/main/plugin.yml b/Network/eHub/build/resources/main/plugin.yml new file mode 100644 index 0000000..3aeb124 --- /dev/null +++ b/Network/eHub/build/resources/main/plugin.yml @@ -0,0 +1,7 @@ +main: com.elevatemc.ehub.eHub +name: eHub +version: '7227fd6' +description: The hub plugin for ElevateMC +author: ElevateMC Development Team +website: https://elevatemc.com +depend: [eLib, Prime] diff --git a/Network/eHub/build/tmp/compileJava/previous-compilation-data.bin b/Network/eHub/build/tmp/compileJava/previous-compilation-data.bin new file mode 100644 index 0000000..b6478c5 Binary files /dev/null and b/Network/eHub/build/tmp/compileJava/previous-compilation-data.bin differ diff --git a/Network/eHub/build/tmp/jar/MANIFEST.MF b/Network/eHub/build/tmp/jar/MANIFEST.MF new file mode 100644 index 0000000..59499bc --- /dev/null +++ b/Network/eHub/build/tmp/jar/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 + diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/command/MusicCommands.java b/Network/eHub/src/main/java/com/elevatemc/ehub/command/MusicCommands.java new file mode 100644 index 0000000..896859c --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/command/MusicCommands.java @@ -0,0 +1,34 @@ +package com.elevatemc.ehub.command; + +import com.elevatemc.ehub.eHub; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import com.xxmicloxx.NoteBlockAPI.model.Song; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class MusicCommands { + @Command(names = {"music skip"}, permission = "music.skip") + public static void skip(Player sender) { + eHub.getInstance().getRadioSongPlayer().playNextSong(); + sender.sendMessage(ChatColor.GREEN + "Skipped this song!"); + } + + @Command(names = {"music list"}, permission = "music.list") + public static void list(Player sender) { + eHub.getInstance().getRadioSongPlayer().getPlaylist().getSongList().forEach(song -> { + sender.sendMessage(ChatColor.YELLOW + song.getTitle()); + }); + + } + + @Command(names = {"music play"}, permission = "music.play") + public static void play(Player sender, @Parameter(name="song-name", wildcard = true) String name) { + Song song = eHub.getInstance().getRadioSongPlayer().getPlaylist().getSongList().stream().filter(s -> s.getTitle().equalsIgnoreCase(name)).findFirst().orElse(null); + if (song == null) { + sender.sendMessage(ChatColor.RED + "Could not find that song!"); + return; + } + eHub.getInstance().getRadioSongPlayer().playSong(eHub.getInstance().getRadioSongPlayer().getPlaylist().getIndex(song)); + } +} \ No newline at end of file diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/command/SpawnCommands.java b/Network/eHub/src/main/java/com/elevatemc/ehub/command/SpawnCommands.java new file mode 100644 index 0000000..3567976 --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/command/SpawnCommands.java @@ -0,0 +1,29 @@ +package com.elevatemc.ehub.command; + +import com.elevatemc.elib.command.Command; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +public final class SpawnCommands { + @Command(names = {"setspawn"}, permission = "op") + public static void setSpawn(Player sender) { + Location loc = sender.getLocation(); + + sender.getWorld().setSpawnLocation( + loc.getBlockX() + 0.5, + loc.getBlockY(), + loc.getBlockZ() + 0.5, + loc.getYaw(), + loc.getPitch() + ); + + sender.sendMessage(ChatColor.YELLOW + "Spawn point updated!"); + } + + @Command(names = {"spawn"}, permission = "op") + public static void teleportToSpawn(Player sender) { + sender.teleport(sender.getWorld().getSpawnLocation()); + sender.sendMessage(ChatColor.GREEN + "You have been teleported to spawn!"); + } +} \ No newline at end of file diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/database/RedisManager.java b/Network/eHub/src/main/java/com/elevatemc/ehub/database/RedisManager.java new file mode 100644 index 0000000..abbbb36 --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/database/RedisManager.java @@ -0,0 +1,115 @@ +package com.elevatemc.ehub.database; + +import com.elevatemc.ehub.eHub; +import com.elevatemc.ehub.profile.Profile; +import com.elevatemc.ehub.type.armor.ArmorType; +import com.elevatemc.ehub.type.particle.ParticleType; +import com.elevatemc.elib.eLib; +import com.google.gson.*; +import lombok.Getter; + +@Getter +public class RedisManager { + + private final eHub instance = eHub.getInstance(); + + + private final JsonParser parser; + + public RedisManager() { + parser = new JsonParser(); + + } + + //How we load all the cosmetics for the player via redis + public void load (Profile profile) { + eLib.getInstance().runRedisCommand(jedis -> { + + if (jedis.hexists("user", profile.getUuid().toString())) { + String stringObject = jedis.hget("users", profile.getUuid().toString()); + JsonObject object = parser.parse(stringObject).getAsJsonObject(); + + String armorType = object.get("armorType").getAsString().toUpperCase(); + profile.setArmorType(armorType.isEmpty() ? null : ArmorType.valueOf(armorType)); + + if (profile.getArmorType() != null) { + profile.setEnchanted(object.get("enchanted").getAsBoolean()); + profile.setAstronaut(object.get("astronaut").getAsBoolean()); + + JsonArray array = object.get("armors").getAsJsonArray(); + + int i = 0; + for (JsonElement element : array) { + profile.getArmors() [i] = element.getAsBoolean(); + i++; + } + } + + String name = object.get("particleType").getAsString(); + + profile.setParticleType(name == null ? null : name.isEmpty() ? null : ParticleType.valueOf(name.replace(" Particle", "").toUpperCase())); + + } else { + JsonObject object = new JsonObject(); + JsonArray array = new JsonArray(); + + object.addProperty("armorType", ""); + object.addProperty("particleType", ""); + + for (boolean value : profile.getArmors()) { + array.add(new JsonPrimitive(value)); + } + + object.add("armors", array); + object.addProperty("enchanted", false); + object.addProperty("astronaut", false); + + jedis.hset("users", profile.getUuid().toString(), object.toString()); + } + return null; + }); + + } + + //How we save armor via redis + public void saveArmor (Profile profile, ArmorType type) { + eLib.getInstance().runRedisCommand(jedis -> { + + String stringObject = jedis.hget("users", profile.getUuid().toString()); + JsonObject object = parser.parse(stringObject).getAsJsonObject(); + + if (type != null) { + JsonArray array = new JsonArray(); + + object.addProperty("enchanted", profile.isEnchanted()); + + for (boolean value : profile.getArmors()) { + array.add(new JsonPrimitive(value)); + } + + object.add("armors", array); + } + + object.addProperty("astronaut", profile.isAstronaut()); + object.addProperty("armorType", type == null ? "" : type.getName()); + + jedis.hset("users", profile.getUuid().toString(), object.toString()); + return null; + }); + } + + + //How we save Particles via redis + public void saveParticle (Profile profile, ParticleType type) { + eLib.getInstance().runRedisCommand(jedis -> { + + String stringObject = jedis.hget("users", profile.getUuid().toString()); + JsonObject object = parser.parse(stringObject).getAsJsonObject(); + + object.addProperty("particleType", type == null ? "" : type.getName()); + + jedis.hset("users", profile.getUuid().toString(), object.toString()); + return null; + }); + } +} diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/eHub.java b/Network/eHub/src/main/java/com/elevatemc/ehub/eHub.java new file mode 100644 index 0000000..5ad08d3 --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/eHub.java @@ -0,0 +1,149 @@ +package com.elevatemc.ehub; + +import cc.fyre.universe.Universe; +import cc.fyre.universe.server.Server; +import com.elevatemc.ehub.database.RedisManager; +import com.elevatemc.ehub.listener.*; +import com.elevatemc.ehub.profile.manager.ProfileManager; +import com.elevatemc.ehub.queue.QueueHandler; +import com.elevatemc.ehub.scoreboard.eHubScoreboardConfiguration; +import com.elevatemc.ehub.tab.eHubTabProvider; +import com.elevatemc.ehub.type.armor.task.ArmorTask; +import com.elevatemc.ehub.type.particle.task.ParticleTask; +import com.elevatemc.ehub.utils.PlayerCountTask; +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.tab.data.TabList; +import com.google.common.collect.ImmutableList; +import com.mysql.jdbc.StringUtils; +import com.xxmicloxx.NoteBlockAPI.model.Playlist; +import com.xxmicloxx.NoteBlockAPI.model.RepeatMode; +import com.xxmicloxx.NoteBlockAPI.model.Song; +import com.xxmicloxx.NoteBlockAPI.songplayer.RadioSongPlayer; +import com.xxmicloxx.NoteBlockAPI.utils.NBSDecoder; +import dev.apposed.prime.spigot.Prime; +import lombok.Getter; +import org.bukkit.Bukkit; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public final class eHub extends JavaPlugin { + @Getter private static eHub instance; + @Getter private Prime prime; + @Getter private QueueHandler queueHandler; + + @Getter + private RedisManager redisManager; + + @Getter + private ParticleTask particleTask; + + @Getter + private ProfileManager profileManager; + @Getter + private ArmorTask armorTask; + + private final List serversToPing = ImmutableList.of("ALL", "Practice"); + + @Getter RadioSongPlayer radioSongPlayer = null; + + @Override + public void onEnable() { + instance = this; + + prime = Prime.getPlugin(Prime.class); + queueHandler = new QueueHandler(); + + getServer().getPluginManager().registerEvents(new HubListener(), this); + getServer().getPluginManager().registerEvents(new PreventionListener(), this); + getServer().getPluginManager().registerEvents(new FunListener(), this); + + setupMusicPlayer(); + this.loadManagers(); + this.loadHandlers(); + this.loadTasks(); + + getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); + + getServer().getScheduler().runTaskTimer(this, new PlayerCountTask(serversToPing), 0, 20L); + + eLib.getInstance().getCommandHandler().registerAll(this); + eLib.getInstance().getScoreboardHandler().setConfiguration(eHubScoreboardConfiguration.create()); + eLib.getInstance().getTabHandler().setTabList(new TabList(this, new eHubTabProvider())); + } + + public String getGlobalPlayerCount() { + int players = 0; + for (Server server : Universe.getInstance().getUniverseHandler().getServers()) { + players += server.getOnlinePlayers().get(); + } + return String.valueOf(players); + } + + public String getServerPlayerCount(String server) { + return String.valueOf(Universe.getInstance().getUniverseHandler().serverFromName(server).getOnlinePlayers().get()); + } + + public String getMaxPlayerCount(String server) { + return String.valueOf(Universe.getInstance().getUniverseHandler().serverFromName("Elevate-Practice").getMaximumPlayers().get()); + } + + public void setupMusicPlayer() { + if (!getDataFolder().exists()) getDataFolder().mkdir(); + File songsDirectory = new File(getDataFolder(), "songs"); + if (!songsDirectory.exists()) songsDirectory.mkdir(); + + if (songsDirectory.isDirectory()) { + ArrayList songs = new ArrayList<>(); + for (File file : songsDirectory.listFiles()) { + if (file.isDirectory()) continue; + Song song = NBSDecoder.parse(file); + if (song == null) continue; + + if (StringUtils.isEmptyOrWhitespaceOnly(song.getTitle())) { + file.delete(); + Bukkit.getLogger().warning("File " + file.getName() + " has no title, deleted!"); + continue; + } + + if (song.getTitle().length() > 12) { + song = new Song(song.getSpeed(), song.getLayerHashMap(), song.getSongHeight(), song.getLength(), song.getTitle().substring(0, 12) + "…", song.getAuthor(), song.getOriginalAuthor(), song.getDescription(), song.getPath(), song.getFirstCustomInstrumentIndex(), song.getCustomInstruments(), song.isStereo()); + } + + songs.add(song); + } + Bukkit.getLogger().info("Loaded " + songs.size() + " songs."); + radioSongPlayer = new RadioSongPlayer(new Playlist(songs.toArray(new Song[0]))); + radioSongPlayer.setRepeatMode(RepeatMode.ALL); + radioSongPlayer.setPlaying(true); + radioSongPlayer.setRandom(true); + } + + getServer().getPluginManager().registerEvents(new MusicListener(), this); + } + + private void loadManagers() { + redisManager = new RedisManager(); + profileManager = new ProfileManager(); + } + + private void loadTasks() { + armorTask = new ArmorTask(); + particleTask = new ParticleTask(); + } + + private void loadHandlers() { + getServer().getPluginManager().registerEvents(new PlayerListener(), this); + } + + @Override + public void onDisable() { + armorTask.cancel(); + particleTask.cancel(); + + getProfileManager().getProfiles().clear(); + } +} diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/listener/FunListener.java b/Network/eHub/src/main/java/com/elevatemc/ehub/listener/FunListener.java new file mode 100644 index 0000000..7c26774 --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/listener/FunListener.java @@ -0,0 +1,133 @@ +package com.elevatemc.ehub.listener; + +import com.elevatemc.ehub.eHub; +import com.elevatemc.elib.util.ParticleEffect; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.Sound; +import org.bukkit.entity.*; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.ProjectileLaunchEvent; +import org.bukkit.event.player.*; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.util.Vector; +import org.spigotmc.event.entity.EntityDismountEvent; + +public class FunListener implements Listener { + @EventHandler + public void onJoin(PlayerJoinEvent e) { + e.getPlayer().setAllowFlight(true); + } + + @EventHandler + public void onDoubleJumpMove(PlayerMoveEvent e) { + Player player = e.getPlayer(); + + if (player.getGameMode().equals(GameMode.CREATIVE)) return; + if (!((LivingEntity) player).isOnGround()) + if (player.getAllowFlight()) return; + + boolean onGround = ((Entity)player).isOnGround(); + if (player.hasMetadata("DOUBLE_JUMPED") && onGround) { + player.removeMetadata("DOUBLE_JUMPED", eHub.getInstance()); + player.setAllowFlight(true); + } + } + + @EventHandler + public void onDoubleJumpFlight(PlayerToggleFlightEvent e) { + Player player = e.getPlayer(); + + if (player.getGameMode().equals(GameMode.CREATIVE)) return; + + e.setCancelled(true); + player.setFlying(false); + player.setAllowFlight(false); + + if (player.hasMetadata("BOOSTED")) { + player.removeMetadata("BOOSTED", eHub.getInstance()); + } + + if (!player.hasMetadata("DOUBLE_JUMPED")) { + player.setMetadata("DOUBLE_JUMPED", new FixedMetadataValue(eHub.getInstance(), true)); + final Location loc = player.getLocation(); + final double otherBoost = 2.5; + final Sound sound = Sound.PISTON_EXTEND; + final Vector vector = loc.getDirection().multiply(otherBoost).setY(1.75); + player.setVelocity(vector); + player.playSound(loc, sound, 2,2); + ParticleEffect.CLOUD.display(0 ,-0.5F, 0, 0.05F, 25, loc, player); + } + } + + @EventHandler + public void onSneak(PlayerToggleSneakEvent e) { + if (e.isSneaking()) { + Player player = e.getPlayer(); + + if (player.getGameMode().equals(GameMode.CREATIVE)) return; + + boolean onGround = ((Entity)player).isOnGround(); + if (!player.hasMetadata("BOOSTED") && !onGround) { + player.setMetadata("BOOSTED", new FixedMetadataValue(eHub.getInstance(), true)); + final Location loc = player.getLocation(); + final double otherBoost = 3; + final Sound sound = Sound.BAT_TAKEOFF; + final Vector vector = loc.getDirection().multiply(otherBoost); + player.setVelocity(vector); + player.playSound(loc, sound, 2,2); + ParticleEffect.FLAME.display(0 ,-0.5F, 0, 0.05F, 25, loc, player); + } + } + } + + @EventHandler + public void onProjectileLaunch(ProjectileLaunchEvent e) { + if (e.getEntity().getShooter() instanceof Player) { + final Player player = (Player)e.getEntity().getShooter(); + if (e.getEntity() instanceof EnderPearl) { + Entity pearl = player.getVehicle(); + if (pearl != null) { + pearl.remove(); + } + Projectile proj = e.getEntity(); + if (proj.getType() == EntityType.ENDER_PEARL) { + Bukkit.getScheduler().runTaskLater(eHub.getInstance(), () -> { + if (!proj.isDead()) { + proj.setPassenger(player); + } + player.getInventory().getItem(player.getInventory().getHeldItemSlot()).setAmount(64); + }, 3L); + } + } else { + e.getEntity().remove(); + } + } + } + + @EventHandler + public void onEntityDismount(EntityDismountEvent e) { + if (e.getEntity() instanceof Player) { + final Player player = (Player)e.getEntity(); + if (player != null && player.getVehicle() instanceof EnderPearl) { + Entity pearl = player.getVehicle(); + if (pearl != null) { + player.eject(); + pearl.remove(); + } + } + } + } + + @EventHandler + public void onQuit(PlayerQuitEvent e) { + Player player = e.getPlayer(); + Entity pearl = player.getVehicle(); + if (pearl != null) { + pearl.remove(); + return; + } + } +} diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/listener/HubListener.java b/Network/eHub/src/main/java/com/elevatemc/ehub/listener/HubListener.java new file mode 100644 index 0000000..8ff30ef --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/listener/HubListener.java @@ -0,0 +1,234 @@ +package com.elevatemc.ehub.listener; + +import com.elevatemc.ehub.eHub; +import com.elevatemc.ehub.menu.cosmetics.CosmeticsMenu; +import com.elevatemc.ehub.menu.selector.ServerSelector; +import com.elevatemc.ehub.utils.HubItems; +import com.elevatemc.elib.util.Pair; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import dev.apposed.prime.spigot.module.profile.identity.ProfileIdentity; +import dev.apposed.prime.spigot.module.profile.punishment.Punishment; +import dev.apposed.prime.spigot.module.profile.punishment.type.PunishmentType; +import dev.apposed.prime.spigot.module.rank.meta.RankMeta; +import dev.apposed.prime.spigot.module.server.scoreboard.PrimeScoreboardStyle; +import dev.apposed.prime.spigot.util.Color; +import org.apache.commons.lang.StringUtils; +import org.bukkit.*; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.*; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.metadata.FixedMetadataValue; + +import java.util.Optional; + +public class HubListener implements Listener { + + private static final ProfileHandler profileHandler = eHub.getInstance().getPrime().getModuleHandler().getModule(ProfileHandler.class); + private static final String networkName = eHub.getInstance().getPrime().getConfig().getString("network.name"); + private static final String appealLink = eHub.getInstance().getPrime().getConfig().getString("network.appeal"); + + @EventHandler(priority = EventPriority.HIGHEST) + public void onPlayerJoin(PlayerJoinEvent e) { + Player player = e.getPlayer(); + + e.setJoinMessage(null); + for (Player otherPlayer : Bukkit.getOnlinePlayers()) { + player.hidePlayer(otherPlayer); + otherPlayer.hidePlayer(player); + } + + player.spigot().setCollidesWithEntities(false); + player.setGameMode(GameMode.SURVIVAL); + player.teleport(player.getWorld().getSpawnLocation()); + + PlayerInventory inventory = player.getInventory(); + inventory.clear(); + inventory.setArmorContents(null); + if (player.hasMetadata("MUSIC_DISABLED")) { + inventory.setItem(7, HubItems.MUSIC_DISABLED); + } else { + inventory.setItem(7, HubItems.MUSIC_ENABLED); + } + + inventory.setItem(4, HubItems.COSMETICS); + + inventory.setItem(8, HubItems.ENDER_PEARLS); + inventory.setHeldItemSlot(0); + + player.setWalkSpeed(0.3F); + player.playSound(player.getLocation(), Sound.SUCCESSFUL_HIT, 1, 1); + player.sendMessage(StringUtils.repeat(" \n", 100)); + + profileHandler.load(player.getUniqueId()).thenAccept(profile -> { + if (profile.hasActivePunishment(PunishmentType.BAN)) { + player.setMetadata("banned", new FixedMetadataValue(eHub.getInstance(), true)); + Optional punishmentOptional = profile.getActivePunishment(PunishmentType.BAN); + if (punishmentOptional.isPresent()) { + Punishment punishment = punishmentOptional.get(); + if (punishment.getRemaining() == Long.MAX_VALUE) { + player.sendMessage( + ChatColor.translateAlternateColorCodes('&', "&cYour account has been permanently suspended from the " + networkName + " Network.\n\n&cAppeal on " + appealLink + ".") + ); + } else { + player.sendMessage( + ChatColor.translateAlternateColorCodes('&', "&cYour account has been suspended from the " + networkName + " Network for " + punishment.formatDuration() + ".\n\n&cAppeal on " + appealLink + ".") + ); + } + return; + } + } + + if (profile.hasActivePunishment(PunishmentType.BLACKLIST)) { + player.setMetadata("banned", new FixedMetadataValue(eHub.getInstance(), true)); + Optional punishmentOptional = profile.getActivePunishment(PunishmentType.BAN); + if (punishmentOptional.isPresent()) { + player.sendMessage( + ChatColor.translateAlternateColorCodes('&', "&cYour account has been permanently blacklisted from the " + networkName + " Network" + "\n&cAppeal on " + appealLink + ".") + ); + player.setMetadata("related", new FixedMetadataValue(eHub.getInstance(), "related")); + return; + } + } + + // identity check - if they have a rank that allows them to bypass ip bans this will be skipped + if (!profile.hasMeta(RankMeta.IP_BYPASS)) { + for (ProfileIdentity identity : profile.getIdentities()) { + Optional punishedIdentityOptional = profileHandler.identityIsPunished(identity); + if (punishedIdentityOptional.isPresent()) { + Profile punishedProfile = punishedIdentityOptional.get(); + + if (punishedProfile.hasActivePunishment(PunishmentType.BAN)) { + player.setMetadata("banned", new FixedMetadataValue(eHub.getInstance(), true)); + Optional punishmentOptional = punishedProfile.getActivePunishment(PunishmentType.BAN); + if (punishmentOptional.isPresent()) { + Punishment punishment = punishmentOptional.get(); + if (punishment.getRemaining() == Long.MAX_VALUE) { + player.sendMessage( + ChatColor.translateAlternateColorCodes('&', "&cYour account has been permanently suspended from the " + networkName + " Network\n&cThis punishment is in relation to &r" + punishedProfile.getColoredName() + "\n&cAppeal on " + appealLink + ".") + ); + } else { + player.sendMessage( + ChatColor.translateAlternateColorCodes('&', "&cYour account has been suspended from the " + networkName + " Network for " + punishment.formatDuration() + "\n&cThis punishment is in relation to &r" + punishedProfile.getColoredName() + "\n&cAppeal on " + appealLink + ".") + ); + } + player.setMetadata("related", new FixedMetadataValue(eHub.getInstance(), "related")); + return; + } + } + + if (punishedProfile.hasActivePunishment(PunishmentType.BLACKLIST)) { + Optional punishmentOptional = punishedProfile.getActivePunishment(PunishmentType.BLACKLIST); + if (punishmentOptional.isPresent()) { + player.sendMessage( + ChatColor.translateAlternateColorCodes('&', "&cYour account has been permanently blacklisted from the " + networkName + " Network\n&cThis punishment is in relation to &r" + punishedProfile.getColoredName() + "\n&cAppeal on " + appealLink + ".") + ); + player.setMetadata("related", new FixedMetadataValue(eHub.getInstance(), "related")); + return; + } + } + + if (!punishedProfile.hasActivePunishment(PunishmentType.BAN) && !punishedProfile.hasActivePunishment(PunishmentType.BLACKLIST)) { + player.removeMetadata("related", eHub.getInstance()); + } + } + } + } + + if(!profile.hasActivePunishment(PunishmentType.BLACKLIST) && !profile.hasActivePunishment(PunishmentType.BAN) && !player.hasMetadata("related")) { + player.removeMetadata("banned", eHub.getInstance()); + } + }); + + if(!player.hasMetadata("banned")) inventory.setItem(0, HubItems.SELECT_SERVER); + + final Pair style = PrimeScoreboardStyle.getStyle(player); + player.sendMessage(""); + player.sendMessage("" + style.getKey() + "Welcome to the " + style.getKey() + ChatColor.BOLD + "Elevate Network" + style.getValue() + "!"); + player.sendMessage(""); + player.sendMessage(" " + ChatColor.GRAY + "►" + style.getKey() + " Website: " + style.getValue() + "https://elevatemc.com"); + player.sendMessage(" " + ChatColor.GRAY + "►" + style.getKey() + " Store: " + style.getValue() + "https://store.elevatemc.com"); + player.sendMessage(" " + ChatColor.GRAY + "►" + style.getKey() + " Twitter: " + style.getValue() + "https://twitter.com/elevatemcnet"); + player.sendMessage(" " + ChatColor.GRAY + "►" + style.getKey() + " Discord: " + style.getValue() + "https://elevatemc.com/discord"); + player.sendMessage(" " + ChatColor.GRAY + "►" + style.getKey() + " Telegram: " + style.getValue() + "https://t.me/ElevateMC"); + player.sendMessage(" " + ChatColor.GRAY + "►" + style.getKey() + " NameMC: " + style.getValue() + "https://namemc.com/elevatemc.com"); + player.sendMessage(""); + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent e) { + e.setQuitMessage(null); + Player player = e.getPlayer(); + eHub.getInstance().getQueueHandler().getPositions().remove(e.getPlayer().getUniqueId()); + if (player.getVehicle() != null) { + player.getVehicle().leaveVehicle(); + } + } + + @EventHandler + public void onPlayerInteract(PlayerInteractEvent e) { + if (!e.hasItem() || !e.getAction().name().contains("RIGHT_")) { + return; + } + + ItemStack item = e.getItem(); + + if(e.getPlayer().hasMetadata("banned")) return; + + if (item.isSimilar(HubItems.SELECT_SERVER)) { + new ServerSelector().openMenu(e.getPlayer()); + return; + } + + if (item.isSimilar(HubItems.COSMETICS)) { + new CosmeticsMenu().openMenu(e.getPlayer()); + return; + } + + if (item.isSimilar(HubItems.ENDER_PEARLS)) { + return; + } + + Player player = e.getPlayer(); + Inventory inventory = player.getInventory(); + + if (item.isSimilar(HubItems.MUSIC_DISABLED)) { + player.removeMetadata("MUSIC_DISABLED", eHub.getInstance()); + eHub.getInstance().getRadioSongPlayer().addPlayer(player); + inventory.setItem(4, HubItems.MUSIC_ENABLED); + return; + } + + if (item.isSimilar(HubItems.MUSIC_ENABLED)) { + player.setMetadata("MUSIC_DISABLED", new FixedMetadataValue(eHub.getInstance(), true)); + eHub.getInstance().getRadioSongPlayer().removePlayer(player); + inventory.setItem(4, HubItems.MUSIC_DISABLED); + return; + } + + if (!e.getPlayer().hasMetadata("build")) { + e.setCancelled(true); + } + } + + @EventHandler + public void onChat(AsyncPlayerChatEvent event) { + if(!event.getPlayer().hasMetadata("banned")) return; + event.setCancelled(true); + event.getPlayer().sendMessage(Color.translate("&cYou are not allowed to type in chat!")); + } + + @EventHandler + public void onCommand(PlayerCommandPreprocessEvent event) { + if(!event.getPlayer().hasMetadata("banned")) return; + if(!event.getMessage().equalsIgnoreCase("/register") && !event.getMessage().equalsIgnoreCase("/resetpassword")) { + event.getPlayer().sendMessage(Color.translate("&cThe only command you are allowed to run is /register.")); + event.setCancelled(true); + } + } +} diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/listener/MusicListener.java b/Network/eHub/src/main/java/com/elevatemc/ehub/listener/MusicListener.java new file mode 100644 index 0000000..5475cc2 --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/listener/MusicListener.java @@ -0,0 +1,19 @@ +package com.elevatemc.ehub.listener; + +import com.elevatemc.ehub.eHub; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; + +public class MusicListener implements Listener { + + @EventHandler(priority = EventPriority.HIGHEST) + public void onPlayerJoin(PlayerJoinEvent e) { + Player player = e.getPlayer(); + if (!player.hasMetadata("MUSIC_DISABLED")) { + eHub.getInstance().getRadioSongPlayer().addPlayer(player); + } + } +} diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/listener/PlayerListener.java b/Network/eHub/src/main/java/com/elevatemc/ehub/listener/PlayerListener.java new file mode 100644 index 0000000..c4f949a --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/listener/PlayerListener.java @@ -0,0 +1,47 @@ +package com.elevatemc.ehub.listener; + +import com.elevatemc.ehub.eHub; +import com.elevatemc.ehub.profile.Profile; +import com.elevatemc.elib.util.TaskUtil; + +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerPreLoginEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; + + +public class PlayerListener implements Listener { + private final eHub plugin = eHub.getInstance(); + + @EventHandler(ignoreCancelled = true) + public void onAsyncPlayerPreLoginEvent(AsyncPlayerPreLoginEvent event) { + Profile profile = new Profile(event.getUniqueId()); + plugin.getRedisManager().load(profile); + } + + @EventHandler(ignoreCancelled = true) + public void onPlayerJoinEvent(PlayerJoinEvent event) { + Player player = event.getPlayer(); + + for (ItemStack stack : player.getInventory().getArmorContents()) { + stack.setType(Material.AIR); + } + + player.updateInventory(); + } + + @EventHandler + public void onPlayerQuitEvent(PlayerQuitEvent event) { + TaskUtil.runTaskAsynchronously(() -> { + Profile profile = plugin.getProfileManager().getByUuid(event.getPlayer().getUniqueId()); + + plugin.getRedisManager().saveArmor(profile, profile.getArmorType()); + plugin.getRedisManager().saveParticle(profile, profile.getParticleType()); + plugin.getProfileManager().getProfiles().remove(profile.getUuid()); + }); + } +} diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/listener/PreventionListener.java b/Network/eHub/src/main/java/com/elevatemc/ehub/listener/PreventionListener.java new file mode 100644 index 0000000..5df9cd6 --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/listener/PreventionListener.java @@ -0,0 +1,104 @@ +package com.elevatemc.ehub.listener; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockBurnEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.FoodLevelChangeEvent; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryMoveItemEvent; +import org.bukkit.event.player.PlayerAchievementAwardedEvent; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerPickupItemEvent; +import org.bukkit.event.weather.WeatherChangeEvent; + +public class PreventionListener implements Listener { + @EventHandler + public void onBreak(BlockBreakEvent e) { + if (!e.getPlayer().hasMetadata("build")) { + e.setCancelled(true); + } + + } + + @EventHandler + public void onPlace(BlockPlaceEvent e) { + if (!e.getPlayer().hasMetadata("build")) { + e.setCancelled(true); + } + } + + @EventHandler + public void onClick(InventoryClickEvent e) { + if (!e.getWhoClicked().hasMetadata("build")) { + e.setCancelled(true); + } + } + + @EventHandler + public void onBurn(BlockBurnEvent e) { + e.setCancelled(true); + } + + @EventHandler + public void onItemDrop(PlayerDropItemEvent e) { + e.setCancelled(true); + } + + @EventHandler + public void onItemPickup(PlayerPickupItemEvent e) { + e.setCancelled(true); + } + + @EventHandler + public void onItemMove(InventoryMoveItemEvent e) { + e.setCancelled(true); + } + + @EventHandler + public void onWeatherChange(WeatherChangeEvent e) { + e.setCancelled(e.toWeatherState()); + } + + @EventHandler + public void onDamage(FoodLevelChangeEvent e) { + if(e.getEntity() instanceof Player) { + e.setFoodLevel(20); + } + } + + @EventHandler + public void onDamage(EntityDamageEvent e) { + if(e.getEntity() instanceof Player) { + e.setCancelled(true); + } + } + + @EventHandler + public void onDamage(PlayerAchievementAwardedEvent e) { + e.setCancelled(true); + } + + @EventHandler + public void onMove(PlayerMoveEvent e) { + Location location = e.getTo(); + if (location.getY() < 5 || location.getX() > 300 || location.getZ() > 300) { + Player player = e.getPlayer(); + if (player.getVehicle() != null) { + Entity pearl = player.getVehicle(); + if (pearl != null) { + player.eject(); + pearl.remove(); + } + } + player.teleport(player.getWorld().getSpawnLocation()); + } + + } +} diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/menu/cosmetics/CosmeticsMenu.java b/Network/eHub/src/main/java/com/elevatemc/ehub/menu/cosmetics/CosmeticsMenu.java new file mode 100644 index 0000000..a6b15ea --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/menu/cosmetics/CosmeticsMenu.java @@ -0,0 +1,133 @@ +package com.elevatemc.ehub.menu.cosmetics; + +import com.elevatemc.ehub.eHub; +import com.elevatemc.ehub.profile.Profile; +import com.elevatemc.ehub.type.armor.ArmorType; +import com.elevatemc.ehub.type.particle.ParticleType; +import com.elevatemc.ehub.utils.CC; +import com.elevatemc.ehub.utils.ItemBuilder; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import com.google.common.collect.Maps; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public class CosmeticsMenu extends Menu { + + @Override + public String getTitle(Player player) { + return CC.DARK_AQUA + "Cosmetics"; + } + + @Override + public int size(Player player) { + return 9; + } + + @Override + public boolean isPlaceholder() { + return true; + } + + @Override + public Map getButtons(Player player) { + Map buttons = Maps.newHashMap(); + + Profile profile = eHub.getInstance().getProfileManager().getByUuid(player.getUniqueId()); + ArmorType armorType = profile.getArmorType(); + + buttons.put(2, new Button() { + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(Material.LEATHER_CHESTPLATE) + .setName(CC.AQUA + "Armor") + .addEnchantment(Enchantment.DURABILITY) + .setLore(Arrays.asList( + "", + CC.GRAY + "Change your armor design.", + "", + CC.AQUA + "Selected Armor" + CC.GRAY + ": " + CC.WHITE + (armorType == null ? "None" : armorType.getName()), + "", + CC.AQUA + "Click to view Armor Designs.")) + .get(); + } + + @Override + public String getName(Player player) { + return null; + } + + @Override + public List getDescription(Player player) { + return null; + } + + @Override + public Material getMaterial(Player player) { + return null; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + if (eHub.getInstance().getProfileManager().getByUuid(player.getUniqueId()).getArmorType() == null) { + new RanksMenu().openMenu(player); + + } else { + + new EditorMenu().openMenu(player); + } + Button.playSuccess(player); + } + }); + + ParticleType particleType = profile.getParticleType(); + + buttons.put(6, new Button() { + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(Material.NETHER_STAR) + .setName(CC.AQUA + "Particles") + .setLore(Arrays.asList( + "", + CC.GRAY + "A lot of different particles", + CC.GRAY + "are spawning around you.", + "", + CC.AQUA + "Selected Particle" + CC.GRAY + ": " + CC.WHITE + (particleType == null ? "None" : particleType.getName().replace(" Particle", "")), + "", + CC.AQUA + "Click to view list of Particles.")) + + .get(); + } + + @Override + public String getName(Player player) { + return null; + } + + @Override + public List getDescription(Player player) { + return null; + } + + @Override + public Material getMaterial(Player player) { + return null; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + new ParticlesMenu().openMenu(player); + Button.playSuccess(player); + } + }); + + return buttons; + } +} diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/menu/cosmetics/EditorMenu.java b/Network/eHub/src/main/java/com/elevatemc/ehub/menu/cosmetics/EditorMenu.java new file mode 100644 index 0000000..6489c39 --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/menu/cosmetics/EditorMenu.java @@ -0,0 +1,413 @@ +package com.elevatemc.ehub.menu.cosmetics; + +import com.elevatemc.ehub.eHub; +import com.elevatemc.ehub.profile.Profile; +import com.elevatemc.ehub.type.armor.ArmorType; +import com.elevatemc.ehub.utils.CC; +import com.elevatemc.ehub.utils.ItemBuilder; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import com.google.common.collect.Maps; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public class EditorMenu extends Menu { + + @Override + public String getTitle(Player player) { + return CC.DARK_AQUA + "Choose your Armor"; + } + + @Override + public int size(Player player) { + return 54; + } + + @Override + public boolean isPlaceholder() { + return true; + } + + @Override + public boolean isAutoUpdate() { + return true; + } + + @Override + public Map getButtons(Player player) { + Map buttons = Maps.newHashMap(); + + Profile profile = eHub.getInstance().getProfileManager().getByUuid(player.getUniqueId()); + ArmorType type = profile.getArmorType(); + + buttons.put(14, new Button() { + @Override + public String getName(Player player) { + return CC.B_GREEN + "Choose your armor"; + } + + @Override + public List getDescription(Player player) { + return Arrays.asList( + "", + CC.GRAY + "Changes your armor design.", + "", + CC.AQUA + "Current Armor" + CC.GRAY + ": " + (type == null ? CC.WHITE + "None" : type.getDisplayColor() + type.getName()), + "", + CC.GRAY + "Click to change your " + CC.B_AQUA + "Armor Design."); + } + + @Override + public Material getMaterial(Player player) { + return Material.PAPER; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + new RanksMenu().openMenu(player); + Button.playSuccess(player); + }; + }); + + buttons.put(45, new Button() { + + @Override + public String getName(Player player) { + return CC.B_RED + "Back"; + } + + @Override + public List getDescription(Player player) { + return Arrays.asList( + "", + CC.RED + "Click here to return to", + CC.RED + "the previous menu."); + } + + @Override + public Material getMaterial(Player player) { + return Material.REDSTONE; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + new CosmeticsMenu().openMenu(player); + Button.playSuccess(player); + } + }); + + if (type == null) { + return buttons; + } + + buttons.put(12, new Button() { + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(type.getItems()[3].clone()) + .setName(CC.RED + "Helmet") + .setLore(Arrays.asList( + "", + CC.GRAY + "Do you want to have", + CC.GRAY + "helmet armor piece on you?", + "", + profile.getArmors()[3] ? + CC.RED + "Click to turn it off." : + CC.GREEN + "Click to turn it on.")) + .setColor(type == ArmorType.ELEVATE ? Color.fromRGB(type.getR(), type.getG(), type.getB()) : type.getColor()) + .get(); + } + + @Override + public String getName(Player player) { + return null; + } + + @Override + public List getDescription(Player player) { + return null; + } + + @Override + public Material getMaterial(Player player) { + return null; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + profile.setAstronaut(false); + + profile.getArmors()[3] = !profile.getArmors()[3]; + Button.playFail(player); + } + }); + + buttons.put(21, new Button() { + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(type.getItems()[2].clone()) + .setName(CC.RED + "Chestplate") + .setLore(Arrays.asList( + "", + CC.GRAY + "Do you want to have", + CC.GRAY + "chestplate armor piece on you?", + "", + profile.getArmors()[2] ? + CC.RED + "Click to turn it off." : + CC.GREEN + "Click to turn it on.")) + .setColor(type == ArmorType.ELEVATE ? Color.fromRGB(type.getR(), type.getG(), type.getB()) : type.getColor()) + .get(); + } + + @Override + public String getName(Player player) { + return null; + } + + @Override + public List getDescription(Player player) { + return null; + } + + @Override + public Material getMaterial(Player player) { + return null; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + profile.getArmors()[2] = !profile.getArmors()[2]; + Button.playFail(player); + } + }); + + buttons.put(30, new Button() { + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(type.getItems()[1].clone()) + .setName(CC.RED + "Leggings") + .setLore(Arrays.asList( + "", + CC.GRAY + "Do you want to have", + CC.GRAY + "leggings armor piece on you?", + "", + profile.getArmors()[1] ? + CC.RED + "Click to turn it off." : + CC.GREEN + "Click to turn it on.")) + .setColor(type == ArmorType.ELEVATE ? Color.fromRGB(type.getR(), type.getG(), type.getB()) : type.getColor()) + .get(); + } + + @Override + public String getName(Player player) { + return null; + } + + @Override + public List getDescription(Player player) { + return null; + } + + @Override + public Material getMaterial(Player player) { + return null; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + profile.getArmors()[1] = !profile.getArmors()[1]; + Button.playFail(player); + } + }); + + buttons.put(39, new Button() { + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(type.getItems()[0].clone()) + .setName(CC.RED + "Boots") + .setLore(Arrays.asList( + "", + CC.GRAY + "Do you want to have", + CC.GRAY + "boots armor piece on you?", + "", + profile.getArmors()[0] ? + CC.RED + "Click to turn it off." : + CC.GREEN + "Click to turn it on.")) + .setColor(type == ArmorType.ELEVATE ? Color.fromRGB(type.getR(), type.getG(), type.getB()) : type.getColor()) + .get(); + } + + @Override + public String getName(Player player) { + return null; + } + + @Override + public List getDescription(Player player) { + return null; + } + + @Override + public Material getMaterial(Player player) { + return null; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + profile.getArmors()[0] = !profile.getArmors()[0]; + Button.playFail(player); + } + }); + + buttons.put(23, new Button() { + @Override + public String getName(Player player) { + return (profile.isEnchanted() ? CC.B_RED : CC.B_GREEN) + "Enchantment"; + } + + @Override + public List getDescription(Player player) { + return Arrays.asList( + "", + CC.GRAY + "Do you want to have", + CC.GRAY + "your armor enchanted?", + "", + profile.isEnchanted() ? + CC.RED + "Click to turn it off." : + CC.GREEN + "Click to turn it on."); + } + + @Override + public Material getMaterial(Player player) { + return Material.INK_SACK; + } + + @Override + public byte getDamageValue(Player player) { + return (byte) (profile.isEnchanted() ? 1 : 10); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + profile.setEnchanted(!profile.isEnchanted()); + Button.playFail(player); + } + }); + + buttons.put(32, new Button() { + + @Override + public String getName(Player player) { + return (profile.isAstronaut() ? CC.B_RED : CC.B_GREEN) + "Astronaut Helmet"; + } + + @Override + public List getDescription(Player player) { + return Arrays.asList( + "", + CC.GRAY + "Do you want to have", + CC.GRAY + "Astronaut Helmet placed", + CC.GRAY + "on your head?", + "", + profile.isAstronaut() ? + CC.RED + "Click to turn it off." : + CC.GREEN + "Click to turn it on."); + } + + @Override + public Material getMaterial(Player player) { + return Material.INK_SACK; + } + + @Override + public byte getDamageValue(Player player) { + return (byte) (profile.isAstronaut() ? 1 : 10); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + profile.setAstronaut(!profile.isAstronaut()); + profile.getArmors()[3] = !profile.isAstronaut(); + + Button.playFail(player); + } + }); + + buttons.put(41, new Button() { + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(Material.INK_SACK) + .setName(check(profile) ? CC.B_RED + "All Armor Pieces" : CC.B_GREEN + "All Armor Pieces") + .setLore(Arrays.asList( + "", + CC.GRAY + "Do you want to have", + CC.GRAY + "all armor pieces", + CC.GRAY + "placed on you?", + "", + areAllTrue(profile.getArmors()) ? + CC.RED + "Click to remove them." : + CC.GREEN + "Click to add them.")) + .setDurability(check(profile) ? 1 : 10) + .get(); + } + + @Override + public String getName(Player player) { + return null; + } + + @Override + public List getDescription(Player player) { + return null; + } + + @Override + public Material getMaterial(Player player) { + return null; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + if (profile.isAstronaut() && areAllTrue(profile.getArmors())) { + profile.setAstronaut(false); + } + + if (areAllTrue(profile.getArmors())) { + for (int i = 0; i < 4; i++) { + profile.getArmors()[i] = false; + } + } else { + for (int i = 0; i < 4; i++) { + profile.getArmors()[i] = true; + } + } + + Button.playFail(player); + } + }); + + return buttons; + } + + private boolean check(Profile profile) { + profile.getArmors()[3] = profile.isAstronaut() || profile.getArmors()[3]; + + return areAllTrue(profile.getArmors()); + } + + private boolean areAllTrue(boolean[] array) { + for (boolean value : array) { + if (!value) { + return false; + } + } + + return true; + } +} diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/menu/cosmetics/ParticlesMenu.java b/Network/eHub/src/main/java/com/elevatemc/ehub/menu/cosmetics/ParticlesMenu.java new file mode 100644 index 0000000..9af2298 --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/menu/cosmetics/ParticlesMenu.java @@ -0,0 +1,163 @@ +package com.elevatemc.ehub.menu.cosmetics; + +import com.elevatemc.ehub.eHub; +import com.elevatemc.ehub.profile.Profile; +import com.elevatemc.ehub.type.particle.ParticleType; +import com.elevatemc.ehub.utils.CC; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import com.elevatemc.elib.util.cosmetics.ArmorUtil; +import com.google.common.collect.Maps; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public class ParticlesMenu extends Menu { + + @Override + public String getTitle(Player player) { + return CC.DARK_AQUA + "Choose Your Particle"; + } + + @Override + public int size(Player player) { + return 27; + } + + @Override + public boolean isPlaceholder() { + return true; + } + + @Override + public Map getButtons(Player player) { + Map buttons = Maps.newHashMap(); + + buttons.put(18, new Button() { + + @Override + public String getName(Player player) { + return CC.B_RED + "Back"; + } + + @Override + public List getDescription(Player player) { + return Arrays.asList( + "", + CC.RED + "Click here to return to", + CC.RED + "the previous menu."); + } + + @Override + public Material getMaterial(Player player) { + return Material.REDSTONE; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + new CosmeticsMenu().openMenu(player); + Button.playSuccess(player); + } + }); + + Profile profile = eHub.getInstance().getProfileManager().getByUuid(player.getUniqueId()); + + int count = 10; + for (ParticleType type : ParticleType.values()) { + buttons.put(count, new Button() { + + @Override + public String getName(Player player) { + return type.getDisplayColor() + type.getName(); + } + + @Override + public List getDescription(Player player) { + return Arrays.asList("", + CC.GRAY + "Changes your particle", + CC.GRAY + "to " + type.getDisplayColor() + type.getName() + CC.GRAY + " particle.", + CC.GRAY + "Left click to select.", + "", + profile.getParticleType() == type ? CC.GREEN + "That effect is already selected." : + (type.hasPermission(player) ? CC.GRAY + "Click here to select " + CC.B_AQUA + type.getName() + CC.GRAY + "." : + CC.RED + "You don't own this particle.")); + } + + @Override + public Material getMaterial(Player player) { + return Material.WOOL; + } + + @Override + public byte getDamageValue(Player player) { + return (byte) (ArmorUtil.parseColor(type.getColor())); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + ParticleType type = ParticleType.getByName(ChatColor.stripColor(getName(player))); + + if (profile.getParticleType() == type) { + Button.playFail(player); + player.sendMessage(CC.B_RED + type.getName() + CC.RED + " effect is already selected!"); + return; + } + + if (!type.hasPermission(player)) { + Button.playFail(player); + player.sendMessage(CC.RED + "You don't have " + CC.B + type.getName() + CC.RED + " particle."); + return; + } + + Button.playSuccess(player); + + profile.setParticleType(type); + player.sendMessage(type.getDisplayColor() + type.getName() + CC.YELLOW + " is now set as your particle effect."); + player.closeInventory(); + } + }); + + count += 2; + } + + if (profile.getParticleType() != null) { + buttons.put(4, new Button() { + + + @Override + public String getName(Player player) { + return CC.B_GREEN + "Remove your Particle"; + } + + @Override + public List getDescription(Player player) { + return Arrays.asList("", + CC.GRAY + "By clicking this item you will", + CC.GRAY + "remove your current particle.", + "", + CC.RED + "Click here to remove your " + profile.getParticleType().getDisplayColor() + profile.getParticleType().getName() + CC.RED + "."); + } + + @Override + public Material getMaterial(Player player) { + return Material.LEVER; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + player.sendMessage(CC.GREEN + "You have deactivated your " + profile.getParticleType().getDisplayColor() + profile.getParticleType().getName() + CC.GREEN + "."); + profile.setParticleType(null); + + Button.playSuccess(player); + } + }); + } + + return buttons; + } +} diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/menu/cosmetics/RanksMenu.java b/Network/eHub/src/main/java/com/elevatemc/ehub/menu/cosmetics/RanksMenu.java new file mode 100644 index 0000000..5a838b8 --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/menu/cosmetics/RanksMenu.java @@ -0,0 +1,208 @@ +package com.elevatemc.ehub.menu.cosmetics; + +import com.elevatemc.ehub.eHub; +import com.elevatemc.ehub.profile.Profile; +import com.elevatemc.ehub.type.armor.ArmorType; +import com.elevatemc.ehub.utils.CC; +import com.elevatemc.ehub.utils.ItemBuilder; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.pagination.PaginatedMenu; +import com.google.common.collect.Maps; +import org.bukkit.ChatColor; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public class RanksMenu extends PaginatedMenu { + + @Override + public String getPrePaginatedTitle(Player player) { + return CC.DARK_AQUA + "Ranks"; + } + + public int getMaxItemsPerPage(Player player) { + return 36; + } + + @Override + public boolean isPlaceholder() { + return true; + } + + @Override + public boolean isAutoUpdate() { + return true; + } + + @Override + public Map getGlobalButtons(Player player) { + Map buttons = Maps.newHashMap(); + + if (!player.hasPermission("core.cosmetic.armor.mod")) { + + buttons.put(18, new Button() { + + @Override + public String getName(Player player) { + return CC.B_RED + "Back"; + } + + @Override + public List getDescription(Player player) { + return Arrays.asList( + "", + CC.RED + "Click here to return to", + CC.RED + "the previous menu."); + } + + @Override + public Material getMaterial(Player player) { + return Material.REDSTONE; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + new EditorMenu().openMenu(player); + Button.playSuccess(player); + } + }); + + return buttons; + } else { + + buttons.put(27, new Button() { + + @Override + public String getName(Player player) { + return CC.B_RED + "Back"; + } + + @Override + public List getDescription(Player player) { + return Arrays.asList( + "", + CC.RED + "Click here to return to", + CC.RED + "the previous menu."); + } + + @Override + public Material getMaterial(Player player) { + return Material.REDSTONE; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + new EditorMenu().openMenu(player); + Button.playSuccess(player); + } + }); + + return buttons; + } + } + + @Override + public Map getAllPagesButtons(Player player) { + Map buttons = Maps.newHashMap(); + + Profile profile = eHub.getInstance().getProfileManager().getByUuid(player.getUniqueId()); + + int slot = 0; + for (ArmorType type : ArmorType.values()) { + + if (!type.hasPermission(player) && !type.isDonator()) { + continue; + } + + buttons.put(slot++, new Button() { + + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(Material.LEATHER_CHESTPLATE) + .setColor(type == ArmorType.ELEVATE ? Color.fromRGB(type.getR(), type.getG(), type.getB()) : type.getColor()) + .setName(type.getDisplayColor() + type.getName()) + .setLore(Arrays.asList("", + CC.AQUA + "Click here to select " + type.getDisplayColor() + type.getName() + CC.AQUA + ".")) + .get(); + } + + @Override + public String getName(Player player) { + return null; + } + + @Override + public List getDescription(Player player) { + return null; + } + + @Override + public Material getMaterial(Player player) { + return null; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + String name = ChatColor.stripColor(getButtonItem(player).getItemMeta().getDisplayName()); + ArmorType type = ArmorType.valueOf(name.replace(" ", "_").replace("+", "_PLUS").toUpperCase()); + + if (!type.hasPermission(player)) { + player.sendMessage(CC.RED + "No permission."); + return; + } + + if (profile.getArmorType() == type) { + Button.playFail(player); + + player.sendMessage(type.getDisplayColor() + type.getName() + CC.RED + " is already selected."); + return; + } + + profile.setArmorType(type); + + for (int i = 0; i < 4; i++) { + if (profile.isAstronaut() && i == 3) { + profile.getArmors()[i] = false; + } + + profile.getArmors()[i] = true; + } + + Button.playSuccess(player); + + player.closeInventory(); + } + }); + } + + Map formattedButtons = Maps.newHashMap(); + int slotfix = 0, nextCheck = 0; + + for (Button formattedButton : buttons.values()) { + if (slotfix == nextCheck - 1) { + slotfix++; + } + + if (slotfix == nextCheck) { + nextCheck += 9; + + formattedButtons.put(slotfix, Button.placeholder(Material.STAINED_GLASS_PANE, (byte) 15, " ")); + formattedButtons.put(slotfix + 8, Button.placeholder(Material.STAINED_GLASS_PANE, (byte) 15, " ")); + + slotfix++; + } + + formattedButtons.put(slotfix++, formattedButton); + } + + buttons = formattedButtons; + + return buttons; + } +} diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/menu/selector/ServerButton.java b/Network/eHub/src/main/java/com/elevatemc/ehub/menu/selector/ServerButton.java new file mode 100644 index 0000000..7a8a373 --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/menu/selector/ServerButton.java @@ -0,0 +1,57 @@ +package com.elevatemc.ehub.menu.selector; + +import com.elevatemc.ehub.eHub; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.util.Pair; +import com.google.common.collect.ImmutableList; +import dev.apposed.prime.spigot.module.server.scoreboard.PrimeScoreboardStyle; +import lombok.AllArgsConstructor; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@AllArgsConstructor +public class ServerButton extends Button { + + private final Material material; + private final String name; + private final String server; + private final String region; + private final List description; + + @Override + public String getName(Player player) { + final Pair style = PrimeScoreboardStyle.getStyle(player); + return style.getKey().toString() + ChatColor.BOLD + name; + } + + @Override + public List getDescription(Player player) { + final Pair style = PrimeScoreboardStyle.getStyle(player); + List meta = new ArrayList<>(); + + meta.add(""); + meta.add(style.getKey() + "┃ " + style.getValue() + "Playing: " + style.getKey() + eHub.getInstance().getServerPlayerCount("Elevate-Practice")); + meta.add(style.getKey() + "┃ " + style.getValue() + "Region: " + style.getKey() + region); + + return Stream.concat(description.stream(), meta.stream()) + .collect(Collectors.toList()); + } + + @Override + public Material getMaterial(Player player) { + return material; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + player.closeInventory(); + eHub.getInstance().getQueueHandler().joinQueue(player, server); + } +} \ No newline at end of file diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/menu/selector/ServerSelector.java b/Network/eHub/src/main/java/com/elevatemc/ehub/menu/selector/ServerSelector.java new file mode 100644 index 0000000..ee8d657 --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/menu/selector/ServerSelector.java @@ -0,0 +1,43 @@ +package com.elevatemc.ehub.menu.selector; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import com.elevatemc.elib.util.Pair; +import com.google.common.collect.ImmutableList; +import dev.apposed.prime.spigot.module.server.scoreboard.PrimeScoreboardStyle; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; + +public class ServerSelector extends Menu { + public ServerSelector() { + setAutoUpdate(true); + setPlaceholder(true); + } + + @Override + public String getTitle(Player player) { + final Pair style = PrimeScoreboardStyle.getStyle(player); + return style.getKey() + "Select a Game"; + } + + @Override + public Map getButtons(Player player) { + Map buttons = new HashMap<>(); + buttons.put(13, new ServerButton(Material.IRON_SWORD, "Practice NA", "Practice", "North America", + ImmutableList.of( + ChatColor.GRAY + "Our official practice playing experience", + ChatColor.GRAY + "with multiple features" + ) + )); + return buttons; + } + + @Override + public int size(Player player) { + return 9*3; + } +} diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/profile/Profile.java b/Network/eHub/src/main/java/com/elevatemc/ehub/profile/Profile.java new file mode 100644 index 0000000..9a48901 --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/profile/Profile.java @@ -0,0 +1,32 @@ +package com.elevatemc.ehub.profile; + +import com.elevatemc.ehub.eHub; +import com.elevatemc.ehub.type.armor.ArmorType; +import com.elevatemc.ehub.type.particle.ParticleType; +import lombok.Getter; +import lombok.Setter; + +import java.util.UUID; + +@Getter +@Setter +public class Profile { + private final eHub instance = eHub.getInstance(); + + private final UUID uuid; + private ArmorType armorType; + private ParticleType particleType; + private boolean enchanted, astronaut; + private boolean[] armors; + + public Profile(UUID uuid) { + this.uuid = uuid; + this.armorType = null; + this.particleType = null; + this.enchanted = false; + this.astronaut = false; + this.armors = new boolean[] {true, true, true, true}; + + getInstance().getProfileManager().getProfiles().put(uuid, this); + } +} diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/profile/manager/ProfileManager.java b/Network/eHub/src/main/java/com/elevatemc/ehub/profile/manager/ProfileManager.java new file mode 100644 index 0000000..e4cca57 --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/profile/manager/ProfileManager.java @@ -0,0 +1,22 @@ +package com.elevatemc.ehub.profile.manager; + + +import com.elevatemc.ehub.profile.Profile; +import lombok.Getter; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +@Getter +public class ProfileManager { + private final Map profiles; + + public ProfileManager() { + profiles = new HashMap<>(); + } + + public Profile getByUuid(UUID uuid) { + return profiles.get(uuid); + } +} diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/queue/QueueHandler.java b/Network/eHub/src/main/java/com/elevatemc/ehub/queue/QueueHandler.java new file mode 100644 index 0000000..8a89706 --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/queue/QueueHandler.java @@ -0,0 +1,32 @@ +package com.elevatemc.ehub.queue; + +import com.elevatemc.ehub.eHub; +import com.elevatemc.ehub.queue.listener.MessageListener; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; +import lombok.Getter; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.UUID; + +public class QueueHandler { + public QueueHandler() { + eHub.getInstance().getServer().getMessenger().registerOutgoingPluginChannel( eHub.getInstance(), "equeue:main"); + eHub.getInstance().getServer().getMessenger().registerIncomingPluginChannel( eHub.getInstance(), "equeue:main", new MessageListener()); + } + + @Getter private HashMap positions = new HashMap<>(); + + public void setPosition(Player player, String server, int position, int total) { + getPositions().put(player.getUniqueId(), new QueuePosition(server, position, total)); + } + + public void joinQueue(Player player, String queueName) { + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + out.writeUTF( "joinqueue" ); + out.writeUTF(queueName); + + player.sendPluginMessage(eHub.getInstance(), "equeue:main", out.toByteArray()); + } +} diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/queue/QueuePosition.java b/Network/eHub/src/main/java/com/elevatemc/ehub/queue/QueuePosition.java new file mode 100644 index 0000000..f420b1c --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/queue/QueuePosition.java @@ -0,0 +1,11 @@ +package com.elevatemc.ehub.queue; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +public class QueuePosition { + @Getter private String server; + @Getter private int position; + @Getter private int total; +} diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/queue/listener/MessageListener.java b/Network/eHub/src/main/java/com/elevatemc/ehub/queue/listener/MessageListener.java new file mode 100644 index 0000000..0bd01fe --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/queue/listener/MessageListener.java @@ -0,0 +1,40 @@ +package com.elevatemc.ehub.queue.listener; + +import com.elevatemc.ehub.eHub; +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteStreams; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.messaging.PluginMessageListener; + +import java.util.UUID; + +public class MessageListener implements PluginMessageListener { + @Override + public void onPluginMessageReceived(String s, Player p, byte[] bytes) { + if (!s.equalsIgnoreCase("equeue:main")) { + return; + } + try { + ByteArrayDataInput in = ByteStreams.newDataInput(bytes); + String subchannel = in.readUTF(); + if (subchannel.equals("position")) { + String playerUUIDString = in.readUTF(); + UUID uuid; + try { + uuid = UUID.fromString(playerUUIDString); + } catch (Exception ex) { + ex.printStackTrace(); + return; + } + Player player = Bukkit.getPlayer(uuid); + String server = in.readUTF(); + int position = in.readInt(); + int total = in.readInt(); + eHub.getInstance().getQueueHandler().setPosition(player, server, position, total); + } + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/scoreboard/eHubScoreGetter.java b/Network/eHub/src/main/java/com/elevatemc/ehub/scoreboard/eHubScoreGetter.java new file mode 100644 index 0000000..53482a2 --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/scoreboard/eHubScoreGetter.java @@ -0,0 +1,86 @@ +package com.elevatemc.ehub.scoreboard; + +import com.elevatemc.ehub.eHub; +import com.elevatemc.ehub.queue.QueueHandler; +import com.elevatemc.ehub.queue.QueuePosition; +import com.elevatemc.elib.autoreboot.AutoRebootHandler; +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.scoreboard.construct.ScoreGetter; +import com.elevatemc.elib.util.Pair; +import com.elevatemc.elib.util.TimeUtils; +import com.xxmicloxx.NoteBlockAPI.model.Song; +import com.xxmicloxx.NoteBlockAPI.songplayer.RadioSongPlayer; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import dev.apposed.prime.spigot.module.profile.punishment.Punishment; +import dev.apposed.prime.spigot.module.profile.punishment.type.PunishmentType; +import dev.apposed.prime.spigot.module.server.scoreboard.PrimeScoreboardStyle; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.Date; +import java.util.LinkedList; +import java.util.Optional; + +public class eHubScoreGetter implements ScoreGetter { + + private static final ProfileHandler profileHandler = eHub.getInstance().getPrime().getModuleHandler().getModule(ProfileHandler.class); + + @Override + public void getScores(LinkedList scores, Player player) { + Profile profile = profileHandler.getCache().getIfPresent(player.getUniqueId()); + final Pair style = PrimeScoreboardStyle.getStyle(player); + + if (profile != null) { + if (profile.hasActivePunishment(PunishmentType.BAN) || profile.hasActivePunishment(PunishmentType.BLACKLIST)|| player.hasMetadata("related")) { + scores.add("&4&l✘ You are banned ✘"); + scores.add("&4Type /register to appeal"); + } else { + RadioSongPlayer radioSongPlayer = eHub.getInstance().getRadioSongPlayer(); + QueueHandler queueHandler = eHub.getInstance().getQueueHandler(); + scores.add(style.getKey().toString() + ChatColor.BOLD + "Info:"); + scores.add(style.getKey() + "┃ " + style.getValue() + "Rank: " + profile.getHighestActiveNonHiddenGrant().getRank().getColoredDisplay()); + scores.add(style.getKey() + "┃ " + style.getValue() + "Global: " + style.getKey() + eHub.getInstance().getGlobalPlayerCount()); + scores.add(" "); + scores.add(style.getKey().toString() + ChatColor.BOLD + "Servers:"); + scores.add(style.getKey() + "┃ " + style.getValue() + "Practice: " + style.getKey() + eHub.getInstance().getServerPlayerCount("Elevate-Practice") + "/" + eHub.getInstance().getMaxPlayerCount("Elevate-Practice")); + + if (radioSongPlayer.getPlayerUUIDs().contains(player.getUniqueId())) { + Song song = radioSongPlayer.getSong(); + String title = song.getTitle(); + if (title.length() > 30) { + title = title.substring(0, 30); + } + scores.add(" "); + scores.add(style.getKey().toString() + ChatColor.BOLD + "Music:"); + scores.add(style.getKey() + "┃ " + style.getValue() + "Title: " + style.getKey() + title); + } + + if (queueHandler.getPositions().containsKey(player.getUniqueId())) { + scores.add(" "); + QueuePosition queuePosition = queueHandler.getPositions().get(player.getUniqueId()); + scores.add(style.getKey().toString() + ChatColor.BOLD + "Queue:"); + scores.add(style.getKey() + "┃&r " + style.getValue() + queuePosition.getServer() + ": " + style.getKey() + queuePosition.getPosition() + "/" + queuePosition.getTotal()); + } + } + } else { + scores.add("&7Loading..."); + } + + if (scores.size() <= 13) { + scores.add(""); + } + + if (scores.size() <= 13) { + scores.add(style.getKey() + " elevatemc.com "); + } + + if (scores.size() <= 13) { + scores.addFirst(""); + } + + if (scores.size() <= 13) { + scores.addFirst( ChatColor.GRAY.toString() + ChatColor.ITALIC + " " + eHubScoreboardConfiguration.date + " "); + } + } +} diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/scoreboard/eHubScoreboardConfiguration.java b/Network/eHub/src/main/java/com/elevatemc/ehub/scoreboard/eHubScoreboardConfiguration.java new file mode 100644 index 0000000..43a3c1e --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/scoreboard/eHubScoreboardConfiguration.java @@ -0,0 +1,41 @@ +package com.elevatemc.ehub.scoreboard; + +import com.elevatemc.ehub.eHub; +import com.elevatemc.elib.scoreboard.config.ScoreboardConfiguration; +import com.elevatemc.elib.scoreboard.construct.TitleGetter; +import com.elevatemc.elib.util.Pair; +import dev.apposed.prime.spigot.module.server.scoreboard.PrimeScoreboardStyle; +import dev.apposed.prime.spigot.util.Color; +import org.apache.commons.lang.StringEscapeUtils; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.text.SimpleDateFormat; +import java.util.Date; + +public final class eHubScoreboardConfiguration { + + private final static SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yy"); + public static String date; + public static ScoreboardConfiguration create() { + ScoreboardConfiguration configuration = new ScoreboardConfiguration(); + + Bukkit.getScheduler().runTaskTimerAsynchronously(eHub.getInstance(), () -> { + date = dateFormat.format(new Date()); + }, 0L, 20 * 60 * 5); + + configuration.setTitleGetter(new TitleGetter() { + @Override + public String getTitle(Player player) { + final Pair style = PrimeScoreboardStyle.getStyle(player); + return Color.translate(style.getKey().toString() + ChatColor.BOLD + "Elevate &7" + "❘" + style.getValue() + " Hub"); + } + }); + + configuration.setScoreGetter(new eHubScoreGetter()); + + return (configuration); + } + +} \ No newline at end of file diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/tab/eHubTabProvider.java b/Network/eHub/src/main/java/com/elevatemc/ehub/tab/eHubTabProvider.java new file mode 100644 index 0000000..1cc8787 --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/tab/eHubTabProvider.java @@ -0,0 +1,127 @@ +package com.elevatemc.ehub.tab; + +import cc.fyre.universe.Universe; +import com.elevatemc.ehub.eHub; +import com.elevatemc.elib.tab.provider.TabProvider; +import com.elevatemc.elib.util.Pair; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Table; +import com.mojang.authlib.properties.Property; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import dev.apposed.prime.spigot.module.server.scoreboard.PrimeScoreboardStyle; +import org.bukkit.ChatColor; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import org.bukkit.entity.Player; + +import java.util.Optional; + +public class eHubTabProvider implements TabProvider { + private static final ProfileHandler profileHandler = eHub.getInstance().getPrime().getModuleHandler().getModule(ProfileHandler.class); + + @Override + public Table provide(Player player) { + Table layout = HashBasedTable.create(); + Profile profile = profileHandler.getCache().getIfPresent(player.getUniqueId()); + + final Pair style = PrimeScoreboardStyle.getStyle(player); + + layout.put(0, 1, style.getKey().toString() + ChatColor.BOLD + "ElevateMC"); + layout.put(1, 1, style.getValue() + "Online: " + eHub.getInstance().getGlobalPlayerCount()); + + layout.put(2, 0, style.getKey().toString() + ChatColor.BOLD + "Website"); + layout.put(3, 0, style.getValue() + "elevatemc.com"); + + layout.put(5, 0, style.getKey().toString() + ChatColor.BOLD + "Telegram"); + layout.put(6, 0, style.getValue() + "t.me/elevatemc"); + + // Too big for 1.7 +// layout.put(8, 0, style.getKey().toString() + ChatColor.BOLD + "Twitter"); +// layout.put(9, 0, style.getValue() + "twitter.com/elevatemc"); + + layout.put(2, 2, style.getKey().toString() + ChatColor.BOLD + "Teamspeak"); + layout.put(3, 2, style.getValue() + "ts.elevatemc.com"); + + layout.put(5, 2, style.getKey().toString() + ChatColor.BOLD + "Store"); + layout.put(6, 2, style.getValue() + "store.elevatemc.com"); + +// layout.put(8, 2, style.getKey().toString() + ChatColor.BOLD + "NameMC"); +// layout.put(9, 2, style.getValue() + "/namemc"); + + layout.put(3, 1, style.getKey().toString() + ChatColor.BOLD + "Your Rank:"); + + if (profile != null) { + layout.put(4, 1, style.getValue() + profile.getHighestActiveNonHiddenGrant().getRank().getColoredDisplay()); + } else { + layout.put(4, 1, style.getValue() + "Loading..."); + } + + layout.put(6, 1, style.getKey().toString() + ChatColor.BOLD + "Server Info:"); + layout.put(7, 1, style.getKey() + "Practice: " + style.getValue() + eHub.getInstance().getServerPlayerCount("Elevate-Practice") + "/" + eHub.getInstance().getMaxPlayerCount("Elevate-Practice")); + return layout; + } + + + @Override + public String getHeader(Player player) { + final Pair style = PrimeScoreboardStyle.getStyle(player); + return + "\n&8▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒\n" + + "&8▒" + style.getKey() + "▟██&8▒" + style.getKey() + "█&8▒▒▒" + style.getKey() + "▟██&8▒" + style.getKey() + "█&8▒" + style.getKey() + "█&8▒" + style.getKey() + "▟█▙&8▒" + style.getKey() + "███&8▒" + style.getKey() + "▟██&8▒\n" + + "&8▒" + style.getKey() + "█&8▒▒▒" + style.getKey() + "█&8▒▒▒" + style.getKey() + "█&8▒▒▒" + style.getKey() + "█&8▒" + style.getKey() + "█&8▒" + style.getKey() + "█&8▒" + style.getKey() + "█&8▒▒" + style.getKey() + "█&8▒&8▒" + style.getKey() + "█&8▒▒▒\n" + + "&8▒" + style.getKey() + "█▀&8▒▒" + style.getKey() + "█&8▒▒▒" + style.getKey() + "█▀&8▒▒" + style.getKey() + "█▟▛&8▒" + style.getKey() + "█▀█&8▒&8▒" + style.getKey() + "█&8▒&8▒" + style.getKey() + "█▀&8▒▒\n" + + "&8▒" + style.getKey() + "▜██&8▒" + style.getKey() + "▜██&8▒" + style.getKey() + "▜██&8▒" + style.getKey() + "▜▛&8▒&8▒" + style.getKey() + "█&8▒" + style.getKey() + "█&8▒&8▒" + style.getKey() + "█&8▒&8▒" + style.getKey() + "▜██&8▒\n" + + "&8▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒\n" + + "\n" + + style.getKey() + "Welcome to the &lElevate Network" + style.getKey() + "!" + + "\n"; + } + + @Override + public String getFooter(Player player) { + final Pair style = PrimeScoreboardStyle.getStyle(player); + return "\n" + style.getKey() + "store.elevatemc.com\n"; + } + + private static final Property STORE = new Property("textures", "ewogICJ0aW1lc3RhbXAiIDogMTY1NjQyOTYxMjc3MSwKICAicHJvZmlsZUlkIiA6ICI0M2NmNWJkNjUyMDM0YzU5ODVjMDIwYWI3NDE0OGQxYiIsCiAgInByb2ZpbGVOYW1lIiA6ICJrYW1pbDQ0NSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iZDQ2NjJjODFhZjE2OGQ5MjZkODExYTAyZTdhZTYxOWViODM0OGNkZDU4OTRiNDE0MDI1ZDhiZThjZTA0MzUyIgogICAgfQogIH0KfQ==", "aMXPfoEJ57sqgnWz93KnlD7n+LEmgbc+tjhQZl4v6G7zW0Ad+jwirO5KJokZJtxNLrqL/3a+LmJBijsd08zHUWkpZWbZc6DUjxqV7sAHohpKhLIgDGG9ZXzkd34AtoUEhpWiDr5nthljhOcZxDbBgpGqJUJxAQxlI3/KBZQSGyXw2jrTep99YlrIzsfk9hEYGYbnjNa3BCXN7ua/gq6uq40rh7wMIhES7jBSAbwMgDuqoc0f3FkXbjuhBQl+rI7N+vj96GSAhQp4t8ijNvqHvtvM+OlgO7PmvGsq55U51nWWSTVw9ddsWLwj6edjAI48tQsO511BTk/6o5RJavCcx3CV3AtujG1Ovd8xKlGiZg06u02A21C3ViO/HaLMdXQqvD2Pl30R84fs3fUuWrKeD2ZUhBJU3VrBMB9Pu97KtuA8eoIgP32dhYHpSLEwYNl0DnCOmN7QDHIeo6cdc5ARdRZ6kaj2K+L8oflYRRV2QasgJRvspWzyLlqz8hnXiczptns1pyGe1aElCZA7iLIZpWfki/ljchu4+0bV0mOAIWtFqPj40YJE3Srp2eZqjmHIHh3xXs+DfU5IJ5NXEtyrJEp/8WDgQUZgiEc65U2Of+9xTZFgddlMaJwV243nvm/+H1qU1QgK2L27PcnZcF960sAhP/ZAd31D0nYPKcpRuwI="); + private static final Property PLAIN_COLOR_STORE = new Property("textures", "ewogICJ0aW1lc3RhbXAiIDogMTY1NjUzMDEwMzkyNiwKICAicHJvZmlsZUlkIiA6ICJmZTYxY2RiMjUyMTA0ODYzYTljY2E2ODAwZDRiMzgzZSIsCiAgInByb2ZpbGVOYW1lIiA6ICJNeVNoYWRvd3MiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2YzOTE5ZjUxNzg4NTlkZGMwOWQyZWU1YTlmN2RmZjVjODEyZTVhMjk2MjIwNjQ1N2E2Yjc0NmIwMmU0NzMwIgogICAgfQogIH0KfQ==", "o5+MVlyvH17ihr0/bL4QTO34ezmR5+zpWRsJkv8dNG/9rJpRnjifD6lMdI7+pBpvmesREc8/5i0RkvZme+/ASBfzOeGGA5ePUYEPaaXcK765GDo3DOctsbZ/rjPNrr0IlNZFopFzm3i9QILEZvqNISiw+yX2IVPjIa04RY4JJ3D2dCqo79MWqWkT/pj9V9XwApL8RTQhE6ZbofG/lVDtRBnhe78dYNfF4xIYVd8BxA2e7zi2Umoha5Mpzn2SPQv1mt+7j5WGoUllbkYKMqpFuSm29muxLsGwe4iI5Jr2nIrz5dofDlKw1zXQay1anGfqsYXKFwiCaFLQp4rQE8o1ScLNPwI5lGbSueprVd3wHXk+zeeg3XtkaJ+5qPujIWpUAsNtuT7bkCgK1TPBb7GeUKphFiyYRaqErMmmnMGiXdP9ikXwV+XLtdutgF+1fBlE5CgC6Xq3d+Ro7mcq6/qhYCIxhjWqwBxZJu5jS6IORH6EoPYu9XQIBgLtbtZMGn51anr6tu6Qp+9wYPUeY1fgzIZOsX9O6/RONjrVlbXn1sSBUFwOZ4iIJRxsnqbZUskiOs3iVA5sbbMN0nBLSMlMQdXroihzTsjQqERIxr4oEFmUkVeFOP18kPLjMHCMCo0VYhHs8AZw8BDlc8wKBlZlgo5FY1BpjVbn5K27HPQTI40="); + private static final Property WEBSITE = new Property("textures", "ewogICJ0aW1lc3RhbXAiIDogMTY1NjQyOTY3Mjk0OSwKICAicHJvZmlsZUlkIiA6ICJhMWU4OThjNWEyNmY0MTYyOTQyYmNmNmM1MzRiMTE5ZiIsCiAgInByb2ZpbGVOYW1lIiA6ICJ0b21keGlpaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hNGQxY2RhODQ4Y2EzZGEwZjg4NTNhYzA4ZmFiYzIxMWE4ZGFhZjEzNmNmZTllMTdiZDI2MTAwODE3ODdkOWM5IgogICAgfQogIH0KfQ==", "fHDjx3C6Ls7FvIqRKk0uW0YoSgE1kc/yUUARTESPovCFnuVcq7QAI8CM+ayuplDW7RyokwxYLTajh6dPDuAnlpSnvUPyZDN8boI7jGP48lIte60fu4m4isLHuGRsZynb+neRunw4Edn833tXKhoPbMqtv+hQddaMrKEk5Cu6npD52k3/AaZJ0Q8JwHCmvSZjyJP7DqmHYuTRQl2PWTFZagx+VXDiq4H3HLROnlWQWn3Ef5gNezmxj/5UuOsvN/DP5aRpexxyCYFk5giU6qgJxxmY1LwCP5tiOC62+y8CxKBZA3Hq5o7HViwo/4agNaJFgJyHFgvh3FUkGc7NNsKA3bIbB/ksW3s6G/ArW9N98K1fUEyaRZJHC2OjSM4BBK2oQjJU1Q1yLcBeIU5SVgx2/rhhxLJaFIhvWhHPhDkGWU0MZ7YF1/ibxOgu83FhlKsOvykSqoFYcZkImjFTQUTJo5eA3tq5BvMeBdZMqGyr/v3qMoFCQ5N+xcD+EIZdjv1HeUs8u3LsjsipxCeWcapc9CI1HJ5z2mQCTowU3p3YqTC44y6OZacswL/BmIrzPkEhf7+UJjZke1eFpg+DAXlVx58m9JDHHH/s8iCUpF8yQ2++y+ddOuvPT7SynBaNdp/wr1Oqp7c46c6rVAQyREi/Q0LGMD4QzD6+0tX4hhqUzr4="); + private static final Property PLAIN_COLOR_WEBSITE = new Property("textures", "ewogICJ0aW1lc3RhbXAiIDogMTY1NjUzMDI3MzgxMywKICAicHJvZmlsZUlkIiA6ICIzOTVkZTJlYjVjNjU0ZmRkOWQ2NDAwY2JhNmNmNjFhNyIsCiAgInByb2ZpbGVOYW1lIiA6ICJzcGFyZXN0ZXZlIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzkxYzIxZWFmOWYzYjVhMTg2OWM3ZDY0NTM4NWQwZDVhYTRjZmUyNGE4NzRmMzViOThkOGFhMDBkNDBlODc4ZTAiCiAgICB9CiAgfQp9", "pdORfhGepV7FkwDpY6/zEpemALAU2pmXoNgaQ6P1hfVoyfjbDeZSHsWKRDvJbqNVg4yuoLuydksWewv2M9EIqqx7v2/X3UDJdN4uqTMjZ7ZENILTg8m9x34WVfyEORaPnPsnUn2sJYT4FukUvaoMQKk+hJU4uoAoufbhOmRfp3IMI84Uc7ZNxHrEYSyrOl6VxLD+HypQecLVc0uiCo0W9oI/Ryu/jo0AJV5+lYHPq4Tvmy0yQpDRqHFMBoqlZhwia8sYAWtMlRPAAauFsKY4m9bPM+F5wyIQvvkMQiOcs5XypU3Tssdk/AM8X9o/0yv85mXq9vbpFUEOBCRUyRVhBMSrTSyrZt/MVSi4LNsYMZ+FYHolU8h8Dyc5XtkoKILARNKHKA4lg8k3kPjeRzHmFjy8TklPRgfhNgVo+3MQ+vLkqVPOVslkKeGZT9QDMpqOj69fXBp6O5diwMuOyU3g/XZdErdnKEvWxXgZZxPh0ej+nZtDDOdFCgvjJFeT92Qrv2Mua7yvI98MyxKHDgRzl1aDCVEh14aM3CQ0QTFH8uswiKzM8mP6O2a0sSUh+xWvuqi6AKsig64W+wDHmhAuJcFwftf71g2wmOgtW0roaZoxH74TISn8Vzw43nNA2OWQ5RNpBVv9EnQFLjzTxwvM/X/AAAQ2NTo//V4w6/mAXoQ="); + private static final Property TEAMSPEAK = new Property("textures", "ewogICJ0aW1lc3RhbXAiIDogMTY1NjQyOTgyMDExOSwKICAicHJvZmlsZUlkIiA6ICIwYzE1OTI3Yjc4OTY0MTA3OTA5MWQyMjkxN2U0NmIyYyIsCiAgInByb2ZpbGVOYW1lIiA6ICJQYXkyV2F5IiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2RlNGI2MzQ5OGU3MTM0NmYzMGQwY2NkODcwMDliMDk2YWVkZTgxM2U2ODMwZjFmMjY4MjQ1OWE5OTU0MWIyZmYiCiAgICB9CiAgfQp9", "fY9msPrtguvXdzYkpBhEkYKm8hDq4ODdxLtWFeSoKi9Mj8Of0Z+eDRfw1tg4PvdPpg6QsNUEH86o+2vvF/ys8n9SfpYBHlM562KuSjDJmoDsxGja6Yq0BPRLC8bd0l936bhL0++FdPmFxKc1OYHpAKjmUJsS9oXFir+WwBKUXi/khqgdPbMzzxbCzUezDLsxdBZQ5dsjhAApEfd/Ajss4UwFdjqHG+cftbFMzqe+1IsuUwqHsfl6vRrjRbFYl8BMEfPLNkn/jIusYAqmYbPyyPeIZ2CjMjSnfDsxGHxI3zsaE07ee9lbZGaTysCV/vDmUz3t3jMxJkMS8pC23HikOAJE5ERJwBbA2NRYANMPAjR6RxFLimW+yaaKu87kUrIlTSrSKtbA4cAoXMSWB1lisz/tob3V0HCPQXpazMjTu4ICMTendw7LhJ42KHPGnqxjVzY1ipZVIXOv0W2WsvJLbVG3BZwTJNDUkdC4etSmvDMUUZmpUQdwBRsnJCYe2Ep4x14qb9j9khdmp5KKcOs1JyNsIrmgCLRKCuH+OSc1thrqk4sDwjpHGma7buHbEtmCGeAFjZxDHoPEHf3yhRWIgoKPe6K1h3ztozNmlcbimPUpgUdh5bVeMLkycGBRz9ydS4PHjPJ1y/AjRkpnbvNzH3lCtK6hoF/vz+PSWajbTwM="); + private static final Property PLAIN_COLOR_TEAMSPEAK = new Property("textures", "ewogICJ0aW1lc3RhbXAiIDogMTY1NjUzMDMzNDUxOCwKICAicHJvZmlsZUlkIiA6ICJmMjc0YzRkNjI1MDQ0ZTQxOGVmYmYwNmM3NWIyMDIxMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJIeXBpZ3NlbCIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jZjk5YWRmMDM3YWNkNDAwMzQ0ZjhhZmRkZmE1ZTQ5ZDM5YmMzNmViZGIzYWQ4ZWM4MzkxOTA5YzU2OTM5ODYwIgogICAgfQogIH0KfQ==", "dCJqNYmwS7zLm9fl+G8riCuXLeznTT3elmzlhXmiv6iwzpZ3oa/tY4fv/hotYTiaXCY0KlPPVmCQC+88T3Gs80nPxBbe+nFfjsYGXG3JU1dGbF66cqpnZuhb0P38FcSdizdZMynYcVaUtGn7JUY9YrdMfuhGOemRmJtlEb7pShyuNQ34AVUCp85pDxjyj+JtAYVQUWWD+P/PU/C+ga3SMtIdNl9fCze4rDShMYO/RmHQgCo8oMlHdzUhgKDMxNgJXWv9SgHmn0VqZOxVs8cvkd3xpC9m2/aowgeuCcsUlVZ8Klmymf5IXsePgE92gJNEUP9l2txSwnfOjnHae5AgdmX2/h8l3PYkHQ7Afi1ceNMIm/E1YR+kuMkD7gAYtvFpLkLocX8n29dArLqw1s5TeogXpe6GB+PwvSWGK3Yu980ZCoMu6sAbyAPyYOxVnSr2gpCZ66QZwEOpRWIwCVEDQ72Cr2Xr99ITAU1ffDTP8M2crPd7VvR57rxyda6mH77Pml+KwcOrosFUdMx3ZyVX91n41MgFl3bIT/guoToXHgg52dQLrWOaTdWMqp/SprtUcocI953m0vQvo1krfC9SraKaOyiSxoiE3agA2FL4iu5kj8T9dFCkro/dSgjorgw9eg9+7Vk+GJmgFaf7KLAgornlxlNtl6KQagfQ2r3ApRk="); + private static final Property TELEGRAM = new Property("textures", "ewogICJ0aW1lc3RhbXAiIDogMTY1NjUyMjAyMDM5OCwKICAicHJvZmlsZUlkIiA6ICIxNzU1N2FjNTEzMWE0YTUzODAwODg3Y2E4ZTQ4YWQyNSIsCiAgInByb2ZpbGVOYW1lIiA6ICJQZW50YXRpbCIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85YTk1ZDZlNDA3ODI4YTM0MDI0NzA4M2E5YTNjOWY1MDQyMDBkNzk3ZTNmYmI0NjFmOGIxNzNkN2NkZjIzZWZmIgogICAgfQogIH0KfQ==", "ETIO4ghhnBoL88HsoFOS+SDSrIlwRZ2CSUJ+Ki+ULtW4PKqBNcTs9Tkug9eYLph6Zsd9XHzvRZIOCka0d9N9RYtTNL262lMJj1S6mKJGhFC3+n14rn2uvlZliN5nYdZH/U0NaszvZ926jnE9GH6M9VBGYnN8Ax5uLkm6QQUbBZABce43KmEi5q1cvJZKAHS+yub2fmv2qfv/S7NLAfcATQquVLTmwVj9IJc8QQJXvbvB28ar1F1IUKIvxGjcFgr5/Oq4YjSgBv7Ge6oJYxvI7fYnOwwOsyYcimXV5NookI8UiAunSpjtYDByN85eSPjCDjUu/gO6F97fWDFJh52b2FAmsFU87pGDDlEiaca6Ku9LILPRVf9923f6duMXZGATGeTPswigaIjiSmXDtyEjLv+COP9G+zul9yvK4M0fFJH7iEig0xKj7ZNGQb4QN25v0LEwbygmYm/CXoWJOhPf69vlx01nlGNweAR4U3tZ5C31mrUIIsIC6xGBCoy0QHoS0rAhEz58mSqthqibjU2n8UbTxL3Cytz0kraY9ZsdVHEPFRJx7/lfB2+/7mB1JjMYwzxNwTKYsjze2bjr3rAmm+tViAsDMbhRROKrm9rQfFnsS4vW9n32OTvfXkGyQ1TbPamz7T/eesWz/1FbmSI8l6/MCf7SwKTgqHaTJOPyPyU="); + private static final Property PLAIN_COLOR_TELEGRAM = new Property("textures", "ewogICJ0aW1lc3RhbXAiIDogMTY1NjUyOTk3MDE5MiwKICAicHJvZmlsZUlkIiA6ICI4NTRhYTNiNDVkZDk0OTNhYmZiZThkNWU4MTBjNmMyYyIsCiAgInByb2ZpbGVOYW1lIiA6ICJERERhdmlkYSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jZDRiZmNjY2Q4OTExYzFjOWQxMDI3ODA2YmY4OWMyZTJiYTQ1ZDAyM2Y3OTJlZDE0MzZkMTFjM2Y1NzYyM2MxIgogICAgfQogIH0KfQ==", "OHARxHfRAffpuL6qojoK56h5Hb9zTKTaYDtRaexQ7Zxd3HGp8DsF4zmdGLTdARtu7eDeY6zYrP7uZBgTPxldz0Dy6a/FdJtnt76hMVJpZFExC21B2F4e6l50Zpb8NNDkfIr/7jzHDiwSG4lG/hWNLbZU+cl1mF4yRapUG7IDCSkO1Zqsv/PcxjWIobNZNaGnJiW5/Q5RthtyoLyEyL8V1P9V+WYDHBHBdjSQDniyBWLbuTblILuh/YzPfCb99f3D95qcIoI/U/D4nrZVGqRZe9V7iSAf2lc2y9/Hd0tcYZFQ7M7D6iXbT+ewflhlO26dAe/jLWN0sIzxlrftl5OiKvMX78ssajGzOzz4QmXHB+CYCSYZtwCgo8Yv7oxKP3U6NUcAjXW/IMcIS5QvcXGNzERuyRlfcYrU4gvWv2NCXHCkMM8cQ5fQmolrljFTLt99lGqHLUDfDAPTtmHBJ6SG+lt5Ux6RhkiLyx4bBf/7rA7MtRF8YiNCIx3rmSXYBoW8eNJh32O8F3SWXX39Ir+MqM9wdtA4BlWJc6CNY/C4SNKo4agtY5556+blIs9Kvu3CfmHEKZtVn++bdGceCFWnbG1S9TjuyK/o6gx2V8SJlg2gvYmfPywgVbEfthV3jUasmYLdwxkjQCxJlAO2Nei0bWXL1cvK5+96rodJHLgCySU="); + private static final Property ELEVATE = new Property("textures", "ewogICJ0aW1lc3RhbXAiIDogMTY1NjUzMTQxNjY5NSwKICAicHJvZmlsZUlkIiA6ICJiMjdjMjlkZWZiNWU0OTEyYjFlYmQ5NDVkMmI2NzE0YSIsCiAgInByb2ZpbGVOYW1lIiA6ICJIRUtUMCIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zZTBkMDdhZmM4YjlmZGIyODVmOTA2NDcwNjgyZDQzMWE2ZmFmMDYxYmQwYTlhYTJlZGViMzIzNzZhMTE4YzYyIgogICAgfQogIH0KfQ==", "FWcUxMtAQih7OE4CC0A5jtrxPkwWmtr32kloEj7L1lQ7RDgEliCeMHsb/tUWWHpWKwczllEnZJ1sNAqJ8XcW3A2+INELThapQAu/stPP+hOEcsZf4/DrJeTgIWlkkUyr1uT45bgFCllBsl0Jx4YI+36ZplC5B0Ci34fjJrQvOHidswYoEf/yYUOLI2b3dngofmi4t69Ec87g9xOHoEeKhMIPLup+wl7an5RrLxb+G7+G3daufZ792a1o3kju+mBYV32fAA4D1/LXweanGaoFJGN2PN8tnydlGWXXcth38EjFR3Vh7Xq11JaU8/JWonpkl+R6RU16aWdXu/inKtMhfiS8wqUkkQPCDHgLx2zdkRcz7aD4medM9cl3sPSrsNDwL4sRHZY4Emu7mlfSyG9QlOhWhdYkQdHk3usaHvGMQem1f5JYRLUL4PhGhqOiKLZ5oOCPUcbTSSyXnCOkPReaByiOOweAZp0gM/oqhGRpRz90i+m44P8JnT8VYvs8vWF+CxRuPXMESCKD0ebtFZcPKNjMUYzx9DuaoOg+UxBPLKGAjob9e6m09ayde4d08zAXUXYQdE/NGP2xPWtcl8N1ZJWJMXMKeC5fjhzV70EqJIcS73L++aaY8gNZrdXBZiR0oKcxjrOC4OV06dnD5+inGF0RkxfG9omYLvtHl9Vxoyk="); + private static final Property RANK = new Property("textures", "ewogICJ0aW1lc3RhbXAiIDogMTY1NjUzMTMyNzUwNiwKICAicHJvZmlsZUlkIiA6ICIwNTkyNTIxZGNjZWE0NzRkYjE0M2NmMDg2MDA1Y2FkNyIsCiAgInByb2ZpbGVOYW1lIiA6ICJwdXIyNCIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lYzM4NzA5YThmYWNkOTI2M2EzNDMzOGZhYTQ2ZDk2YWUwYmExZmM4MTQyNTllMTNjYTc4YTY4ODkyOTkwMGZkIgogICAgfQogIH0KfQ==", "thV8esXRHwzH4C/2RoiPahhSNlpssRFpWZI38S3QWiJWBdeuPvKxYj9eb+Znh0nRPF4WS4e/kfL0Zq6B3XzRbgBMgv/Uoy/5oDmrEWOIAloBrTCsdmoXvg9roPj2J5TaasExpuZE5xWKNenA0f2DyGdcHdC5uJausgId6iGR4N+6XmkV+BChlI6OTMsNwdeslgWTU/d7XVclHqE3Ji/oyZnv1JX1sIzzvzuqxF3tCtnOYJybsLlR+1H7kAfbRm7v29FXafHlNHOtRHo1oSZH/PUz2ZFClBlUuIyV0eF4URoYaTrb4+a04NMdohiwb9/1Ot4Fd2MgD4Q40V+WP7foa6hTOE4WMMKrxLWqZ7KK+QF7sdvD8XTGW+ZFscSrobUhW7bCdDjNrf/s43/iL2Wp50m+DpQHnVN5Wz2gm696q9rkbPNNSCS82NVLCRdHZMEs/KdJBJLSYQB/OmYCT985PutdgSqsEeUE6YUemVNbC/bnsvIMgV0DOCCTfrLdGnSRZ+epCUTXU3CjL11lTwKjSemY5xjaxJX1pVTqekgiwjPb+Eqk5+lIQOC30nnGQR6qMQPNLda5AZzi2qtpMnCHqRj2CXGtE8L2fWuWbess+Ip9D1F5kDzD7DwkJQbM2lbigbrE7vDfMo35I7pUdsZd0KJHPAbpb0D+nyl/WxQNFyg="); + private static final Property SERVER_INFO = new Property("textures", "ewogICJ0aW1lc3RhbXAiIDogMTY1NjUzMDU2MTYxOCwKICAicHJvZmlsZUlkIiA6ICJkOGNkMTNjZGRmNGU0Y2IzODJmYWZiYWIwOGIyNzQ4OSIsCiAgInByb2ZpbGVOYW1lIiA6ICJaYWNoeVphY2giLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzUwMDcwMGY2Y2ViNDQ4YmY5ODc4NmQxYzVkZDNkZTQ1MWNjMWNhZjA2MzBiMjE5N2I2ODk0ODMzZTE0MGQ2IgogICAgfQogIH0KfQ==", "k2uc3xXRsktxkKc3oxnsw2i53Cxa3l0eNu9dey0sG1DStJIjQALsT5cpSnPclbxN/KbS/me7zL15MG3Zn9lv/fEN0npW9M+DECEG6C7CbActEZoUb1/3ulc2MskQkPIlmSDu9eK+MCQ9w25qPlYHcYpWuYYeiXUTpCbnBzMoh414jYa2xjlTm4ATsCGf9uJkmFR1AW+nbVEyYTLfQdNTBUzjEmUJNsyG4UgBP/wPoagUHJapS6hqP8/hh2dhrRCeSm+YSHdEiusJ4uv5sB3lGQtjiDuWhgIa2Sc51m0YegCRPvo+wFtpN9KrM9nsD4mHfs0ZVWHc5Poxpw/A9Q2MByzXrmJA4mCtuayrOiDdszYyWg9K4CfHQV/RGQ/V2iRNqs/WjSjwGfqhxnn2BdCgxoQvGNw2Yul6Km76VYsgKjMTRJl8xk0XQkcq4bjiqraMRc/yyZUL9LOEm6icJ7/UDWyCyYZfYTN/sHg4OHbDtgW16O0XmgNj3N2Hly1IzwlLMHz748ogagycXIFpEKWoFalW3QFh4tEk/RvoZWCKSl9I2GPQkPkaXWDvv1mEL0wml9268R0AqZT6VRwkHnLwbtJU3CpwVFX+c8VN4OL0Je9K2uYs6yHb/DpMOpTf/5JYAGQUOkVnpbsUca4MdYZjSkU4CtcCC3ushSNtRXeOB0Q="); + private static final Property INFO_PLAIN = new Property("textures", "ewogICJ0aW1lc3RhbXAiIDogMTY1NjUzMDY0NDI5MSwKICAicHJvZmlsZUlkIiA6ICI1NTZjNDNmOWJmZjU0MjI1OTI0NDU5M2EwN2QyYzE1MSIsCiAgInByb2ZpbGVOYW1lIiA6ICJHaW50b2tsIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzkxMDFlZWMyNjI5NzNhM2UxMjk4NTc1MDY5NjgyODUwMTk0ODBjODhjOTgxODRkYzc0NTg4NTU0MzlkZGNjYjAiCiAgICB9CiAgfQp9", "kSE1tZTqtjPudXxGzbpDPrapuf0hDb6aP1gXoItg3Kki8SFCPoK1UGgcpoFlkDHrdB5K0r/JR9DBzCr2OfmuL5A8W/fKl/XjUa+hQZs1cak68tkyCAc5VaaU8wkfABO7S4yVooPFpB3rJ+O4i6bk0zNNj/ig0sDoXAWzF0Cf6Q0EDN7cvaiGGJT5zhZCPyh+VVuM4t6poEmaCsoOJdUIhJAvEqS91q2ar8x847uLuA+Rz0qux6w6UY3QpzX9DtCluVS4DUXv69N1tCvzBGJQfqRR3PAaSBTM/rwNBvej+yr84FHOvUHbn14P4pZefLYVXvXZcYUqwTk2jXqF+nt5LNQaHhYWPolNn7lRrk5nMHNUVFt8PLfURqGMAgZeA1iNdegK/517d6ALYJC+969ZaZOJ7bcsSLJwKwZHsPo/5zWIMaNMBYUoPHzSdD7ZWZy4YWbohbmLE9a5Vf1wF5CfZORm6pyQQnRJUw8f3yEQcslQe1yvzVrdxW7d1dBTuScGv2I8C7n/rSuklWv+r+42cvilY7VvaITlybnJyNNF9ro2oNtPfhSc5LwNpBbPq/P9f0K8bJiMPV0cPSN56XXZVQuNCZ1NV04C3WwZIo3Zsxj5wu4NCVrWKXlOQwXEgw/xCAgvzgiM/gnWWKyHmenZhYGAa7vJWxTxHMeR/w2j/H4="); + + @Override + public Table getHeads(Player player) { + Table heads = HashBasedTable.create(); + + // Website + heads.put(2, 0, WEBSITE); + heads.put(3, 0, PLAIN_COLOR_WEBSITE); + + heads.put(5, 0, TELEGRAM); + heads.put(6, 0, PLAIN_COLOR_TELEGRAM); + + heads.put(2, 2, TEAMSPEAK); + heads.put(3, 2, PLAIN_COLOR_TEAMSPEAK); + + heads.put(5, 2, STORE); + heads.put(6, 2, PLAIN_COLOR_STORE); + + heads.put(0, 1, ELEVATE); + heads.put(1, 1, INFO_PLAIN); + + heads.put(3, 1, RANK); + heads.put(4, 1, INFO_PLAIN); + + heads.put(6, 1, SERVER_INFO); + heads.put(7, 1, INFO_PLAIN); + + return heads; + } +} diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/type/armor/ArmorType.java b/Network/eHub/src/main/java/com/elevatemc/ehub/type/armor/ArmorType.java new file mode 100644 index 0000000..6713ad5 --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/type/armor/ArmorType.java @@ -0,0 +1,217 @@ +package com.elevatemc.ehub.type.armor; + +import com.elevatemc.ehub.utils.CC; +import com.elevatemc.elib.util.cosmetics.ArmorUtil; +import lombok.Getter; +import lombok.Setter; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + + +@Getter +public enum ArmorType { + + BASIC("Basic", CC.GRAY, Color.GRAY, 8, null), + VIP("VIP", CC.GREEN, Color.LIME, 5, null), + MVP("MVP", CC.BLUE, Color.BLUE, 11, null), + PRO("PRO", CC.GOLD, Color.ORANGE, 1, null), + + ELEVATE("Elevate", CC.DARK_AQUA, Color.BLUE, 11, (Runnable) () -> { + ArmorType type = ArmorType.ELEVATE; + + if (type.helper >= 600) { + type.reverse = true; + } else if (type.helper <= 0) { + type.reverse = false; + } + + if (type.reverse) { + type.helper -= 2; + } else { + type.helper += 2; + } + + Color color = ArmorUtil.COLORS.get(type.helper); + + if (color != null) { + type.r = color.getRed(); + type.g = color.getGreen(); + type.b = color.getBlue(); + + int glassColor = ArmorUtil.parseColor(color); + + if (glassColor != -1) { + type.astronaut = glassColor; + } + } + }), + + MEDIA("Media", CC.PINK, Color.fromRGB(255, 0, 255), 6, (Runnable) () -> { + ArmorType type = ArmorType.MEDIA; + + if (type.r >= 255) { + type.reverse = true; + } else if (type.r <= 170) { + type.reverse = false; + } + + if (type.reverse) { + type.r -= 2; + type.b -= 2; + } else { + type.r += 2; + type.b += 2; + } + }), + + MOD("Moderator", CC.DARK_PURPLE, Color.PURPLE, 2, (Runnable) () -> { + ArmorType type = ArmorType.MOD; + + if (type.b <= 50) { + type.reverse = true; + } else if (type.b >= 102) { + type.reverse = false; + } + + if (type.reverse) { + if (type.r > 51) { + type.r--; + } + + type.b++; + } else { + if (type.r < 80) { + type.r++; + } + + type.b--; + } + }), + + ADMIN("Admin", CC.RED, Color.RED, 14, (Runnable) () -> { + ArmorType type = ArmorType.ADMIN; + + if (type.r >= 255) { + type.reverse = true; + } else if (type.r <= 161) { + type.reverse = false; + } + + if (type.reverse) { + type.r -= 2; + } else { + type.r += 2; + } + }), + + DEVELOPER("Developer", CC.AQUA, Color.AQUA, 3, (Runnable) () -> { + ArmorType type = ArmorType.DEVELOPER; + + if (type.b >= 255) { + type.reverse = true; + } else if (type.b <= 153) { + type.reverse = false; + } + + if (type.reverse) { + if (type.g > 76) { + type.g -= 2; + } + + type.b -= 3; + } else { + if (type.g < 128) { + type.g += 2; + } + + type.b += 2; + } + }), + + OWNER("Owner", CC.D_RED, Color.MAROON, 14, (Runnable) () -> { + ArmorType type = ArmorType.OWNER; + + if (type.r >= 200) { + type.reverse = true; + } else if (type.r <= 128) { + type.reverse = false; + } + + if (type.reverse) { + type.r -= 2; + } else { + type.r += 2; + } + }); + + private final String name, displayColor; + private final Color color; + private int astronaut; + private int r, g, b, helper; + + @Setter + private Runnable runnable; + + private String armorType; + + @Setter + private boolean reverse; + + private ItemStack[] items; + + ArmorType(String name, String displayColor, Color color, int astronaut, Object object) { + this.name = name; + this.displayColor = displayColor; + this.color = color; + this.astronaut = astronaut; + + if(object != null) { + if (object instanceof String) { + armorType = (String) object; + + } else { + runnable = (Runnable) object; + } + } + + r = color.getRed(); + g = color.getGreen(); + b = color.getBlue(); + + reverse = false; + } + + public boolean isDonator() { + return ordinal() <= 3; + } + + public boolean hasPermission(Player player) { + return player.hasPermission(getPermissionForAll()) || player.hasPermission(getPermission()); + } + + public String getPermissionForAll() { + return "core.cosmetic.armor.*"; + } + + public String getPermission() { + return "core.cosmetic.armor." + name().toLowerCase(); + } + + public ItemStack[] getItems() { + if (items != null) { + return items; + } + + items = new ItemStack[4]; + + items[3] = new ItemStack(Material.LEATHER_HELMET); + items[2] = new ItemStack(Material.LEATHER_CHESTPLATE); + items[1] = new ItemStack(Material.LEATHER_LEGGINGS); + items[0] = new ItemStack(Material.LEATHER_BOOTS); + + return items; + } +} + diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/type/armor/task/ArmorTask.java b/Network/eHub/src/main/java/com/elevatemc/ehub/type/armor/task/ArmorTask.java new file mode 100644 index 0000000..cd2f838 --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/type/armor/task/ArmorTask.java @@ -0,0 +1,94 @@ +package com.elevatemc.ehub.type.armor.task; + +import com.elevatemc.ehub.eHub; +import com.elevatemc.ehub.profile.Profile; +import com.elevatemc.ehub.type.armor.ArmorType; +import com.elevatemc.ehub.utils.ItemBuilder; +import org.bukkit.Bukkit; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.scheduler.BukkitRunnable; + +public class ArmorTask extends BukkitRunnable { + + public ArmorTask() { + runTaskTimerAsynchronously(eHub.getInstance(), 40L, 1L); + } + + @Override + public void run() { + + for (ArmorType type : ArmorType.values()) { + if (type.getRunnable() == null && type.getArmorType() == null) { + + for (ItemStack item : type.getItems()) { + if (item == null) { + continue; + } + + ItemBuilder.copyOf(item).setColor(type.getColor()); + } + + continue; + } + + Color color = null; + + if (type.getRunnable() != null) { + type.getRunnable().run(); + color = Color.fromRGB(type.getR(), type.getG(), type.getB()); + + } else if (type.getArmorType() != null) { + + ArmorType newType = ArmorType.valueOf(type.getArmorType()); + color = Color.fromRGB(newType.getR(), newType.getG(), newType.getB()); + } + + if (color == null) { + continue; + } + + for (ItemStack item : type.getItems()) { + if (item == null) { + continue; + } + + ItemBuilder.copyOf(item).setColor(color); + } + } + + for (Player player : Bukkit.getOnlinePlayers()) { + Profile profile = eHub.getInstance().getProfileManager().getByUuid(player.getUniqueId()); + ArmorType type = profile.getArmorType(); + + if (type == null) { + continue; + } + + PlayerInventory inventory = player.getInventory(); + + for (ItemStack item : type.getItems()) { + ItemBuilder builder = ItemBuilder.copyOf(item); + + if (profile.isEnchanted()) { + builder.addEnchantment(Enchantment.DURABILITY); + } else { + builder.clearEnchantments(); + } + } + + inventory.setHelmet(profile.isAstronaut() ? + new ItemBuilder(Material.STAINED_GLASS).setDurability(type.getAstronaut()).get() : profile.getArmors()[3] ? type.getItems()[3] : null); + + inventory.setChestplate(profile.getArmors()[2] ? type.getItems()[2] : null); + inventory.setLeggings(profile.getArmors()[1] ? type.getItems()[1] : null); + inventory.setBoots(profile.getArmors()[0] ? type.getItems()[0] : null); + + player.updateInventory(); + } + } +} diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/type/particle/ParticleType.java b/Network/eHub/src/main/java/com/elevatemc/ehub/type/particle/ParticleType.java new file mode 100644 index 0000000..22fbf75 --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/type/particle/ParticleType.java @@ -0,0 +1,113 @@ +package com.elevatemc.ehub.type.particle; + + +import com.elevatemc.ehub.type.particle.impl.ParticleCallable; +import com.elevatemc.ehub.type.particle.impl.ParticleMeta; +import com.elevatemc.ehub.utils.CC; +import com.elevatemc.ehub.utils.ParticleUtil; +import lombok.Getter; +import lombok.Setter; + +import org.bukkit.Color; +import org.bukkit.Effect; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.Arrays; + +import static com.elevatemc.elib.util.ParticleMath.cos; +import static com.elevatemc.elib.util.ParticleMath.sin; + +@Getter +public enum ParticleType { + + VIP("VIP Particle", CC.GREEN, 32, Color.LIME, (location, player) -> { + double angle = (double) ParticleType.VIP.ticks * 0.19634954084936207; + double cos = Math.cos(angle); + double sin = Math.sin(angle); + + Location topRingLocation = location.clone().add(0.1, 0, 0.1).add(0.8 * cos, 1.4, 0.8 * sin); + + for (int i = 0; i < 5; ++i) { + ParticleUtil.sendsParticleToAll(new ParticleMeta(topRingLocation, Effect.HAPPY_VILLAGER)); + } + }), + MVP("MVP Particle", CC.BLUE, 32, Color.BLUE, (location, player) -> { + Location location2 = location.clone().add(0.1, 0.0, 0.1); + + double angle = (double) ParticleType.MVP.ticks * 0.19634954084936207; + double cos = Math.cos(angle); + double sin = Math.sin(angle); + + Location bottomRingLocation = location2.clone().add(0.8 * cos, 0.6, 0.8 * sin); + Location topRingLocation = location2.clone().add(0.8 * cos, 1.4, 0.8 * sin); + + for (int i = 0; i < 5; ++i) { + ParticleUtil.sendsParticleToAll(new ParticleMeta(bottomRingLocation, Effect.LAVADRIP)); + ParticleUtil.sendsParticleToAll(new ParticleMeta(topRingLocation, Effect.LAVADRIP)); + } + }), + + PRO("Pro Particle", CC.GOLD, 40, Color.ORANGE, (location, player) -> { + Location location2 = location.clone().add(0.1, 0.0, 0.1); + + double angle = (double) ParticleType.PRO.ticks * 0.15707963267948966; + double cos = Math.cos(angle); + double sin = Math.sin(angle); + + ArrayList particleMetaList = new ArrayList<>(); + particleMetaList.add(new ParticleMeta(location2.clone().add(1.0 * cos, 0.5 + 1.0 * cos, 1.0 * sin), Effect.WATERDRIP)); + particleMetaList.add(new ParticleMeta(location2.clone().add(1.0 * cos, 1.0 + 1.0 * cos, 1.0 * sin), Effect.WATERDRIP)); + particleMetaList.add(new ParticleMeta(location2.clone().add(1.0 * cos, 1.5 + 1.0 * cos, 1.0 * sin), Effect.WATERDRIP)); + + ParticleUtil.sendsParticleToAll(particleMetaList.toArray(new ParticleMeta[0])); + }), + ELEVATE("Elevate Particle", CC.DARK_AQUA, 32, Color.TEAL, (location, player) -> { + + double angle = (double) ParticleType.ELEVATE.ticks * 0.19634954084; + double cos = Math.cos(angle); + double sin = Math.sin(angle); + + for (double t = 0; t <= 2 * Math.PI; t += Math.PI / 8) { + for (double i = 0; i <= 1; i += 1) { + + Location cone = location.clone().add(0.4 * (2 * Math.PI - t) * 0.5 * cos(t + angle + i * Math.PI), 0.5 * t, 0.4 * (2 * Math.PI - t) * 0.5 * sin(t + angle + i * Math.PI)); + + ParticleUtil.sendsParticleToAll(new ParticleMeta(cone, Effect.COLOURED_DUST)); + } + } + }); + private final String name, displayColor; + private final int frequency; + private final Color color; + private final ParticleCallable callable; + + @Setter + private int ticks; + + public static ParticleType getByName(String input) { + return Arrays.stream(values()).filter((type) -> type.name().equalsIgnoreCase(input) || type.getName().equalsIgnoreCase(input)).findFirst().orElse(null); + } + + public boolean hasPermission(Player player) { + return player.hasPermission(getPermissionForAll()) || player.hasPermission(getPermission()); + } + + public String getPermissionForAll() { + return "core.cosmetics.particle.*"; + } + + public String getPermission() { + return "core.cosmetics.particle." + name().toLowerCase(); + } + + ParticleType(String name, String displayColor, int frequency, Color color, ParticleCallable callable) { + this.name = name; + this.callable = callable; + this.displayColor = displayColor; + this.frequency = frequency; + this.color = color; + this.ticks = 0; + } + } diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/type/particle/impl/ParticleCallable.java b/Network/eHub/src/main/java/com/elevatemc/ehub/type/particle/impl/ParticleCallable.java new file mode 100644 index 0000000..2bf3d26 --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/type/particle/impl/ParticleCallable.java @@ -0,0 +1,8 @@ +package com.elevatemc.ehub.type.particle.impl; + +import org.bukkit.Location; +import org.bukkit.entity.Player; + +public interface ParticleCallable { + void call(Location location, Player player); +} diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/type/particle/impl/ParticleMeta.java b/Network/eHub/src/main/java/com/elevatemc/ehub/type/particle/impl/ParticleMeta.java new file mode 100644 index 0000000..73d3836 --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/type/particle/impl/ParticleMeta.java @@ -0,0 +1,27 @@ +package com.elevatemc.ehub.type.particle.impl; + +import java.beans.ConstructorProperties; + +import lombok.Getter; +import lombok.Setter; + +import org.bukkit.Effect; +import org.bukkit.Location; + +@Getter +@Setter +public final class ParticleMeta { + private final Location location; + private final Effect effect; + private float offsetX = 0.0f; + private float offsetY = 0.0f; + private float offsetZ = 0.0f; + private float speed = 1.0f; + private int amount = 1; + + @ConstructorProperties(value={"location", "effect"}) + public ParticleMeta(Location location, Effect effect) { + this.location = location; + this.effect = effect; + } +} diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/type/particle/task/ParticleTask.java b/Network/eHub/src/main/java/com/elevatemc/ehub/type/particle/task/ParticleTask.java new file mode 100644 index 0000000..773a639 --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/type/particle/task/ParticleTask.java @@ -0,0 +1,38 @@ +package com.elevatemc.ehub.type.particle.task; + +import com.elevatemc.ehub.eHub; +import com.elevatemc.ehub.profile.Profile; +import com.elevatemc.ehub.type.particle.ParticleType; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +public class ParticleTask extends BukkitRunnable { + private final eHub plugin = eHub.getInstance(); + + public ParticleTask() { + runTaskTimerAsynchronously(eHub.getInstance(), 40L, 1L); + } + + @Override + public void run() { + for (ParticleType particle : ParticleType.values()) { + if (particle.getTicks() >= particle.getFrequency()) { + particle.setTicks(-1); + } + + particle.setTicks(particle.getTicks() + 1); + } + + for (Player player : Bukkit.getOnlinePlayers()) { + Profile profile = plugin.getProfileManager().getByUuid(player.getUniqueId()); + ParticleType type = profile.getParticleType(); + + if (type == null) { + continue; + } + + type.getCallable().call(player.getLocation(), player); + } + } +} diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/utils/CC.java b/Network/eHub/src/main/java/com/elevatemc/ehub/utils/CC.java new file mode 100644 index 0000000..8baf563 --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/utils/CC.java @@ -0,0 +1,81 @@ +package com.elevatemc.ehub.utils; + +import org.bukkit.ChatColor; + +//Thank you qLib +public final class CC { + + public static final String BLUE = ChatColor.BLUE.toString(); + public static final String AQUA = ChatColor.AQUA.toString(); + public static final String YELLOW = ChatColor.YELLOW.toString(); + public static final String RED = ChatColor.RED.toString(); + public static final String GRAY = ChatColor.GRAY.toString(); + public static final String GOLD = ChatColor.GOLD.toString(); + public static final String GREEN = ChatColor.GREEN.toString(); + public static final String WHITE = ChatColor.WHITE.toString(); + public static final String BLACK = ChatColor.BLACK.toString(); + public static final String BOLD = ChatColor.BOLD.toString(); + public static final String ITALIC = ChatColor.ITALIC.toString(); + public static final String STRIKE_THROUGH = ChatColor.STRIKETHROUGH.toString(); + public static final String RESET = ChatColor.RESET.toString(); + public static final String MAGIC = ChatColor.MAGIC.toString(); + public static final String OBFUSCATED = MAGIC; + public static final String B = BOLD; + public static final String M = MAGIC; + public static final String O = MAGIC; + public static final String I = ITALIC; + public static final String S = STRIKE_THROUGH; + public static final String R = RESET; + public static final String DARK_BLUE = ChatColor.DARK_BLUE.toString(); + public static final String DARK_AQUA = ChatColor.DARK_AQUA.toString(); + public static final String DARK_GRAY = ChatColor.DARK_GRAY.toString(); + public static final String DARK_GREEN = ChatColor.DARK_GREEN.toString(); + public static final String DARK_PURPLE = ChatColor.DARK_PURPLE.toString(); + public static final String DARK_RED = ChatColor.DARK_RED.toString(); + public static final String D_BLUE = DARK_BLUE; + public static final String D_AQUA = DARK_AQUA; + public static final String D_GRAY = DARK_GRAY; + public static final String D_GREEN = DARK_GREEN; + public static final String D_PURPLE = DARK_PURPLE; + public static final String D_RED = DARK_RED; + public static final String LIGHT_PURPLE = ChatColor.LIGHT_PURPLE.toString(); + public static final String L_PURPLE = LIGHT_PURPLE; + public static final String PINK = L_PURPLE; + public static final String B_BLUE = BLUE + B; + public static final String B_AQUA = AQUA + B; + public static final String B_YELLOW = YELLOW + B; + public static final String B_RED = RED + B; + public static final String B_GRAY = GRAY + B; + public static final String B_GOLD = GOLD + B; + public static final String B_GREEN = GREEN + B; + public static final String B_WHITE = WHITE + B; + public static final String B_BLACK = BLACK + B; + public static final String BD_BLUE = D_BLUE + B; + public static final String BD_AQUA = D_AQUA + B; + public static final String BD_GRAY = D_GRAY + B; + public static final String BD_GREEN = D_GREEN + B; + public static final String BD_PURPLE = D_PURPLE + B; + public static final String BD_RED = D_RED + B; + public static final String BL_PURPLE = L_PURPLE + B; + public static final String I_BLUE = BLUE + I; + public static final String I_AQUA = AQUA + I; + public static final String I_YELLOW = YELLOW + I; + public static final String I_RED = RED + I; + public static final String I_GRAY = GRAY + I; + public static final String I_GOLD = GOLD + I; + public static final String I_GREEN = GREEN + I; + public static final String I_WHITE = WHITE + I; + public static final String I_BLACK = BLACK + I; + public static final String ID_RED = D_RED + I; + public static final String ID_BLUE = D_BLUE + I; + public static final String ID_AQUA = D_AQUA + I; + public static final String ID_GRAY = D_GRAY + I; + public static final String ID_GREEN = D_GREEN + I; + public static final String ID_PURPLE = D_PURPLE + I; + public static final String IL_PURPLE = L_PURPLE + I; + public static final String S_GRAY = GRAY + STRIKE_THROUGH; + + public static String formatInteger(int value) { + return String.format("%,d", value); + } +} diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/utils/HubItems.java b/Network/eHub/src/main/java/com/elevatemc/ehub/utils/HubItems.java new file mode 100644 index 0000000..16ac703 --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/utils/HubItems.java @@ -0,0 +1,32 @@ +package com.elevatemc.ehub.utils; + +import com.elevatemc.elib.util.ItemUtils; +import lombok.experimental.UtilityClass; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import java.util.Collections; + +@UtilityClass +public final class HubItems { + public static final ItemStack SELECT_SERVER = new ItemStack(Material.WATCH); + public static final ItemStack MUSIC_ENABLED = new ItemStack(Material.GREEN_RECORD); + public static final ItemStack MUSIC_DISABLED = new ItemStack(Material.RECORD_3); + + public static final ItemStack COSMETICS = new ItemStack(Material.NETHER_STAR); + public static final ItemStack ENDER_PEARLS = new ItemStack(Material.ENDER_PEARL); + + static { + ItemUtils.setDisplayName(SELECT_SERVER, ChatColor.DARK_AQUA + "Games " + ChatColor.GRAY + "(Right Click)" ); + ItemUtils.setLore(SELECT_SERVER, Collections.singletonList(ChatColor.GRAY + "Use this item to select the game you wish to play")); + ItemUtils.setDisplayName(MUSIC_ENABLED, ChatColor.DARK_AQUA + "Toggle Music: " + ChatColor.GREEN + "Enabled " + ChatColor.GRAY + "(Right Click)" ); + ItemUtils.setDisplayName(MUSIC_DISABLED, ChatColor.DARK_AQUA + "Toggle Music: " + ChatColor.RED + "Disabled " + ChatColor.GRAY + "(Right Click)" ); + ItemUtils.setLore(MUSIC_ENABLED, Collections.singletonList(ChatColor.GRAY + "Use this item to toggle the music")); + ItemUtils.setLore(MUSIC_DISABLED, Collections.singletonList(ChatColor.GRAY + "Use this item to toggle the music")); + ItemUtils.setDisplayName(COSMETICS, ChatColor.DARK_AQUA + "Cosmetics " + ChatColor.GRAY + "(Right Click)"); + ItemUtils.setDisplayName(ENDER_PEARLS, ChatColor.DARK_AQUA + "Ender Pearl " + ChatColor.GRAY + "(Right Click)"); + ItemUtils.setLore(ENDER_PEARLS, Collections.singletonList(ChatColor.GRAY + "Use this item to fly away with enderpearls")); + ENDER_PEARLS.setAmount(64); + } +} \ No newline at end of file diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/utils/ItemBuilder.java b/Network/eHub/src/main/java/com/elevatemc/ehub/utils/ItemBuilder.java new file mode 100644 index 0000000..9da9903 --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/utils/ItemBuilder.java @@ -0,0 +1,167 @@ +package com.elevatemc.ehub.utils; + +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.LeatherArmorMeta; +import org.bukkit.inventory.meta.SkullMeta; +import org.bukkit.material.MaterialData; + +import java.util.ArrayList; +import java.util.List; + + +//Thank you qLib +public class ItemBuilder { + private final ItemStack itemStack; + + public static ItemBuilder copyOf(ItemBuilder builder) { + return new ItemBuilder(builder.get()); + } + + public static ItemBuilder copyOf(ItemStack item) { + return new ItemBuilder(item); + } + + public ItemBuilder(Material material) { + itemStack = new ItemStack(material); + } + + public ItemBuilder(ItemStack itemStack) { + this.itemStack = itemStack; + } + + public ItemBuilder setAmount(int amount) { + itemStack.setAmount(Math.min(amount, 64)); + return this; + } + + public ItemBuilder setName(String name) { + ItemMeta meta = itemStack.getItemMeta(); + meta.setDisplayName(Color.translate(name)); + itemStack.setItemMeta(meta); + return this; + } + + public ItemBuilder addLoreLine(String name) { + ItemMeta meta = itemStack.getItemMeta(); + List lore = meta.getLore(); + + if (lore == null) { + lore = new ArrayList<>(); + } + + lore.add(Color.translate(name)); + meta.setLore(lore); + itemStack.setItemMeta(meta); + return this; + } + + public ItemBuilder setLore(List lore) { + List toSet = new ArrayList<>(); + ItemMeta meta = itemStack.getItemMeta(); + + lore.forEach((string) -> toSet.add(Color.translate(string))); + + meta.setLore(toSet); + itemStack.setItemMeta(meta); + return this; + } + + public ItemBuilder setDurability(int durability) { + itemStack.setDurability((short)durability); + return this; + } + + public ItemBuilder setData(int data) { + itemStack.setData(new MaterialData(itemStack.getType(), (byte)data)); + return this; + } + + public ItemBuilder addEnchantment(Enchantment enchantment, int level) { + itemStack.addUnsafeEnchantment(enchantment, level); + return this; + } + + public ItemBuilder addEnchantment(Enchantment enchantment) { + itemStack.addUnsafeEnchantment(enchantment, 1); + return this; + } + + public ItemBuilder setType(Material material) { + itemStack.setType(material); + return this; + } + + public ItemBuilder clearLore() { + ItemMeta meta = itemStack.getItemMeta(); + meta.setLore(new ArrayList<>()); + + itemStack.setItemMeta(meta); + return this; + } + + public ItemBuilder clearEnchantments() { + itemStack.getEnchantments().keySet().forEach(itemStack::removeEnchantment); + return this; + } + + public ItemBuilder setColor(org.bukkit.Color color) { + if (itemStack.getType() != Material.LEATHER_BOOTS + && itemStack.getType() != Material.LEATHER_CHESTPLATE + && itemStack.getType() != Material.LEATHER_HELMET + && itemStack.getType() != Material.LEATHER_LEGGINGS) { + + throw new IllegalArgumentException("color() only applicable for leather armor."); + } else { + LeatherArmorMeta meta = (LeatherArmorMeta) itemStack.getItemMeta(); + meta.setColor(color); + + itemStack.setItemMeta(meta); + return this; + } + } + + public ItemBuilder setOwner(String owner) { + SkullMeta meta = (SkullMeta) itemStack.getItemMeta(); + meta.setOwner(owner); + itemStack.setItemMeta(meta); + return this; + } + + public ItemStack get() { + return itemStack; + } + + public static void rename(ItemStack stack, String name) { + ItemMeta meta = stack.getItemMeta(); + meta.setDisplayName(Color.translate(name)); + stack.setItemMeta(meta); + } + + public static ItemStack createItem(Material material, String name) { + ItemStack item = new ItemStack(material); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(Color.translate(name)); + item.setItemMeta(meta); + return item; + } + + public static ItemStack createItem(Material material, String name, int amount) { + ItemStack item = new ItemStack(material, amount); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(Color.translate(name)); + item.setItemMeta(meta); + return item; + } + + public static ItemStack createItem(Material material, String name, int amount, int damage) { + ItemStack item = new ItemStack(material, amount, (short) damage); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(Color.translate(name)); + item.setItemMeta(meta); + return item; + } +} diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/utils/ParticleUtil.java b/Network/eHub/src/main/java/com/elevatemc/ehub/utils/ParticleUtil.java new file mode 100644 index 0000000..a502d44 --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/utils/ParticleUtil.java @@ -0,0 +1,60 @@ +package com.elevatemc.ehub.utils; + +import com.elevatemc.ehub.type.particle.impl.ParticleMeta; +import com.elevatemc.elib.util.Pair; + +import net.minecraft.server.v1_8_R3.EnumParticle; +import net.minecraft.server.v1_8_R3.Packet; +import net.minecraft.server.v1_8_R3.PacketPlayOutWorldParticles; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import org.bukkit.entity.Player; + +import java.util.ArrayList; + +public class ParticleUtil { + + public static void sendsParticleToAll(ParticleMeta... particleMetas) { + ArrayList> packets = new ArrayList<>(); + + for (ParticleMeta meta : particleMetas) { + PacketPlayOutWorldParticles packet; + packet = new PacketPlayOutWorldParticles(); + + packet.a = EnumParticle.values()[meta.getEffect().getId()]; + packet.j = false; + + packet.b = (float) meta.getLocation().getX(); + packet.c = (float) meta.getLocation().getY(); + packet.d = (float) meta.getLocation().getZ(); + packet.e = meta.getOffsetX(); + packet.f = meta.getOffsetY(); + packet.g = meta.getOffsetZ(); + packet.h = meta.getSpeed(); + packet.i = meta.getAmount(); + packets.add(new Pair<>(meta.getLocation(), packet)); + } + + for (Pair pair : packets) { + double squared = 256 * 256; + + Location center = pair.getKey(); + String worldName = center.getWorld().getName(); + + for (Player player : Bukkit.getOnlinePlayers()) { + if (!player.getWorld().getName().equals(worldName) || player.getLocation().distanceSquared(center) > squared) { + continue; + } + ((CraftPlayer)player).getHandle().playerConnection.sendPacket(pair.getValue()); + } + + } + } + + private ParticleUtil() { + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } +} + diff --git a/Network/eHub/src/main/java/com/elevatemc/ehub/utils/PlayerCountTask.java b/Network/eHub/src/main/java/com/elevatemc/ehub/utils/PlayerCountTask.java new file mode 100644 index 0000000..1c4d8db --- /dev/null +++ b/Network/eHub/src/main/java/com/elevatemc/ehub/utils/PlayerCountTask.java @@ -0,0 +1,37 @@ +package com.elevatemc.ehub.utils; + +import com.elevatemc.ehub.eHub; +import org.bukkit.Bukkit; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.util.List; + +public class PlayerCountTask implements Runnable { + List servers; + + public PlayerCountTask(List servers) { + this.servers = servers; + } + + public void run() { + if (eHub.getInstance().getServer().getOnlinePlayers().size() == 0) { + return; + } + for (String server : servers) { + pingBungee(server); + } + } + + private void pingBungee(String server) { + ByteArrayOutputStream b = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(b); + try { + out.writeUTF("PlayerCount"); + out.writeUTF(server); + } catch (Exception ex) { + ex.printStackTrace(); + } + Bukkit.getServer().sendPluginMessage(eHub.getInstance(), "BungeeCord", b.toByteArray()); + } +} diff --git a/Network/eHub/src/main/resources/plugin.yml b/Network/eHub/src/main/resources/plugin.yml new file mode 100644 index 0000000..a54c196 --- /dev/null +++ b/Network/eHub/src/main/resources/plugin.yml @@ -0,0 +1,7 @@ +main: com.elevatemc.ehub.eHub +name: eHub +version: '${version}' +description: The hub plugin for ElevateMC +author: ElevateMC Development Team +website: https://elevatemc.com +depend: [eLib, Prime] diff --git a/Network/eLib/build.gradle b/Network/eLib/build.gradle new file mode 100644 index 0000000..78ba868 --- /dev/null +++ b/Network/eLib/build.gradle @@ -0,0 +1,63 @@ +plugins { + id 'com.github.johnrengelman.shadow' version '7.1.2' + id 'java' + id 'maven-publish' +} + +group 'com.elevatemc' +version '1.0' + +sourceSets { + main.java.srcDirs = ['src/main/java'] + main.resources.srcDirs = ['src/main/resources'] +} + +compileJava.options.encoding = 'UTF-8' + +repositories { + mavenCentral() + maven { + url 'https://repo.dmulloy2.net/repository/public/' + } + maven { url 'https://jitpack.io' } +} + +dependencies { + compileOnly 'org.projectlombok:lombok:1.18.22' + annotationProcessor 'org.projectlombok:lombok:1.18.22' + compileOnly files('../lib/espigot.jar') + compileOnly files('../lib/primespigot.jar') + compileOnly files('../lib/lcapi.jar') + implementation 'org.mongodb:mongo-java-driver:3.12.10' + implementation 'redis.clients:jedis:4.2.0' + implementation 'org.reflections:reflections:0.10.2' + + shadow 'org.mongodb:mongo-java-driver:3.12.10' + shadow 'redis.clients:jedis:4.2.0' + shadow 'org.reflections:reflections:0.10.2' +} + +shadowJar { + configurations = [project.configurations.shadow] +} + +build { + dependsOn(shadowJar) +} + +publishing { + publications { + shadow(MavenPublication) { publication -> + project.shadow.component(publication) + } + } +} + +processResources { + def props = [version: 'git rev-parse --verify --short HEAD'.execute().text.trim()] + inputs.properties props + filteringCharset 'UTF-8' + filesMatching('plugin.yml') { + expand props + } +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/autoreboot/AutoRebootHandler.java b/Network/eLib/src/main/java/com/elevatemc/elib/autoreboot/AutoRebootHandler.java new file mode 100644 index 0000000..929a521 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/autoreboot/AutoRebootHandler.java @@ -0,0 +1,43 @@ +package com.elevatemc.elib.autoreboot; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.autoreboot.task.ServerRebootTask; +import lombok.Getter; + +public class AutoRebootHandler { + + @Getter private ServerRebootTask serverRebootTask = null; + + public AutoRebootHandler() { + eLib.getInstance().getCommandHandler().registerPackage(eLib.getInstance(),"com.elevatemc.elib.autoreboot.command"); + } + + public void rebootServer(long time) { + + if (this.serverRebootTask != null) { + throw new IllegalStateException("A reboot is already in progress."); + } + + this.serverRebootTask = new ServerRebootTask(time); + this.serverRebootTask.runTaskTimer(eLib.getInstance(),20L,20L); + } + + public boolean isRebooting() { + return this.serverRebootTask != null; + } + + public int getRebootSecondsRemaining() { + return this.serverRebootTask == null ? -1 : this.serverRebootTask.getSecondsRemaining(); + } + + public void cancelReboot() { + + if (this.serverRebootTask == null) { + return; + } + + this.serverRebootTask.cancel(); + this.serverRebootTask = null; + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/autoreboot/command/RebootCancelCommand.java b/Network/eLib/src/main/java/com/elevatemc/elib/autoreboot/command/RebootCancelCommand.java new file mode 100644 index 0000000..5d4d49a --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/autoreboot/command/RebootCancelCommand.java @@ -0,0 +1,27 @@ +package com.elevatemc.elib.autoreboot.command; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.command.Command; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; + +public class RebootCancelCommand { + + @Command( + names = {"shutdown cancel"}, + permission = "elib.command.reboot.cancel" + ) + public static void execute(CommandSender sender) { + + if (!eLib.getInstance().getAutoRebootHandler().isRebooting()) { + sender.sendMessage(ChatColor.RED + "No reboot has been scheduled."); + return; + } + + eLib.getInstance().getAutoRebootHandler().cancelReboot(); + eLib.getInstance().getServer().broadcastMessage(ChatColor.RED + "⚠ " + ChatColor.DARK_RED + ChatColor.STRIKETHROUGH + "------------------------" + ChatColor.RED + " ⚠"); + eLib.getInstance().getServer().broadcastMessage(ChatColor.RED + "The server reboot has been cancelled."); + eLib.getInstance().getServer().broadcastMessage(ChatColor.RED + "⚠ " + ChatColor.DARK_RED + ChatColor.STRIKETHROUGH + "------------------------" + ChatColor.RED + " ⚠"); + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/autoreboot/command/RebootScheduleCommand.java b/Network/eLib/src/main/java/com/elevatemc/elib/autoreboot/command/RebootScheduleCommand.java new file mode 100644 index 0000000..ae8e5d3 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/autoreboot/command/RebootScheduleCommand.java @@ -0,0 +1,26 @@ +package com.elevatemc.elib.autoreboot.command; + +import com.elevatemc.elib.command.param.Parameter; +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.util.TimeUtils; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; + +public class RebootScheduleCommand { + + @Command( + names = {"shutdown","shutdown schedule", "reboot", "reboot schedule"}, + permission = "elib.command.reboot.schedule" + ) + public static void execute(CommandSender sender,@Parameter(name = "time")long time) { + if(eLib.getInstance().getAutoRebootHandler().isRebooting()) { + sender.sendMessage(ChatColor.RED + "Server is currently restarting already!"); + return; + } + eLib.getInstance().getAutoRebootHandler().rebootServer(time); + sender.sendMessage(ChatColor.GOLD + "Scheduled a reboot in " + TimeUtils.formatIntoDetailedString((int)(time / 1000))); + + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/autoreboot/task/ServerRebootTask.java b/Network/eLib/src/main/java/com/elevatemc/elib/autoreboot/task/ServerRebootTask.java new file mode 100644 index 0000000..8255d50 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/autoreboot/task/ServerRebootTask.java @@ -0,0 +1,60 @@ +package com.elevatemc.elib.autoreboot.task; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.util.TaskUtil; +import com.elevatemc.elib.util.TimeUtils; +import lombok.Getter; +import org.bukkit.ChatColor; +import org.bukkit.scheduler.BukkitRunnable; +import com.elevatemc.elib.util.message.MessageBuilder; +import com.elevatemc.elib.util.message.MessageTranslator; + +public class ServerRebootTask extends BukkitRunnable { + private static final String line = MessageTranslator.translate("&4&m---------------------------------"); + @Getter private int secondsRemaining; + @Getter private boolean wasWhitelisted; + + public ServerRebootTask(long time) { + this.secondsRemaining = (int)(time / 1000); + this.wasWhitelisted = eLib.getInstance().getServer().hasWhitelist(); + } + + public void run() { + + if (this.secondsRemaining == 300) { + eLib.getInstance().getServer().setWhitelist(true); + } else if (this.secondsRemaining == 5) { + eLib.getInstance().getServer().setWhitelist(this.wasWhitelisted); + + eLib.getInstance().getLogger().info("Sending everyone to hub..."); + TaskUtil.runSync(() -> { + for (Player player : Bukkit.getOnlinePlayers()) { + player.kickPlayer(ChatColor.RED + "The server was shutdown..."); + } + }); + } + + if (secondsRemaining > 0 && (secondsRemaining <= 10 || (secondsRemaining <= 60 && secondsRemaining % 5 == 0) || (secondsRemaining % 30 == 0))) { + String message = MessageBuilder + .error("Server is rebooting in {}.") + .prefix("⚠") + .element(TimeUtils.formatIntoMMSS(secondsRemaining)) + .build(); + Bukkit.broadcastMessage(line); + Bukkit.broadcastMessage(message); + Bukkit.broadcastMessage(line); + } + + if (secondsRemaining <= 0) { + TaskUtil.runSync(Bukkit::shutdown); + } + secondsRemaining--; + } + + public synchronized void cancel() throws IllegalStateException { + super.cancel(); + eLib.getInstance().getServer().setWhitelist(this.wasWhitelisted); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/border/Border.java b/Network/eLib/src/main/java/com/elevatemc/elib/border/Border.java new file mode 100644 index 0000000..f61cadb --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/border/Border.java @@ -0,0 +1,285 @@ +package com.elevatemc.elib.border; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.border.event.border.BorderChangeEvent; +import com.elevatemc.elib.border.runnable.BorderTask; +import com.elevatemc.elib.cuboid.Cuboid; +import lombok.Getter; +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Entity; + +public class Border { + + @Getter private final Location origin; + @Getter private Material material; + @Getter private int size; + @Getter private int height; + @Getter private boolean wrapTerrain; + @Getter private BorderConfiguration borderConfiguration; + @Getter private Effect particle; + @Getter private BorderTask borderTask; + @Getter private Cuboid physicalBounds; + + @Getter private static final boolean[] airBlocks = new boolean[256]; + + public Border(Location origin,Material material,int size,int height) { + this.material = Material.BEDROCK; + this.wrapTerrain = false; + this.borderConfiguration = BorderConfiguration.DEFAULT_CONFIGURATION; + this.origin = origin; + this.size = size; + this.height = height; + this.material = material == null ? Material.BEDROCK : material; + this.physicalBounds = new Cuboid(origin.clone().add((double)(size + 1), (double)origin.getWorld().getMaxHeight() - origin.getY(), (double)(size + 1)), origin.clone().subtract((double)(size + 1), origin.getY(), (double)(size + 1))); + this.borderTask = new BorderTask(this); + + eLib.getInstance().getBorderHandler().addBorder(this); + } + + public Cuboid contract(int amount) { + this.size -= amount; + + final Cuboid prev = this.physicalBounds.clone(); + + this.physicalBounds = this.physicalBounds.inset(Cuboid.CuboidDirection.HORIZONTAL, amount); + + return prev; + } + + public Cuboid expand(int amount) { + + this.size += amount; + + final Cuboid prev = this.physicalBounds.clone(); + + this.physicalBounds = this.physicalBounds.expand(Cuboid.CuboidDirection.NORTH, amount).expand(Cuboid.CuboidDirection.SOUTH, amount).expand(Cuboid.CuboidDirection.EAST, amount).expand(Cuboid.CuboidDirection.WEST, amount); + + return prev; + } + + public Cuboid setSize(int size) { + return this.setSize(size, true); + } + + public Cuboid setSize(int size, boolean callEvent) { + return this.setSize(size, this.height, callEvent); + } + + public Cuboid setSize(int size, int height, boolean callEvent) { + int previousSize = this.size; + this.size = size; + this.height = height; + Cuboid prev = this.physicalBounds.clone(); + + this.physicalBounds = new Cuboid(this.origin.clone().add((double)(size + 1), (double)this.origin.getWorld().getMaxHeight() - this.origin.getY(), (double)(size + 1)), this.origin.clone().subtract((double)(size + 1), this.origin.getY(), (double)(size + 1))); + + if (callEvent) { + eLib.getInstance().getServer().getPluginManager().callEvent(new BorderChangeEvent(this,previousSize,prev,BorderTask.BorderAction.SET)); + } + + return prev; + } + + public boolean contains(Block block) { + return this.contains(block.getX(), block.getZ()); + } + + public boolean contains(Entity entity) { + return this.contains(entity.getLocation()); + } + + public boolean contains(Location location) { + return this.contains(location.getBlockX(), location.getBlockZ()); + } + + public boolean contains(int x, int z) { + return x > this.physicalBounds.getLowerX() && x < this.physicalBounds.getUpperX() && z > this.physicalBounds.getLowerZ() && z < this.physicalBounds.getUpperZ(); + } + + public void fill() { + World world = this.origin.getWorld(); + int xMin = this.physicalBounds.getLowerX(); + int xMax = this.physicalBounds.getUpperX(); + int zMin = this.physicalBounds.getLowerZ(); + int zMax = this.physicalBounds.getUpperZ(); + int tick = 0; + int chunksPerTick = 20; + + int chunkX; + for(chunkX = zMin >> 4; chunkX <= zMax >> 4; ++chunkX) { + + final int finalChunkX = chunkX; + + eLib.getInstance().getServer().getScheduler().runTaskLater(eLib.getInstance(), () -> { + Chunk chunk = world.getChunkAt(xMin >> 4, finalChunkX); + + for(int z = Math.max(zMin, finalChunkX << 4); z < Math.min(zMax, finalChunkX + 1 << 4); ++z) { + this.fillAtXZ(world, chunk, xMin, z); + } + + }, (long)(tick++ / chunksPerTick)); + eLib.getInstance().getServer().getScheduler().runTaskLater(eLib.getInstance(), () -> { + Chunk chunk = world.getChunkAt(xMax >> 4, finalChunkX); + + for(int z = Math.max(zMin + 1, finalChunkX << 4); z < Math.min(zMax + 1, finalChunkX + 1 << 4); ++z) { + this.fillAtXZ(world, chunk, xMax, z); + } + + }, (long)(tick++ / chunksPerTick)); + } + + for(chunkX = xMin >> 4; chunkX <= xMax >> 4; ++chunkX) { + + final int finalChunkX = chunkX; + + eLib.getInstance().getServer().getScheduler().runTaskLater(eLib.getInstance(), () -> { + Chunk chunk = world.getChunkAt(finalChunkX, zMin >> 4); + + for(int x = Math.max(xMin + 1, finalChunkX << 4); x < Math.min(xMax + 1, finalChunkX + 1 << 4); ++x) { + this.fillAtXZ(world, chunk, x, zMin); + } + + }, (long)(tick++ / chunksPerTick)); + eLib.getInstance().getServer().getScheduler().runTaskLater(eLib.getInstance(), () -> { + Chunk chunk = world.getChunkAt(finalChunkX, zMax >> 4); + + for(int x = Math.max(xMin, finalChunkX << 4); x < Math.min(xMax, finalChunkX + 1 << 4); ++x) { + this.fillAtXZ(world, chunk, x, zMax); + } + + }, (long)(tick++ / chunksPerTick)); + } + + } + + private void fillAtXZ(World world, Chunk chunk, int x, int z) { + int y; + if (this.wrapTerrain) { + for(y = world.getHighestBlockYAt(x, z); airBlocks[chunk.getBlock(x, y, z).getType().getId()] && y > 0; --y) { + ; + } + + for(y += this.height; y >= 0; --y) { + chunk.getBlock(x, y, z).setTypeIdAndData(this.material.getId(), (byte)0, false); + } + } else { + for(y = 0; y <= this.origin.getBlockY() + this.height; ++y) { + chunk.getBlock(x, y, z).setTypeIdAndData(this.material.getId(), (byte)0, false); + } + } + + } + + public Location correctLocation(Location location) { + Cuboid cuboid = this.getPhysicalBounds(); + int validX = location.getBlockX(); + int validZ = location.getBlockZ(); + EnsureAction xAction = null; + EnsureAction zAction = null; + if (location.getBlockX() + 2 > cuboid.getUpperX()) { + xAction = EnsureAction.DECREASE; + validX = xAction.apply(cuboid.getUpperX(), 4); + } else if (location.getBlockX() - 2 < cuboid.getLowerX()) { + xAction = EnsureAction.INCREASE; + validX = xAction.apply(cuboid.getLowerX(), 4); + } + + if (location.getBlockZ() + 2 > cuboid.getUpperZ()) { + zAction = EnsureAction.DECREASE; + validZ = zAction.apply(cuboid.getUpperZ(), 4); + } else if (location.getBlockZ() - 2 < cuboid.getLowerZ()) { + zAction = EnsureAction.INCREASE; + validZ = zAction.apply(cuboid.getLowerZ(), 4); + } + + int validY = location.getWorld().getHighestBlockYAt(validX, validZ); + Location validLoc = new Location(location.getWorld(), (double)validX + 0.5D, (double)validY + 0.5D, (double)validZ + 0.5D); + + for(int var9 = 0; !isSafe(validLoc) && var9++ < 30; validLoc = new Location(location.getWorld(), (double)validX + 0.5D, (double)validY + 0.5D, (double)validZ + 0.5D)) { + if (xAction != null) { + validX = xAction.apply(validX, 1); + } + + if (zAction != null) { + validZ = zAction.apply(validZ, 1); + } + + validY = location.getWorld().getHighestBlockYAt(validX, validZ); + } + + validLoc.setPitch(location.getPitch()); + validLoc.setYaw(location.getYaw()); + return validLoc; + } + + private static boolean isSafe(Location location) { + return location.getBlock().getRelative(BlockFace.DOWN).getType().isSolid() && location.getBlock().isEmpty() && location.getBlock().getRelative(BlockFace.UP).isEmpty(); + } + + public Cuboid getPhysicalBounds() { + return this.physicalBounds.clone(); + } + + public Border setMaterial(Material material) { + this.material = material; + return this; + } + + public Border setHeight(int height) { + this.height = height; + return this; + } + + public Border setWrapTerrain(boolean wrapTerrain) { + this.wrapTerrain = wrapTerrain; + return this; + } + + public Border setBorderConfiguration(BorderConfiguration borderConfiguration) { + this.borderConfiguration = borderConfiguration; + return this; + } + + public Border setParticle(Effect particle) { + this.particle = particle; + return this; + } + + static { + airBlocks[Material.LOG.getId()] = true; + airBlocks[Material.LOG_2.getId()] = true; + airBlocks[Material.LEAVES.getId()] = true; + airBlocks[Material.LEAVES_2.getId()] = true; + airBlocks[Material.HUGE_MUSHROOM_1.getId()] = true; + airBlocks[Material.HUGE_MUSHROOM_2.getId()] = true; + airBlocks[Material.SNOW.getId()] = true; + + for (int i = 0; i < Material.values().length; i++) { + + final Material material = Material.values()[i]; + + if (material.isBlock() && !material.isSolid()) { + airBlocks[material.getId()] = true; + } + + } + + airBlocks[Material.WATER.getId()] = false; + airBlocks[Material.STATIONARY_WATER.getId()] = false; + airBlocks[Material.LAVA.getId()] = false; + airBlocks[Material.STATIONARY_LAVA.getId()] = false; + } + + public enum EnsureAction { + + INCREASE, + DECREASE; + + public int apply(int previous, int amount) { + return this == INCREASE ? previous + amount : previous - amount; + } + + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/border/BorderConfiguration.java b/Network/eLib/src/main/java/com/elevatemc/elib/border/BorderConfiguration.java new file mode 100644 index 0000000..6431020 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/border/BorderConfiguration.java @@ -0,0 +1,28 @@ +package com.elevatemc.elib.border; + +import com.elevatemc.elib.border.event.border.BorderChangeEvent; +import com.elevatemc.elib.border.event.player.PlayerEnterBorderEvent; +import com.elevatemc.elib.border.event.player.PlayerExitBorderEvent; +import com.elevatemc.elib.border.action.DefaultBorderActions; +import lombok.Getter; + +import java.util.HashSet; +import java.util.Set; +import java.util.function.Consumer; + +public class BorderConfiguration { + + public static final BorderConfiguration DEFAULT_CONFIGURATION = new BorderConfiguration(); + + @Getter private final Set> defaultBorderChangeActions = new HashSet<>(); + @Getter private final Set> defaultBorderEnterActions = new HashSet<>(); + @Getter private final Set> defaultBorderExitActions = new HashSet<>(); + + public BorderConfiguration() { + this.defaultBorderChangeActions.add(DefaultBorderActions.ENSURE_PLAYERS_IN_BORDER); + this.defaultBorderExitActions.add(DefaultBorderActions.PUSHBACK_ON_EXIT); + this.defaultBorderExitActions.add(DefaultBorderActions.CANCEL_EXIT); + } + + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/border/BorderHandler.java b/Network/eLib/src/main/java/com/elevatemc/elib/border/BorderHandler.java new file mode 100644 index 0000000..707b39e --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/border/BorderHandler.java @@ -0,0 +1,33 @@ +package com.elevatemc.elib.border; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.border.runnable.EnsureInsideRunnable; +import com.elevatemc.elib.border.listener.BorderListener; +import com.elevatemc.elib.border.listener.InternalBorderListener; +import lombok.Getter; +import org.bukkit.World; + +import java.util.HashMap; +import java.util.Map; + +public class BorderHandler { + + @Getter private final Map borderMap = new HashMap<>(); + + public BorderHandler() { + eLib.getInstance().getServer().getPluginManager().registerEvents(new BorderListener(), eLib.getInstance()); + eLib.getInstance().getServer().getPluginManager().registerEvents(new InternalBorderListener(), eLib.getInstance()); + + new EnsureInsideRunnable().runTaskTimer(eLib.getInstance(), 5L, 5L); + } + + public Border getBorderForWorld(World world) { + return this.borderMap.get(world); + } + + void addBorder(Border border) { + this.borderMap.put(border.getOrigin().getWorld(), border); + } + + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/border/action/DefaultBorderActions.java b/Network/eLib/src/main/java/com/elevatemc/elib/border/action/DefaultBorderActions.java new file mode 100644 index 0000000..f3ddf01 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/border/action/DefaultBorderActions.java @@ -0,0 +1,114 @@ +package com.elevatemc.elib.border.action; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.border.Border; +import com.elevatemc.elib.border.event.border.BorderChangeEvent; +import com.elevatemc.elib.border.event.player.PlayerExitBorderEvent; +import com.elevatemc.elib.cuboid.Cuboid; +import com.google.common.collect.Maps; + +import lombok.Getter; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.Vector; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +public class DefaultBorderActions { + + @Getter private static Map lastMessaged = Maps.newHashMap(); + + public static final Consumer CANCEL_EXIT = (event) -> { + + final Player player = event.getPlayer(); + + event.setCancelled(true); + + if (!lastMessaged.containsKey(player.getUniqueId()) || System.currentTimeMillis() - lastMessaged.get(player.getUniqueId()) > TimeUnit.SECONDS.toMillis(1L)) { + player.sendMessage(ChatColor.RED + "You have reached the border!"); + lastMessaged.put(player.getUniqueId(), System.currentTimeMillis()); + } + + }; + + public static final Consumer PUSHBACK_ON_EXIT = (event) -> { + event.getPlayer().setMetadata("Border-Pushback", new FixedMetadataValue(eLib.getInstance(), System.currentTimeMillis())); + new BukkitRunnable() { + + @Override + public void run() { + + final Border border = event.getBorder(); + final Player player = event.getPlayer(); + final Location location = event.getTo(); + + final Cuboid cuboid = border.getPhysicalBounds(); + + double validX = location.getX(); + double validZ = location.getZ(); + + if (location.getBlockX() + 2 > cuboid.getUpperX()) { + validX = (double)(cuboid.getUpperX() - 3); + } else if (location.getBlockX() - 2 < cuboid.getLowerX()) { + validX = (double)(cuboid.getLowerX() + 4); + } + + if (location.getBlockZ() + 2 > cuboid.getUpperZ()) { + validZ = (double)(cuboid.getUpperZ() - 3); + } else if (location.getBlockZ() - 2 < cuboid.getLowerZ()) { + validZ = (double)(cuboid.getLowerZ() + 4); + } + + final Location validLoc = new Location(location.getWorld(), validX, location.getY(), validZ); + final Vector velocity = validLoc.toVector().subtract(event.getTo().toVector()).multiply(0.18D); + + if (player.getVehicle() != null) { + player.getVehicle().setVelocity(velocity); + } else { + player.setVelocity(velocity); + } + + if (!DefaultBorderActions.lastMessaged.containsKey(player.getUniqueId()) || System.currentTimeMillis() - DefaultBorderActions.lastMessaged.get(player.getUniqueId()) > TimeUnit.SECONDS.toMillis(1L)) { + player.sendMessage(ChatColor.RED + "You have reached the border!"); + DefaultBorderActions.lastMessaged.put(player.getUniqueId(), System.currentTimeMillis()); + } + + } + }.runTask(eLib.getInstance()); + }; + public static final Consumer ENSURE_PLAYERS_IN_BORDER = (event) -> { + + final Border border = event.getBorder(); + + for (Player loopPlayer : eLib.getInstance().getServer().getOnlinePlayers()) { + + + if (loopPlayer.getWorld() == border.getOrigin().getWorld() && !border.contains(loopPlayer.getLocation().getBlockX(),loopPlayer.getLocation().getBlockZ())) { + + final Location location = border.correctLocation(loopPlayer.getLocation()); + + if (loopPlayer.getVehicle() == null) { + loopPlayer.teleport(location); + } else { + + final Entity vehicle = loopPlayer.getVehicle(); + + loopPlayer.leaveVehicle(); + + vehicle.teleport(location); + loopPlayer.teleport(location); + vehicle.setPassenger(loopPlayer); + } + } + } + + }; + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/border/event/border/BorderChangeEvent.java b/Network/eLib/src/main/java/com/elevatemc/elib/border/event/border/BorderChangeEvent.java new file mode 100644 index 0000000..1a7eb9e --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/border/event/border/BorderChangeEvent.java @@ -0,0 +1,22 @@ +package com.elevatemc.elib.border.event.border; + +import com.elevatemc.elib.border.runnable.BorderTask; +import lombok.Getter; +import com.elevatemc.elib.border.Border; +import com.elevatemc.elib.cuboid.Cuboid; + +public class BorderChangeEvent extends BorderEvent { + + @Getter private int previousSize; + @Getter private Cuboid previousBounds; + @Getter private BorderTask.BorderAction action; + + public BorderChangeEvent(Border border, int previousSize, Cuboid previousBounds, BorderTask.BorderAction action) { + super(border); + this.previousSize = previousSize; + this.previousBounds = previousBounds; + this.action = action; + } + +} + diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/border/event/border/BorderEvent.java b/Network/eLib/src/main/java/com/elevatemc/elib/border/event/border/BorderEvent.java new file mode 100644 index 0000000..464acb6 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/border/event/border/BorderEvent.java @@ -0,0 +1,27 @@ +package com.elevatemc.elib.border.event.border; + +import lombok.Getter; +import com.elevatemc.elib.border.Border; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +public class BorderEvent extends Event { + + @Getter private static final HandlerList handlerList = new HandlerList(); + + @Getter private Border border; + + public BorderEvent(Border border) { + this.border = border; + } + + public HandlerList getHandlers() { + return handlerList; + } + + public Border getBorder() { + return this.border; + } + +} + diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/border/event/player/PlayerBorderEvent.java b/Network/eLib/src/main/java/com/elevatemc/elib/border/event/player/PlayerBorderEvent.java new file mode 100644 index 0000000..6f2d311 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/border/event/player/PlayerBorderEvent.java @@ -0,0 +1,20 @@ +package com.elevatemc.elib.border.event.player; + +import lombok.Getter; +import lombok.Setter; +import com.elevatemc.elib.border.Border; +import com.elevatemc.elib.border.event.border.BorderEvent; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; + +public class PlayerBorderEvent extends BorderEvent implements Cancellable { + + @Getter private Player player; + @Getter @Setter private boolean cancelled; + + public PlayerBorderEvent(Border border, Player player) { + super(border); + this.player = player; + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/border/event/player/PlayerEnterBorderEvent.java b/Network/eLib/src/main/java/com/elevatemc/elib/border/event/player/PlayerEnterBorderEvent.java new file mode 100644 index 0000000..ba0b6eb --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/border/event/player/PlayerEnterBorderEvent.java @@ -0,0 +1,20 @@ +package com.elevatemc.elib.border.event.player; + +import lombok.Getter; +import com.elevatemc.elib.border.Border; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +public class PlayerEnterBorderEvent extends PlayerBorderEvent { + + @Getter private final Location from; + @Getter private final Location to; + + public PlayerEnterBorderEvent(Border border, Player player, Location from, Location to) { + super(border, player); + this.from = from; + this.to = to; + } + + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/border/event/player/PlayerExitBorderEvent.java b/Network/eLib/src/main/java/com/elevatemc/elib/border/event/player/PlayerExitBorderEvent.java new file mode 100644 index 0000000..338a24b --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/border/event/player/PlayerExitBorderEvent.java @@ -0,0 +1,19 @@ +package com.elevatemc.elib.border.event.player; + +import lombok.Getter; +import com.elevatemc.elib.border.Border; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +public class PlayerExitBorderEvent extends PlayerBorderEvent { + + @Getter private final Location from; + @Getter private final Location to; + + public PlayerExitBorderEvent(Border border, Player player, Location from, Location to) { + super(border, player); + this.from = from; + this.to = to; + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/border/listener/BorderListener.java b/Network/eLib/src/main/java/com/elevatemc/elib/border/listener/BorderListener.java new file mode 100644 index 0000000..3f55efb --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/border/listener/BorderListener.java @@ -0,0 +1,168 @@ +package com.elevatemc.elib.border.listener; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.border.Border; +import com.elevatemc.elib.border.action.DefaultBorderActions; +import com.elevatemc.elib.border.event.player.PlayerBorderEvent; +import com.elevatemc.elib.border.event.player.PlayerEnterBorderEvent; +import com.elevatemc.elib.border.event.player.PlayerExitBorderEvent; +import com.elevatemc.elib.cuboid.Cuboid; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.entity.Horse; +import org.bukkit.entity.Player; +import org.bukkit.entity.Vehicle; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerPortalEvent; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.event.vehicle.VehicleUpdateEvent; +import org.bukkit.util.Vector; + +import java.util.concurrent.TimeUnit; + +public class BorderListener implements Listener { + + @EventHandler + public void onPlayerMove(PlayerMoveEvent event) { + + final Location fromLoc = event.getFrom(); + final Location toLoc = event.getTo(); + final Border border = eLib.getInstance().getBorderHandler().getBorderForWorld(fromLoc.getWorld()); + + if (border == null) { + return; + } + + final boolean from = border.contains(fromLoc.getBlockX(), fromLoc.getBlockZ()); + final boolean to = border.contains(toLoc.getBlockX(), toLoc.getBlockZ()); + final boolean movedBlock = event.getFrom().getBlockX() != event.getTo().getBlockX() || event.getFrom().getBlockY() != event.getTo().getBlockY() || event.getFrom().getBlockZ() != event.getTo().getBlockZ(); + + if (!movedBlock) { + return; + } + + PlayerBorderEvent playerBorderEvent = null; + + if (from && !to) { + playerBorderEvent = new PlayerExitBorderEvent(border, event.getPlayer(), fromLoc, toLoc); + } else if (!from && to) { + playerBorderEvent = new PlayerEnterBorderEvent(border, event.getPlayer(), fromLoc, toLoc); + } + + if (playerBorderEvent != null) { + + eLib.getInstance().getServer().getPluginManager().callEvent(playerBorderEvent); + + if (playerBorderEvent.isCancelled()) { + event.setCancelled(true); + } + } + + } + + @EventHandler + public void onVehicleUpdate(VehicleUpdateEvent event) { + + if (!(event.getVehicle().getPassenger() instanceof Player)) { + return; + } + + final Border border = eLib.getInstance().getBorderHandler().getBorderForWorld(event.getVehicle().getWorld()); + + if (border == null) { + return; + } + + final Player player = (Player)event.getVehicle().getPassenger(); + final Vehicle vehicle = event.getVehicle(); + + if (border.contains(vehicle.getLocation()) && !(vehicle instanceof Horse)) { + return; + } + + final Location location = vehicle.getLocation(); + final Cuboid cuboid = border.getPhysicalBounds(); + + double validX = location.getX(); + double validZ = location.getZ(); + + if (location.getBlockX() + 2 > cuboid.getUpperX()) { + validX = (double)(cuboid.getUpperX() - 3); + } else if (location.getBlockX() - 2 < cuboid.getLowerX()) { + validX = (double)(cuboid.getLowerX() + 4); + } + + if (location.getBlockZ() + 2 > cuboid.getUpperZ()) { + validZ = (double)(cuboid.getUpperZ() - 3); + } else if (location.getBlockZ() - 2 < cuboid.getLowerZ()) { + validZ = (double)(cuboid.getLowerZ() + 4); + } + + final Location validLoc = new Location(location.getWorld(), validX, location.getY(), validZ); + final Vector velocity = validLoc.toVector().subtract(location.toVector()).multiply(2); + + vehicle.setVelocity(velocity); + + if (!DefaultBorderActions.getLastMessaged().containsKey(player.getUniqueId()) || System.currentTimeMillis() - DefaultBorderActions.getLastMessaged().get(player.getUniqueId()) > TimeUnit.SECONDS.toMillis(1L)) { + player.sendMessage(ChatColor.RED + "You have reached the border!"); + DefaultBorderActions.getLastMessaged().put(player.getUniqueId(), System.currentTimeMillis()); + } + + } + + @EventHandler + public void onPlayerPortal(PlayerPortalEvent event) { + + final Border border = eLib.getInstance().getBorderHandler().getBorderForWorld(event.getTo().getWorld()); + + if (border == null) { + return; + } + + final Location location = event.useTravelAgent() ? event.getPortalTravelAgent().findOrCreate(event.getTo()) : event.getTo(); + final Cuboid cuboid = border.getPhysicalBounds(); + + double validX = location.getX(); + double validZ = location.getZ(); + + final int buffer = 30; + + if (location.getBlockX() + 2 > cuboid.getUpperX()) { + validX = (double)(cuboid.getUpperX() - buffer); + } else if (location.getBlockX() - 2 < cuboid.getLowerX()) { + validX = (double)(cuboid.getLowerX() + buffer + 1); + } + + if (location.getBlockZ() + 2 > cuboid.getUpperZ()) { + validZ = (double)(cuboid.getUpperZ() - buffer); + } else if (location.getBlockZ() - 2 < cuboid.getLowerZ()) { + validZ = (double)(cuboid.getLowerZ() + buffer + 1); + } + + final Location validLoc = new Location(location.getWorld(), validX, location.getY(), validZ); + + event.setTo(validLoc); + } + + @EventHandler + public void onPlayerTeleport(PlayerTeleportEvent event) { + + final Location toLoc = event.getTo(); + final Border border = eLib.getInstance().getBorderHandler().getBorderForWorld(toLoc.getWorld()); + + if (border == null) { + return; + } + + final boolean to = border.contains(toLoc.getBlockX(), toLoc.getBlockZ()); + + if (!to && event.getCause() == PlayerTeleportEvent.TeleportCause.ENDER_PEARL) { + event.setCancelled(true); + event.setTo(event.getFrom()); + } + + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/border/listener/InternalBorderListener.java b/Network/eLib/src/main/java/com/elevatemc/elib/border/listener/InternalBorderListener.java new file mode 100644 index 0000000..de8878f --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/border/listener/InternalBorderListener.java @@ -0,0 +1,26 @@ +package com.elevatemc.elib.border.listener; + +import com.elevatemc.elib.border.event.border.BorderChangeEvent; +import com.elevatemc.elib.border.event.player.PlayerEnterBorderEvent; +import com.elevatemc.elib.border.event.player.PlayerExitBorderEvent; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +public class InternalBorderListener implements Listener { + + @EventHandler + public void onBorderChange(BorderChangeEvent event) { + event.getBorder().getBorderConfiguration().getDefaultBorderChangeActions().forEach((c) -> c.accept(event)); + } + + @EventHandler + public void onBorderExit(PlayerExitBorderEvent event) { + event.getBorder().getBorderConfiguration().getDefaultBorderExitActions().forEach((c) -> c.accept(event)); + } + + @EventHandler + public void onBorderEnter(PlayerEnterBorderEvent event) { + event.getBorder().getBorderConfiguration().getDefaultBorderEnterActions().forEach((c) -> c.accept(event)); + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/border/runnable/BorderTask.java b/Network/eLib/src/main/java/com/elevatemc/elib/border/runnable/BorderTask.java new file mode 100644 index 0000000..21c233c --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/border/runnable/BorderTask.java @@ -0,0 +1,101 @@ +package com.elevatemc.elib.border.runnable; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.border.event.border.BorderChangeEvent; +import lombok.Getter; +import com.elevatemc.elib.border.Border; +import com.elevatemc.elib.cuboid.Cuboid; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.concurrent.TimeUnit; + +public class BorderTask extends BukkitRunnable { + + @Getter private Border border; + @Getter private int borderChange = 5; + @Getter private long borderChangeDelay = 10L; + @Getter private BorderAction action; + @Getter private boolean first; + @Getter private int tracker; + + public BorderTask(Border border) { + this.action = BorderAction.NONE; + this.first = true; + this.tracker = 0; + this.border = border; + this.runTaskTimer(eLib.getInstance(), 1L, 1L); + } + + public void run() { + if (this.action != BorderAction.NONE) { + ++this.tracker; + } + + if (this.border != null && this.tracker % 20 == 0) { + + int seconds = this.tracker / 20; + + if ((long)seconds % this.borderChangeDelay == 0L) { + + int previousSize = this.border.getSize(); + + Cuboid previous = null; + + for (BorderAction borderAction : BorderAction.values()) { + + switch (borderAction) { + + case SHRINK: { + previous = this.border.contract(this.borderChange); + break; + } + + case GROW: { + previous = this.border.expand(this.borderChange); + break; + } + + case NONE: { + + } + + default: { + return; + } + } + + } + + eLib.getInstance().getServer().getPluginManager().callEvent(new BorderChangeEvent(this.border, previousSize, previous, this.action)); + this.border.fill(); + } + } + + } + + public BorderTask setAction(BorderAction action) { + this.action = action; + this.tracker = 0; + return this; + } + + public BorderTask setBorderChangeDelay(long time, TimeUnit timeUnit) { + this.borderChangeDelay = timeUnit.toSeconds(time); + this.tracker = 0; + return this; + } + + public BorderTask setBorderChange(int borderChange) { + this.borderChange = borderChange; + return this; + } + + public enum BorderAction { + + SHRINK, + GROW, + SET, + NONE + + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/border/runnable/EnsureInsideRunnable.java b/Network/eLib/src/main/java/com/elevatemc/elib/border/runnable/EnsureInsideRunnable.java new file mode 100644 index 0000000..008e49b --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/border/runnable/EnsureInsideRunnable.java @@ -0,0 +1,61 @@ +package com.elevatemc.elib.border.runnable; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.border.Border; +import org.bukkit.Location; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; + +import org.bukkit.scheduler.BukkitRunnable; + + +public class EnsureInsideRunnable extends BukkitRunnable { + + public void run() { + + for (Player loopPlayer : eLib.getInstance().getServer().getOnlinePlayers()) { + + final Border border = eLib.getInstance().getBorderHandler().getBorderForWorld(loopPlayer.getWorld()); + + if (border != null && loopPlayer.getWorld() == border.getOrigin().getWorld() && this.shouldEnsure(loopPlayer) && !border.contains(loopPlayer.getLocation().getBlockX(),loopPlayer.getLocation().getBlockZ())) { + + final Location location = border.correctLocation(loopPlayer.getLocation()); + + if (loopPlayer.getVehicle() != null) { + + final Entity vehicle = loopPlayer.getVehicle(); + + loopPlayer.leaveVehicle(); + + vehicle.teleport(location); + vehicle.setPassenger(loopPlayer); + } + + loopPlayer.teleport(location); + } + } + + } + + private boolean isSafe(Location location) { + return location.getBlock().getRelative(BlockFace.DOWN).getType().isSolid(); + } + + private boolean shouldEnsure(Player player) { + + if (!player.hasMetadata("Border-Pushback")) { + return true; + } + + try { + final long pushed = (player.getMetadata("Border-Pushback").get(0)).asLong(); + final long delta = System.currentTimeMillis() - pushed; + + return delta >= 500L; + } catch (Exception ex) { + return true; + } + + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/border/runnable/ParticleRunnable.java b/Network/eLib/src/main/java/com/elevatemc/elib/border/runnable/ParticleRunnable.java new file mode 100644 index 0000000..c2eef11 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/border/runnable/ParticleRunnable.java @@ -0,0 +1,70 @@ +package com.elevatemc.elib.border.runnable; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.cuboid.Cuboid; +import com.elevatemc.elib.border.Border; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +public class ParticleRunnable extends BukkitRunnable { + + private static final int RADIUS = 15; + + public void run() { + + for (Player loopPlayer : eLib.getInstance().getServer().getOnlinePlayers()) { + + final Border border = eLib.getInstance().getBorderHandler().getBorderForWorld(loopPlayer.getWorld()); + + if (border == null || border.getParticle() == null || !this.shouldCheck(loopPlayer,border)) { + continue; + } + + for(int x = loopPlayer.getLocation().getBlockX() - 15; x < loopPlayer.getLocation().getBlockX() + 15; ++x) { + + for(int y = loopPlayer.getLocation().getBlockY() - 5; y < loopPlayer.getLocation().getBlockY() + 5; ++y) { + + for(int z = loopPlayer.getLocation().getBlockZ() - 15; z < loopPlayer.getLocation().getBlockZ() + 15; ++z) { + + final Cuboid cuboid = border.getPhysicalBounds(); + + float finalX = (float)x; + float finalZ = (float)z; + + if (x < 0) { + ++finalX; + } + + if (z < 0) { + ++finalZ; + } + + Location location = null; + + if ((x > 0 && x == cuboid.getUpperX() || x < 0 && x == cuboid.getLowerX()) && (z > 0 && z <= cuboid.getUpperZ() || z < 0 && z >= cuboid.getLowerZ())) { + location = new Location(loopPlayer.getWorld(), (double)(finalX + (x < 0 ? 0.1F : -0.1F)), (double)((float)y + 0.5F), (double)finalZ + 0.5D); + } + + if ((z > 0 && z == cuboid.getUpperZ() || z < 0 && z == cuboid.getLowerZ()) && (x > 0 && x <= cuboid.getUpperX() || x < 0 && x >= cuboid.getLowerX())) { + location = new Location(loopPlayer.getWorld(), (double)finalX + 0.5D, (double)((float)y + 0.5F), (double)(finalZ + (z < 0 ? 0.1F : -0.1F))); + } + + if (location != null) { + loopPlayer.spigot().playEffect(location, border.getParticle(), border.getMaterial().getId(), 0, 0.0F, 0.0F, 0.0F, 0.0F, 1, 15); + } + } + } + } + } + } + + private boolean shouldCheck(Player player,Border border) { + Cuboid cuboid = border.getPhysicalBounds().clone().inset(Cuboid.CuboidDirection.HORIZONTAL, 15); + return !this.contains(player.getLocation().getBlockX(), player.getLocation().getBlockZ(), cuboid); + } + + public boolean contains(int x, int z, Cuboid cuboid) { + return x >= cuboid.getLowerX() && x <= cuboid.getUpperX() && z >= cuboid.getLowerZ() && z <= cuboid.getUpperZ(); + } +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/bossbar/BossBarData.java b/Network/eLib/src/main/java/com/elevatemc/elib/bossbar/BossBarData.java new file mode 100644 index 0000000..a63772a --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/bossbar/BossBarData.java @@ -0,0 +1,14 @@ +package com.elevatemc.elib.bossbar; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@AllArgsConstructor +class BossBarData { + + @Getter private final int entityId; + @Getter @Setter private String message; + @Getter @Setter private float health; + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/bossbar/BossBarHandler.java b/Network/eLib/src/main/java/com/elevatemc/elib/bossbar/BossBarHandler.java new file mode 100644 index 0000000..f8113b1 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/bossbar/BossBarHandler.java @@ -0,0 +1,226 @@ +package com.elevatemc.elib.bossbar; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.util.EntityUtils; +import com.elevatemc.elib.util.PlayerUtils; +import com.google.common.base.Preconditions; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import gnu.trove.map.hash.TObjectIntHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import lombok.Getter; +import net.md_5.bungee.api.ChatColor; +import net.minecraft.server.v1_8_R3.DataWatcher; +import net.minecraft.server.v1_8_R3.Entity; +import net.minecraft.server.v1_8_R3.EntityPlayer; +import net.minecraft.server.v1_8_R3.MinecraftServer; +import net.minecraft.server.v1_8_R3.PacketPlayOutEntityDestroy; +import net.minecraft.server.v1_8_R3.PacketPlayOutEntityMetadata; +import net.minecraft.server.v1_8_R3.PacketPlayOutEntityTeleport; +import net.minecraft.server.v1_8_R3.PacketPlayOutSpawnEntityLiving; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; + +public class BossBarHandler { + + @Getter private final Map displaying = new HashMap<>(); + @Getter private final Map lastUpdatedPosition = new HashMap<>(); + + + private Object2IntMap classToIdMap = null; + + public BossBarHandler() { + + eLib.getInstance().getServer().getPluginManager().registerEvents(new BossBarListener(), eLib.getInstance()); + + try { + final Field dataWatcherClassToIdField = DataWatcher.class.getDeclaredField("classToId"); + + dataWatcherClassToIdField.setAccessible(true); + + this.classToIdMap = (Object2IntMap)dataWatcherClassToIdField.get(null); + } catch (Exception ex) { + ex.printStackTrace(); + } + + eLib.getInstance().getServer().getScheduler().runTaskTimer(eLib.getInstance(),() -> { + + for (UUID uuid : this.displaying.keySet()) { + + final Player player = eLib.getInstance().getServer().getPlayer(uuid); + + if (player == null) { + continue; + } + + final int updateTicks = PlayerUtils.getProtocol(player) >= 47 ? 60 : 3; + + if (this.lastUpdatedPosition.containsKey(player.getUniqueId()) && MinecraftServer.currentTick - + lastUpdatedPosition.get(player.getUniqueId()) < updateTicks) { + return; + } + + this.updatePosition(player); + + this.lastUpdatedPosition.put(player.getUniqueId(),MinecraftServer.currentTick); + } + + },1,1); + + } + + + public void setBossBar(Player player, String message, float health) { + + try { + + if (message == null) { + removeBossBar(player); + return; + } + + Preconditions.checkArgument(health >= 0.0F && health <= 1.0F, "Health must be between 0 and 1"); + + if (message.length() > 64) { + message = message.substring(0, 64); + } + + message = ChatColor.translateAlternateColorCodes('&', message); + + if (!this.displaying.containsKey(player.getUniqueId())) { + this.sendSpawnPacket(player, message, health); + } else { + this.sendUpdatePacket(player, message, health); + } + + final BossBarData bossBarData = this.displaying.get(player.getUniqueId()); + + bossBarData.setMessage(message); + bossBarData.setHealth(health); + } catch (Exception ex) { + ex.printStackTrace(); + } + + } + + public void removeBossBar(Player player) { + + if (!this.displaying.containsKey(player.getUniqueId())) { + return; + } + + final int entityId = this.displaying.get(player.getUniqueId()).getEntityId(); + + ((CraftPlayer)player).getHandle().playerConnection.sendPacket(new PacketPlayOutEntityDestroy(entityId)); + + this.displaying.remove(player.getUniqueId()); + this.lastUpdatedPosition.remove(player.getUniqueId()); + } + + private void sendSpawnPacket(Player bukkitPlayer, String message, float health) throws Exception { + + final EntityPlayer player = ((CraftPlayer)bukkitPlayer).getHandle(); + final int version = PlayerUtils.getProtocol(bukkitPlayer); + + this.displaying.put(bukkitPlayer.getUniqueId(), new BossBarData(EntityUtils.getFakeEntityId(), message, health)); + + final BossBarData stored = this.displaying.get(bukkitPlayer.getUniqueId()); + + final PacketPlayOutSpawnEntityLiving packet = new PacketPlayOutSpawnEntityLiving(); + packet.id = (stored.getEntityId()); //this.spawnPacketAField.set(packet,stored.getEntityId()); + + final DataWatcher watcher = new DataWatcher((Entity)null); + + if (version < 47) { + packet.type = ((byte)EntityType.ENDER_DRAGON.getTypeId()); + + watcher.a(6, health * 200.0F); + packet.x = ((int)(player.locX * 32.0D)); + packet.y = (-6400); + packet.z = ((int)(player.locZ * 32.0D)); + + } else { + packet.type = ((byte)EntityType.WITHER.getTypeId()); + + watcher.a(6, health * 300.0F); + watcher.a(20, 880); + + final double pitch = Math.toRadians(player.pitch); + final double yaw = Math.toRadians(player.yaw); + packet.x = ((int)((player.locX - Math.sin(yaw) * Math.cos(pitch) * 32.0D) * 32.0D)); + packet.y = ((int)((player.locY - Math.sin(pitch) * 32.0D) * 32.0D)); + packet.z = ( (int)((player.locZ + Math.sin(yaw) * Math.cos(pitch) * 32.0D) * 32.0D)); + } + + watcher.a(version < 47 ? 10 : 2, message); + packet.l = (watcher); + + player.playerConnection.sendPacket(packet); + } + + private void sendUpdatePacket(Player bukkitPlayer, String message, float health) { + + final EntityPlayer player = ((CraftPlayer)bukkitPlayer).getHandle(); + final int version = PlayerUtils.getProtocol(bukkitPlayer); + final BossBarData stored = this.displaying.get(bukkitPlayer.getUniqueId()); + final PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(); + packet.a = (stored.getEntityId()); + + final List objects = new ArrayList<>(); + + if (health != stored.getHealth()) { + + if (version != 47) { + objects.add(createWatchableObject(6, health * 200.0F)); + } else { + objects.add(createWatchableObject(6, health * 300.0F)); + } + + } + + if (!message.equals(stored.getMessage())) { + objects.add(createWatchableObject(version != 47 ? 10 : 2, message)); + } + packet.b = objects; + player.playerConnection.sendPacket(packet); + } + + private DataWatcher.WatchableObject createWatchableObject(int id, Object object) { + return new DataWatcher.WatchableObject(this.classToIdMap.get(object.getClass()), id, object); + } + + private void updatePosition(Player bukkitPlayer) { + + if (!this.displaying.containsKey(bukkitPlayer.getUniqueId())) { + return; + } + + final EntityPlayer player = ((CraftPlayer)bukkitPlayer).getHandle(); + final int version = PlayerUtils.getProtocol(bukkitPlayer); + + int x; + int y; + int z; + + if (version != 47) { + x = (int)(player.locX * 32.0D); + y = -6400; + z = (int)(player.locZ * 32.0D); + } else { + final double pitch = Math.toRadians((double)player.pitch); + final double yaw = Math.toRadians((double)player.yaw); + x = (int)((player.locX - Math.sin(yaw) * Math.cos(pitch) * 32.0D) * 32.0D); + y = (int)((player.locY - Math.sin(pitch) * 32.0D) * 32.0D); + z = (int)((player.locZ + Math.cos(yaw) * Math.cos(pitch) * 32.0D) * 32.0D); + } + + player.playerConnection.sendPacket(new PacketPlayOutEntityTeleport( + this.displaying.get(bukkitPlayer.getUniqueId()).getEntityId(),x,y,z,(byte)0,(byte)0, true)); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/bossbar/BossBarListener.java b/Network/eLib/src/main/java/com/elevatemc/elib/bossbar/BossBarListener.java new file mode 100644 index 0000000..bf08d52 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/bossbar/BossBarListener.java @@ -0,0 +1,35 @@ +package com.elevatemc.elib.bossbar; + +import com.elevatemc.elib.eLib; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerTeleportEvent; + +public class BossBarListener implements Listener { + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + eLib.getInstance().getBossBarHandler().removeBossBar(event.getPlayer()); + } + + @EventHandler + public void onPlayerTeleport(PlayerTeleportEvent event) { + + final Player player = event.getPlayer(); + + if (!eLib.getInstance().getBossBarHandler().getDisplaying().containsKey(player.getUniqueId())) { + return; + } + + final BossBarData data = eLib.getInstance().getBossBarHandler().getDisplaying().get(player.getUniqueId()); + + final String message = data.getMessage(); + final float health = data.getHealth(); + + eLib.getInstance().getBossBarHandler().removeBossBar(player); + eLib.getInstance().getBossBarHandler().setBossBar(player,message,health); + + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/combatlogger/CombatLogger.java b/Network/eLib/src/main/java/com/elevatemc/elib/combatlogger/CombatLogger.java new file mode 100644 index 0000000..d28e9da --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/combatlogger/CombatLogger.java @@ -0,0 +1,120 @@ +package com.elevatemc.elib.combatlogger; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.combatlogger.adapter.CombatLoggerAdapter; +import lombok.Getter; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +public class CombatLogger { + + public static final String COMBAT_LOGGER_METADATA = "eLib-CombatLogger"; + + @Getter private final String playerName; + @Getter private final UUID playerUuid; + @Getter private final ItemStack[] armor; + @Getter private final ItemStack[] inventory; + @Getter private double health = 20.0D; + @Getter private final Set effects = new HashSet(); + @Getter private long despawnTime; + @Getter private EntityType entityType; + @Getter private String nameFormat; + @Getter private CombatLoggerAdapter eventAdapter; + @Getter private LivingEntity spawnedEntity; + + public CombatLogger(Player player,long time,TimeUnit unit) { + this.entityType = EntityType.VILLAGER; + this.nameFormat = ChatColor.YELLOW + "%s"; + this.playerName = player.getName(); + this.playerUuid = player.getUniqueId(); + this.armor = player.getInventory().getArmorContents(); + this.inventory = player.getInventory().getContents(); + this.despawnTime = unit.toSeconds(time); + } + + public CombatLogger setDespawnTime(long time, TimeUnit unit) { + this.despawnTime = unit.toSeconds(time); + return this; + } + + public CombatLogger setEntityType(EntityType entityType) { + if (!entityType.isAlive() && !entityType.isSpawnable()) { + throw new IllegalArgumentException("EntityType must be living and spawnable!"); + } else { + this.entityType = entityType; + return this; + } + } + + public CombatLogger setHealth(double health) { + this.health = health; + return this; + } + + public CombatLogger setNameFormat(String nameFormat) { + this.nameFormat = nameFormat; + return this; + } + + public CombatLogger setPotionEffects(Collection effects) { + this.effects.addAll(effects); + return this; + } + + public CombatLogger setAdapter(CombatLoggerAdapter adapter) { + this.eventAdapter = adapter; + return this; + } + + public LivingEntity spawn(Location location) { + + final LivingEntity entity = (LivingEntity)location.getWorld().spawnEntity(location, this.entityType); + + entity.setMetadata(COMBAT_LOGGER_METADATA, new FixedMetadataValue(eLib.getInstance(), "001100010010011110100001")); + + eLib.getInstance().getCombatLoggerHandler().getCombatLoggerMap().put(entity.getUniqueId(), this); + eLib.getInstance().getCombatLoggerHandler().getCombatLoggerMap().put(this.playerUuid, this); + + entity.setCustomName(String.format(this.nameFormat, this.playerName)); + entity.setCustomNameVisible(true); + entity.setCanPickupItems(false); + entity.addPotionEffects(this.effects); + entity.addPotionEffect(new PotionEffect(PotionEffectType.SLOW, 2147483647, 100), true); + entity.setMaxHealth(this.health + 2.0D); + entity.setHealth(this.health); + + new BukkitRunnable() { + + @Override + public void run() { + + if (!entity.isDead() && entity.isValid()) { + entity.remove(); + eLib.getInstance().getCombatLoggerHandler().getCombatLoggerMap().remove(entity.getUniqueId()); + eLib.getInstance().getCombatLoggerHandler().getCombatLoggerMap().remove(this); + } + + } + }.runTaskLater(eLib.getInstance(), this.despawnTime * 20L); + + this.spawnedEntity = entity; + + return entity; + } + + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/combatlogger/CombatLoggerHandler.java b/Network/eLib/src/main/java/com/elevatemc/elib/combatlogger/CombatLoggerHandler.java new file mode 100644 index 0000000..5e12374 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/combatlogger/CombatLoggerHandler.java @@ -0,0 +1,19 @@ +package com.elevatemc.elib.combatlogger; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.combatlogger.listener.CombatLoggerListener; +import lombok.Getter; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class CombatLoggerHandler { + + @Getter private final Map combatLoggerMap = new HashMap<>(); + + public CombatLoggerHandler() { + eLib.getInstance().getServer().getPluginManager().registerEvents(new CombatLoggerListener(), eLib.getInstance().getInstance()); + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/combatlogger/adapter/CombatLoggerAdapter.java b/Network/eLib/src/main/java/com/elevatemc/elib/combatlogger/adapter/CombatLoggerAdapter.java new file mode 100644 index 0000000..9fb8650 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/combatlogger/adapter/CombatLoggerAdapter.java @@ -0,0 +1,16 @@ +package com.elevatemc.elib.combatlogger.adapter; + +import com.elevatemc.elib.combatlogger.CombatLogger; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; + +public class CombatLoggerAdapter { + + public void onEntityDamageByEntity(CombatLogger logger,EntityDamageByEntityEvent event) { + } + + public void onEntityDeath(CombatLogger logger, EntityDeathEvent event) { + + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/combatlogger/listener/CombatLoggerListener.java b/Network/eLib/src/main/java/com/elevatemc/elib/combatlogger/listener/CombatLoggerListener.java new file mode 100644 index 0000000..cffc700 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/combatlogger/listener/CombatLoggerListener.java @@ -0,0 +1,166 @@ +package com.elevatemc.elib.combatlogger.listener; + +import net.minecraft.server.v1_8_R3.EntityPlayer; +import net.minecraft.server.v1_8_R3.MinecraftServer; +import net.minecraft.server.v1_8_R3.PlayerInteractManager; +import org.bukkit.craftbukkit.v1_8_R3.CraftServer; +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.combatlogger.CombatLogger; +import com.mojang.authlib.GameProfile; +import org.bukkit.Material; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntityInteractEvent; +import org.bukkit.event.entity.EntityPortalEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.world.ChunkUnloadEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.UUID; + +public class CombatLoggerListener implements Listener { + public CombatLoggerListener() { + } + + @EventHandler( + priority = EventPriority.HIGHEST + ) + public void onEntityDeath(EntityDeathEvent event) { + + if (!event.getEntity().hasMetadata(CombatLogger.COMBAT_LOGGER_METADATA)) { + return; + } + + final CombatLogger logger = eLib.getInstance().getCombatLoggerHandler().getCombatLoggerMap().get(event.getEntity().getUniqueId()); + + if (logger == null) { + return; + } + + for (int i = 0; i < logger.getArmor().length; i++) { + + final ItemStack item = logger.getArmor()[i]; + + event.getDrops().add(item); + } + + for (int i = 0; i < logger.getInventory().length; i++) { + + final ItemStack item = logger.getInventory()[i]; + + event.getDrops().add(item); + } + + logger.getEventAdapter().onEntityDeath(logger, event); + + final Player killer = event.getEntity().getKiller(); + + Player target = eLib.getInstance().getServer().getPlayer(logger.getPlayerUuid()); + + if (target == null) { + + final MinecraftServer server = ((CraftServer) eLib.getInstance().getServer()).getServer(); + final EntityPlayer entity = new EntityPlayer(server, server.getWorldServer(0), new GameProfile(logger.getPlayerUuid(), + logger.getPlayerName()), new PlayerInteractManager(server.getWorldServer(0))); + + target = entity.getBukkitEntity(); + + if (target != null) { + target.loadData(); + } + } + + if (target != null) { + target.getInventory().clear(); + target.getInventory().setArmorContents(null); + target.saveData(); + } + + eLib.getInstance().getCombatLoggerHandler().getCombatLoggerMap().remove(event.getEntity().getUniqueId()); + eLib.getInstance().getCombatLoggerHandler().getCombatLoggerMap().remove(logger.getPlayerUuid()); + } + + @EventHandler( + priority = EventPriority.HIGHEST + ) + public void onEntityInteract(PlayerInteractEntityEvent event) { + if (event.getRightClicked().hasMetadata(CombatLogger.COMBAT_LOGGER_METADATA)) { + event.setCancelled(true); + } + + } + + @EventHandler( + priority = EventPriority.HIGHEST + ) + public void onChunkUnload(ChunkUnloadEvent event) { + + for (Entity entity : event.getChunk().getEntities()) { + + if (entity.hasMetadata(CombatLogger.COMBAT_LOGGER_METADATA) && !entity.isDead()) { + event.setCancelled(true); + } + + } + } + + @EventHandler( + priority = EventPriority.HIGHEST + ) + public void onEntityPortal(EntityPortalEvent event) { + if (event.getEntity().hasMetadata(CombatLogger.COMBAT_LOGGER_METADATA)) { + event.setCancelled(true); + } + + } + + @EventHandler( + priority = EventPriority.HIGHEST + ) + public void onPlayerJoin(PlayerJoinEvent event) { + + final CombatLogger logger = eLib.getInstance().getCombatLoggerHandler().getCombatLoggerMap().get(event.getPlayer().getUniqueId()); + + if (logger != null && logger.getSpawnedEntity() != null && logger.getSpawnedEntity().isValid() && !logger.getSpawnedEntity().isDead()) { + + final UUID entityId = logger.getSpawnedEntity().getUniqueId(); + + logger.getSpawnedEntity().remove(); + eLib.getInstance().getCombatLoggerHandler().getCombatLoggerMap().remove(entityId); + eLib.getInstance().getCombatLoggerHandler().getCombatLoggerMap().remove(event.getPlayer().getUniqueId()); + } + + } + + @EventHandler( + priority = EventPriority.HIGHEST + ) + public void onEntityDamageByEntity(EntityDamageByEntityEvent event) { + if (event.getEntity().hasMetadata(CombatLogger.COMBAT_LOGGER_METADATA)) { + + final CombatLogger logger = eLib.getInstance().getCombatLoggerHandler().getCombatLoggerMap().get(event.getEntity().getUniqueId()); + + if (logger != null) { + logger.getEventAdapter().onEntityDamageByEntity(logger, event); + } + } + } + + @EventHandler( + priority = EventPriority.HIGHEST + ) + public void onEntityPressurePlate(EntityInteractEvent event) { + boolean pressurePlate = event.getBlock().getType() == Material.STONE_PLATE || event.getBlock().getType() == Material.GOLD_PLATE || event.getBlock().getType() == Material.IRON_PLATE || event.getBlock().getType() == Material.WOOD_PLATE; + if (pressurePlate && event.getEntity().hasMetadata(CombatLogger.COMBAT_LOGGER_METADATA)) { + event.setCancelled(true); + } + + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/Command.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/Command.java new file mode 100644 index 0000000..55a0086 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/Command.java @@ -0,0 +1,24 @@ +package com.elevatemc.elib.command; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface Command { + + String[] names(); + + String permission(); + + boolean hidden() default false; + + boolean async() default false; + + String description() default ""; + + boolean logToConsole() default false; + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/CommandHandler.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/CommandHandler.java new file mode 100644 index 0000000..225599e --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/CommandHandler.java @@ -0,0 +1,231 @@ +package com.elevatemc.elib.command; + +import com.elevatemc.elib.command.bukkit.CommandMap; +import com.elevatemc.elib.command.defaults.BuildCommand; +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.command.bukkit.Command; +import com.elevatemc.elib.command.bukkit.HelpTopic; +import com.elevatemc.elib.command.command.CommandConfiguration; +import com.elevatemc.elib.command.command.CommandNode; +import com.elevatemc.elib.command.defaults.CommandInfoCommand; +import com.elevatemc.elib.command.defaults.EvalCommand; +import com.elevatemc.elib.command.param.ParameterType; +import com.elevatemc.elib.command.param.defaults.*; +import com.elevatemc.elib.command.param.defaults.filter.NormalFilter; +import com.elevatemc.elib.command.param.defaults.filter.StrictFilter; +import com.elevatemc.elib.command.param.defaults.offlineplayer.OfflinePlayerWrapper; +import com.elevatemc.elib.command.param.defaults.offlineplayer.OfflinePlayerWrapperParameterType; +import com.elevatemc.elib.command.processor.MethodProcessor; +import com.elevatemc.elib.command.utils.EasyClass; +import com.elevatemc.elib.util.ClassUtils; +import lombok.Getter; + + +import net.minecraft.server.v1_8_R3.MinecraftServer; +import org.bukkit.*; +import org.bukkit.command.SimpleCommandMap; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitRunnable; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import java.util.*; + +public class CommandHandler implements Listener { + + public static CommandNode ROOT_NODE = new CommandNode(); + + private static Map, ParameterType> PARAMETER_TYPE_MAP = new HashMap<>(); + private static org.bukkit.command.CommandMap commandMap = getCommandMap(); + protected static Map knownCommands = getKnownCommands(); + + @Getter private CommandConfiguration commandConfiguration; + + public CommandHandler() { + + new BukkitRunnable() { + + @Override + public void run() { + try { + swapCommandMap(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + }.runTaskLater(eLib.getInstance(), 5L); + + this.commandConfiguration = new CommandConfiguration("&cNo permission."); + + this.registerParameterType(Boolean.class, new BooleanParameterType()); + this.registerParameterType(boolean.class, new BooleanParameterType()); + this.registerParameterType(Integer.class, new IntegerParameterType()); + this.registerParameterType(int.class, new IntegerParameterType()); + this.registerParameterType(Double.class, new DoubleParameterType()); + this.registerParameterType(double.class, new DoubleParameterType()); + this.registerParameterType(Float.class, new FloatParameterType()); + this.registerParameterType(float.class, new FloatParameterType()); + this.registerParameterType(Long.class,new LongParameterType()); + this.registerParameterType(long.class,new LongParameterType()); + this.registerParameterType(String.class, new StringParameterType()); + this.registerParameterType(Player.class, new PlayerParameterType()); + this.registerParameterType(World.class, new WorldParameterType()); + this.registerParameterType(ItemStack.class, new ItemStackParameterType()); + this.registerParameterType(OfflinePlayer.class, new OfflinePlayerParameterType()); + this.registerParameterType(UUID.class, new UUIDParameterType()); + this.registerParameterType(Enchantment.class,new EnchantmentParameterType()); + this.registerParameterType(GameMode.class,new GameModeParameterType()); + this.registerParameterType(EntityType.class,new EntityTypeParameterType()); + this.registerParameterType(OfflinePlayerWrapper.class,new OfflinePlayerWrapperParameterType()); + registerParameterType(NormalFilter.class, new NormalFilter()); + registerParameterType(StrictFilter.class, new StrictFilter()); + + this.registerClass(BuildCommand.class); + this.registerClass(EvalCommand.class); + this.registerClass(CommandInfoCommand.class); + } + + protected static org.bukkit.command.CommandMap getCommandMap() { + return MinecraftServer.getServer().server.getCommandMap(); + } + + protected static Map getKnownCommands() { + return (Map)(new EasyClass(commandMap)).getField("knownCommands").get(); + } + + public void registerMethod(Method method) { + method.setAccessible(true); + + Set nodes = new MethodProcessor().process(method); + + if (nodes != null) { + + nodes.forEach((node) -> { + + if (node != null) { + + com.elevatemc.elib.command.bukkit.Command command = new com.elevatemc.elib.command.bukkit.Command(node, JavaPlugin.getProvidingPlugin(method.getDeclaringClass())); + + register(command); + + node.getChildren().values().forEach((n) -> registerHelpTopic(n, node.getAliases())); + } + + }); + } + + } + + protected void registerHelpTopic(CommandNode node, Set aliases) { + + if (node.getMethod() != null) { + eLib.getInstance().getServer().getHelpMap().addTopic(new HelpTopic(node, aliases)); + } + + if (node.hasCommands()) { + node.getChildren().values().forEach((n) -> registerHelpTopic(n, null)); + } + + } + + private void register(com.elevatemc.elib.command.bukkit.Command command) { + try { + + Map knownCommands = getKnownCommands(); + Iterator iterator = knownCommands.entrySet().iterator(); + + while(iterator.hasNext()) { + + Map.Entry entry = (Map.Entry)iterator.next(); + + if (entry.getValue().getName().equalsIgnoreCase(command.getName())) { + (entry.getValue()).unregister(commandMap); + iterator.remove(); + } + } + + for (String alias : command.getAliases()) { + knownCommands.put(alias,command); + } + + command.register(commandMap); + knownCommands.put(command.getName(), command); + } catch (Exception ex) { + ex.printStackTrace(); + } + + } + + public void registerClass(Class clazz) { + + Method[] methods = clazz.getMethods(); + + for (int i = 0; i < methods.length; i++) { + + Method method = methods[i]; + + registerMethod(method); + } + + } + + public void unregisterClass(Class clazz) { + + Map knownCommands = getKnownCommands(); + Iterator iterator = knownCommands.values().iterator(); + + while(iterator.hasNext()) { + org.bukkit.command.Command command = (org.bukkit.command.Command)iterator.next(); + if (command instanceof com.elevatemc.elib.command.bukkit.Command) { + CommandNode node = ((Command)command).getNode(); + if (node.getOwningClass() == clazz) { + command.unregister(commandMap); + iterator.remove(); + } + } + } + + } + + public void registerPackage(Plugin plugin, String packageName) { + ClassUtils.getClassesInPackage(plugin, packageName).forEach(this::registerClass); + } + + public void registerAll(Plugin plugin) { + registerPackage(plugin, plugin.getClass().getPackage().getName()); + } + + private void swapCommandMap() throws Exception { + + Field commandMapField = eLib.getInstance().getServer().getClass().getDeclaredField("commandMap"); + + commandMapField.setAccessible(true); + + Object oldCommandMap = commandMapField.get(eLib.getInstance().getServer()); + Object newCommandMap = new CommandMap(Bukkit.getServer()); + Field knownCommandsField = SimpleCommandMap.class.getDeclaredField("knownCommands"); + + knownCommandsField.setAccessible(true); + + knownCommandsField.set(newCommandMap, knownCommandsField.get(oldCommandMap)); + + commandMapField.set(eLib.getInstance().getServer(), newCommandMap); + + } + + public void registerParameterType(Class clazz, ParameterType type) { + PARAMETER_TYPE_MAP.put(clazz, type); + } + + public ParameterType getParameterType(Class clazz) { + return PARAMETER_TYPE_MAP.get(clazz); + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/argument/ArgumentProcessor.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/argument/ArgumentProcessor.java new file mode 100644 index 0000000..a0b6feb --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/argument/ArgumentProcessor.java @@ -0,0 +1,56 @@ +package com.elevatemc.elib.command.argument; + +import com.elevatemc.elib.command.flag.Flag; +import com.elevatemc.elib.command.processor.Processor; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.regex.Matcher; + +public class ArgumentProcessor implements Processor { + + public Arguments process(String[] value) { + + final Set flags = new HashSet(); + final List arguments = new ArrayList(); + + for (int i = 0; i < value.length; i++) { + + final String s = value[i]; + + if (!s.isEmpty()) { + + if (s.charAt(0) == '-' && !s.equals("-") && this.matches(s)) { + String flag = this.getFlagName(s); + flags.add(flag); + } else { + arguments.add(s); + } + + } + } + + return new Arguments(arguments, flags); + } + + private String getFlagName(String flag) { + + final Matcher matcher = Flag.FLAG_PATTERN.matcher(flag); + + + if (matcher.matches()) { + + final String name = matcher.replaceAll("$2$3"); + + return name.length() == 1 ? name : name.toLowerCase(); + } + + return null; + } + + private boolean matches(String flag) { + return Flag.FLAG_PATTERN.matcher(flag).matches(); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/argument/Arguments.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/argument/Arguments.java new file mode 100644 index 0000000..975bdf3 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/argument/Arguments.java @@ -0,0 +1,55 @@ +package com.elevatemc.elib.command.argument; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.List; +import java.util.Set; + +@AllArgsConstructor +public class Arguments { + + @Getter private List arguments; + @Getter private Set flags; + + public boolean hasFlag(String flag) { + return this.flags.contains(flag.toLowerCase()); + } + + public String join(int from, int to, char delimiter) { + + if (to > this.arguments.size() - 1 || to < 1) { + to = this.arguments.size() - 1; + } + + final StringBuilder builder = new StringBuilder(); + + for (int i = from; i <= to; ++i) { + + builder.append(this.arguments.get(i)); + if (i != to) { + builder.append(delimiter); + } + + } + + return builder.toString(); + } + + public String join(int from, char delimiter) { + return this.join(from, -1, delimiter); + } + + public String join(int from) { + return this.join(from, ' '); + } + + public String join(char delimiter) { + return this.join(0, delimiter); + } + + public String join() { + return this.join(' '); + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/bukkit/Command.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/bukkit/Command.java new file mode 100644 index 0000000..5133282 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/bukkit/Command.java @@ -0,0 +1,294 @@ +package com.elevatemc.elib.command.bukkit; + +import com.elevatemc.elib.command.argument.ArgumentProcessor; +import com.elevatemc.elib.command.argument.Arguments; +import com.elevatemc.elib.command.command.CommandNode; +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.command.flag.Flag; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import lombok.Getter; +import com.elevatemc.elib.command.param.ParameterData; +import com.elevatemc.elib.command.param.ParameterType; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.commons.lang.StringUtils; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandException; +import org.bukkit.command.CommandSender; +import org.bukkit.command.PluginIdentifiableCommand; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; + +import org.spigotmc.SpigotConfig; +import com.elevatemc.elib.util.TaskUtil; + +import java.util.*; +import java.util.stream.Collectors; + +public class Command extends org.bukkit.command.Command implements PluginIdentifiableCommand { + + @Getter protected CommandNode node; + private JavaPlugin owningPlugin; + + public Command(CommandNode node, JavaPlugin plugin) { + super(node.getName(), "", "/", Lists.newArrayList(node.getRealAliases())); + this.node = node; + this.owningPlugin = plugin; + } + + @Override + public boolean execute(CommandSender sender, String label, String[] args) { + label = label.replace(this.owningPlugin.getName().toLowerCase() + ":", ""); + + String[] newArgs = this.concat(label, args); + Arguments arguments = (new ArgumentProcessor()).process(newArgs); + + CommandNode executionNode = this.node.findCommand(arguments); + String realLabel = this.getFullLabel(executionNode); + + if (executionNode.canUse(sender)) { + + if (executionNode.isAsync()) { + TaskUtil.executeWithPoolIfRequired(() -> { + try { + + if (!executionNode.invoke(sender,arguments)) { + executionNode.getUsage(realLabel).send(sender); + } + + } catch (CommandException ex) { + + executionNode.getUsage(realLabel).send(sender); + sender.sendMessage(ChatColor.RED + "An error occurred while processing your commands."); + + if (sender.isOp()) { + this.sendStackTrace(sender,ex); + } + + } + + + }); + + + + } else { + + try { + + if (!executionNode.invoke(sender, arguments)) { + executionNode.getUsage(realLabel).send(sender); + } + } catch (CommandException ex) { + executionNode.getUsage(realLabel).send(sender); + sender.sendMessage(ChatColor.RED + "An error occurred while processing your commands."); + + if (sender.isOp()) { + this.sendStackTrace(sender,ex); + } + + } + + } + + } else if (executionNode.isHidden()) { + sender.sendMessage(SpigotConfig.unknownCommandMessage); + } else { + sender.sendMessage(eLib.getInstance().getCommandHandler().getCommandConfiguration().getNoPermissionMessage()); + } + + return true; + } + + public List tabComplete(CommandSender sender, String cmdLine) { + + if (!(sender instanceof Player)) { + return ImmutableList.of(); + } + + final String[] rawArgs = cmdLine.replace(this.owningPlugin.getName().toLowerCase() + ":", "").split(" "); + + if (rawArgs.length < 1) { + + if (!this.node.canUse(sender)) { + return ImmutableList.of(); + } + + return ImmutableList.of(); + } + + final Arguments arguments = new ArgumentProcessor().process(rawArgs); + final CommandNode realNode = this.node.findCommand(arguments); + + if (!realNode.canUse(sender)) { + return ImmutableList.of(); + } + + final List realArgs = arguments.getArguments(); + int currentIndex = realArgs.size() - 1; + + if (currentIndex < 0) { + currentIndex = 0; + } + + if (cmdLine.endsWith(" ") && realArgs.size() >= 1) { + ++currentIndex; + } + + if (currentIndex < 0) { + return ImmutableList.of(); + } + + final List completions = new ArrayList<>(); + + if (realNode.hasCommands()) { + + final String name = (realArgs.size() == 0) ? "" : realArgs.get(realArgs.size() - 1); + + completions.addAll(realNode.getChildren().values().stream().filter(node -> node.canUse(sender) && (StringUtils.startsWithIgnoreCase(node.getName(),name) || StringUtils.isEmpty(name))).map(CommandNode::getName).collect((Collectors.toList()))); + + if (completions.size() > 0) { + return completions; + } + + } + + if (rawArgs[rawArgs.length - 1].equalsIgnoreCase(realNode.getName()) && !cmdLine.endsWith(" ")) { + return ImmutableList.of(); + } + + if (realNode.getValidFlags() != null && !realNode.getValidFlags().isEmpty()) { + + for (String flags : realNode.getValidFlags()) { + + final String arg = rawArgs[rawArgs.length - 1]; + + if ((Flag.FLAG_PATTERN.matcher(arg).matches() || arg.equals("-")) && (StringUtils.startsWithIgnoreCase(flags, arg.substring(1)) || arg.equals("-"))) { + completions.add("-" + flags); + } + + } + + if (completions.size() > 0) { + return completions; + } + + } + + try { + ParameterType parameterType = null; + ParameterData data = null; + + if (realNode.getParameters() != null) { + + final List params = realNode.getParameters().stream().filter(filter -> filter instanceof ParameterData).map(ParameterData.class::cast).collect(Collectors.toList()); + + final int fixed = Math.max(0, currentIndex - 1); + + data = params.get(fixed); + + parameterType = eLib.getInstance().getCommandHandler().getParameterType(data.getType()); + + if (data.getParameterType() != null) { + + try { + parameterType = data.getParameterType().newInstance(); + } catch (InstantiationException | IllegalAccessException ex) { + ex.printStackTrace(); + } + + } + } + + if (parameterType != null) { + + if (currentIndex < realArgs.size() && realArgs.get(currentIndex).equalsIgnoreCase(realNode.getName())) { + realArgs.add(""); + ++currentIndex; + } + + final String argumentBeingCompleted = (currentIndex >= realArgs.size() || realArgs.size() == 0) ? "" : realArgs.get(currentIndex); + final List suggested = parameterType.tabComplete((Player)sender, data.getTabCompleteFlags(), argumentBeingCompleted); + + completions.addAll(suggested.stream().filter(s -> StringUtils.startsWithIgnoreCase(s,argumentBeingCompleted)).collect(Collectors.toList())); + } + } + + catch (Exception ignored) {} + + return completions; + } + + public Plugin getPlugin() { + return this.owningPlugin; + } + + private String[] concat(String label, String[] args) { + + final String[] labelAsArray = new String[]{label}; + final String[] newArgs = new String[args.length + 1]; + + System.arraycopy(labelAsArray, 0, newArgs, 0, 1); + System.arraycopy(args, 0, newArgs, 1, args.length); + + return newArgs; + } + + private String getFullLabel(CommandNode node) { + + ArrayList labels; + + for (labels = new ArrayList(); node != null; node = node.getParent()) { + + String name = node.getName(); + + if (name != null) { + labels.add(name); + } + } + + Collections.reverse(labels); + labels.remove(0); + + final StringBuilder builder = new StringBuilder(); + + labels.forEach((s) -> builder.append(s).append(' ')); + + return builder.toString(); + } + + public void sendStackTrace(CommandSender sender,Exception exception) { + + final String rootCauseMessage = ExceptionUtils.getRootCauseMessage(exception); + + sender.sendMessage(ChatColor.RED + "Message: " + rootCauseMessage); + + final String cause = ExceptionUtils.getStackTrace(exception); + final StringTokenizer tokenizer = new StringTokenizer(cause); + + String exceptionType = ""; + String details = ""; + + boolean parsingNeeded = false; + + while(tokenizer.hasMoreTokens()) { + + String token = tokenizer.nextToken(); + + if (token.equalsIgnoreCase("Caused")) { + tokenizer.nextToken(); + parsingNeeded = true; + exceptionType = tokenizer.nextToken(); + } else if (token.equalsIgnoreCase("at") && parsingNeeded) { + details = tokenizer.nextToken(); + break; + } + } + + sender.sendMessage(ChatColor.RED + "Exception: " + exceptionType.replace(":", "")); + sender.sendMessage(ChatColor.RED + "Details:"); + sender.sendMessage(ChatColor.RED + details); + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/bukkit/CommandMap.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/bukkit/CommandMap.java new file mode 100644 index 0000000..5495162 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/bukkit/CommandMap.java @@ -0,0 +1,105 @@ +package com.elevatemc.elib.command.bukkit; + +import com.elevatemc.elib.command.command.CommandNode; +import org.apache.commons.lang.Validate; +import org.bukkit.Location; +import org.bukkit.Server; +import org.bukkit.command.CommandException; +import org.bukkit.command.CommandSender; +import org.bukkit.command.SimpleCommandMap; +import org.bukkit.entity.Player; +import org.bukkit.util.StringUtil; + +import java.util.*; + +public class CommandMap extends SimpleCommandMap { + + public CommandMap(Server server) { + super(server); + } + + @Override + public List tabComplete(CommandSender sender, String cmdLine, Location location) { + Validate.notNull(sender, "Sender cannot be null"); + Validate.notNull(cmdLine, "Command line cannot null"); + + final int spaceIndex = cmdLine.indexOf(32); + + String prefix; + + if (spaceIndex == -1) { + + final ArrayList completions = new ArrayList(); + final Map knownCommands = this.knownCommands; + prefix = sender instanceof Player ? "/" : ""; + + for (Map.Entry entry : knownCommands.entrySet()) { + + final String name = entry.getKey(); + + if (StringUtil.startsWithIgnoreCase(name, cmdLine)) { + + final org.bukkit.command.Command command = entry.getValue(); + + if (command instanceof Command) { + + CommandNode executionNode = ((Command)command).node.getCommand(name); + + if (executionNode == null) { + executionNode = ((Command)command).node; + } + + if (!executionNode.hasCommands()) { + + CommandNode testNode = executionNode.getCommand(name); + + if (testNode == null) { + testNode = ((Command)command).node.getCommand(name); + } + + if (testNode.canUse(sender)) { + completions.add(prefix + name); + } + } else if (executionNode.getSubCommands(sender, false).size() != 0) { + completions.add(prefix + name); + } + } else if (command.testPermissionSilent(sender)) { + completions.add(prefix + name); + } + } + } + + Collections.sort(completions, String.CASE_INSENSITIVE_ORDER); + return completions; + } else { + + final String commandName = cmdLine.substring(0, spaceIndex); + + final org.bukkit.command.Command target = this.getCommand(commandName); + + if (target == null) { + return null; + } else if (!target.testPermissionSilent(sender)) { + return null; + } else { + prefix = cmdLine.substring(spaceIndex + 1); + final String[] args = prefix.split(" "); + + try { + + final List completions = target instanceof Command ? ((Command)target).tabComplete(sender, cmdLine) : target.tabComplete(sender, commandName, args, location); + + if (completions != null && completions.size() > 0) { + completions.sort(String.CASE_INSENSITIVE_ORDER); + } + + return completions; + } catch (CommandException ex) { + throw ex; + } catch (Throwable ex) { + throw new CommandException("Unhandled exception executing tab-completer for '" + cmdLine + "' in " + target, ex); + } + } + } + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/bukkit/HelpTopic.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/bukkit/HelpTopic.java new file mode 100644 index 0000000..78eaf25 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/bukkit/HelpTopic.java @@ -0,0 +1,53 @@ +package com.elevatemc.elib.command.bukkit; + +import com.elevatemc.elib.command.command.CommandNode; +import org.apache.commons.lang.StringUtils; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; + +import java.util.Set; + +public class HelpTopic extends org.bukkit.help.HelpTopic { + + private CommandNode node; + + public HelpTopic(CommandNode node, Set aliases) { + + this.node = node; + this.name = "/" + node.getName(); + + String description = node.getDescription(); + + if (description.length() < 32) { + this.shortText = description; + } else { + this.shortText = description.substring(0, 32); + } + + StringBuilder sb = new StringBuilder(); + + sb.append(ChatColor.GOLD); + sb.append("Description: "); + sb.append(ChatColor.WHITE); + sb.append(node.getDescription()); + sb.append("\n"); + sb.append(ChatColor.GOLD); + sb.append("Usage: "); + sb.append(ChatColor.WHITE); + sb.append(node.getUsageForHelpTopic()); + + if (aliases != null && aliases.size() > 0) { + sb.append("\n"); + sb.append(ChatColor.GOLD); + sb.append("Aliases: "); + sb.append(ChatColor.WHITE); + sb.append(StringUtils.join(aliases, ", ")); + } + + this.fullText = sb.toString(); + } + + public boolean canSee(CommandSender commandSender) { + return this.node.canUse(commandSender); + } +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/command/CommandConfiguration.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/command/CommandConfiguration.java new file mode 100644 index 0000000..1009a8e --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/command/CommandConfiguration.java @@ -0,0 +1,33 @@ +package com.elevatemc.elib.command.command; + +import lombok.Getter; +import org.bukkit.ChatColor; + +public class CommandConfiguration { + + @Getter private String noPermissionMessage; + + @Getter private String playerOnlyCommandMessage; + @Getter private String consoleOnlyCommandMessage; + + public CommandConfiguration(String noPermissionMessage) { + this.noPermissionMessage = ChatColor.translateAlternateColorCodes('&',noPermissionMessage); + + this.playerOnlyCommandMessage = ChatColor.RED + "This is a player-only command. It can only be used from in-game."; + this.consoleOnlyCommandMessage = ChatColor.RED + "This is a console-only utility command. It cannot be used from in-game."; + } + + public void setNoPermissionMessage(String noPermissionMessage) { + this.noPermissionMessage = ChatColor.translateAlternateColorCodes('&',noPermissionMessage); + } + + public void setPlayerOnlyCommandMessage(String playerOnlyCommandMessage) { + this.playerOnlyCommandMessage = ChatColor.translateAlternateColorCodes('&',playerOnlyCommandMessage); + } + + public void setConsoleOnlyCommandMessage(String consoleOnlyCommandMessage) { + this.consoleOnlyCommandMessage = ChatColor.translateAlternateColorCodes('&',consoleOnlyCommandMessage); + } + + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/command/CommandNode.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/command/CommandNode.java new file mode 100644 index 0000000..c4a9086 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/command/CommandNode.java @@ -0,0 +1,472 @@ +package com.elevatemc.elib.command.command; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.command.flag.Data; +import com.elevatemc.elib.command.flag.FlagData; +import com.elevatemc.elib.command.param.ParameterData; +import com.elevatemc.elib.command.param.ParameterType; +import com.google.common.base.Stopwatch; +import com.google.common.base.Strings; +import com.google.common.collect.Lists; + +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import mkremins.fanciful.FancyMessage; +import com.elevatemc.elib.command.CommandHandler; +import com.elevatemc.elib.command.argument.Arguments; +import org.apache.commons.lang3.StringUtils; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandException; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; +import org.spigotmc.SpigotConfig; + +import javax.annotation.Nonnull; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +public class CommandNode { + + @Getter @NonNull private String name; + @Getter @Setter private Set aliases = new HashSet<>(); + @Getter @NonNull private String permission; + @Getter @Setter private String description; + @Getter @Setter private boolean async; + @Getter @Setter private boolean hidden; + @Getter @Setter protected Method method; + @Getter @Setter protected Class owningClass; + @Getter @Setter private List validFlags; + @Getter @Setter private List parameters; + @Getter @Setter private Map children = new TreeMap<>(); + @Getter @Setter private CommandNode parent; + @Getter @Setter private boolean logToConsole; + + public CommandNode() { + } + + public CommandNode(Class owningClass) { + this.owningClass = owningClass; + } + + public void registerCommand(CommandNode commandNode) { + commandNode.setParent(this); + this.children.put(commandNode.getName(), commandNode); + } + + public boolean hasCommand(String name) { + return this.children.containsKey(name.toLowerCase()); + } + + public CommandNode getCommand(String name) { + return this.children.get(name.toLowerCase()); + } + + public boolean hasCommands() { + return this.children.size() > 0; + } + + public CommandNode findCommand(Arguments arguments) { + + if (arguments.getArguments().size() > 0) { + + final String trySub = arguments.getArguments().get(0); + + if (this.hasCommand(trySub)) { + arguments.getArguments().remove(0); + CommandNode returnNode = this.getCommand(trySub); + return returnNode.findCommand(arguments); + } + + } + + return this; + } + + public boolean isValidFlag(String test) { + return test.length() == 1 ? this.validFlags.contains(test) : this.validFlags.contains(test.toLowerCase()); + } + + public boolean canUse(CommandSender sender) { + + if (this.permission == null || this.permission.equals("")) { + return true; + } + + if (this.permission.equals("op") && sender.isOp()) { + return true; + } + + if (this.permission.equals("console") && sender instanceof ConsoleCommandSender) { + return true; + } + + return sender.isOp() || sender instanceof ConsoleCommandSender || sender.hasPermission(this.permission); + } + + public FancyMessage getUsage(String realLabel) { + + final FancyMessage usage = (new FancyMessage("Usage: /" + realLabel)).color(ChatColor.RED); + + if (!Strings.isNullOrEmpty(this.getDescription())) { + usage.tooltip(ChatColor.YELLOW + this.getDescription()); + } + + final List flags = Lists.newArrayList(); + + flags.addAll(this.parameters.stream().filter((datax) -> datax instanceof FlagData).map((datax) -> (FlagData)datax).collect(Collectors.toList())); + + final List parameters = Lists.newArrayList(); + + parameters.addAll(this.parameters.stream().filter((datax) -> datax instanceof ParameterData).map((datax) -> (ParameterData)datax).collect(Collectors.toList())); + + boolean flagFirst = true; + + if (!flags.isEmpty()) { + + usage.then("(").color(ChatColor.RED); + + if (!Strings.isNullOrEmpty(this.getDescription())) { + usage.tooltip(ChatColor.YELLOW + this.getDescription()); + } + + for (FlagData data : flags) { + + final String name = data.getNames().get(0); + + if (!flagFirst) { + + usage.then(" | ").color(ChatColor.RED); + + if (!Strings.isNullOrEmpty(this.getDescription())) { + usage.tooltip(ChatColor.YELLOW + this.getDescription()); + } + + } + + flagFirst = false; + usage.then("-" + name).color(ChatColor.AQUA); + + if (!Strings.isNullOrEmpty(data.getDescription())) { + usage.tooltip(ChatColor.GRAY + data.getDescription()); + } + + } + + usage.then(") ").color(ChatColor.RED); + if (!Strings.isNullOrEmpty(this.getDescription())) { + usage.tooltip(ChatColor.YELLOW + this.getDescription()); + } + + } + + if (!parameters.isEmpty()) { + + for(int index = 0; index < parameters.size(); ++index) { + + final ParameterData data = parameters.get(index); + final boolean required = data.getDefaultValue().isEmpty(); + + usage.then((required ? "<" : "[") + data.getName() + (data.isWildcard() ? "..." : "") + (required ? ">" : "]") + (index != parameters.size() - 1 ? " " : "")).color(ChatColor.RED); + if (!Strings.isNullOrEmpty(this.getDescription())) { + usage.tooltip(ChatColor.YELLOW + this.getDescription()); + } + } + } + + return usage; + } + + public FancyMessage getUsage() { + + final FancyMessage usage = new FancyMessage(""); + final List flags = Lists.newArrayList(); + + flags.addAll(this.parameters.stream().filter((datax) -> datax instanceof FlagData).map((datax) -> (FlagData)datax).collect(Collectors.toList())); + + final List parameters = Lists.newArrayList(); + + parameters.addAll(this.parameters.stream().filter((datax) -> datax instanceof ParameterData).map((datax) -> (ParameterData)datax).collect(Collectors.toList())); + + boolean flagFirst = true; + + if (!flags.isEmpty()) { + usage.then("(").color(ChatColor.DARK_AQUA); + + for (FlagData data : flags) { + + final String name = data.getNames().get(0); + + if (!flagFirst) { + usage.then(" | ").color(ChatColor.DARK_AQUA); + } + + flagFirst = false; + + usage.then("-" + name).color(ChatColor.DARK_AQUA); + + if (!Strings.isNullOrEmpty(data.getDescription())) { + usage.tooltip(ChatColor.GRAY + data.getDescription()); + } + + } + + usage.then(") ").color(ChatColor.DARK_AQUA); + } + + if (!parameters.isEmpty()) { + + for(int index = 0; index < parameters.size(); ++index) { + + final ParameterData data = parameters.get(index); + final boolean required = data.getDefaultValue().isEmpty(); + + usage.then((required ? "<" : "[") + data.getName() + (data.isWildcard() ? "..." : "") + (required ? ">" : "]") + (index != parameters.size() - 1 ? " " : "")).color(ChatColor.DARK_AQUA); + } + } + + return usage; + } + + public boolean invoke(CommandSender sender, Arguments arguments) throws CommandException { + if (this.method == null) { + if (this.hasCommands()) { + if (this.getSubCommands(sender, true).isEmpty()) { + + if (this.isHidden()) { + sender.sendMessage(SpigotConfig.unknownCommandMessage); + } else { + sender.sendMessage(ChatColor.RED + "No permission."); + } + } + } else { + sender.sendMessage(SpigotConfig.unknownCommandMessage); + } + + return true; + } else { + + final List objects = new ArrayList<>(this.method.getParameterCount()); + + objects.add(sender); + int index = 0; + + for (Data unknownData : this.parameters) { + + if (unknownData instanceof FlagData) { + + final FlagData flagData = (FlagData)unknownData; + + boolean value = flagData.isDefaultValue(); + + for (String flagDataName : flagData.getNames()) { + + if (arguments.hasFlag(flagDataName)) { + value = !value; + break; + } + } + + objects.add(flagData.getMethodIndex(),value); + } else if (unknownData instanceof ParameterData) { + + final ParameterData parameterData = (ParameterData)unknownData; + + String argument; + + try { + argument = arguments.getArguments().get(index); + } catch (Exception ex) { + + if (parameterData.getDefaultValue().isEmpty()) { + return false; + } + + argument = parameterData.getDefaultValue(); + } + + if (parameterData.isWildcard() && (argument.isEmpty() || !argument.equals(parameterData.getDefaultValue()))) { + argument = arguments.join(index); + } + + ParameterType type = eLib.getInstance().getCommandHandler().getParameterType(parameterData.getType()); + + if (parameterData.getParameterType() != null) { + + try { + type = (ParameterType)parameterData.getParameterType().newInstance(); + } catch (IllegalAccessException | InstantiationException ex) { + ex.printStackTrace(); + throw new CommandException("Failed to create ParameterType instance: " + parameterData.getParameterType().getName(),ex); + } + } + + if (type == null) { + Class t = parameterData.getParameterType() == null ? parameterData.getType() : parameterData.getParameterType(); + sender.sendMessage(ChatColor.RED + "No parameter type found: " + t.getSimpleName()); + return true; + } + + final Object result = type.transform(sender, argument); + + if (result == null) { + return true; + } + + objects.add(parameterData.getMethodIndex(), result); + ++index; + } + } + + try { + + Stopwatch stopwatch = Stopwatch.createStarted(); + this.method.invoke(null, objects.toArray()); + stopwatch.stop(); + + int executionThreshold = eLib.getInstance().getConfig().getInt("Command.TimeThreshold", 10); + + if (!this.async && this.logToConsole && stopwatch.elapsed(TimeUnit.MILLISECONDS) >= (long)executionThreshold) { + eLib.getInstance().getLogger().warning("Command '/" + this.getFullLabel() + "' took " + + stopwatch.elapsed(TimeUnit.MILLISECONDS) + "ms!"); + } + + return true; + } catch (InvocationTargetException| IllegalAccessException ex) { + ex.printStackTrace(); + throw new CommandException("An error occurred while executing the command", ex); + } + } + } + + public List getSubCommands(CommandSender sender, boolean print) { + + final List commands = new ArrayList<>(); + + if (this.canUse(sender)) { + + final String command = (sender instanceof Player ? "/" : "") + this.getFullLabel() + (this.parameters != null ? " " + this.getUsage().toOldMessageFormat() : "") + (!Strings.isNullOrEmpty(this.description) ? ChatColor.GRAY + " - " + this.getDescription() : ""); + + if (this.parent == null) { + commands.add(command); + } else if (this.parent.getName() != null && CommandHandler.ROOT_NODE.getCommand(this.parent.getName()) != this.parent) { + commands.add(command); + } + + if (this.hasCommands()) { + + for (CommandNode commandNode : this.getChildren().values()) { + + commands.addAll(commandNode.getSubCommands(sender,false)); + } + + } + + } + + if (!commands.isEmpty() && print) { + sender.sendMessage(ChatColor.GRAY.toString() + ChatColor.STRIKETHROUGH + StringUtils.repeat('-', 35)); + + for (String command : commands.stream().sorted(String::compareTo).collect(Collectors.toList())) { + sender.sendMessage(ChatColor.DARK_AQUA + command); + } + + sender.sendMessage(ChatColor.GRAY.toString() + ChatColor.STRIKETHROUGH + StringUtils.repeat('-', 35)); + } + + return commands; + } + + public Set getRealAliases() { + + final Set aliases = this.getAliases(); + + aliases.remove(this.getName()); + + return aliases; + } + + public String getFullLabel() { + + final List labels = new ArrayList(); + + for(CommandNode node = this; node != null; node = node.getParent()) { + + final String name = node.getName(); + + if (name != null) { + labels.add(name); + } + } + + Collections.reverse(labels); + + labels.remove(0); + + final StringBuilder builder = new StringBuilder(); + + labels.forEach((s) -> builder.append(s).append(' ')); + + return builder.toString().trim(); + } + + public String getUsageForHelpTopic() { + return this.method != null && this.parameters != null ? "/" + this.getFullLabel() + " " + ChatColor.stripColor(this.getUsage().toOldMessageFormat()) : ""; + } + + public CommandNode(@Nonnull String name,@Nonnull String permission) { + + if (name == null) { + throw new NullPointerException("name"); + } else if (permission == null) { + throw new NullPointerException("permission"); + } else { + this.name = name; + this.permission = permission; + } + + } + + public CommandNode(@NonNull String name, Set aliases, @NonNull String permission, String description, boolean async, boolean hidden, Method method, Class owningClass, List validFlags, List parameters, Map children, CommandNode parent, boolean logToConsole) { + if (name == null) { + throw new NullPointerException("name"); + } else if (permission == null) { + throw new NullPointerException("permission"); + } else { + this.name = name; + this.aliases = aliases; + this.permission = permission; + this.description = description; + this.async = async; + this.hidden = hidden; + this.method = method; + this.owningClass = owningClass; + this.validFlags = validFlags; + this.parameters = parameters; + this.children = children; + this.parent = parent; + this.logToConsole = logToConsole; + } + } + + public void setName(@NonNull String name) { + if (name == null) { + throw new NullPointerException("name"); + } else { + this.name = name; + } + } + + public void setPermission(@NonNull String permission) { + if (permission == null) { + throw new NullPointerException("permission"); + } else { + this.permission = permission; + } + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/defaults/BuildCommand.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/defaults/BuildCommand.java new file mode 100644 index 0000000..ec5aecd --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/defaults/BuildCommand.java @@ -0,0 +1,19 @@ +package com.elevatemc.elib.command.defaults; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.eLib; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.metadata.FixedMetadataValue; + +public class BuildCommand { + @Command(names = {"build"}, permission = "op") + public static void build(final Player sender) { + if (sender.hasMetadata("build")) { + sender.removeMetadata("build", eLib.getInstance()); + } else { + sender.setMetadata("build", new FixedMetadataValue(eLib.getInstance(), true)); + } + sender.sendMessage(ChatColor.YELLOW + "You are " + (sender.hasMetadata("build") ? (ChatColor.GREEN + "now") : (ChatColor.RED + "no longer")) + ChatColor.YELLOW + " in build mode."); + } +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/defaults/CommandInfoCommand.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/defaults/CommandInfoCommand.java new file mode 100644 index 0000000..89d7de3 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/defaults/CommandInfoCommand.java @@ -0,0 +1,47 @@ +package com.elevatemc.elib.command.defaults; + +import com.elevatemc.elib.command.argument.ArgumentProcessor; +import com.elevatemc.elib.command.argument.Arguments; +import com.elevatemc.elib.command.command.CommandNode; +import com.elevatemc.elib.command.CommandHandler; +import com.elevatemc.elib.command.param.Parameter; +import com.elevatemc.elib.command.Command; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.java.JavaPlugin; + +public class CommandInfoCommand { + + @Command(names = {"cmdinfo"}, permission = "elib.command.commandinfo", hidden = true) + public static void execute(CommandSender sender,@Parameter(name = "command",wildcard = true) String command) { + + final String[] args = command.split(" "); + final ArgumentProcessor processor = new ArgumentProcessor(); + final Arguments arguments = processor.process(args); + final CommandNode node = CommandHandler.ROOT_NODE.getCommand(arguments.getArguments().get(0)); + + if (node != null) { + + final CommandNode realNode = node.findCommand(arguments); + + if (realNode != null) { + + final JavaPlugin plugin = JavaPlugin.getProvidingPlugin(realNode.getOwningClass()); + + sender.sendMessage(ChatColor.WHITE + realNode.getFullLabel() + ChatColor.GRAY + ":"); + sender.sendMessage(ChatColor.GRAY + "-> " + ChatColor.AQUA + "Plugin: " + ChatColor.WHITE + plugin.getName()); + sender.sendMessage(ChatColor.GRAY + "-> " + ChatColor.AQUA + "Sub commands:"); + + for (CommandNode value : realNode.getChildren().values()) { + sender.sendMessage(" " + ChatColor.GRAY + "-> " + ChatColor.AQUA + value.getSubCommands(sender,false)); + } + + return; + } + + } + + sender.sendMessage(ChatColor.RED + "Command not found."); + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/defaults/EvalCommand.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/defaults/EvalCommand.java new file mode 100644 index 0000000..92f3c46 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/defaults/EvalCommand.java @@ -0,0 +1,22 @@ +package com.elevatemc.elib.command.defaults; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.command.param.Parameter; +import com.elevatemc.elib.command.Command; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; + +public class EvalCommand { + + @Command(names = {"eval"}, permission = "console", description = "Evaluates a commands", hidden = true) + public static void execute(CommandSender sender,@Parameter(name = "command",wildcard = true) String commandLine) { + + if (!(sender instanceof ConsoleCommandSender)) { + sender.sendMessage(eLib.getInstance().getCommandHandler().getCommandConfiguration().getConsoleOnlyCommandMessage()); + } else { + Bukkit.dispatchCommand(eLib.getInstance().getServer().getConsoleSender(), commandLine); + } + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/flag/Data.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/flag/Data.java new file mode 100644 index 0000000..9136512 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/flag/Data.java @@ -0,0 +1,4 @@ +package com.elevatemc.elib.command.flag; + +public interface Data { +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/flag/Flag.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/flag/Flag.java new file mode 100644 index 0000000..58638a6 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/flag/Flag.java @@ -0,0 +1,20 @@ +package com.elevatemc.elib.command.flag; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.regex.Pattern; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface Flag { + + Pattern FLAG_PATTERN = Pattern.compile("(-)([a-zA-Z])([\\w]*)?"); + + String[] value(); + + boolean defaultValue() default false; + + String description() default ""; +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/flag/FlagData.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/flag/FlagData.java new file mode 100644 index 0000000..a80b44e --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/flag/FlagData.java @@ -0,0 +1,15 @@ +package com.elevatemc.elib.command.flag; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.List; + +@AllArgsConstructor +public class FlagData implements Data { + + @Getter private List names; + @Getter private String description; + @Getter private boolean defaultValue; + @Getter private int methodIndex; +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/param/Parameter.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/Parameter.java new file mode 100644 index 0000000..93c5a27 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/Parameter.java @@ -0,0 +1,20 @@ +package com.elevatemc.elib.command.param; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface Parameter { + + String name(); + + String defaultValue() default ""; + + String[] tabCompleteFlags() default {}; + + boolean wildcard() default false; + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/param/ParameterData.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/ParameterData.java new file mode 100644 index 0000000..60575e6 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/ParameterData.java @@ -0,0 +1,20 @@ +package com.elevatemc.elib.command.param; + +import com.elevatemc.elib.command.flag.Data; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Set; + +@AllArgsConstructor +public final class ParameterData implements Data { + + @Getter private String name; + @Getter private String defaultValue; + @Getter private Class type; + @Getter private boolean wildcard; + @Getter private int methodIndex; + @Getter private Set tabCompleteFlags; + @Getter private Class parameterType; + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/param/ParameterType.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/ParameterType.java new file mode 100644 index 0000000..9075caf --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/ParameterType.java @@ -0,0 +1,24 @@ +package com.elevatemc.elib.command.param; + +import com.elevatemc.elib.eLib; +import com.mysql.jdbc.StringUtils; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public interface ParameterType { + + T transform(CommandSender sender,String source); + + default List tabComplete(Player sender,Set flags,String source) { + return eLib.getInstance().getServer().getOnlinePlayers() + .stream() + .filter(loopPlayer -> StringUtils.startsWithIgnoreCase(loopPlayer.getName(),source) && eLib.getInstance().getVisibilityHandler().treatAsOnline(loopPlayer,sender)) + .map(Player::getName) + .collect(Collectors.toList()); + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/BooleanParameterType.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/BooleanParameterType.java new file mode 100644 index 0000000..71ed118 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/BooleanParameterType.java @@ -0,0 +1,46 @@ +package com.elevatemc.elib.command.param.defaults; + +import com.elevatemc.elib.command.param.ParameterType; +import org.apache.commons.lang.StringUtils; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public class BooleanParameterType implements ParameterType { + + private static final Map MAP = new HashMap<>(); + + static { + MAP.put("true", true); + MAP.put("on", true); + MAP.put("yes", true); + + MAP.put("false", false); + MAP.put("off", false); + MAP.put("no", false); + } + + public Boolean transform(CommandSender sender, String source) { + + if (!MAP.containsKey(source.toLowerCase())) { + sender.sendMessage(ChatColor.RED + source + " is not a valid boolean."); + return null; + } + + return MAP.get(source.toLowerCase()); + } + + public List tabComplete(Player sender, Set flags, String source) { + return MAP.keySet() + .stream() + .filter(string -> StringUtils.startsWithIgnoreCase(string, source)) + .collect(Collectors.toList()); + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/DoubleParameterType.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/DoubleParameterType.java new file mode 100644 index 0000000..3687a3d --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/DoubleParameterType.java @@ -0,0 +1,41 @@ +package com.elevatemc.elib.command.param.defaults; + +import com.elevatemc.elib.command.param.ParameterType; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class DoubleParameterType implements ParameterType { + + public Double transform(CommandSender sender, String source) { + + if (source.toLowerCase().contains("e")) { + sender.sendMessage(ChatColor.RED + source + " is not a valid number."); + return null; + } + + try { + + final double parsed = Double.parseDouble(source); + + if (Double.isNaN(parsed) || !Double.isFinite(parsed)) { + sender.sendMessage(ChatColor.RED + source + " is not a valid number."); + return null; + } + + return parsed; + } catch (NumberFormatException exception) { + sender.sendMessage(ChatColor.RED + source + " is not a valid number."); + return null; + } + } + + public List tabComplete(Player sender, Set flags, String source) { + return new ArrayList<>(); + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/EnchantmentParameterType.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/EnchantmentParameterType.java new file mode 100644 index 0000000..bf09240 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/EnchantmentParameterType.java @@ -0,0 +1,24 @@ +package com.elevatemc.elib.command.param.defaults; + +import com.elevatemc.elib.command.param.ParameterType; +import com.elevatemc.elib.util.EnchantmentWrapper; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.enchantments.Enchantment; + +public class EnchantmentParameterType implements ParameterType { + + @Override + public Enchantment transform(CommandSender sender,String source) { + + final EnchantmentWrapper toReturn = EnchantmentWrapper.parse(source); + + if (toReturn == null) { + sender.sendMessage(ChatColor.RED + "Enchant " + ChatColor.YELLOW + source + ChatColor.RED + " not found."); + return null; + } else { + return toReturn.getBukkitEnchantment(); + } + + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/EntityTypeParameterType.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/EntityTypeParameterType.java new file mode 100644 index 0000000..9731fe5 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/EntityTypeParameterType.java @@ -0,0 +1,34 @@ +package com.elevatemc.elib.command.param.defaults; + +import com.elevatemc.elib.command.param.ParameterType; +import com.elevatemc.elib.util.EntityUtils; +import org.apache.commons.lang.StringUtils; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class EntityTypeParameterType implements ParameterType { + + public EntityType transform(CommandSender commandSender, String source) { + + final EntityType type = EntityUtils.parse(source); + + if (type == null) { + commandSender.sendMessage(ChatColor.RED + "No such entity type '" + ChatColor.YELLOW + source + ChatColor.RED + "' found."); + return null; + } + + return type; + } + + public List tabComplete(Player sender, Set flags, String source) { + return Arrays.stream(EntityType.values()).filter(entityType -> StringUtils.startsWithIgnoreCase(EntityUtils.getName(entityType),source)).map(EntityUtils::getName).collect(Collectors.toList()); + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/FloatParameterType.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/FloatParameterType.java new file mode 100644 index 0000000..f2429e2 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/FloatParameterType.java @@ -0,0 +1,41 @@ +package com.elevatemc.elib.command.param.defaults; + +import com.elevatemc.elib.command.param.ParameterType; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class FloatParameterType implements ParameterType { + + public Float transform(CommandSender sender, String source) { + + if (source.toLowerCase().contains("e")) { + sender.sendMessage(ChatColor.RED + source + " is not a valid number."); + return null; + } + + try { + + final float parsed = Float.parseFloat(source); + + if (Float.isNaN(parsed) || !Float.isFinite(parsed)) { + sender.sendMessage(ChatColor.RED + source + " is not a valid number."); + return null; + } + + return parsed; + } catch (NumberFormatException exception) { + sender.sendMessage(ChatColor.RED + source + " is not a valid number."); + return null; + } + } + + public List tabComplete(Player sender, Set flags, String source) { + return new ArrayList<>(); + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/GameModeParameterType.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/GameModeParameterType.java new file mode 100644 index 0000000..49067ab --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/GameModeParameterType.java @@ -0,0 +1,46 @@ +package com.elevatemc.elib.command.param.defaults; + +import com.elevatemc.elib.command.param.ParameterType; +import org.apache.commons.lang.StringUtils; +import org.bukkit.ChatColor; +import org.bukkit.GameMode; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public class GameModeParameterType implements ParameterType { + + private static final Map MAP = new HashMap<>(); + + static { + MAP.put("c", GameMode.CREATIVE); + MAP.put("creative", GameMode.CREATIVE); + MAP.put("1", GameMode.CREATIVE); + + MAP.put("s", GameMode.SURVIVAL); + MAP.put("survival", GameMode.SURVIVAL); + MAP.put("0", GameMode.SURVIVAL); + } + + public GameMode transform(CommandSender sender,String source) { + + if (!MAP.containsKey(source.toLowerCase())) { + sender.sendMessage(ChatColor.RED + source + " not found."); + return null; + } + + return MAP.get(source.toLowerCase()); + } + + public List tabComplete(Player sender,Set flags,String source) { + return MAP.keySet() + .stream() + .filter(string -> StringUtils.startsWithIgnoreCase(string, source)) + .collect(Collectors.toList()); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/IntegerParameterType.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/IntegerParameterType.java new file mode 100644 index 0000000..b72db14 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/IntegerParameterType.java @@ -0,0 +1,29 @@ +package com.elevatemc.elib.command.param.defaults; + +import com.elevatemc.elib.command.param.ParameterType; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class IntegerParameterType implements ParameterType { + + public Integer transform(CommandSender sender, String source) { + + try { + return (Integer.parseInt(source)); + } catch (NumberFormatException exception) { + sender.sendMessage(ChatColor.RED + source + " is not a valid number."); + return null; + } + + } + + public List tabComplete(Player sender, Set flags, String source) { + return new ArrayList<>(); + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/ItemStackParameterType.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/ItemStackParameterType.java new file mode 100644 index 0000000..fe52c0e --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/ItemStackParameterType.java @@ -0,0 +1,34 @@ +package com.elevatemc.elib.command.param.defaults; + +import com.elevatemc.elib.command.param.ParameterType; +import com.elevatemc.elib.util.ItemUtils; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class ItemStackParameterType implements ParameterType { + + @Override + public ItemStack transform(CommandSender sender, String source) { + + final ItemStack item = ItemUtils.get(source); + + if (item == null) { + sender.sendMessage(ChatColor.RED + "No item with the name " + source + " found."); + return null; + } + + return item; + } + + @Override + public List tabComplete(Player sender, Set flags, String source) { + return new ArrayList<>(); // it would probably be too intensive to go through all the aliases + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/LongParameterType.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/LongParameterType.java new file mode 100644 index 0000000..7db7aaa --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/LongParameterType.java @@ -0,0 +1,42 @@ +package com.elevatemc.elib.command.param.defaults; + +import com.elevatemc.elib.command.param.ParameterType; +import com.elevatemc.elib.util.TimeUtils; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class LongParameterType implements ParameterType { + + @Override + public Long transform(CommandSender sender,String source) { + + if (source.equalsIgnoreCase("perm") || source.equalsIgnoreCase("permanent")) { + return Long.valueOf(Integer.MAX_VALUE); + } + + try { + + final int toReturn = TimeUtils.parseTime(source); + + if ((toReturn * 1000L) <= 0) { + sender.sendMessage(ChatColor.RED + "Duration must be higher then 0."); + return null; + } + + return toReturn * 1000L; + } catch (NullPointerException | IllegalArgumentException ex) { + sender.sendMessage(ChatColor.RED + "Invalid duration"); + return null; + } + } + + @Override + public List tabComplete(Player sender,Set flags,String source) { + return new ArrayList<>(); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/OfflinePlayerParameterType.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/OfflinePlayerParameterType.java new file mode 100644 index 0000000..7b12337 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/OfflinePlayerParameterType.java @@ -0,0 +1,20 @@ +package com.elevatemc.elib.command.param.defaults; + +import com.elevatemc.elib.command.param.ParameterType; +import com.elevatemc.elib.eLib; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class OfflinePlayerParameterType implements ParameterType { + + public OfflinePlayer transform(CommandSender sender, String source) { + + if (sender instanceof Player && (source.equalsIgnoreCase("self") || source.equals(""))) { + return ((Player) sender); + } + + return eLib.getInstance().getServer().getOfflinePlayer(source); + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/PlayerParameterType.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/PlayerParameterType.java new file mode 100644 index 0000000..2603d24 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/PlayerParameterType.java @@ -0,0 +1,26 @@ +package com.elevatemc.elib.command.param.defaults; + +import com.elevatemc.elib.command.param.ParameterType; +import com.elevatemc.elib.eLib; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class PlayerParameterType implements ParameterType { + + public Player transform(CommandSender sender, String source) { + if (!(sender instanceof Player) || !source.equalsIgnoreCase("self") && !source.equals("")) { + + final Player player = eLib.getInstance().getServer().getPlayer(source); + + if (player != null && (!(sender instanceof Player) || eLib.getInstance().getVisibilityHandler().treatAsOnline(player, (Player)sender))) { + return player; + } else { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "No player with the name \"" + source + "\" found."); + return null; + } + } else { + return (Player)sender; + } + } +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/StringParameterType.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/StringParameterType.java new file mode 100644 index 0000000..0ed04bc --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/StringParameterType.java @@ -0,0 +1,12 @@ +package com.elevatemc.elib.command.param.defaults; + +import com.elevatemc.elib.command.param.ParameterType; +import org.bukkit.command.CommandSender; + +public class StringParameterType implements ParameterType { + + public String transform(CommandSender sender,String value) { + return value; + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/UUIDParameterType.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/UUIDParameterType.java new file mode 100644 index 0000000..1c217fe --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/UUIDParameterType.java @@ -0,0 +1,29 @@ +package com.elevatemc.elib.command.param.defaults; + +import com.elevatemc.elib.command.param.ParameterType; +import com.elevatemc.elib.util.UUIDUtils; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.UUID; + +public class UUIDParameterType implements ParameterType { + + public UUID transform(CommandSender sender, String source) { + + if (sender instanceof Player && (source.equalsIgnoreCase("self") || source.equals(""))) { + return ((Player) sender).getUniqueId(); + } + + final UUID uuid = UUIDUtils.uuid(source); + + if (uuid == null) { + sender.sendMessage(ChatColor.RED + source + " has never joined the server."); + return null; + } + + return uuid; + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/WorldParameterType.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/WorldParameterType.java new file mode 100644 index 0000000..699b61a --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/WorldParameterType.java @@ -0,0 +1,32 @@ +package com.elevatemc.elib.command.param.defaults; + +import com.elevatemc.elib.command.param.ParameterType; +import com.elevatemc.elib.eLib; +import org.bukkit.ChatColor; +import org.bukkit.World; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class WorldParameterType implements ParameterType { + + public World transform(CommandSender sender, String source) { + + final World world = eLib.getInstance().getServer().getWorld(source); + + if (world == null) { + sender.sendMessage(ChatColor.RED + "No world with the name " + source + " found."); + return null; + } + + return world; + } + + public List tabComplete(Player sender, Set flags, String source) { + return eLib.getInstance().getServer().getWorlds().stream().map(World::getName).collect(Collectors.toList()); + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/filter/BaseFilter.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/filter/BaseFilter.java new file mode 100644 index 0000000..d8dc059 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/filter/BaseFilter.java @@ -0,0 +1,36 @@ +package com.elevatemc.elib.command.param.defaults.filter; + +import com.elevatemc.elib.command.param.ParameterType; +import com.google.common.collect.ImmutableList; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; + +abstract class BaseFilter implements ParameterType { + protected Set bannedPatterns; + + BaseFilter() { + this.bannedPatterns = new HashSet(); + } + + @Override + public String transform(final CommandSender sender, String value) { + for (final Pattern bannedPattern : this.bannedPatterns) { + if (bannedPattern.matcher(value).find()) { + sender.sendMessage(ChatColor.RED + "Command contains inappropriate content."); + return null; + } + } + return value; + } + + @Override + public List tabComplete(final Player sender, Set flags, String prefix) { + return ImmutableList.of(); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/filter/NormalFilter.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/filter/NormalFilter.java new file mode 100644 index 0000000..1be55c3 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/filter/NormalFilter.java @@ -0,0 +1,17 @@ +package com.elevatemc.elib.command.param.defaults.filter; + +import java.util.regex.Pattern; + +public class NormalFilter extends BaseFilter { + public NormalFilter() { + this.bannedPatterns.add(Pattern.compile("n+[i1l|]+gg+[e3]+r+", 2)); + this.bannedPatterns.add(Pattern.compile("k+i+l+l+ *y*o*u+r+ *s+e+l+f+", 2)); + this.bannedPatterns.add(Pattern.compile("f+a+g+[o0]+t+", 2)); + this.bannedPatterns.add(Pattern.compile("\\bk+y+s+\\b", 2)); + this.bannedPatterns.add(Pattern.compile("b+e+a+n+e+r+", 2)); + this.bannedPatterns.add(Pattern.compile("\\d{1,3}[,.]\\d{1,3}[,.]\\d{1,3}[,.]\\d{1,3}", 2)); + this.bannedPatterns.add(Pattern.compile("optifine\\.(?=\\w+)(?!net)", 2)); + this.bannedPatterns.add(Pattern.compile("gyazo\\.(?=\\w+)(?!com)", 2)); + this.bannedPatterns.add(Pattern.compile("prntscr\\.(?=\\w+)(?!com)", 2)); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/filter/StrictFilter.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/filter/StrictFilter.java new file mode 100644 index 0000000..49a5a92 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/filter/StrictFilter.java @@ -0,0 +1,4 @@ +package com.elevatemc.elib.command.param.defaults.filter; + +public class StrictFilter extends NormalFilter { +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/offlineplayer/OfflinePlayerWrapper.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/offlineplayer/OfflinePlayerWrapper.java new file mode 100644 index 0000000..c972f4a --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/offlineplayer/OfflinePlayerWrapper.java @@ -0,0 +1,99 @@ +package com.elevatemc.elib.command.param.defaults.offlineplayer; + +import java.util.*; + +import net.minecraft.server.v1_8_R3.EntityPlayer; +import net.minecraft.server.v1_8_R3.MinecraftServer; +import net.minecraft.server.v1_8_R3.PlayerInteractManager; +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.util.Callback; +import com.elevatemc.elib.util.TaskUtil; +import com.elevatemc.elib.util.UUIDUtils; +import lombok.Getter; +import org.bukkit.entity.*; + +import org.bukkit.craftbukkit.v1_8_R3.*; +import com.mojang.authlib.*; + +public class OfflinePlayerWrapper { + + private String source; + + @Getter private UUID uniqueId; + @Getter private String name; + + public OfflinePlayerWrapper(String source) { + this.source = source; + } + + public void loadAsync(Callback callback) { + TaskUtil.executeWithPoolIfRequired(() -> callback.callback(this.loadSync())); + } + + public Player loadSync() { + if ((this.source.charAt(0) == '\"' || this.source.charAt(0) == '\'') && (this.source.charAt(this.source.length() - 1) == '\"' || + this.source.charAt(this.source.length() - 1) == '\'')) { + + this.source = this.source.replace("'", "").replace("\"", ""); + + this.uniqueId = UUIDUtils.uuid(this.source); + + if (this.uniqueId == null) { + this.name = this.source; + return null; + } + + this.name = UUIDUtils.name(this.uniqueId); + + if (eLib.getInstance().getServer().getPlayer(this.uniqueId) != null) { + return eLib.getInstance().getServer().getPlayer(this.uniqueId); + } + if (!eLib.getInstance().getServer().getOfflinePlayer(this.uniqueId).hasPlayedBefore()) { + return null; + } + + final MinecraftServer server = ((CraftServer) eLib.getInstance().getServer()).getServer(); + final EntityPlayer entity = new EntityPlayer(server, server.getWorldServer(0), new GameProfile(this.uniqueId, this.name), + new PlayerInteractManager(server.getWorldServer(0))); + final Player player = entity.getBukkitEntity(); + + if (player != null) { + player.loadData(); + } + + return player; + } else { + + if (eLib.getInstance().getServer().getPlayer(this.source) != null) { + return eLib.getInstance().getServer().getPlayer(this.source); + } + + this.uniqueId = UUIDUtils.uuid(this.source); + + if (this.uniqueId == null) { + this.name = this.source; + return null; + } + + this.name = UUIDUtils.name(this.uniqueId); + + if (eLib.getInstance().getServer().getPlayer(this.uniqueId) != null) { + return eLib.getInstance().getServer().getPlayer(this.uniqueId); + } + + if (!eLib.getInstance().getServer().getOfflinePlayer(this.uniqueId).hasPlayedBefore()) { + return null; + } + + final MinecraftServer server = ((CraftServer) eLib.getInstance().getServer()).getServer(); + final EntityPlayer entity = new EntityPlayer(server, server.getWorldServer(0), new GameProfile(this.uniqueId, this.name), + new PlayerInteractManager(server.getWorldServer(0))); + final Player player = entity.getBukkitEntity(); + if (player != null) { + player.loadData(); + } + return player; + } + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/offlineplayer/OfflinePlayerWrapperParameterType.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/offlineplayer/OfflinePlayerWrapperParameterType.java new file mode 100644 index 0000000..458b5ab --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/param/defaults/offlineplayer/OfflinePlayerWrapperParameterType.java @@ -0,0 +1,14 @@ +package com.elevatemc.elib.command.param.defaults.offlineplayer; + +import com.elevatemc.elib.command.param.ParameterType; + +import org.bukkit.command.CommandSender; + +public class OfflinePlayerWrapperParameterType implements ParameterType { + + @Override + public OfflinePlayerWrapper transform(final CommandSender sender,final String source) { + return new OfflinePlayerWrapper(source); + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/processor/MethodProcessor.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/processor/MethodProcessor.java new file mode 100644 index 0000000..d230357 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/processor/MethodProcessor.java @@ -0,0 +1,156 @@ +package com.elevatemc.elib.command.processor; + +import com.elevatemc.elib.command.command.CommandNode; +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.command.flag.Data; +import com.elevatemc.elib.command.flag.Flag; +import com.elevatemc.elib.command.flag.FlagData; +import com.elevatemc.elib.command.param.ParameterData; +import com.google.common.collect.Sets; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.CommandHandler; +import org.bukkit.command.CommandSender; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.*; + +public class MethodProcessor implements Processor> { + + public Set process(Method value) { + + if (value.isAnnotationPresent(Command.class) && value.getParameterCount() >= 1 && CommandSender.class.isAssignableFrom(value.getParameterTypes()[0])) { + + final Command command = value.getAnnotation(Command.class); + final Class owningClass = value.getDeclaringClass(); + + final List allParams = new ArrayList<>(); + final List flagNames = new ArrayList<>(); + + if (value.getParameterCount() > 1) { + + for (int i = 1; i < value.getParameterCount(); ++i) { + + final Parameter parameter = value.getParameters()[i]; + + if (parameter.isAnnotationPresent(com.elevatemc.elib.command.param.Parameter.class)) { + + final com.elevatemc.elib.command.param.Parameter param = parameter.getAnnotation(com.elevatemc.elib.command.param.Parameter.class); + final ParameterData data = new ParameterData(param.name(), param.defaultValue(), parameter.getType(), param.wildcard(), i, Sets.newHashSet(param.tabCompleteFlags()), parameter.isAnnotationPresent(Type.class) ? ((Type)parameter.getAnnotation(Type.class)).value() : null); + + allParams.add(data); + } else { + + if (!parameter.isAnnotationPresent(Flag.class)) { + throw new IllegalArgumentException("Every parameter, other than the sender, must have the Param or the Flag annotation! (" + value.getDeclaringClass().getName() + ":" + value.getName() + ")"); + } + + final Flag flag = parameter.getAnnotation(Flag.class); + final FlagData data = new FlagData(Arrays.asList(flag.value()), flag.description(), flag.defaultValue(), i); + + + allParams.add(data); + flagNames.addAll(Arrays.asList(flag.value())); + } + } + } + + final Set registered = new HashSet<>(); + + for (int i = 0; i < command.names().length; i++) { + + String name = command.names()[i]; + + boolean change = true; + boolean hadChild = false; + + name = name.toLowerCase().trim(); + + String[] cmdNames; + + if (name.contains(" ")) { + cmdNames = name.split(" "); + } else { + cmdNames = new String[]{name}; + } + + String primary = cmdNames[0]; + + CommandNode workingNode = new CommandNode(owningClass); + + if (CommandHandler.ROOT_NODE.hasCommand(primary)) { + workingNode = eLib.getInstance().getCommandHandler().ROOT_NODE.getCommand(primary); + change = false; + } + + if (change) { + workingNode.setName(cmdNames[0]); + } else { + workingNode.getAliases().add(cmdNames[0]); + } + + CommandNode parentNode = new CommandNode(owningClass); + + if (workingNode.hasCommand(cmdNames[0])) { + parentNode = workingNode.getCommand(cmdNames[0]); + } else { + parentNode.setName(cmdNames[0]); + parentNode.setPermission(""); + } + + if (cmdNames.length > 1) { + hadChild = true; + workingNode.registerCommand(parentNode); + CommandNode childNode = new CommandNode(owningClass); + + for(int i2 = 1; i2 < cmdNames.length; ++i2) { + + String subName = cmdNames[i2]; + + childNode.setName(subName); + + if (parentNode.hasCommand(subName)) { + childNode = parentNode.getCommand(subName); + } + + parentNode.registerCommand(childNode); + + if (i2 == cmdNames.length - 1) { + childNode.setMethod(value); + childNode.setAsync(command.async()); + childNode.setHidden(command.hidden()); + childNode.setPermission(command.permission()); + childNode.setDescription(command.description()); + childNode.setValidFlags(flagNames); + childNode.setParameters(allParams); + childNode.setLogToConsole(command.logToConsole()); + } else { + parentNode = childNode; + childNode = new CommandNode(owningClass); + } + } + } + + if (!hadChild) { + parentNode.setMethod(value); + parentNode.setAsync(command.async()); + parentNode.setHidden(command.hidden()); + parentNode.setPermission(command.permission()); + parentNode.setDescription(command.description()); + parentNode.setValidFlags(flagNames); + parentNode.setParameters(allParams); + parentNode.setLogToConsole(command.logToConsole()); + workingNode.registerCommand(parentNode); + } + + CommandHandler.ROOT_NODE.registerCommand(workingNode); + registered.add(workingNode); + } + + return registered; + } else { + + return null; + } + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/processor/Processor.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/processor/Processor.java new file mode 100644 index 0000000..304eaf6 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/processor/Processor.java @@ -0,0 +1,8 @@ +package com.elevatemc.elib.command.processor; + +@FunctionalInterface +public interface Processor { + + R process(T var1); + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/processor/Type.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/processor/Type.java new file mode 100644 index 0000000..f42c28e --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/processor/Type.java @@ -0,0 +1,15 @@ +package com.elevatemc.elib.command.processor; + +import com.elevatemc.elib.command.param.ParameterType; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface Type { + + Class value(); +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/utils/EasyClass.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/utils/EasyClass.java new file mode 100644 index 0000000..bb49404 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/utils/EasyClass.java @@ -0,0 +1,33 @@ +package com.elevatemc.elib.command.utils; + +public class EasyClass { + + private Class clazz; + private T object; + + public EasyClass(T object) { + + if (object != null) { + this.clazz = (Class)object.getClass(); + } + + this.object = object; + } + + public Class getClazz() { + return this.clazz; + } + + public T get() { + return this.object; + } + + public EasyMethod getMethod(String name, Object... parameters) { + return new EasyMethod(this, name, parameters); + } + + public EasyField getField(String name) { + return new EasyField(this, name); + } +} + diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/utils/EasyField.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/utils/EasyField.java new file mode 100644 index 0000000..81ea546 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/utils/EasyField.java @@ -0,0 +1,49 @@ +package com.elevatemc.elib.command.utils; + +import java.lang.reflect.Field; + +public class EasyField { + + private Field field; + private EasyClass owner; + + public EasyField(EasyClass owner, String name) { + this.owner = owner; + + try { + if (this.owner != null && this.owner.getClazz() != null) { + this.field = this.owner.getClazz().getDeclaredField(name); + } + } catch (NoSuchFieldException ex) { + ex.printStackTrace(); + } + + } + + public T get() { + try { + if (this.field != null) { + this.field.setAccessible(true); + return (T)this.field.get(this.owner.get()); + } + } catch (IllegalAccessException ex) { + ex.printStackTrace(); + } + + return null; + } + + public void set(T value) { + if (this.field.isAccessible()) { + this.field.setAccessible(true); + } + + try { + this.field.set(this.owner.get(), value); + } catch (IllegalAccessException ex) { + ex.printStackTrace(); + } + + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/command/utils/EasyMethod.java b/Network/eLib/src/main/java/com/elevatemc/elib/command/utils/EasyMethod.java new file mode 100644 index 0000000..ed3d0c3 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/command/utils/EasyMethod.java @@ -0,0 +1,57 @@ +package com.elevatemc.elib.command.utils; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class EasyMethod { + + private EasyClass owner; + private Method method; + private Object[] parameters; + + public EasyMethod(EasyClass owner, String name, Object... parameters) { + this.owner = owner; + this.parameters = parameters; + + Class[] classes = new Class[parameters.length]; + + for(int i = 0; i < parameters.length; ++i) { + classes[i] = parameters[i].getClass(); + } + + try { + this.method = owner.getClazz().getDeclaredMethod(name, classes); + } catch (NoSuchMethodException ex) { + ex.printStackTrace(); + } + + } + + public Object invoke() { + + this.method.setAccessible(true); + + if (this.method.getReturnType().equals(Void.TYPE)) { + + try { + this.method.invoke(this.owner.getClazz(), this.parameters); + } catch (InvocationTargetException| IllegalAccessException ex) { + ex.printStackTrace(); + } + + return null; + + } else { + + try { + return this.method.getReturnType().cast(this.method.invoke(this.owner.getClazz(), this.parameters)); + } catch (InvocationTargetException | IllegalAccessException ex) { + ex.printStackTrace(); + return null; + } + + } + } + + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/cuboid/Cuboid.java b/Network/eLib/src/main/java/com/elevatemc/elib/cuboid/Cuboid.java new file mode 100644 index 0000000..59a6639 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/cuboid/Cuboid.java @@ -0,0 +1,726 @@ +package com.elevatemc.elib.cuboid; + +import com.elevatemc.elib.eLib; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.configuration.serialization.ConfigurationSerializable; + +import java.util.*; + +public class Cuboid implements Iterable, Cloneable, ConfigurationSerializable { + + protected final String worldName; + protected final int x1, y1, z1; + protected final int x2, y2, z2; + + /** + * Construct a Cuboid given two Location objects which represent any two corners of the Cuboid. + * Note: The 2 locations must be on the same world. + * + * @param l1 - One of the corners + * @param l2 - The other corner + */ + public Cuboid(Location l1,Location l2) { + if (!l1.getWorld().equals(l2.getWorld())) { + throw new IllegalArgumentException("Locations must be on the same world"); + } + + this.worldName = l1.getWorld().getName(); + this.x1 = Math.min(l1.getBlockX(), l2.getBlockX()); + this.y1 = Math.min(l1.getBlockY(), l2.getBlockY()); + this.z1 = Math.min(l1.getBlockZ(), l2.getBlockZ()); + this.x2 = Math.max(l1.getBlockX(), l2.getBlockX()); + this.y2 = Math.max(l1.getBlockY(), l2.getBlockY()); + this.z2 = Math.max(l1.getBlockZ(), l2.getBlockZ()); + } + + /** + * Construct a one-block Cuboid at the given Location of the Cuboid. + * + * @param l1 location of the Cuboid + */ + public Cuboid(Location l1) { + this(l1, l1); + } + + /** + * Copy constructor. + * + * @param other - The Cuboid to copy + */ + public Cuboid(Cuboid other) { + this(other.getWorld().getName(), other.x1, other.y1, other.z1, other.x2, other.y2, other.z2); + } + + /** + * Construct a Cuboid in the given World and xyz co-ordinates + * + * @param world - The Cuboid's world + * @param x1 - X co-ordinate of corner 1 + * @param y1 - Y co-ordinate of corner 1 + * @param z1 - Z co-ordinate of corner 1 + * @param x2 - X co-ordinate of corner 2 + * @param y2 - Y co-ordinate of corner 2 + * @param z2 - Z co-ordinate of corner 2 + */ + public Cuboid(World world,int x1,int y1,int z1,int x2,int y2,int z2) { + this.worldName = world.getName(); + this.x1 = Math.min(x1, x2); + this.x2 = Math.max(x1, x2); + this.y1 = Math.min(y1, y2); + this.y2 = Math.max(y1, y2); + this.z1 = Math.min(z1, z2); + this.z2 = Math.max(z1, z2); + } + + /** + * Construct a Cuboid in the given world name and xyz co-ordinates. + * + * @param worldName - The Cuboid's world name + * @param x1 - X co-ordinate of corner 1 + * @param y1 - Y co-ordinate of corner 1 + * @param z1 - Z co-ordinate of corner 1 + * @param x2 - X co-ordinate of corner 2 + * @param y2 - Y co-ordinate of corner 2 + * @param z2 - Z co-ordinate of corner 2 + */ + private Cuboid(String worldName, int x1, int y1, int z1, int x2, int y2, int z2) { + this.worldName = worldName; + this.x1 = Math.min(x1, x2); + this.x2 = Math.max(x1, x2); + this.y1 = Math.min(y1, y2); + this.y2 = Math.max(y1, y2); + this.z1 = Math.min(z1, z2); + this.z2 = Math.max(z1, z2); + } + + /** + * Construct a Cuboid using a map with the following keys: worldName, x1, x2, y1, y2, z1, z2 + * + * @param map - The map of keys. + */ + public Cuboid(Map map) { + this.worldName = (String) map.get("worldName"); + this.x1 = (Integer) map.get("x1"); + this.x2 = (Integer) map.get("x2"); + this.y1 = (Integer) map.get("y1"); + this.y2 = (Integer) map.get("y2"); + this.z1 = (Integer) map.get("z1"); + this.z2 = (Integer) map.get("z2"); + } + + + @Override + public Map serialize() { + Map map = new HashMap(); + map.put("worldName", this.worldName); + map.put("x1", this.x1); + map.put("y1", this.y1); + map.put("z1", this.z1); + map.put("x2", this.x2); + map.put("y2", this.y2); + map.put("z2", this.z2); + return map; + } + + /** + * Get the Location of the lower northeast corner of the Cuboid (minimum XYZ co-ordinates). + * + * @return Location of the lower northeast corner + */ + public Location getLowerNE() { + return new Location(this.getWorld(), this.x1, this.y1, this.z1); + } + + /** + * Get the Location of the upper southwest corner of the Cuboid (maximum XYZ co-ordinates). + * + * @return Location of the upper southwest corner + */ + public Location getUpperSW() { + return new Location(this.getWorld(), this.x2, this.y2, this.z2); + } + + /** + * Get the blocks in the Cuboid. + * + * @return The blocks in the Cuboid + */ + public List getBlocks() { + Iterator blockI = this.iterator(); + List copy = new ArrayList(); + while (blockI.hasNext()) + copy.add(blockI.next()); + return copy; + } + + /** + * Get the the centre of the Cuboid. + * + * @return Location at the centre of the Cuboid + */ + public Location getCenter() { + int x1 = this.getUpperX() + 1; + int y1 = this.getUpperY() + 1; + int z1 = this.getUpperZ() + 1; + return new Location(this.getWorld(), this.getLowerX() + (x1 - this.getLowerX()) / 2.0, this.getLowerY() + (y1 - this.getLowerY()) / 2.0, this.getLowerZ() + (z1 - this.getLowerZ()) / 2.0); + } + + /** + * Get the Cuboid's world. + * + * @return The World object representing this Cuboid's world + * @throws IllegalStateException if the world is not loaded + */ + public World getWorld() { + World world = eLib.getInstance().getServer().getWorld(this.worldName); + if (world == null) throw new IllegalStateException("World '" + this.worldName + "' is not loaded"); + return world; + } + + /** + * Get the size of this Cuboid along the X axis + * + * @return Size of Cuboid along the X axis + */ + public int getSizeX() { + return (this.x2 - this.x1) + 1; + } + + /** + * Get the size of this Cuboid along the Y axis + * + * @return Size of Cuboid along the Y axis + */ + public int getSizeY() { + return (this.y2 - this.y1) + 1; + } + + /** + * Get the size of this Cuboid along the Z axis + * + * @return Size of Cuboid along the Z axis + */ + public int getSizeZ() { + return (this.z2 - this.z1) + 1; + } + + /** + * Get the minimum X co-ordinate of this Cuboid + * + * @return the minimum X co-ordinate + */ + public int getLowerX() { + return this.x1; + } + + /** + * Get the minimum Y co-ordinate of this Cuboid + * + * @return the minimum Y co-ordinate + */ + public int getLowerY() { + return this.y1; + } + + /** + * Get the minimum Z co-ordinate of this Cuboid + * + * @return the minimum Z co-ordinate + */ + public int getLowerZ() { + return this.z1; + } + + /** + * Get the maximum X co-ordinate of this Cuboid + * + * @return the maximum X co-ordinate + */ + public int getUpperX() { + return this.x2; + } + + /** + * Get the maximum Y co-ordinate of this Cuboid + * + * @return the maximum Y co-ordinate + */ + public int getUpperY() { + return this.y2; + } + + /** + * Get the maximum Z co-ordinate of this Cuboid + * + * @return the maximum Z co-ordinate + */ + public int getUpperZ() { + return this.z2; + } + + /** + * Get the Blocks at the eight corners of the Cuboid. + * + * @return array of Block objects representing the Cuboid corners + */ + public Block[] corners() { + Block[] res = new Block[8]; + World w = this.getWorld(); + res[0] = w.getBlockAt(this.x1, this.y1, this.z1); + res[1] = w.getBlockAt(this.x1, this.y1, this.z2); + res[2] = w.getBlockAt(this.x1, this.y2, this.z1); + res[3] = w.getBlockAt(this.x1, this.y2, this.z2); + res[4] = w.getBlockAt(this.x2, this.y1, this.z1); + res[5] = w.getBlockAt(this.x2, this.y1, this.z2); + res[6] = w.getBlockAt(this.x2, this.y2, this.z1); + res[7] = w.getBlockAt(this.x2, this.y2, this.z2); + return res; + } + + /** + * Expand the Cuboid in the given direction by the given amount. Negative amounts will shrink the Cuboid in the given direction. Shrinking a cuboid's face past the opposite face is not an error and will return a valid Cuboid. + * + * @param dir - The direction in which to expand + * @param amount - The number of blocks by which to expand + * @return A new Cuboid expanded by the given direction and amount + */ + public Cuboid expand(CuboidDirection dir, int amount) { + switch (dir) { + case NORTH: + return new Cuboid(this.worldName, this.x1 - amount, this.y1, this.z1, this.x2, this.y2, this.z2); + case SOUTH: + return new Cuboid(this.worldName, this.x1, this.y1, this.z1, this.x2 + amount, this.y2, this.z2); + case EAST: + return new Cuboid(this.worldName, this.x1, this.y1, this.z1 - amount, this.x2, this.y2, this.z2); + case WEST: + return new Cuboid(this.worldName, this.x1, this.y1, this.z1, this.x2, this.y2, this.z2 + amount); + case DOWN: + return new Cuboid(this.worldName, this.x1, this.y1 - amount, this.z1, this.x2, this.y2, this.z2); + case UP: + return new Cuboid(this.worldName, this.x1, this.y1, this.z1, this.x2, this.y2 + amount, this.z2); + default: + throw new IllegalArgumentException("Invalid direction " + dir); + } + } + + /** + * Shift the Cuboid in the given direction by the given amount. + * + * @param dir - The direction in which to shift + * @param amount - The number of blocks by which to shift + * @return A new Cuboid shifted by the given direction and amount + */ + public Cuboid shift(CuboidDirection dir, int amount) { + return expand(dir, amount).expand(dir.opposite(), -amount); + } + + /** + * Outset (grow) the Cuboid in the given direction by the given amount. + * + * @param dir - The direction in which to outset (must be HORIZONTAL, VERTICAL, or BOTH) + * @param amount - The number of blocks by which to outset + * @return A new Cuboid outset by the given direction and amount + */ + public Cuboid outset(CuboidDirection dir, int amount) { + Cuboid c; + switch (dir) { + case HORIZONTAL: + c = expand(CuboidDirection.NORTH, amount).expand(CuboidDirection.SOUTH, amount).expand(CuboidDirection.EAST, amount).expand(CuboidDirection.WEST, amount); + break; + case VERTICAL: + c = expand(CuboidDirection.DOWN, amount).expand(CuboidDirection.UP, amount); + break; + case BOTH: + c = outset(CuboidDirection.HORIZONTAL, amount).outset(CuboidDirection.VERTICAL, amount); + break; + default: + throw new IllegalArgumentException("Invalid direction " + dir); + } + return c; + } + + /** + * Inset (shrink) the Cuboid in the given direction by the given amount. Equivalent + * to calling outset() with a negative amount. + * + * @param dir - The direction in which to inset (must be HORIZONTAL, VERTICAL, or BOTH) + * @param amount - The number of blocks by which to inset + * @return A new Cuboid inset by the given direction and amount + */ + public Cuboid inset(CuboidDirection dir, int amount) { + return this.outset(dir, -amount); + } + + /** + * Return true if the point at (x,y,z) is contained within this Cuboid. + * + * @param x - The X co-ordinate + * @param y - The Y co-ordinate + * @param z - The Z co-ordinate + * @return true if the given point is within this Cuboid, false otherwise + */ + public boolean contains(int x, int y, int z) { + return x >= this.x1 && x <= this.x2 && y >= this.y1 && y <= this.y2 && z >= this.z1 && z <= this.z2; + } + + /** + * Check if the given Block is contained within this Cuboid. + * + * @param b - The Block to check for + * @return true if the Block is within this Cuboid, false otherwise + */ + public boolean contains(Block b) { + return this.contains(b.getLocation()); + } + + /** + * Check if the given Location is contained within this Cuboid. + * + * @param l - The Location to check for + * @return true if the Location is within this Cuboid, false otherwise + */ + public boolean contains(Location l) { + if (!this.worldName.equals(l.getWorld().getName())) return false; + return this.contains(l.getBlockX(), l.getBlockY(), l.getBlockZ()); + } + + /** + * Get the volume of this Cuboid. + * + * @return The Cuboid volume, in blocks + */ + public int getVolume() { + return this.getSizeX() * this.getSizeY() * this.getSizeZ(); + } + + /** + * Get the average light level of all empty (air) blocks in the Cuboid. Returns 0 if there are no empty blocks. + * + * @return The average light level of this Cuboid + */ + public byte getAverageLightLevel() { + long total = 0; + int n = 0; + for (Block b : this) { + if (b.isEmpty()) { + total += b.getLightLevel(); + ++n; + } + } + return n > 0 ? (byte) (total / n) : 0; + } + + /** + * Contract the Cuboid, returning a Cuboid with any air around the edges removed, just large enough to include all non-air blocks. + * + * @return A new Cuboid with no external air blocks + */ + public Cuboid contract() { + return this.contract(CuboidDirection.DOWN).contract(CuboidDirection.SOUTH).contract(CuboidDirection.EAST).contract(CuboidDirection.UP).contract(CuboidDirection.NORTH).contract(CuboidDirection.WEST); + } + + /** + * Contract the Cuboid in the given direction, returning a new Cuboid which has no exterior empty space. + * E.g. A direction of DOWN will push the top face downwards as much as possible. + * + * @param dir - The direction in which to contract + * @return A new Cuboid contracted in the given direction + */ + public Cuboid contract(CuboidDirection dir) { + Cuboid face = getFace(dir.opposite()); + switch (dir) { + case DOWN: + while (face.containsOnly(0) && face.getLowerY() > this.getLowerY()) { + face = face.shift(CuboidDirection.DOWN, 1); + } + return new Cuboid(this.worldName, this.x1, this.y1, this.z1, this.x2, face.getUpperY(), this.z2); + case UP: + while (face.containsOnly(0) && face.getUpperY() < this.getUpperY()) { + face = face.shift(CuboidDirection.UP, 1); + } + return new Cuboid(this.worldName, this.x1, face.getLowerY(), this.z1, this.x2, this.y2, this.z2); + case NORTH: + while (face.containsOnly(0) && face.getLowerX() > this.getLowerX()) { + face = face.shift(CuboidDirection.NORTH, 1); + } + return new Cuboid(this.worldName, this.x1, this.y1, this.z1, face.getUpperX(), this.y2, this.z2); + case SOUTH: + while (face.containsOnly(0) && face.getUpperX() < this.getUpperX()) { + face = face.shift(CuboidDirection.SOUTH, 1); + } + return new Cuboid(this.worldName, face.getLowerX(), this.y1, this.z1, this.x2, this.y2, this.z2); + case EAST: + while (face.containsOnly(0) && face.getLowerZ() > this.getLowerZ()) { + face = face.shift(CuboidDirection.EAST, 1); + } + return new Cuboid(this.worldName, this.x1, this.y1, this.z1, this.x2, this.y2, face.getUpperZ()); + case WEST: + while (face.containsOnly(0) && face.getUpperZ() < this.getUpperZ()) { + face = face.shift(CuboidDirection.WEST, 1); + } + return new Cuboid(this.worldName, this.x1, this.y1, face.getLowerZ(), this.x2, this.y2, this.z2); + default: + throw new IllegalArgumentException("Invalid direction " + dir); + } + } + + /** + * Get the Cuboid representing the face of this Cuboid. The resulting Cuboid will be one block thick in the axis perpendicular to the requested face. + * + * @param dir - which face of the Cuboid to get + * @return The Cuboid representing this Cuboid's requested face + */ + public Cuboid getFace(CuboidDirection dir) { + switch (dir) { + case DOWN: + return new Cuboid(this.worldName, this.x1, this.y1, this.z1, this.x2, this.y1, this.z2); + case UP: + return new Cuboid(this.worldName, this.x1, this.y2, this.z1, this.x2, this.y2, this.z2); + case NORTH: + return new Cuboid(this.worldName, this.x1, this.y1, this.z1, this.x1, this.y2, this.z2); + case SOUTH: + return new Cuboid(this.worldName, this.x2, this.y1, this.z1, this.x2, this.y2, this.z2); + case EAST: + return new Cuboid(this.worldName, this.x1, this.y1, this.z1, this.x2, this.y2, this.z1); + case WEST: + return new Cuboid(this.worldName, this.x1, this.y1, this.z2, this.x2, this.y2, this.z2); + default: + throw new IllegalArgumentException("Invalid direction " + dir); + } + } + + /** + * Check if the Cuboid contains only blocks of the given type + * + * @param blockId - The block ID to check for + * @return true if this Cuboid contains only blocks of the given type + */ + public boolean containsOnly(int blockId) { + for (Block b : this) { + if (b.getTypeId() != blockId) return false; + } + return true; + } + + /** + * Get the Cuboid big enough to hold both this Cuboid and the given one. + * + * @param other - The other cuboid. + * @return A new Cuboid large enough to hold this Cuboid and the given Cuboid + */ + public Cuboid getBoundingCuboid(Cuboid other) { + if (other == null) return this; + + int xMin = Math.min(this.getLowerX(), other.getLowerX()); + int yMin = Math.min(this.getLowerY(), other.getLowerY()); + int zMin = Math.min(this.getLowerZ(), other.getLowerZ()); + int xMax = Math.max(this.getUpperX(), other.getUpperX()); + int yMax = Math.max(this.getUpperY(), other.getUpperY()); + int zMax = Math.max(this.getUpperZ(), other.getUpperZ()); + + return new Cuboid(this.worldName, xMin, yMin, zMin, xMax, yMax, zMax); + } + + /** + * Get a block relative to the lower NE point of the Cuboid. + * + * @param x - The X co-ordinate + * @param y - The Y co-ordinate + * @param z - The Z co-ordinate + * @return The block at the given position + */ + public Block getRelativeBlock(int x, int y, int z) { + return this.getWorld().getBlockAt(this.x1 + x, this.y1 + y, this.z1 + z); + } + + /** + * Get a block relative to the lower NE point of the Cuboid in the given World. This + * version of getRelativeBlock() should be used if being called many times, to avoid + * excessive calls to getWorld(). + * + * @param w - The world + * @param x - The X co-ordinate + * @param y - The Y co-ordinate + * @param z - The Z co-ordinate + * @return The block at the given position + */ + public Block getRelativeBlock(World w, int x, int y, int z) { + return w.getBlockAt(this.x1 + x, y1 + y, this.z1 + z); + } + + /** + * Get a list of the chunks which are fully or partially contained in this cuboid. + * + * @return A list of Chunk objects + */ + public List getChunks() { + List res = new ArrayList(); + + World w = this.getWorld(); + int x1 = this.getLowerX() & ~0xf; + int x2 = this.getUpperX() & ~0xf; + int z1 = this.getLowerZ() & ~0xf; + int z2 = this.getUpperZ() & ~0xf; + for (int x = x1; x <= x2; x += 16) { + for (int z = z1; z <= z2; z += 16) { + res.add(w.getChunkAt(x >> 4, z >> 4)); + } + } + return res; + } + + public Iterator iterator() { + return new CuboidIterator(this.getWorld(), this.x1, this.y1, this.z1, this.x2, this.y2, this.z2); + } + + @Override + public Cuboid clone() { + return new Cuboid(this); + } + + @Override + public String toString() { + return new String("Cuboid: " + this.worldName + "," + this.x1 + "," + this.y1 + "," + this.z1 + "=>" + this.x2 + "," + this.y2 + "," + this.z2); + } + + public List getWalls() { + List blocks = new ArrayList(); + + Location min = new Location(getWorld(), this.x1, this.y1, this.z1); + Location max = new Location(getWorld(), this.x2, this.y2, this.z2); + + int minX = min.getBlockX(); + int minY = min.getBlockY(); + int minZ = min.getBlockZ(); + int maxX = max.getBlockX(); + int maxY = max.getBlockY(); + int maxZ = max.getBlockZ(); + Location minLoc; + Location maxLoc; + for (int x = minX; x <= maxX; ++x) { + for (int y = minY; y <= maxY; ++y) { + minLoc = new Location(getWorld(), x, y, minZ); + maxLoc = new Location(getWorld(), x, y, maxZ); + + blocks.add(minLoc.getBlock()); + blocks.add(maxLoc.getBlock()); + } + } + for (int y = minY; y <= maxY; ++y) { + for (int z = minZ; z <= maxZ; ++z) { + minLoc = new Location(getWorld(), minX, y, z); + maxLoc = new Location(getWorld(), maxX, y, z); + + blocks.add(minLoc.getBlock()); + blocks.add(maxLoc.getBlock()); + } + } + + return blocks; + } + + public List getFaces() { + List blocks = new ArrayList(); + + Location min = new Location(getWorld(), this.x1, this.y1, this.z1); + Location max = new Location(getWorld(), this.x2, this.y2, this.z2); + + int minX = min.getBlockX(); + int minY = min.getBlockY(); + int minZ = min.getBlockZ(); + int maxX = max.getBlockX(); + int maxY = max.getBlockY(); + int maxZ = max.getBlockZ(); + + for (int x = minX; x <= maxX; ++x) { + for (int y = minY; y <= maxY; ++y) { + blocks.add(new Location(getWorld(), x, y, minZ).getBlock()); + blocks.add(new Location(getWorld(), x, y, maxZ).getBlock()); + } + } + for (int y = minY; y <= maxY; ++y) { + for (int z = minZ; z <= maxZ; ++z) { + blocks.add(new Location(getWorld(), minX, y, z).getBlock()); + blocks.add(new Location(getWorld(), maxX, y, z).getBlock()); + } + } + for (int z = minZ; z <= maxZ; ++z) { + for (int x = minX; x <= maxX; ++x) { + blocks.add(new Location(getWorld(), x, minY, z).getBlock()); + blocks.add(new Location(getWorld(), x, maxY, z).getBlock()); + } + } + + return blocks; + } + + public enum CuboidDirection { + NORTH,EAST,SOUTH,WEST,UP,DOWN,HORIZONTAL,VERTICAL,BOTH,UNKNOWN; + + public CuboidDirection opposite() { + switch (this) { + case NORTH: + return SOUTH; + case EAST: + return WEST; + case SOUTH: + return NORTH; + case WEST: + return EAST; + case HORIZONTAL: + return VERTICAL; + case VERTICAL: + return HORIZONTAL; + case UP: + return DOWN; + case DOWN: + return UP; + case BOTH: + return BOTH; + default: + return UNKNOWN; + } + } + } + + public class CuboidIterator implements Iterator { + private World w; + private int baseX, baseY, baseZ; + private int x, y, z; + private int sizeX, sizeY, sizeZ; + + public CuboidIterator(World w, int x1, int y1, int z1, int x2, int y2, int z2) { + this.w = w; + this.baseX = Math.min(x1, x2); + this.baseY = Math.min(y1, y2);; + this.baseZ = Math.min(z1, z2);; + this.sizeX = Math.abs(x2 - x1) + 1; + this.sizeY = Math.abs(y2 - y1) + 1; + this.sizeZ = Math.abs(z2 - z1) + 1; + this.x = this.y = this.z = 0; + } + + public boolean hasNext() { + return this.x < this.sizeX && this.y < this.sizeY && this.z < this.sizeZ; + } + + public Block next() { + Block b = this.w.getBlockAt(this.baseX + this.x, this.baseY + this.y, this.baseZ + this.z); + if (++x >= this.sizeX) { + this.x = 0; + if (++this.y >= this.sizeY) { + this.y = 0; + ++this.z; + } + } + return b; + } + + public void remove() { + } + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/eLib.java b/Network/eLib/src/main/java/com/elevatemc/elib/eLib.java new file mode 100644 index 0000000..92bce6b --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/eLib.java @@ -0,0 +1,306 @@ +package com.elevatemc.elib; + +import com.elevatemc.elib.autoreboot.AutoRebootHandler; +import com.elevatemc.elib.border.BorderHandler; +import com.elevatemc.elib.bossbar.BossBarHandler; +import com.elevatemc.elib.combatlogger.CombatLoggerHandler; +import com.elevatemc.elib.command.CommandHandler; +import com.elevatemc.elib.event.HalfHourEvent; +import com.elevatemc.elib.event.HourEvent; +import com.elevatemc.elib.fake.FakeEntityHandler; +import com.elevatemc.elib.hologram.HologramHandler; +import com.elevatemc.elib.nametag.NameTagHandler; +import com.elevatemc.elib.npc.NPCManager; +import com.elevatemc.elib.npc.entry.NPCEntry; +import com.elevatemc.elib.npc.entry.NPCEntryAdapter; +import com.elevatemc.elib.pidgin.PidginHandler; +import com.elevatemc.elib.redis.RedisCommand; +import com.elevatemc.elib.scoreboard.ScoreboardHandler; +import com.elevatemc.elib.serialization.*; +import com.elevatemc.elib.skin.MojangSkinHandler; +import com.elevatemc.elib.skinfix.SkinFixCommand; +import com.elevatemc.elib.skinfix.SkinFixHandler; +import com.elevatemc.elib.tab.TabListManager; +import com.elevatemc.elib.util.InventoryAdapter; +import com.elevatemc.elib.util.ItemUtils; +import com.elevatemc.elib.util.json.ClassAdapter; +import com.elevatemc.elib.util.json.ColorAdapter; +import com.elevatemc.elib.util.json.GsonProvider; +import com.elevatemc.elib.uuid.UUIDCache; +import com.elevatemc.elib.visibility.VisibilityHandler; +import com.elevatemc.spigot.eSpigot; +import org.bukkit.Bukkit; +import com.elevatemc.elib.util.json.map.AllowNullMapTypeAdapterFactory; +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +import com.google.gson.*; +import lombok.Getter; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.bukkit.Location; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.potion.PotionEffect; +import org.bukkit.util.BlockVector; +import org.bukkit.util.Vector; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; + +import java.awt.*; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Calendar; +import java.util.EnumMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public final class eLib extends JavaPlugin { + + @Getter + private static eLib instance; + + @Getter + private JedisPool localJedisPool; + @Getter + private JedisPool backboneJedisPool; + @Getter + private long backboneLastError; + @Getter + private long redisLastError; + + @Getter + private CommandHandler commandHandler; + @Getter + private HologramHandler hologramHandler; + + @Getter + private TabListManager tabHandler; + @Getter + private NameTagHandler nameTagHandler; + @Getter + private ScoreboardHandler scoreboardHandler; + @Getter + private AutoRebootHandler autoRebootHandler; + @Getter + private CombatLoggerHandler combatLoggerHandler; + + @Getter + private BorderHandler borderHandler; + @Getter + private BossBarHandler bossBarHandler; + @Getter + private VisibilityHandler visibilityHandler; + + @Getter + private PidginHandler pidginHandler; + @Getter + private UUIDCache uuidCache; + @Getter private MojangSkinHandler mojangSkinHandler; + @Getter private FakeEntityHandler fakeEntityHandler; + @Getter private NPCManager npcManager; + + public static final Gson GSON = new com.google.gson.GsonBuilder() + .registerTypeHierarchyAdapter(PotionEffect.class, new PotionEffectAdapter()) + .registerTypeHierarchyAdapter(ItemStack.class, new ItemStackAdapter()) + .registerTypeHierarchyAdapter(Location.class, new LocationAdapter()) + .registerTypeHierarchyAdapter(Vector.class, new VectorAdapter()) + .registerTypeAdapter(BlockVector.class, new BlockVectorAdapter()) + .setPrettyPrinting() + .serializeNulls() + .create(); + + public static final Gson PLAIN_GSON = new GsonBuilder() + .registerTypeHierarchyAdapter(PotionEffect.class, new PotionEffectAdapter()) + .registerTypeHierarchyAdapter(ItemStack.class, new ItemStackAdapter()) + .registerTypeHierarchyAdapter(Location.class, new LocationAdapter()) + .registerTypeHierarchyAdapter(Vector.class, new VectorAdapter()) + .registerTypeAdapter(BlockVector.class, new BlockVectorAdapter()) + .serializeNulls() + .create(); + + @Override + public void onEnable() { + + instance = this; + registerSerializers(); + this.saveDefaultConfig(); + + try { + getLogger().info("Connecting to local redis"); + this.localJedisPool = new JedisPool( + new JedisPoolConfig(), + this.getConfig().getString("Redis.Host"), + this.getConfig().getInt("Redis.Port", 6379), + 20000, + this.getConfig().getString("Redis.Pass", null), + this.getConfig().getInt("Redis.DbId", 5) + ); + getLogger().info("Connected to local redis"); + + } catch (Exception ex) { + this.localJedisPool = null; + + System.out.println("*********************************************"); + System.out.println(" REDIS"); + System.out.println("-> FAILED TO CONNECT TO LOCAL POOL"); + System.out.println("-> INSTANCE: " + this.getConfig().getString("Redis.Host")); + System.out.println("*********************************************"); + } + getLogger().info("Connecting to backbone redis"); + + try { + this.backboneJedisPool = new JedisPool( + new JedisPoolConfig(), + this.getConfig().getString("Backbone.Host"), + this.getConfig().getInt("Backbone.Port", 6379), + 20000, + this.getConfig().getString("Backbone.Pass", null), + this.getConfig().getInt("Backbone.DbId", 0) + ); + getLogger().info("Connected to local redis"); + + } catch (Exception ex) { + this.backboneJedisPool = null; + + System.out.println("*********************************************"); + System.out.println(" REDIS"); + System.out.println("-> FAILED TO CONNECT TO BACKBONE POOL"); + System.out.println("-> INSTANCE: " + this.getConfig().getString("Redis.Host")); + System.out.println("*********************************************"); + } + + this.mojangSkinHandler = new MojangSkinHandler(this); + this.commandHandler = new CommandHandler(); + this.hologramHandler = new HologramHandler(this); + + this.tabHandler = new TabListManager(); + this.nameTagHandler = new NameTagHandler(); + this.scoreboardHandler = new ScoreboardHandler(); + + this.fakeEntityHandler = new FakeEntityHandler(this); + this.npcManager = new NPCManager(); + this.autoRebootHandler = new AutoRebootHandler(); + this.combatLoggerHandler = new CombatLoggerHandler(); + + this.borderHandler = new BorderHandler(); + this.bossBarHandler = new BossBarHandler(); + this.visibilityHandler = new VisibilityHandler(); + + this.pidginHandler = new PidginHandler("pidgin", this.backboneJedisPool); + + this.uuidCache = new UUIDCache(); + + ItemUtils.load(); + + eSpigot.getInstance().addPacketHandler(new InventoryAdapter()); + this.getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); + + this.setupHourEvents(); + if (!Bukkit.getOnlineMode()) { + SkinFixHandler skinFix = new SkinFixHandler(); + commandHandler.registerClass(SkinFixCommand.class); + + getServer().getPluginManager().registerEvents(skinFix, this); + + eSpigot.getInstance().addPacketHandler(skinFix); + } + } + + public void registerSerializers() { + + + GsonProvider.registerTypeAdapter(Class.class, new ClassAdapter()); + GsonProvider.registerTypeHierarchyAdapter(NPCEntry.class, new NPCEntryAdapter()); + GsonProvider.registerTypeHierarchyAdapter(ItemStack.class, new ItemStackAdapter()); + GsonProvider.registerTypeHierarchyAdapter(Location.class, new LocationAdapter()); + GsonProvider.registerTypeHierarchyAdapter(Color.class, new ColorAdapter()); + + GsonProvider.registerTypeAdapter(EnumMap.class, (InstanceCreator) type -> { + Type[] types = (((ParameterizedType) type).getActualTypeArguments()); + return new EnumMap((Class) types[0]); + }); + + GsonProvider.registerTypeAdapterFactory(new AllowNullMapTypeAdapterFactory(GsonProvider.createConstructor(), true)); + } + public void onDisable() { + NPCManager.getInstance().saveAllNPCs(); + this.mojangSkinHandler.saveUUIDSkinCache(); + } + + public T runRedisCommand(RedisCommand redisCommand) { + + Jedis jedis = this.localJedisPool.getResource(); + + T result = null; + + try { + result = redisCommand.execute(jedis); + } catch (Exception e) { + e.printStackTrace(); + + this.redisLastError = System.currentTimeMillis(); + + if (jedis != null) { + jedis.close(); + jedis = null; + } + } finally { + + if (jedis != null) { + jedis.close(); + } + + } + + return result; + } + + public T runBackboneRedisCommand(RedisCommand redisCommand) { + + Jedis jedis = this.backboneJedisPool.getResource(); + + T result = null; + + try { + result = redisCommand.execute(jedis); + } catch (Exception e) { + e.printStackTrace(); + this.backboneLastError = System.currentTimeMillis(); + + if (jedis != null) { + jedis.close(); + jedis = null; + } + } finally { + + if (jedis != null) { + jedis.close(); + } + + } + + return result; + } + + private void setupHourEvents() { + + final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(( + new ThreadFactoryBuilder()).setNameFormat("eLib - Hour Event Thread").setDaemon(true).build()); + + final int minOfHour = Calendar.getInstance().get(12); + final int minToHour = 60 - minOfHour; + final int minToHalfHour = minToHour >= 30 ? minToHour : 30 - minOfHour; + + executor.scheduleAtFixedRate(() -> eLib.getInstance().getServer().getScheduler().runTask(this, () + -> eLib.getInstance().getServer().getPluginManager().callEvent( + new HourEvent(Calendar.getInstance().get(Calendar.HOUR_OF_DAY)))), minToHour, 60L, TimeUnit.MINUTES); + + executor.scheduleAtFixedRate(() -> eLib.getInstance().getServer().getScheduler().runTask(this, () + -> eLib.getInstance().getServer().getPluginManager().callEvent( + new HalfHourEvent(Calendar.getInstance().get(Calendar.HOUR_OF_DAY), Calendar.getInstance().get(Calendar.MINUTE)))), minToHalfHour, 30L, TimeUnit.MINUTES); + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/economy/EconomyHandler.java b/Network/eLib/src/main/java/com/elevatemc/elib/economy/EconomyHandler.java new file mode 100644 index 0000000..9c67262 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/economy/EconomyHandler.java @@ -0,0 +1,85 @@ +package com.elevatemc.elib.economy; + +import com.elevatemc.elib.eLib; +import lombok.Getter; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class EconomyHandler { + + @Getter private Map balances = new HashMap<>(); + + public EconomyHandler() { + + eLib.getInstance().getServer().getPluginManager().registerEvents(new Listener() { + + @EventHandler() + public void onPlayerQuit(PlayerQuitEvent event) { + eLib.getInstance().getServer().getScheduler().runTaskAsynchronously(eLib.getInstance(),() -> { + save(event.getPlayer().getUniqueId()); + }); + } + + }, eLib.getInstance()); + + + eLib.getInstance().runRedisCommand((redis) -> { + + for (String key : redis.keys("balance.*")) { + + final UUID uuid = UUID.fromString(key.substring(8)); + + balances.put(uuid,Double.parseDouble(redis.get(key))); + } + + return null; + }); + } + + public void setBalance(UUID uuid, double balance) { + this.balances.put(uuid, balance); + eLib.getInstance().getServer().getScheduler().runTaskAsynchronously(eLib.getInstance(), () -> save(uuid)); + } + + public double getBalance(UUID uuid) { + + if (!this.balances.containsKey(uuid)) { + load(uuid); + } + + return balances.get(uuid); + } + + public void withdraw(UUID uuid, double amount) { + setBalance(uuid,getBalance(uuid) - amount); + eLib.getInstance().getServer().getScheduler().runTaskAsynchronously(eLib.getInstance(), () -> save(uuid)); + } + + public void deposit(UUID uuid, double amount) { + setBalance(uuid,getBalance(uuid) + amount); + eLib.getInstance().getServer().getScheduler().runTaskAsynchronously(eLib.getInstance(), () -> save(uuid)); + } + + private void load(UUID uuid) { + } + + private void save(UUID uuid) { + eLib.getInstance().runRedisCommand((redis) -> redis.set("balance." + uuid.toString(), String.valueOf(getBalance(uuid)))); + } + + public void save() { + eLib.getInstance().runRedisCommand((redis) -> { + + for (Map.Entry entry : this.balances.entrySet()) { + redis.set("balance." + entry.getKey().toString(),String.valueOf(entry.getValue())); + } + + return null; + }); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/event/HalfHourEvent.java b/Network/eLib/src/main/java/com/elevatemc/elib/event/HalfHourEvent.java new file mode 100644 index 0000000..0a9493b --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/event/HalfHourEvent.java @@ -0,0 +1,19 @@ +package com.elevatemc.elib.event; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +@AllArgsConstructor +public class HalfHourEvent extends Event { + + @Getter private static final HandlerList handlerList = new HandlerList(); + @Getter private int hour; + @Getter private int minute; + + public HandlerList getHandlers() { + return (handlerList); + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/event/HourEvent.java b/Network/eLib/src/main/java/com/elevatemc/elib/event/HourEvent.java new file mode 100644 index 0000000..7934bab --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/event/HourEvent.java @@ -0,0 +1,18 @@ +package com.elevatemc.elib.event; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +@AllArgsConstructor +public class HourEvent extends Event { + + @Getter private static final HandlerList handlerList = new HandlerList(); + @Getter private int hour; + + public HandlerList getHandlers() { + return (handlerList); + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/fake/FakeEntity.java b/Network/eLib/src/main/java/com/elevatemc/elib/fake/FakeEntity.java new file mode 100644 index 0000000..01479ef --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/fake/FakeEntity.java @@ -0,0 +1,59 @@ +package com.elevatemc.elib.fake; + +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; + +import java.util.Set; +import java.util.UUID; + +/** + * @author ImHacking + */ +public interface FakeEntity { + + boolean show(Player player); + + boolean showToAll(); + + boolean hide(Player player); + + boolean hideFromAll(); + + void teleport(Location location); + + boolean isShownToPlayer(UUID uuid); + + String getName(); + + int getId(); + + int getEntityId(); + + UUID getUUID(); + + void addToTeam(Player player); + + void handleDisconnect(UUID uuid); + + Set getCurrentlyViewing(); + + Set getShouldBeAbleToView(); + + World getWorld(); + + Location getLocation(); + + default int range() { + return 1024; + } + + ; + + default boolean showOnJoin() { + return true; + } + + ; + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/fake/FakeEntityHandler.java b/Network/eLib/src/main/java/com/elevatemc/elib/fake/FakeEntityHandler.java new file mode 100644 index 0000000..9a1362f --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/fake/FakeEntityHandler.java @@ -0,0 +1,119 @@ +package com.elevatemc.elib.fake; + +import com.elevatemc.elib.fake.impl.player.FakePlayerEntityTask; +import com.elevatemc.elib.fake.impl.player.FakePlayerLookTask; +import com.elevatemc.elib.fake.impl.player.FakePlayerPacketHandler; +import com.elevatemc.elib.util.TaskUtil; +import com.elevatemc.spigot.eSpigot; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import lombok.AccessLevel; +import lombok.Getter; +import net.minecraft.server.v1_8_R3.PacketPlayInUseEntity; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.*; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +/** + * @author ImHacking + */ +public class FakeEntityHandler { + private final Int2ObjectMap entityMap = new Int2ObjectOpenHashMap<>(); + + @Getter(AccessLevel.PROTECTED) + private final Executor entityExecutor = Executors.newFixedThreadPool(2); + + public FakeEntityHandler(JavaPlugin plugin) { + plugin.getServer().getPluginManager().registerEvents(new FakeEntityListener(this), plugin); + TaskUtil.scheduleAtFixedRateOnPool(new FakePlayerEntityTask(this), 50, 50, TimeUnit.MILLISECONDS); + TaskUtil.scheduleAtFixedRateOnPool(new FakePlayerLookTask(this), 50, 50, TimeUnit.MILLISECONDS); + System.out.println("FakeEntityHandler Created: " + System.currentTimeMillis()); + eSpigot. + getInstance() + .addPacketHandler( + new FakePlayerPacketHandler(this)); + } + + public Collection getEntities() { + return Collections.unmodifiableCollection(this.entityMap.values()); + } + + public void registerFakeEntity(FakeEntity entity) { + this.entityMap.put(entity.getEntityId(), entity); + } + + public void removeFakeEntity(int id) { + this.entityMap.remove(id); + } + + public FakeEntity getEntityById(int id) { + for (FakeEntity entity : this.getEntities()) { + if (entity.getId() == id) { + return entity; + } + } + return null; + } + + public FakeEntity getEntityByEntityId(int id) { + return this.entityMap.getOrDefault(id, null); + } + + public Set getFakeEntitiesByType(Class type) { + Set entities = new HashSet<>(); + + this.getEntities().forEach(fakeEntity -> { + if (fakeEntity.getClass().getSimpleName().equals(type.getSimpleName())) { + entities.add(fakeEntity); + } + }); + + return entities; + } + + public Set getAllEntitiesPlayerCanSee(UUID uuid) { + Set entities = new HashSet<>(); + + this.getEntities().forEach(fakeEntity -> { + if (fakeEntity.isShownToPlayer(uuid)) { + entities.add(fakeEntity); + } + }); + + return entities; + } + + protected void handleMovement(Player player, Location from, Location to) { + if (from.getBlockZ() == to.getBlockZ() && from.getBlockX() == to.getBlockX()) { + return; + } + + this.entityExecutor.execute(() -> { + UUID uuid = player.getUniqueId(); + + for (FakeEntity fakeEntity : this.getAllEntitiesPlayerCanSee(uuid)) { + if (!fakeEntity.getWorld().getUID().equals(to.getWorld().getUID())) { + if (fakeEntity.getCurrentlyViewing().contains(uuid)) { + fakeEntity.hide(player); + } + continue; + } + + if (fakeEntity.getLocation().distanceSquared(to) < fakeEntity.range()) { + if (!fakeEntity.getCurrentlyViewing().contains(uuid)) { + fakeEntity.show(player); + } + } else { + if (fakeEntity.getCurrentlyViewing().contains(uuid)) { + fakeEntity.hide(player); + } + } + } + }); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/fake/FakeEntityListener.java b/Network/eLib/src/main/java/com/elevatemc/elib/fake/FakeEntityListener.java new file mode 100644 index 0000000..2df808f --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/fake/FakeEntityListener.java @@ -0,0 +1,100 @@ +package com.elevatemc.elib.fake; + +import lombok.AllArgsConstructor; +import com.elevatemc.elib.util.PlayerTeamUtils; +import com.elevatemc.elib.util.TaskUtil; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.*; + +import java.util.Collections; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +/** + * @author ImHacking + */ +@AllArgsConstructor +public class FakeEntityListener implements Listener { + private FakeEntityHandler handler; + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + Location location = player.getLocation(); + PlayerTeamUtils.sendUpdatePlayers(player, "eEntities", Collections.emptyList(), 0); + TaskUtil.scheduleOnPool(() -> { + for (FakeEntity fakeEntity : handler.getEntities()) { + fakeEntity.addToTeam(player); + + if (!fakeEntity.showOnJoin()) { + continue; + } + + fakeEntity.getShouldBeAbleToView().add(player.getUniqueId()); + + if (!fakeEntity.getWorld().getUID().equals(location.getWorld().getUID())) { + continue; + } + + + if (!(fakeEntity.getLocation().distanceSquared(location) < fakeEntity.range())) { + continue; + } + + fakeEntity.show(player); + } + }, 500, TimeUnit.MILLISECONDS); + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + Player player = event.getPlayer(); + UUID uuid = player.getUniqueId(); + this.handler.getEntityExecutor().execute(() -> + handler.getAllEntitiesPlayerCanSee(uuid).forEach(fakeEntity -> fakeEntity.handleDisconnect(uuid))); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlayerTeleport(PlayerTeleportEvent event) { + Player player = event.getPlayer(); + Location from = event.getFrom(); + Location to = event.getTo(); + this.handler.handleMovement(player, from, to); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlayerPortal(PlayerPortalEvent event) { + Player player = event.getPlayer(); + Location from = event.getFrom(); + Location to = event.getTo(); + this.handler.handleMovement(player, from, to); + } + + @EventHandler + public void onPlayerChangeWorld(PlayerChangedWorldEvent event) { + this.updateWithDelay(event.getPlayer()); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onRespawn(PlayerRespawnEvent event) { + this.updateWithDelay(event.getPlayer()); + } + + private void updateWithDelay(Player player) { + Set entityList = this.handler.getAllEntitiesPlayerCanSee(player.getUniqueId()); + + if (entityList.isEmpty()) { + return; + } + + TaskUtil.scheduleOnPool(() -> { + for (FakeEntity fakeEntity : entityList) { + fakeEntity.show(player); + } + }, 100, TimeUnit.MILLISECONDS); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/fake/impl/hologram/FakeEntityArmorStand.java b/Network/eLib/src/main/java/com/elevatemc/elib/fake/impl/hologram/FakeEntityArmorStand.java new file mode 100644 index 0000000..846fee7 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/fake/impl/hologram/FakeEntityArmorStand.java @@ -0,0 +1,207 @@ +package com.elevatemc.elib.fake.impl.hologram; + +import net.minecraft.server.v1_8_R3.*; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.craftbukkit.v1_8_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import org.bukkit.entity.Player; +import com.elevatemc.elib.fake.FakeEntity; + + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +public class FakeEntityArmorStand implements FakeEntity { + + private final int id; + private final EntityArmorStand entityArmorStand; + + private Location location; + + private PacketPlayOutSpawnEntityLiving spawnEntity; + private PacketPlayOutEntityDestroy destroyEntity; + private PacketPlayOutEntityMetadata metadata; + + private final Set shouldBeAbleToView; + private final Set currentlyViewing; + + public FakeEntityArmorStand(int id, Location location) { + this.id = id; + this.entityArmorStand = new EntityArmorStand(((CraftWorld) location.getWorld()).getHandle()); + this.entityArmorStand.setPosition(location.getX(), location.getY(), location.getZ()); + this.location = location; + + this.spawnEntity = new PacketPlayOutSpawnEntityLiving(this.entityArmorStand); + this.destroyEntity = new PacketPlayOutEntityDestroy(this.getEntityId()); + + this.shouldBeAbleToView = new HashSet<>(); + this.currentlyViewing = new HashSet<>(); + + this.setupPackets(); + } + + public void setInvisible(boolean b) { + this.entityArmorStand.setInvisible(b); + this.setupPackets(); + } + + public void setGravity(boolean b) { + this.entityArmorStand.setGravity(b); + this.setupPackets(); + } + + public void setSmall(boolean b) { + this.entityArmorStand.setSmall(b); + this.setupPackets(); + } + + public void n(boolean b) { + this.entityArmorStand.n(b); + this.setupPackets(); + } + + public void setName(String name) { + this.entityArmorStand.setCustomName(name); + this.entityArmorStand.setCustomNameVisible(true); + this.updateMetadata(); + } + + public void update(Player player) { + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(this.metadata); + } + + public PacketPlayOutEntityMetadata updateMetadata() { + return this.metadata = new PacketPlayOutEntityMetadata(this.getEntityId(), this.entityArmorStand.getDataWatcher(), true); + } + + public void setupPackets() { + this.spawnEntity = new PacketPlayOutSpawnEntityLiving(this.entityArmorStand); + this.destroyEntity = new PacketPlayOutEntityDestroy(this.getEntityId()); + this.updateMetadata(); + } + + @Override + public boolean show(Player player) { + if (!player.getWorld().getUID().equals(this.location.getWorld().getUID())) { + return false; + } + + PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection; + playerConnection.sendPacket(this.destroyEntity); + playerConnection.sendPacket(this.spawnEntity); + playerConnection.sendPacket(this.metadata); + + this.currentlyViewing.add(player.getUniqueId()); + this.shouldBeAbleToView.add(player.getUniqueId()); + return true; + } + + @Override + public boolean showToAll() { + for (Player player : Bukkit.getOnlinePlayers()) { + this.show(player); + } + return false; + } + + @Override + public boolean hide(Player player) { + if (!this.currentlyViewing.contains(player.getUniqueId())) { + return false; + } + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(this.destroyEntity); + this.currentlyViewing.remove(player.getUniqueId()); + return true; + } + + @Override + public boolean hideFromAll() { + for (Player player : Bukkit.getOnlinePlayers()) { + this.hide(player); + } + return true; + } + + @Override + public void teleport(Location location) { + this.entityArmorStand.setPosition(location.getX(), location.getY(), location.getZ()); + this.entityArmorStand.world = ((CraftWorld) location.getWorld()).getHandle(); + this.location = location; + + this.setupPackets(); + + PacketPlayOutEntityTeleport packetPlayOutEntityTeleport = new PacketPlayOutEntityTeleport(this.entityArmorStand); + for (UUID uuid : this.getCurrentlyViewing()) { + Player player = Bukkit.getPlayer(uuid); + + if (player == null) { + continue; + } + + if (!player.getWorld().getName().equals(this.location.getWorld().getName())) { + this.hide(player); + continue; + } + + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packetPlayOutEntityTeleport); + } + } + + @Override + public boolean isShownToPlayer(UUID uuid) { + return this.shouldBeAbleToView.contains(uuid); + } + + @Override + public String getName() { + return this.entityArmorStand.getName(); + } + + @Override + public int getId() { + return this.id; + } + + @Override + public int getEntityId() { + return this.entityArmorStand.getId(); + } + + @Override + public UUID getUUID() { + return this.entityArmorStand.getUniqueID(); + } + + @Override + public void addToTeam(Player player) { + //ignore + } + + @Override + public void handleDisconnect(UUID uuid) { + this.currentlyViewing.remove(uuid); + this.shouldBeAbleToView.remove(uuid); + } + + @Override + public Set getCurrentlyViewing() { + return this.currentlyViewing; + } + + @Override + public Set getShouldBeAbleToView() { + return this.shouldBeAbleToView; + } + + @Override + public org.bukkit.World getWorld() { + return this.location.getWorld(); + } + + @Override + public Location getLocation() { + return this.location; + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/fake/impl/hologram/FakeEntityHorse.java b/Network/eLib/src/main/java/com/elevatemc/elib/fake/impl/hologram/FakeEntityHorse.java new file mode 100644 index 0000000..16e5b00 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/fake/impl/hologram/FakeEntityHorse.java @@ -0,0 +1,194 @@ +package com.elevatemc.elib.fake.impl.hologram; + +import lombok.Getter; +import net.minecraft.server.v1_8_R3.*; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_8_R3.*; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import org.bukkit.entity.Player; +import com.elevatemc.elib.fake.FakeEntity; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +@Getter +public class FakeEntityHorse implements FakeEntity { + + private final int id; + private final EntityHorse entityHorse; + + private PacketPlayOutSpawnEntityLiving spawnEntity; + private PacketPlayOutEntityDestroy destroyEntity; + private PacketPlayOutEntityMetadata metadata; + + private final Set shouldBeAbleToView; + private final Set currentlyViewing; + + private Location location; + + public FakeEntityHorse(int id, Location location) { + this.id = id; + this.entityHorse = new EntityHorse(((CraftWorld) location.getWorld()).getHandle()); + this.entityHorse.setPosition(location.getX(), location.getY(), location.getZ()); + this.location = location; + + this.shouldBeAbleToView = new HashSet<>(); + this.currentlyViewing = new HashSet<>(); + + this.setupPackets(); + } + + public PacketPlayOutEntityMetadata updateMetadata() { + return this.metadata = new PacketPlayOutEntityMetadata(this.getEntityId(), this.entityHorse.getDataWatcher(), true); + } + + public void setSize(int size) { + this.entityHorse.getDataWatcher().add(16, size); + this.updateMetadata(); + } + + public void setName(String name) { + this.entityHorse.setCustomName(name); + this.entityHorse.setCustomNameVisible(true); + this.updateMetadata(); + } + + public void update(Player player) { + if (!this.shouldBeAbleToView.contains(player.getUniqueId())) { + return; + } + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(this.metadata); + } + + @Override + public boolean show(Player player) { + if (!player.getWorld().getUID().equals(this.location.getWorld().getUID())) { + return false; + } + + PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection; + playerConnection.sendPacket(this.destroyEntity); + playerConnection.sendPacket(this.spawnEntity); + playerConnection.sendPacket(this.metadata); + + this.currentlyViewing.add(player.getUniqueId()); + this.shouldBeAbleToView.add(player.getUniqueId()); + return true; + } + + @Override + public boolean showToAll() { + for (Player player : Bukkit.getOnlinePlayers()) { + this.show(player); + } + return false; + } + + @Override + public boolean hide(Player player) { + if (!this.currentlyViewing.contains(player.getUniqueId())) { + return false; + } + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(this.destroyEntity); + this.currentlyViewing.remove(player.getUniqueId()); + return true; + } + + @Override + public boolean hideFromAll() { + for (Player player : Bukkit.getOnlinePlayers()) { + this.hide(player); + } + return true; + } + + @Override + public void teleport(Location location) { + this.entityHorse.setPosition(location.getX(), location.getY(), location.getZ()); + this.entityHorse.world = ((CraftWorld) location.getWorld()).getHandle(); + this.location = location; + + this.setupPackets(); + + PacketPlayOutEntityTeleport packetPlayOutEntityTeleport = new PacketPlayOutEntityTeleport(this.entityHorse); + for (UUID uuid : this.getCurrentlyViewing()) { + Player player = Bukkit.getPlayer(uuid); + + if (player == null) { + continue; + } + + if (!player.getWorld().getName().equals(this.location.getWorld().getName())) { + this.hide(player); + continue; + } + + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packetPlayOutEntityTeleport); + } + } + + @Override + public boolean isShownToPlayer(UUID uuid) { + return this.shouldBeAbleToView.contains(uuid); + } + + @Override + public String getName() { + return this.entityHorse.getName(); + } + + @Override + public int getId() { + return this.id; + } + + @Override + public int getEntityId() { + return this.entityHorse.getId(); + } + + @Override + public UUID getUUID() { + return this.entityHorse.getUniqueID(); + } + + @Override + public void addToTeam(Player player) { + //ignore + } + + @Override + public void handleDisconnect(UUID uuid) { + this.currentlyViewing.remove(uuid); + this.shouldBeAbleToView.remove(uuid); + } + + @Override + public Set getCurrentlyViewing() { + return this.currentlyViewing; + } + + @Override + public Set getShouldBeAbleToView() { + return this.shouldBeAbleToView; + } + + @Override + public World getWorld() { + return this.location.getWorld(); + } + + @Override + public Location getLocation() { + return this.location; + } + + public void setupPackets() { + this.spawnEntity = new PacketPlayOutSpawnEntityLiving(this.entityHorse); + this.destroyEntity = new PacketPlayOutEntityDestroy(this.getEntityId()); + this.updateMetadata(); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/fake/impl/hologram/FakeEntitySkull.java b/Network/eLib/src/main/java/com/elevatemc/elib/fake/impl/hologram/FakeEntitySkull.java new file mode 100644 index 0000000..55977d0 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/fake/impl/hologram/FakeEntitySkull.java @@ -0,0 +1,184 @@ +package com.elevatemc.elib.fake.impl.hologram; + +import lombok.Getter; +import net.minecraft.server.v1_8_R3.*; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; + +import org.bukkit.craftbukkit.v1_8_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import org.bukkit.entity.Player; +import com.elevatemc.elib.fake.FakeEntity; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +@Getter +public class FakeEntitySkull implements FakeEntity { + + private final int id; + private final EntityWitherSkull entityWitherSkull; + + private final Set shouldBeAbleToView; + private final Set currentlyViewing; + + private PacketPlayOutSpawnEntity spawnEntity; + private PacketPlayOutEntityDestroy destroyEntity; + private PacketPlayOutEntityMetadata metadata; + + private Location location; + + public FakeEntitySkull(int id, Location location) { + this.id = id; + this.entityWitherSkull = new EntityWitherSkull(((CraftWorld) location.getWorld()).getHandle()); + this.entityWitherSkull.setPosition(location.getX(), location.getY(), location.getZ()); + this.location = location; + + this.shouldBeAbleToView = new HashSet<>(); + this.currentlyViewing = new HashSet<>(); + + this.setupPackets(); + } + + public void updateMetadata() { + this.metadata = new PacketPlayOutEntityMetadata(this.getEntityId(), this.entityWitherSkull.getDataWatcher(), true); + } + + public void update(Player player) { + if (!this.shouldBeAbleToView.contains(player.getUniqueId())) { + return; + } + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(this.metadata); + } + + @Override + public boolean show(Player player) { + if (!player.getWorld().getUID().equals(this.location.getWorld().getUID())) { + return false; + } + + PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection; + playerConnection.sendPacket(this.destroyEntity); + playerConnection.sendPacket(this.spawnEntity); + playerConnection.sendPacket(this.metadata); + + this.currentlyViewing.add(player.getUniqueId()); + this.shouldBeAbleToView.add(player.getUniqueId()); + return true; + } + + @Override + public boolean showToAll() { + for (Player player : Bukkit.getOnlinePlayers()) { + this.show(player); + } + return false; + } + + @Override + public boolean hide(Player player) { + if (!this.currentlyViewing.contains(player.getUniqueId())) { + return false; + } + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(this.destroyEntity); + this.currentlyViewing.remove(player.getUniqueId()); + return true; + } + + @Override + public boolean hideFromAll() { + for (Player player : Bukkit.getOnlinePlayers()) { + this.hide(player); + } + return true; + } + + @Override + public void teleport(Location location) { + this.entityWitherSkull.setPosition(location.getX(), location.getY(), location.getZ()); + this.entityWitherSkull.world = ((CraftWorld) location.getWorld()).getHandle(); + this.location = location; + + this.setupPackets(); + + PacketPlayOutEntityTeleport packetPlayOutEntityTeleport = new PacketPlayOutEntityTeleport(this.entityWitherSkull); + for (UUID uuid : this.getCurrentlyViewing()) { + Player player = Bukkit.getPlayer(uuid); + + if (player == null) { + continue; + } + + if (!player.getWorld().getName().equals(this.location.getWorld().getName())) { + this.hide(player); + continue; + } + + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packetPlayOutEntityTeleport); + } + } + + @Override + public boolean isShownToPlayer(UUID uuid) { + return this.shouldBeAbleToView.contains(uuid); + } + + @Override + public String getName() { + return this.entityWitherSkull.getName(); + } + + @Override + public int getId() { + return this.id; + } + + @Override + public int getEntityId() { + return this.entityWitherSkull.getId(); + } + + @Override + public UUID getUUID() { + return this.entityWitherSkull.getUniqueID(); + } + + @Override + public void addToTeam(Player player) { + //ignore + } + + @Override + public void handleDisconnect(UUID uuid) { + this.currentlyViewing.remove(uuid); + this.shouldBeAbleToView.remove(uuid); + } + + @Override + public Set getCurrentlyViewing() { + return this.currentlyViewing; + } + + @Override + public Set getShouldBeAbleToView() { + return this.shouldBeAbleToView; + } + + @Override + public World getWorld() { + return this.location.getWorld(); + } + + @Override + public Location getLocation() { + return this.location; + } + + public void setupPackets() { + this.spawnEntity = new PacketPlayOutSpawnEntity(this.entityWitherSkull, 66); + this.destroyEntity = new PacketPlayOutEntityDestroy(this.getEntityId()); + this.updateMetadata(); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/fake/impl/player/FakePlayerEntity.java b/Network/eLib/src/main/java/com/elevatemc/elib/fake/impl/player/FakePlayerEntity.java new file mode 100644 index 0000000..2ee76ef --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/fake/impl/player/FakePlayerEntity.java @@ -0,0 +1,246 @@ +package com.elevatemc.elib.fake.impl.player; + +import net.minecraft.server.v1_8_R3.*; +import org.bukkit.craftbukkit.v1_8_R3.CraftServer; +import org.bukkit.craftbukkit.v1_8_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.util.PlayerTeamUtils; +import com.elevatemc.elib.util.TaskUtil; +import com.elevatemc.elib.util.message.MessageTranslator; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; +import com.elevatemc.elib.fake.FakeEntity; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +/** + * @author ImHacking + */ +@Getter(AccessLevel.PROTECTED) +public class FakePlayerEntity implements FakeEntity { + private final int id; + private final String displayName; + private final GameProfile gameProfile; + + private final EntityPlayer entityPlayer; + private final Set shouldBeAbleToView; + private final Set currentlyViewing; + private final Map lookGoals; + + @Getter + @Setter + private String command; + + @Getter + private Location location; + + private PacketPlayOutEntityDestroy destroy; + private PacketPlayOutPlayerInfo add; + private PacketPlayOutNamedEntitySpawn namedEntitySpawn; + private PacketPlayOutEntityHeadRotation entityHeadRotation; + private PacketPlayOutEntity.PacketPlayOutEntityLook entityLook; + private PacketPlayOutAnimation animation; + + public FakePlayerEntity(int id, String displayName, Location location) { + this.id = id; + this.displayName = MessageTranslator.translate(displayName); + this.gameProfile = new GameProfile(UUID.randomUUID(), this.displayName); + this.location = location; + this.shouldBeAbleToView = new HashSet<>(); + this.currentlyViewing = new HashSet<>(); + this.lookGoals = new HashMap<>(); + + MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer(); + WorldServer worldServer = ((CraftWorld) location.getWorld()).getHandle(); + PlayerInteractManager interactManager = new PlayerInteractManager(worldServer); + this.entityPlayer = new EntityPlayer(server, worldServer, this.gameProfile, interactManager); + this.entityPlayer.setPosition(location.getX(), location.getY(), location.getZ()); + this.entityPlayer.getDataWatcher().watch(10, (byte) 127); + this.entityPlayer.getBukkitEntity().setRemoveWhenFarAway(false); + this.entityPlayer.getBukkitEntity().setPlayerListName(displayName); + this.setupPackets(); + } + + public void updateSkin(String val, String sig) { + if (val == null || sig == null) { + return; + } + this.entityPlayer.getProfile().getProperties().clear(); + this.entityPlayer.getProfile().getProperties().put("textures", new Property("textures", val, sig)); + this.setupPackets(); + } + + @Override + public boolean show(Player player) { + if (!player.getWorld().getUID().equals(this.location.getWorld().getUID())) { + return false; + } + + PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection; + playerConnection.sendPacket(this.destroy); + playerConnection.sendPacket(this.add); + playerConnection.sendPacket(this.namedEntitySpawn); + playerConnection.sendPacket(this.entityHeadRotation); + playerConnection.sendPacket(this.entityLook); + playerConnection.sendPacket(this.animation); + + this.shouldBeAbleToView.add(player.getUniqueId()); + this.currentlyViewing.add(player.getUniqueId()); + + if (eLib.getInstance().getTabHandler().getTabList() != null) { //no point of removing because we can just filter it to the end of the tab! + TaskUtil.scheduleOnPool(() -> { + playerConnection.sendPacket(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, this.entityPlayer)); + }, 300, TimeUnit.MILLISECONDS); + } + + return true; + } + + @Override + public boolean showToAll() { + for (Player player : Bukkit.getOnlinePlayers()) { + this.show(player); + } + return true; + } + + @Override + public boolean hide(Player player) { + if (!this.currentlyViewing.contains(player.getUniqueId())) { + return false; + } + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(this.destroy); + this.currentlyViewing.remove(player.getUniqueId()); + return true; + } + + @Override + public boolean hideFromAll() { + for (Player player : Bukkit.getOnlinePlayers()) { + this.hide(player); + } + return true; + } + + @Override + public void teleport(Location location) { + this.entityPlayer.setPosition(location.getX(), location.getY(), location.getZ()); + this.entityPlayer.world = ((CraftWorld) location.getWorld()).getHandle(); + this.location = location; + + this.setupPackets(); + + PacketPlayOutEntityTeleport packetPlayOutEntityTeleport = new PacketPlayOutEntityTeleport(this.entityPlayer); + for (UUID uuid : this.getCurrentlyViewing()) { + Player player = Bukkit.getPlayer(uuid); + + if (player == null) { + continue; + } + + if (!player.getWorld().getName().equals(this.location.getWorld().getName())) { + this.hide(player); + continue; + } + + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packetPlayOutEntityTeleport); + } + } + + @Override + public boolean isShownToPlayer(UUID uuid) { + return this.shouldBeAbleToView.contains(uuid); + } + + @Override + public String getName() { + return this.displayName; + } + + @Override + public int getId() { + return this.id; + } + + @Override + public int getEntityId() { + return this.entityPlayer.getId(); + } + + @Override + public UUID getUUID() { + return this.entityPlayer.getUniqueID(); + } + + @Override + public void addToTeam(Player player) { + PlayerTeamUtils.sendUpdatePlayers(player, "zEntities", Collections.singletonList(this.displayName), 3); + } + + @Override + public void handleDisconnect(UUID uuid) { + this.currentlyViewing.remove(uuid); + this.shouldBeAbleToView.remove(uuid); + } + + public Set getCurrentlyViewing() { + return this.currentlyViewing; + } + + public Set getShouldBeAbleToView() { + return this.shouldBeAbleToView; + } + + @Override + public World getWorld() { + return this.getLocation().getWorld(); + } + + + public void updateRotation(Player player, float locationYaw, float locationPitch) { + byte pitch = (byte) ((int) (locationPitch * 256.0F / 360.0F)); + byte yaw = (byte) ((int) (locationYaw * 256.0F / 360.0F)); // convert to radiant tings + PacketPlayOutEntity.PacketPlayOutEntityLook look = new PacketPlayOutEntity.PacketPlayOutEntityLook(this.entityPlayer.getId(), yaw, pitch, this.entityPlayer.onGround); + PacketPlayOutEntityHeadRotation rotation = new PacketPlayOutEntityHeadRotation(this.entityPlayer, yaw); + PlayerConnection connection = ((CraftPlayer) player).getHandle().playerConnection; + connection.sendPacket(look); + connection.sendPacket(rotation); + } + + public Location getCurrentLocation() { + return this.entityPlayer.getBukkitEntity().getEyeLocation(); + } + + public float[] getCurrentAngles() { + Location current = this.getLocation(); + return new float[]{current.getYaw(), current.getPitch()}; + } + + public float[] getGoalAngles(Location goal) { + return this.getGoalAngles(this.getCurrentLocation(), goal); + } + + public float[] getGoalAngles(Location current, Location goal) { + Location targetLocation = current.clone(); + targetLocation.setDirection(goal.clone().add(0, 1.5, 0).subtract(current).toVector()); + return new float[]{targetLocation.getYaw(), targetLocation.getPitch()}; + } + + private void setupPackets() { + this.destroy = new PacketPlayOutEntityDestroy(this.getEntityId()); + this.add = new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, this.entityPlayer); + this.namedEntitySpawn = new PacketPlayOutNamedEntitySpawn(this.entityPlayer); + this.entityHeadRotation = new PacketPlayOutEntityHeadRotation(this.entityPlayer, (byte) ((this.location.getYaw() * 256.0F) / 360.0F)); + this.entityLook = new PacketPlayOutEntity.PacketPlayOutEntityLook(this.getEntityId(), (byte) this.location.getYaw(), (byte) 0, true); + this.animation = new PacketPlayOutAnimation(this.entityPlayer, 0); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/fake/impl/player/FakePlayerEntityTask.java b/Network/eLib/src/main/java/com/elevatemc/elib/fake/impl/player/FakePlayerEntityTask.java new file mode 100644 index 0000000..c7e26e3 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/fake/impl/player/FakePlayerEntityTask.java @@ -0,0 +1,42 @@ +package com.elevatemc.elib.fake.impl.player; + +import lombok.AllArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import com.elevatemc.elib.fake.FakeEntity; +import com.elevatemc.elib.fake.FakeEntityHandler; + +import java.util.UUID; + +@AllArgsConstructor +public class FakePlayerEntityTask implements Runnable { + private FakeEntityHandler fakeEntityHandler; + @Override + public void run() { + for (Player player : Bukkit.getOnlinePlayers()) { + UUID uuid = player.getUniqueId(); + Location playerLocation = player.getLocation(); + UUID worldId = player.getWorld().getUID(); + + for (FakeEntity fakeEntity : this.fakeEntityHandler.getAllEntitiesPlayerCanSee(uuid)) { + if (!fakeEntity.getWorld().getUID().equals(worldId)) { + if (fakeEntity.getCurrentlyViewing().contains(uuid)) { + fakeEntity.hide(player); + } + continue; + } + + if (fakeEntity.getLocation().distanceSquared(playerLocation) < fakeEntity.range()) { + if (!fakeEntity.getCurrentlyViewing().contains(uuid)) { + fakeEntity.show(player); + } + } else { + if (fakeEntity.getCurrentlyViewing().contains(uuid)) { + fakeEntity.hide(player); + } + } + } + } + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/fake/impl/player/FakePlayerInteractEvent.java b/Network/eLib/src/main/java/com/elevatemc/elib/fake/impl/player/FakePlayerInteractEvent.java new file mode 100644 index 0000000..91d2f01 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/fake/impl/player/FakePlayerInteractEvent.java @@ -0,0 +1,25 @@ +package com.elevatemc.elib.fake.impl.player; + +import com.elevatemc.elib.util.event.PlayerEvent; +import lombok.Getter; +import lombok.Setter; +import org.bukkit.entity.Player; + +/** + * @author ImHacking + */ + +@Getter +public class FakePlayerInteractEvent extends PlayerEvent { + + private final String fakePlayerName; + + @Setter + private String command; + + public FakePlayerInteractEvent(Player player, String fakePlayerName, String command) { + super(player); + this.fakePlayerName = fakePlayerName; + this.command = command; + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/fake/impl/player/FakePlayerLookGoal.java b/Network/eLib/src/main/java/com/elevatemc/elib/fake/impl/player/FakePlayerLookGoal.java new file mode 100644 index 0000000..489d430 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/fake/impl/player/FakePlayerLookGoal.java @@ -0,0 +1,101 @@ +package com.elevatemc.elib.fake.impl.player; + +import org.bukkit.entity.Player; + +/** + * @author ImHacking + */ +public class FakePlayerLookGoal { + public static final int GOAL_UPDATE_TICKS = 10; + + public float goalYaw; + public float goalPitch; + + public float yawIncrements; + public float pitchIncrements; + + public float currentYaw; + public float currentPitch; + + public int tick; + private boolean enabled; + + private int incrementsRemaining; + + public FakePlayerLookGoal(float[] current, float[] goals) { + this.goalYaw = current[0]; + this.goalPitch = current[1]; + + this.tick = 0; + this.enabled = true; + + this.incrementsRemaining = GOAL_UPDATE_TICKS; + + this.updateGoal(goals[0], goals[1]); + } + + public void updateGoal(float goalYaw, float goalPitch) { + this.currentYaw = this.goalYaw; + this.currentPitch = this.goalPitch; + + if (Math.abs(goalYaw - currentYaw) > 180) { + if (goalYaw < currentYaw) { + goalYaw += 360; + } else { + this.currentYaw += 360; + } + } + + this.goalYaw = goalYaw; + this.goalPitch = goalPitch; + + this.yawIncrements = (this.goalYaw - currentYaw) / (float) GOAL_UPDATE_TICKS; + this.pitchIncrements = (this.goalPitch - currentPitch) / (float) GOAL_UPDATE_TICKS; + this.incrementsRemaining = GOAL_UPDATE_TICKS; + } + + public void updateCurrent(float currentYaw, float currentPitch) { + this.currentYaw = currentYaw; + this.currentPitch = currentPitch; + } + + public void tick() { + if (this.incrementsRemaining > 0) { + this.currentYaw = increaseAngle(this.currentYaw, this.yawIncrements); + this.currentPitch = increaseAngle(this.currentPitch, this.pitchIncrements); + this.incrementsRemaining--; + } + } + + private float increaseAngle(float angle, float increase) { + if (angle + increase > 360) { + return (angle + increase) % 360; + } else if (angle + increase < 0) { + float deduct = Math.abs(angle + increase); + return 360 - deduct; + } else { + return angle + increase; + } + } + + public void sendFacing(FakePlayerEntity entity, Player player) { + entity.updateRotation(player, currentYaw, currentPitch); + } + + public void disable() { + this.enabled = false; + } + + public void enable() { + this.enabled = true; + } + + public boolean isEnabled() { + return this.enabled; + } + + public boolean hasReachedGoal() { + return this.incrementsRemaining <= 0; + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/fake/impl/player/FakePlayerLookTask.java b/Network/eLib/src/main/java/com/elevatemc/elib/fake/impl/player/FakePlayerLookTask.java new file mode 100644 index 0000000..37e0534 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/fake/impl/player/FakePlayerLookTask.java @@ -0,0 +1,61 @@ +package com.elevatemc.elib.fake.impl.player; + +import lombok.AllArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import com.elevatemc.elib.fake.FakeEntity; +import com.elevatemc.elib.fake.FakeEntityHandler; + +import java.util.UUID; + +@AllArgsConstructor +public class FakePlayerLookTask implements Runnable { + private FakeEntityHandler handler; + @Override + public void run() { + try { + this.runLoop(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void runLoop() { + for (Player online : Bukkit.getOnlinePlayers()) { + UUID uuid = online.getUniqueId(); + for (FakeEntity fakeEntity : this.handler.getAllEntitiesPlayerCanSee(online.getPlayer().getUniqueId())) { + if (!(fakeEntity instanceof FakePlayerEntity )) { + continue; + } + FakePlayerEntity fakePlayer = (FakePlayerEntity) fakeEntity; + if (!(online.getLocation().getWorld().getUID().equals(fakePlayer.getLocation().getWorld().getUID()))) { + continue; + } + + FakePlayerLookGoal goal = fakePlayer.getLookGoals().computeIfAbsent(uuid, (id) -> + new FakePlayerLookGoal(fakePlayer.getCurrentAngles(), fakePlayer.getGoalAngles(online.getLocation()))); + + if (goal.isEnabled() || !goal.hasReachedGoal()) { + goal.tick(); + goal.sendFacing(fakePlayer, online); + } + + if (goal.tick == 0) { + Location playerLoc = online.getLocation(); + if (playerLoc.distanceSquared(fakePlayer.getLocation()) <= 100) { + final float[] goalangles = fakePlayer.getGoalAngles(playerLoc); + goal.updateGoal(goalangles[0], goalangles[1]); + goal.enable(); + } else { + goal.disable(); + float[] original = fakePlayer.getCurrentAngles(); + goal.updateGoal(original[0], original[1]); + } + } + + goal.tick = (goal.tick + 1) % FakePlayerLookGoal.GOAL_UPDATE_TICKS; + } + } + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/fake/impl/player/FakePlayerPacketHandler.java b/Network/eLib/src/main/java/com/elevatemc/elib/fake/impl/player/FakePlayerPacketHandler.java new file mode 100644 index 0000000..dddedb1 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/fake/impl/player/FakePlayerPacketHandler.java @@ -0,0 +1,71 @@ +package com.elevatemc.elib.fake.impl.player; + +import com.elevatemc.elib.util.TaskUtil; +import com.elevatemc.spigot.handler.PacketHandler; +import lombok.AllArgsConstructor; +import net.minecraft.server.v1_8_R3.Entity; +import net.minecraft.server.v1_8_R3.Packet; +import net.minecraft.server.v1_8_R3.PacketPlayInUseEntity; +import net.minecraft.server.v1_8_R3.PlayerConnection; +import com.elevatemc.elib.eLib; +import org.bukkit.entity.Player; +import com.elevatemc.elib.fake.FakeEntity; +import com.elevatemc.elib.fake.FakeEntityHandler; + +/** + * @author ImHacking + */ +@AllArgsConstructor +public class FakePlayerPacketHandler implements PacketHandler { + private FakeEntityHandler handler; + + @Override + public void handleReceivedPacket(PlayerConnection playerConnection, Packet packet) { + if (packet instanceof PacketPlayInUseEntity) { + PacketPlayInUseEntity useEntity = (PacketPlayInUseEntity) packet; + if (useEntity.a() != PacketPlayInUseEntity.EnumEntityUseAction.INTERACT) { + return; + } + + Entity entity = useEntity.a(playerConnection.player.world); + if (entity != null) { + return; + } + + int id = useEntity.a; + FakeEntity fakeEntity = handler.getEntityByEntityId(id); + + if (!(fakeEntity instanceof FakePlayerEntity)) { + return; + } + FakePlayerEntity fakePlayer = (FakePlayerEntity) fakeEntity; + Player player = playerConnection.player.getBukkitEntity(); + + if (player.getLocation().distanceSquared(fakePlayer.getCurrentLocation()) > 36) { + return; + } + + FakePlayerInteractEvent interactEvent = new FakePlayerInteractEvent(player, fakePlayer.getName(), fakePlayer.getCommand()); + interactEvent.call(eLib.getInstance()); + + TaskUtil.runSync(() -> { + String command = interactEvent.getCommand(); + + if (command == null) { + return; + } + + if (!command.startsWith("/")) { + command = "/" + command; + } + + if (command.equalsIgnoreCase("/")) { + return; + } + +// player.performCommand(command); + player.chat(command); + }); + } + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/hologram/Hologram.java b/Network/eLib/src/main/java/com/elevatemc/elib/hologram/Hologram.java new file mode 100644 index 0000000..0005cc6 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/hologram/Hologram.java @@ -0,0 +1,105 @@ +package com.elevatemc.elib.hologram; + +import lombok.Getter; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +import java.util.*; + +@Getter +public class Hologram { + + private final List lines; + private final Set setup; + private Location location; + + public Hologram(Location location) { + this.lines = new ArrayList<>(); + this.setup = new HashSet<>(); + this.updateLocation(location); + } + + public void setup(Player player) { + this.setup.add(player.getUniqueId()); + for (HologramLine line : this.lines) { + line.setup(player); + } + } + + public void update(Player player) { + if (!setup.contains(player.getUniqueId())) { + return; + } + for (HologramLine line : this.lines) { + line.update(player); + } + } + + public void hide(Player player) { + if (!setup.contains(player.getUniqueId())) { + return; + } + this.setup.remove(player.getUniqueId()); + for (HologramLine line : this.lines) { + line.hide(player); + } + } + + public boolean isSetup(UUID uuid) { + return this.setup.contains(uuid); + } + + public void addLine(String line) { + this.addLine(lines.size(), line); + } + + public void addLine(int index, String line) { + HologramLine hologramLine = new HologramLine(line, location, 0); + this.lines.add(index, hologramLine); + this.updateRelativeLocation(); + hologramLine.setupBulk(this.setup); + } + + public HologramLine getLine(int index) { + if (index >= this.lines.size()) { + return null; + } + return this.lines.get(index); + } + + public List getLines() { + return this.lines; + } + + public boolean isEmpty() { + return this.getLines().isEmpty(); + } + + public void removeLine(int index) { + if (index >= this.lines.size()) { + return; + } + HologramLine line = this.lines.remove(index); + if (line == null) { + return; + } + line.hideBulk(this.setup); + this.updateRelativeLocation(); + } + + public void updateRelativeLocation() { + int size = this.lines.size(); + if (size <= 0) { + return; + } + for (int i = 0; i < size; i++) { + HologramLine hologramLine = this.lines.get(i); + hologramLine.updateLocation(this.location, (size - i) * 0.3); + } + } + + public void updateLocation(Location location) { + this.location = location; + this.updateRelativeLocation(); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/hologram/HologramHandler.java b/Network/eLib/src/main/java/com/elevatemc/elib/hologram/HologramHandler.java new file mode 100644 index 0000000..176a5d1 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/hologram/HologramHandler.java @@ -0,0 +1,92 @@ +package com.elevatemc.elib.hologram; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.hologram.placeholder.Placeholder; +import com.elevatemc.elib.hologram.placeholder.PlayerNamePlaceholder; +import com.elevatemc.elib.util.TaskUtil; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; + + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +public class HologramHandler { + + private final Int2ObjectMap hologramMap; + private final Set placeholders; + + public HologramHandler(eLib instance) { + this.hologramMap = new Int2ObjectOpenHashMap<>(); + this.placeholders = new HashSet<>(); + this.registerPlaceholder(new PlayerNamePlaceholder()); + instance.getServer().getPluginManager().registerEvents(new HologramListener(this), instance); + TaskUtil.scheduleAtFixedRateOnPool(new HologramTask(this), 50, 50, TimeUnit.MILLISECONDS); + } + + public void registerPlaceholder(Placeholder placeholder) { + this.placeholders.add(placeholder); + } + + public int registerHologram(Hologram hologram) { + return this.registerHologram(this.hologramMap.size(), hologram); + } + + public int registerHologram(int index, Hologram hologram) { + this.hologramMap.put(index, hologram); + for (Player player : Bukkit.getOnlinePlayers()) { + hologram.setup(player); + } + return index; + } + + public void removeHologram(int index) { + Hologram hologram = this.getHologram(index); + if (hologram != null) { + for (Player player : Bukkit.getOnlinePlayers()) { + hologram.hide(player); + } + } + this.hologramMap.remove(index); + } + + public Hologram getHologram(int id) { + return this.hologramMap.get(id); + } + + public Set getHolograms() { + return new HashSet<>(this.hologramMap.values()); + } + + public void handleMovement(Player player, Location from, Location to) { + if (from.getBlockZ() == to.getBlockZ() && from.getBlockX() == to.getBlockX()) { + return; + } + + for (Hologram hologram : this.getHolograms()) { + Location location = hologram.getLocation(); + + if (to.getWorld().getUID() != location.getWorld().getUID()) { + hologram.hide(player); + continue; + } + + if (to.distanceSquared(location) <= 1600D) { + if (hologram.isSetup(player.getUniqueId())) { + continue; + } + hologram.setup(player); + } else { + hologram.hide(player); + } + } + } + + public Set getPlaceholders() { + return this.placeholders; + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/hologram/HologramLine.java b/Network/eLib/src/main/java/com/elevatemc/elib/hologram/HologramLine.java new file mode 100644 index 0000000..b8abb9c --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/hologram/HologramLine.java @@ -0,0 +1,157 @@ +package com.elevatemc.elib.hologram; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.fake.impl.hologram.FakeEntityArmorStand; +import com.elevatemc.elib.fake.impl.hologram.FakeEntityHorse; +import com.elevatemc.elib.fake.impl.hologram.FakeEntitySkull; +import com.elevatemc.elib.hologram.placeholder.Placeholder; +import com.elevatemc.elib.util.PlayerUtils; +import net.minecraft.server.v1_8_R3.PacketPlayOutAttachEntity; +import net.minecraft.server.v1_8_R3.PacketPlayOutEntityMetadata; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import com.elevatemc.elib.util.message.MessageTranslator; +import lombok.Getter; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; + + +import java.util.Set; +import java.util.UUID; + +public class HologramLine { + + private static final double SKULL_OFFSET = 55.87D; + + //1.7 packets + private final FakeEntitySkull fakeEntitySkull; + private final FakeEntityHorse fakeEntityHorse; + private final PacketPlayOutAttachEntity attachEntityPacket; + + //1.8 packets + private final FakeEntityArmorStand fakeEntityArmorStand; + + @Getter + private String line; + + public HologramLine(String line, Location location, int offset) { + this.fakeEntitySkull = new FakeEntitySkull(-1, location.clone().add(0, SKULL_OFFSET + offset, 0)); + this.fakeEntityHorse = new FakeEntityHorse(-1, location); + this.fakeEntityArmorStand = new FakeEntityArmorStand(-1, location.clone().add(0, offset, 0)); + + this.fakeEntityHorse.setSize(-1700000); + this.fakeEntityArmorStand.setSmall(true); + this.fakeEntityArmorStand.setInvisible(true); + this.fakeEntityArmorStand.setGravity(false); + + this.attachEntityPacket = new PacketPlayOutAttachEntity(); + attachEntityPacket.a = 0; + attachEntityPacket.b = this.fakeEntityHorse.getEntityId(); + attachEntityPacket.c = this.fakeEntitySkull.getEntityId(); + + this.updateLocation(location, offset); + this.updateLine(line); + } + + public void updateLocation(Location location, double offset) { + this.fakeEntityArmorStand.teleport(location.clone().add(0, offset, 0)); + this.fakeEntitySkull.teleport(location.clone().add(0, SKULL_OFFSET + offset, 0)); + this.fakeEntityHorse.teleport(location); + } + + public void setup(Player player) { + int version = PlayerUtils.getProtocol(player); + + //reset coz logging in & out + this.fakeEntityArmorStand.getCurrentlyViewing().remove(player.getUniqueId()); + this.fakeEntityHorse.getCurrentlyViewing().remove(player.getUniqueId()); + this.fakeEntitySkull.getCurrentlyViewing().remove(player.getUniqueId()); + + if (version >= 47) { + this.fakeEntityArmorStand.show(player); + this.update(player); + } else { + this.fakeEntitySkull.show(player); + this.fakeEntityHorse.show(player); + this.update(player); + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(this.attachEntityPacket); + } + } + + public void update(Player player) { + String formattedLine = this.line; + + for (Placeholder placeholder : eLib.getInstance().getHologramHandler().getPlaceholders()) { + formattedLine = placeholder.formatLine(player, formattedLine); + } + + if (this.fakeEntityArmorStand.getCurrentlyViewing().contains(player.getUniqueId())) { + this.fakeEntityArmorStand.setName(formattedLine); + PacketPlayOutEntityMetadata metadata = this.fakeEntityArmorStand.updateMetadata(); + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(metadata); + } else if (this.fakeEntityHorse.getCurrentlyViewing().contains(player.getUniqueId())) { + this.fakeEntityHorse.setName(formattedLine); + PacketPlayOutEntityMetadata metadata = this.fakeEntityHorse.updateMetadata(); + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(metadata); + } + } + + public void hide(Player player) { + this.fakeEntitySkull.hide(player); + this.fakeEntityHorse.hide(player); + this.fakeEntityArmorStand.hide(player); + } + + public void updateLine(String line) { + line = MessageTranslator.translate(line); + if (this.line != null && this.line.equals(line)) { + return; + } + this.line = line; + + for (UUID uuid : this.fakeEntityArmorStand.getCurrentlyViewing()) { + Player player = Bukkit.getPlayer(uuid); + if (player == null) { + continue; + } + + this.update(player); + } + + for (UUID uuid : this.fakeEntityHorse.getCurrentlyViewing()) { + Player player = Bukkit.getPlayer(uuid); + if (player == null) { + continue; + } + + this.update(player); + } + } + + public void setupBulk(Set uuids) { + for (Player player : Bukkit.getOnlinePlayers()) { + if (!uuids.contains(player.getUniqueId())) { + continue; + } + this.setup(player); + } + } + + public void updateBulk(Set uuids) { + for (Player player : Bukkit.getOnlinePlayers()) { + if (!uuids.contains(player.getUniqueId())) { + continue; + } + this.update(player); + } + } + + public void hideBulk(Set uuids) { + for (Player player : Bukkit.getOnlinePlayers()) { + if (!uuids.contains(player.getUniqueId())) { + continue; + } + this.hide(player); + } + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/hologram/HologramListener.java b/Network/eLib/src/main/java/com/elevatemc/elib/hologram/HologramListener.java new file mode 100644 index 0000000..d3a8697 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/hologram/HologramListener.java @@ -0,0 +1,65 @@ +package com.elevatemc.elib.hologram; + +import com.elevatemc.elib.util.TaskUtil; +import lombok.AllArgsConstructor; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.*; + +@AllArgsConstructor +public class HologramListener implements Listener { + private HologramHandler hologramHandler; + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlayerJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + for (Hologram hologram : this.hologramHandler.getHolograms()) { + Location location = hologram.getLocation(); + if (!player.getWorld().getUID().equals(location.getWorld().getUID())) { + hologram.hide(player); + continue; + } + hologram.setup(event.getPlayer()); + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlayerChangedWorld(PlayerChangedWorldEvent event) { + this.updateWithDelay(event.getPlayer()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlayerRespawn(PlayerRespawnEvent event) { + this.updateWithDelay(event.getPlayer()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlayerTeleport(PlayerTeleportEvent event) { + this.hologramHandler.handleMovement(event.getPlayer(), event.getFrom(), event.getTo()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlayerQuit(PlayerQuitEvent event) { + for (Hologram hologram : this.hologramHandler.getHolograms()) { + hologram.hide(event.getPlayer()); + } + } + + private void updateWithDelay(Player player) { + TaskUtil.runTaskLaterAsynchronously(() -> { + + for (Hologram hologram : this.hologramHandler.getHolograms()) { + Location location = hologram.getLocation(); + + if (!player.getWorld().getUID().equals(location.getWorld().getUID())) { + hologram.hide(player); + continue; + } + + hologram.setup(player); + } + }, 5); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/hologram/HologramTask.java b/Network/eLib/src/main/java/com/elevatemc/elib/hologram/HologramTask.java new file mode 100644 index 0000000..1e9ad6c --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/hologram/HologramTask.java @@ -0,0 +1,42 @@ +package com.elevatemc.elib.hologram; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; + +@Getter +@RequiredArgsConstructor +public class HologramTask implements Runnable { + + private final HologramHandler hologramHandler; + + @Override + public void run() { + for (Player player : Bukkit.getOnlinePlayers()) { + + World world = player.getWorld(); + + for (Hologram hologram : this.hologramHandler.getHolograms()) { + Location location = hologram.getLocation(); + + if (world.getUID() != location.getWorld().getUID()) { + hologram.hide(player); + continue; + } + + Location playerLocation = player.getLocation(); + if (playerLocation.distanceSquared(location) <= 1600D) { + if (hologram.isSetup(player.getUniqueId())) { + continue; + } + hologram.setup(player); + } else { + hologram.hide(player); + } + } + } + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/hologram/UpdatingHologram.java b/Network/eLib/src/main/java/com/elevatemc/elib/hologram/UpdatingHologram.java new file mode 100644 index 0000000..9b1f1ed --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/hologram/UpdatingHologram.java @@ -0,0 +1,43 @@ +package com.elevatemc.elib.hologram; + +import lombok.RequiredArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +import java.util.UUID; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class UpdatingHologram extends Hologram { + + private final ScheduledExecutorService executorService; + + public UpdatingHologram(Location location, int delayMillis) { + super(location); + this.executorService = Executors.newSingleThreadScheduledExecutor(); + this.executorService.scheduleAtFixedRate(new UpdatingHologramTask(this), delayMillis, delayMillis, TimeUnit.MILLISECONDS); + } + + public void stopTask() { + this.executorService.shutdown(); + } + + @RequiredArgsConstructor + public static class UpdatingHologramTask implements Runnable { + + private final UpdatingHologram hologram; + + @Override + public void run() { + for (UUID uuid : this.hologram.getSetup()) { + Player player = Bukkit.getPlayer(uuid); + if (player == null) { + continue; + } + this.hologram.update(player); + } + } + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/hologram/placeholder/Placeholder.java b/Network/eLib/src/main/java/com/elevatemc/elib/hologram/placeholder/Placeholder.java new file mode 100644 index 0000000..a5ec9ed --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/hologram/placeholder/Placeholder.java @@ -0,0 +1,9 @@ +package com.elevatemc.elib.hologram.placeholder; + +import org.bukkit.entity.Player; + +public interface Placeholder { + + String formatLine(Player player, String line); + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/hologram/placeholder/PlayerNamePlaceholder.java b/Network/eLib/src/main/java/com/elevatemc/elib/hologram/placeholder/PlayerNamePlaceholder.java new file mode 100644 index 0000000..0c511d6 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/hologram/placeholder/PlayerNamePlaceholder.java @@ -0,0 +1,10 @@ +package com.elevatemc.elib.hologram.placeholder; + +import org.bukkit.entity.Player; + +public class PlayerNamePlaceholder implements Placeholder { + @Override + public String formatLine(Player player, String line) { + return line.replace("%DISPLAY_NAME%", player.getName()); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/menu/Attributes.java b/Network/eLib/src/main/java/com/elevatemc/elib/menu/Attributes.java new file mode 100644 index 0000000..46fa164 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/menu/Attributes.java @@ -0,0 +1,336 @@ +package com.elevatemc.elib.menu; + +import com.google.common.base.Function; +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import com.google.common.collect.Iterators; +import com.google.common.collect.Maps; +import net.minecraft.server.v1_8_R3.NBTBase; +import net.minecraft.server.v1_8_R3.NBTTagCompound; +import net.minecraft.server.v1_8_R3.NBTTagList; +import org.bukkit.craftbukkit.v1_8_R3.inventory.CraftItemStack; +import org.bukkit.inventory.ItemStack; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.lang.reflect.Field; +import java.util.Iterator; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ConcurrentMap; + +public class Attributes { + public enum Operation { + ADD_NUMBER(0), MULTIPLY_PERCENTAGE(1), ADD_PERCENTAGE(2); + private int id; + + Operation(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + public static Operation fromId(int id) { + // Linear scan is very fast for small N + for (Operation op : values()) { + if (op.getId() == id) { + return op; + } + } + throw new IllegalArgumentException("Corrupt operation ID " + id + " detected."); + } + } + + public static class AttributeType { + private static ConcurrentMap LOOKUP = Maps.newConcurrentMap(); + public static final AttributeType GENERIC_MAX_HEALTH = new AttributeType("generic.maxHealth").register(); + public static final AttributeType GENERIC_FOLLOW_RANGE = new AttributeType("generic.followRange").register(); + public static final AttributeType GENERIC_ATTACK_DAMAGE = new AttributeType("generic.attackDamage").register(); + public static final AttributeType GENERIC_MOVEMENT_SPEED = new AttributeType("generic.movementSpeed").register(); + public static final AttributeType GENERIC_KNOCKBACK_RESISTANCE = new AttributeType("generic.knockbackResistance").register(); + + private final String minecraftId; + + /** + * Construct a new attribute type. + *

+ * Remember to {@link #register()} the type. + * + * @param minecraftId - the ID of the type. + */ + public AttributeType(String minecraftId) { + this.minecraftId = minecraftId; + } + + /** + * Retrieve the associated minecraft ID. + * + * @return The associated ID. + */ + public String getMinecraftId() { + return minecraftId; + } + + /** + * Register the type in the central registry. + * + * @return The registered type. + */ + // Constructors should have no side-effects! + public AttributeType register() { + AttributeType old = LOOKUP.putIfAbsent(minecraftId, this); + return old != null ? old : this; + } + + /** + * Retrieve the attribute type associated with a given ID. + * + * @param minecraftId The ID to search for. + * @return The attribute type, or NULL if not found. + */ + public static AttributeType fromId(String minecraftId) { + return LOOKUP.get(minecraftId); + } + + /** + * Retrieve every registered attribute type. + * + * @return Every type. + */ + public static Iterable values() { + return LOOKUP.values(); + } + } + + public static class Attribute { + private NBTTagCompound data; + + private Attribute(Builder builder) { + data = new NBTTagCompound(); + setAmount(builder.amount); + setOperation(builder.operation); + setAttributeType(builder.type); + setName(builder.name); + setUUID(builder.uuid); + } + + private Attribute(NBTTagCompound data) { + this.data = data; + } + + public double getAmount() { + return data.getDouble("Amount"); + } + + public void setAmount(double amount) { + data.setDouble("Amount", amount); + } + + public Operation getOperation() { + return Operation.fromId(data.getInt("Operation")); + } + + public void setOperation(@Nonnull Operation operation) { + Preconditions.checkNotNull(operation, "operation cannot be NULL."); + data.setInt("Operation", operation.getId()); + } + + public AttributeType getAttributeType() { + return AttributeType.fromId(data.getString("AttributeName")); + } + + public void setAttributeType(@Nonnull AttributeType type) { + Preconditions.checkNotNull(type, "type cannot be NULL."); + data.setString("AttributeName", type.getMinecraftId()); + } + + public String getName() { + return data.getString("Name"); + } + + public void setName(@Nonnull String name) { + data.setString("Name", name); + } + + public UUID getUUID() { + return new UUID(data.getLong("UUIDMost"), data.getLong("UUIDLeast")); + } + + public void setUUID(@Nonnull UUID id) { + Preconditions.checkNotNull("id", "id cannot be NULL."); + data.setLong("UUIDLeast", id.getLeastSignificantBits()); + data.setLong("UUIDMost", id.getMostSignificantBits()); + } + + /** + * Construct a new attribute builder with a random UUID and default operation of adding numbers. + * + * @return The attribute builder. + */ + public static Builder newBuilder() { + return new Builder().uuid(UUID.randomUUID()).operation(Operation.ADD_NUMBER); + } + + // Makes it easier to construct an attribute + public static class Builder { + private double amount; + private Operation operation = Operation.ADD_NUMBER; + private AttributeType type; + private String name; + private UUID uuid; + + private Builder() { + // Don't make this accessible + } + + public Builder amount(double amount) { + this.amount = amount; + return this; + } + + public Builder operation(Operation operation) { + this.operation = operation; + return this; + } + + public Builder type(AttributeType type) { + this.type = type; + return this; + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder uuid(UUID uuid) { + this.uuid = uuid; + return this; + } + + public Attribute build() { + return new Attribute(this); + } + } + } + + // This may be modified + public net.minecraft.server.v1_8_R3.ItemStack nmsStack; + + private NBTTagCompound parent; + private NBTTagList attributes; + + public Attributes(ItemStack stack) { + // Create a CraftItemStack (under the hood) + this.nmsStack = CraftItemStack.asNMSCopy(stack); + + // Load NBT + if (nmsStack.getTag() == null) { + nmsStack.setTag(new NBTTagCompound()); + } + + parent = nmsStack.getTag(); + + // Load attribute list + if (parent.hasKey("AttributeModifiers")) { + attributes = parent.getList("AttributeModifiers", 0); + } else { + attributes = new NBTTagList(); + parent.set("AttributeModifiers", attributes); + } + } + + /** + * Retrieve the modified item stack. + * + * @return The modified item stack. + */ + public ItemStack getStack() { + return CraftItemStack.asCraftMirror(nmsStack); + } + + /** + * Retrieve the number of attributes. + * + * @return Number of attributes. + */ + public int size() { + return attributes.size(); + } + + /** + * Add a new attribute to the list. + * + * @param attribute - the new attribute. + */ + public void add(Attribute attribute) { + attributes.add(attribute.data); + } + + /** + * Remove the first instance of the given attribute. + *

+ * The attribute will be removed using its UUID. + * + * @param attribute - the attribute to remove. + * @return TRUE if the attribute was removed, FALSE otherwise. + */ + public boolean remove(Attribute attribute) { + UUID uuid = attribute.getUUID(); + + for (Iterator it = values().iterator(); it.hasNext(); ) { + if (Objects.equal(it.next().getUUID(), uuid)) { + it.remove(); + return true; + } + } + return false; + } + + public void clear() { + parent.set("AttributeModifiers", attributes = new NBTTagList()); + } + + /** + * Retrieve the attribute at a given index. + * + * @param index - the index to look up. + * @return The attribute at that index. + */ + public Attribute get(int index) { + return new Attribute(attributes.get(index)); + } + + // We can't make Attributes itself iterable without splitting it up into separate classes + public Iterable values() { + final List list = getList(); + + return new Iterable() { + @Override + public Iterator iterator() { + // Generics disgust me sometimes + return Iterators.transform(list.iterator(), new Function() { + + @Override + public Attribute apply(@Nullable NBTBase data) { + return new Attribute((NBTTagCompound) data); + } + }); + } + }; + } + + @SuppressWarnings("unchecked") + private List getList() { + try { + Field listField = NBTTagList.class.getDeclaredField("list"); + listField.setAccessible(true); + return (List) listField.get(attributes); + + } catch (Exception e) { + throw new RuntimeException("Unable to access reflection.", e); + } + } +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/menu/Button.java b/Network/eLib/src/main/java/com/elevatemc/elib/menu/Button.java new file mode 100644 index 0000000..9c66bb6 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/menu/Button.java @@ -0,0 +1,128 @@ +package com.elevatemc.elib.menu; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.List; + +public abstract class Button { + + /** @deprecated */ + @Deprecated + public static Button placeholder(Material material, byte data, String... title) { + return placeholder(material, data, title != null && title.length != 0 ? Joiner.on(" ").join(title) : " "); + } + + public static Button placeholder(Material material) { + return placeholder(material, " "); + } + + public static Button placeholder(Material material, String title) { + return placeholder(material,(byte)0,title); + } + + public static Button placeholder(Material material, byte data, String title) { + return new Button() { + + @Override + public String getName(Player player) { + return title; + } + + @Override + public List getDescription(Player player) { + return ImmutableList.of(); + } + + @Override + public Material getMaterial(Player player) { + return material; + } + + @Override + public byte getDamageValue(Player player) { + return data; + } + + }; + } + + public static Button fromItem(ItemStack item) { + + return new Button() { + + @Override + public ItemStack getButtonItem(Player player) { + return item; + } + + @Override + public String getName(Player var1) { + return null; + } + + @Override + public List getDescription(Player var1) { + return null; + } + + @Override + public Material getMaterial(Player var1) { + return null; + } + }; + + } + + public abstract String getName(Player var1); + + public abstract List getDescription(Player var1); + + public abstract Material getMaterial(Player var1); + + public byte getDamageValue(Player player) { + return 0; + } + + public void clicked(Player player, int slot, ClickType clickType) { + } + + public boolean shouldCancel(Player player, int slot, ClickType clickType) { + return true; + } + + public int getAmount(Player player) { + return 1; + } + + public ItemStack getButtonItem(Player player) { + ItemStack buttonItem = new ItemStack(this.getMaterial(player), this.getAmount(player), (short)this.getDamageValue(player)); + ItemMeta meta = buttonItem.getItemMeta(); + meta.setDisplayName(this.getName(player)); + List description = this.getDescription(player); + if (description != null) { + meta.setLore(description); + } + + buttonItem.setItemMeta(meta); + return buttonItem; + } + + public static void playFail(Player player) { + player.playSound(player.getLocation(), Sound.DIG_GRASS, 20.0F, 0.1F); + } + + public static void playSuccess(Player player) { + player.playSound(player.getLocation(), Sound.NOTE_PIANO, 20.0F, 15.0F); + } + + public static void playNeutral(Player player) { + player.playSound(player.getLocation(), Sound.CLICK, 20.0F, 1.0F); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/menu/ButtonListener.java b/Network/eLib/src/main/java/com/elevatemc/elib/menu/ButtonListener.java new file mode 100644 index 0000000..71feb30 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/menu/ButtonListener.java @@ -0,0 +1,115 @@ +package com.elevatemc.elib.menu; + +import com.elevatemc.elib.eLib; + +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.metadata.FixedMetadataValue; + +public class ButtonListener implements Listener { + + @EventHandler(priority = EventPriority.MONITOR) + public void onButtonPress(InventoryClickEvent event) { + + final Player player = (Player) event.getWhoClicked(); + + final Menu openMenu = Menu.getCurrentlyOpenedMenus().get(player.getUniqueId()); + + if (openMenu != null) { + + if (event.getSlot() != event.getRawSlot()) { + + if ((event.getClick() == ClickType.SHIFT_LEFT || event.getClick() == ClickType.SHIFT_RIGHT)) { + event.setCancelled(true); + + if (openMenu.isNoncancellingInventory()) { + + if (event.getCurrentItem() != null) { + player.getOpenInventory().getTopInventory().addItem(event.getCurrentItem()); + event.setCurrentItem(null); + } + } + } + + return; + + } + if (openMenu.getButtons().containsKey(event.getSlot())) { + + player.setMetadata("CLICKED_BUTTON",new FixedMetadataValue(eLib.getInstance(),player.getUniqueId().toString())); + + final Button button = openMenu.getButtons().get(event.getSlot()); + + final boolean cancel = button.shouldCancel(player, event.getSlot(), event.getClick()); + + if (!cancel && (event.getClick() == ClickType.SHIFT_LEFT || event.getClick() == ClickType.SHIFT_RIGHT)) { + event.setCancelled(true); + + if (event.getCurrentItem() != null) { + player.getInventory().addItem(event.getCurrentItem()); + } + + } else { + event.setCancelled(cancel); + } + + + button.clicked(player,event.getSlot(),event.getClick()); + + if (Menu.getCurrentlyOpenedMenus().containsKey(player.getUniqueId())) { + + final Menu newMenu = Menu.getCurrentlyOpenedMenus().get(player.getUniqueId()); + + if (newMenu == openMenu && newMenu.isUpdateAfterClick()) { + newMenu.openMenu(player); + } + + + } + + if (event.isCancelled()) { + eLib.getInstance().getServer().getScheduler().runTaskLater(eLib.getInstance(), player::updateInventory, 1L); + } + + player.removeMetadata("CLICKED_BUTTON", eLib.getInstance()); + + } else { + if ((event.getClick() == ClickType.SHIFT_LEFT || event.getClick() == ClickType.SHIFT_RIGHT)) { + event.setCancelled(true); + + if (openMenu.isNoncancellingInventory()) { + + if (event.getCurrentItem() != null) { + player.getOpenInventory().getTopInventory().addItem(event.getCurrentItem()); + event.setCurrentItem(null); + } + } + } + } + } + } + + @EventHandler + public void onInventoryClose(InventoryCloseEvent event) { + + final Player player = (Player)event.getPlayer(); + final Menu openMenu = Menu.getCurrentlyOpenedMenus().get(player.getUniqueId()); + + if (openMenu != null) { + + if (!player.hasMetadata("CLICKED_BUTTON")) { + openMenu.onClose(player); + } + + Menu.cancelCheck(player); + Menu.getCurrentlyOpenedMenus().remove(player.getUniqueId()); + } + + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/menu/Menu.java b/Network/eLib/src/main/java/com/elevatemc/elib/menu/Menu.java new file mode 100644 index 0000000..a5005bb --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/menu/Menu.java @@ -0,0 +1,167 @@ +package com.elevatemc.elib.menu; + +import com.elevatemc.elib.eLib; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftHumanEntity; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import lombok.Getter; +import lombok.Setter; +import net.minecraft.server.v1_8_R3.EntityPlayer; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.scheduler.BukkitRunnable; + +import java.lang.reflect.Method; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public abstract class Menu { + private static final Method METHOD_OPEN_CUSTOM_INVENTORY; + static { + eLib.getInstance().getServer().getPluginManager().registerEvents(new ButtonListener(), eLib.getInstance()); + currentlyOpenedMenus = new HashMap<>(); + checkTasks = new HashMap<>(); + try { + METHOD_OPEN_CUSTOM_INVENTORY = CraftHumanEntity.class.getDeclaredMethod("openCustomInventory", Inventory.class, EntityPlayer.class, String.class); + METHOD_OPEN_CUSTOM_INVENTORY.setAccessible(true); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static Method openInventoryMethod; + @Getter private ConcurrentHashMap buttons = new ConcurrentHashMap<>(); + + @Getter @Setter private boolean autoUpdate = false; + @Getter @Setter private boolean updateAfterClick = true; + @Getter @Setter private boolean placeholder = false; + @Getter @Setter private boolean noncancellingInventory = false; + + @Getter private static Map currentlyOpenedMenus; + @Getter private static Map checkTasks; + + private Inventory createInventory(Player player) { + + final Inventory inventory = eLib.getInstance().getServer().createInventory(player, size(player), getTitle(player)); + + for (Map.Entry buttonEntry : getButtons(player).entrySet()) { + + this.buttons.put(buttonEntry.getKey(), buttonEntry.getValue()); + + final ItemStack item = createItemStack(player, buttonEntry.getValue()); + + inventory.setItem(buttonEntry.getKey(), item); + } + + if (this.isPlaceholder()) { + + final Button placeholder = Button.placeholder(Material.STAINED_GLASS_PANE, (byte) 15); + + for (int index = 0; index < this.size(player); index++) { + + if (this.getButtons(player).get(index) == null) { + this.buttons.put(index, placeholder); + inventory.setItem(index, placeholder.getButtonItem(player)); + } + + } + + } + + return inventory; + } + + private ItemStack createItemStack(Player player, Button button) { + ItemStack item = button.getButtonItem(player); + + if (item.getType() != Material.SKULL_ITEM) { + + ItemMeta meta = item.getItemMeta(); + + if (meta != null && meta.hasDisplayName()) { + meta.setDisplayName(meta.getDisplayName() + "§k§e§r§e§m"); + } + + item.setItemMeta(meta); + } + + return item; + } + + public void openMenu(Player player) { + + final EntityPlayer entityPlayer = ((CraftPlayer)player).getHandle(); + final Inventory inventory = this.createInventory(player); + + try { + + METHOD_OPEN_CUSTOM_INVENTORY.invoke(player, inventory, entityPlayer, "minecraft:chest"); + this.update(player); + } catch (Exception var5) { + var5.printStackTrace(); + } + + } + + private void update(Player player) { + cancelCheck(player); + currentlyOpenedMenus.put(player.getUniqueId(),this); + + this.onOpen(player); + + final BukkitRunnable runnable = new BukkitRunnable() { + @Override + public void run() { + + if (!player.isOnline()) { + Menu.cancelCheck(player); + Menu.currentlyOpenedMenus.remove(player.getUniqueId()); + } + + if (isAutoUpdate()) { + player.getOpenInventory().getTopInventory().setContents(createInventory(player).getContents()); + } + + } + }; + + runnable.runTaskTimer(eLib.getInstance(), 40L, 40L); + checkTasks.put(player.getUniqueId(), runnable); + } + + + public static void cancelCheck(Player player) { + if (checkTasks.containsKey(player.getUniqueId())) { + checkTasks.remove(player.getUniqueId()).cancel(); + } + + } + + public int size(Player player) { + int highest = 0; + + for (int buttonValue : getButtons(player).keySet()) { + if (buttonValue > highest) { + highest = buttonValue; + } + } + + return (int) (Math.ceil((highest + 1) / 9D) * 9D); + } + + public int getSlot(int x, int y) { + return ((9 * y) + x); + } + + + public abstract String getTitle(Player player); + + public abstract Map getButtons(Player player); + + public void onOpen(Player player) {} + + public void onClose(Player player) {} + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/menu/buttons/AddButton.java b/Network/eLib/src/main/java/com/elevatemc/elib/menu/buttons/AddButton.java new file mode 100644 index 0000000..1a407e9 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/menu/buttons/AddButton.java @@ -0,0 +1,4 @@ +package com.elevatemc.elib.menu.buttons; + +public class AddButton { +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/menu/buttons/BackButton.java b/Network/eLib/src/main/java/com/elevatemc/elib/menu/buttons/BackButton.java new file mode 100644 index 0000000..5be3d60 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/menu/buttons/BackButton.java @@ -0,0 +1,44 @@ +package com.elevatemc.elib.menu.buttons; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import lombok.AllArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.ArrayList; +import java.util.List; + +@AllArgsConstructor +public class BackButton extends Button { + + private Menu back; + + @Override + public Material getMaterial(Player player) { + return Material.BED; + } + + @Override + public byte getDamageValue(Player player) { + return 0; + } + + @Override + public String getName(Player player) { + return "§cGo back"; + } + + @Override + public List getDescription(Player player) { + return new ArrayList(); + } + + @Override + public void clicked(Player player, int i, ClickType clickType) { + Button.playNeutral(player); + this.back.openMenu(player); + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/menu/buttons/BooleanButton.java b/Network/eLib/src/main/java/com/elevatemc/elib/menu/buttons/BooleanButton.java new file mode 100644 index 0000000..2d33413 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/menu/buttons/BooleanButton.java @@ -0,0 +1,51 @@ +package com.elevatemc.elib.menu.buttons; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.util.Callback; +import lombok.AllArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.ArrayList; +import java.util.List; + +@AllArgsConstructor +public class BooleanButton extends Button { + + private boolean confirm; + private Callback callback; + + public void clicked(Player player, int i, ClickType clickType) { + + if (this.confirm) { + player.playSound(player.getLocation(), Sound.NOTE_PIANO, 20.0F, 0.1F); + } else { + player.playSound(player.getLocation(), Sound.DIG_GRAVEL, 20.0F, 0.1F); + } + + player.closeInventory(); + + this.callback.callback(this.confirm); + } + + @Override + public String getName(Player player) { + return this.confirm ? "§aConfirm" : "§cCancel"; + } + + @Override + public List getDescription(Player player) { + return new ArrayList(); + } + + @Override + public byte getDamageValue(Player player) { + return (byte)(this.confirm ? 5 : 14); + } + + @Override + public Material getMaterial(Player player) { + return Material.WOOL; + }} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/menu/buttons/CloseButton.java b/Network/eLib/src/main/java/com/elevatemc/elib/menu/buttons/CloseButton.java new file mode 100644 index 0000000..7d2bcfb --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/menu/buttons/CloseButton.java @@ -0,0 +1,46 @@ +package com.elevatemc.elib.menu.buttons; + +import com.elevatemc.elib.menu.Button; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.ArrayList; +import java.util.List; + + +@NoArgsConstructor +@AllArgsConstructor +public class CloseButton extends Button { + + private String name; + private List lore; + + @Override + public Material getMaterial(Player player) { + return Material.REDSTONE_BLOCK; + } + + @Override + public byte getDamageValue(Player player) { + return 0; + } + + @Override + public String getName(Player player) { + return this.name == null ? "§cClose Menu":this.name; + } + + @Override + public List getDescription(Player player) { + return this.lore == null ? new ArrayList<>():this.lore; + } + + @Override + public void clicked(Player player, int i, ClickType clickType) { + player.closeInventory(); + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/menu/buttons/JumpToMenuButton.java b/Network/eLib/src/main/java/com/elevatemc/elib/menu/buttons/JumpToMenuButton.java new file mode 100644 index 0000000..cc107c7 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/menu/buttons/JumpToMenuButton.java @@ -0,0 +1,44 @@ +package com.elevatemc.elib.menu.buttons; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import lombok.AllArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +@AllArgsConstructor +public class JumpToMenuButton extends Button { + + private Menu menu; + private ItemStack itemStack; + + @Override + public String getName(Player player) { + return this.itemStack.getItemMeta().getDisplayName(); + } + + @Override + public List getDescription(Player player) { + return this.itemStack.getItemMeta().getLore(); + } + + @Override + public Material getMaterial(Player player) { + return this.itemStack.getType(); + } + + @Override + public byte getDamageValue(Player player) { + return (byte)this.itemStack.getDurability(); + } + + @Override + public void clicked(Player player,int i,ClickType clickType) { + menu.openMenu(player); + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/menu/buttons/TexturedHeadButton.java b/Network/eLib/src/main/java/com/elevatemc/elib/menu/buttons/TexturedHeadButton.java new file mode 100644 index 0000000..515e7cc --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/menu/buttons/TexturedHeadButton.java @@ -0,0 +1,37 @@ +package com.elevatemc.elib.menu.buttons; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.util.ItemUtils; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +public abstract class TexturedHeadButton extends Button { + + private final String texture; + + public TexturedHeadButton(String texture) { + this.texture = texture; + } + + public String getTexture(Player player) { + return texture; + } + + @Override + public Material getMaterial(Player var1) { + return Material.SKULL_ITEM; + } + + @Override + public byte getDamageValue(Player player) { + return 3; + } + + @Override + public ItemStack getButtonItem(Player player) { + return super.getButtonItem(player); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/menu/menus/ConfirmMenu.java b/Network/eLib/src/main/java/com/elevatemc/elib/menu/menus/ConfirmMenu.java new file mode 100644 index 0000000..b33c60f --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/menu/menus/ConfirmMenu.java @@ -0,0 +1,42 @@ +package com.elevatemc.elib.menu.menus; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import com.elevatemc.elib.menu.buttons.BooleanButton; +import com.elevatemc.elib.util.Callback; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; + +@AllArgsConstructor +public class ConfirmMenu extends Menu { + + private String title; + @Getter private Callback response; + + public Map getButtons(Player player) { + HashMap buttons = new HashMap(); + + for(int i = 0; i < 9; ++i) { + if (i == 3) { + buttons.put(i, new BooleanButton(true, this.response)); + } else if (i == 5) { + buttons.put(i, new BooleanButton(false, this.response)); + } else { + buttons.put(i, Button.placeholder(Material.STAINED_GLASS_PANE, (byte)14, new String[0])); + } + } + + return buttons; + } + + @Override + public String getTitle(Player player) { + return this.title; + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/menu/menus/TextEditorMenu.java b/Network/eLib/src/main/java/com/elevatemc/elib/menu/menus/TextEditorMenu.java new file mode 100644 index 0000000..8f45416 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/menu/menus/TextEditorMenu.java @@ -0,0 +1,159 @@ +package com.elevatemc.elib.menu.menus; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.pagination.PaginatedMenu; +import com.elevatemc.elib.util.TaskUtil; +import com.google.common.collect.Maps; +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.*; + +// Ported from Kotlin to Java from MineXD/Joeleoli's Cubed +public abstract class TextEditorMenu extends PaginatedMenu { + + private final LinkedList lines; + + private boolean changed = false; + private boolean colors = false; + + public TextEditorMenu(LinkedList lines) { + this.lines = lines; + setUpdateAfterClick(true); + } + + public TextEditorMenu(Collection lines) { + this(new LinkedList<>(lines)); + } + + @Override + public Map getGlobalButtons(Player player) { + final Map buttons = Maps.newHashMap(); + + + + return buttons; + } + + @Override + public String getPrePaginatedTitle(Player player) { + return null; + } + + @Override + public Map getAllPagesButtons(Player player) { + return null; + } + + abstract void onSave(Player player, List list); + + @Override + public void onClose(Player player) { + if(changed) { + TaskUtil.runTaskLater(() -> { + new ConfirmMenu("Discard Changes?", confirmed -> { + if(confirmed) { + player.sendMessage(Color.translate("&eDiscarded changes.")); + changed = false; + onClose(player); + } else { + TaskUtil.runTaskLater(() -> openMenu(player), 1); + } + }).openMenu(player); + }, 1); + } + } + + private final class AddLineButton extends Button { + + @Override + public String getName(Player var1) { + return Color.translate("&b&lCreate New Line"); + } + + @Override + public List getDescription(Player var1) { + return Color.translate(Arrays.asList( + "", + "&7Create a new line by completing", + "&7the setup procedure.", + "", + "&a&lLEFT-CLICK &ato create new line" + )); + } + + @Override + public Material getMaterial(Player var1) { + return null; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + super.clicked(player, slot, clickType); + } + } + + private final class SaveButton extends Button { + + @Override + public String getName(Player var1) { + return Color.translate("&aSave Changes"); + } + + @Override + public List getDescription(Player var1) { + return Color.translate(Arrays.asList( + "", + "&7Save any changes made to the", + "&7lines of text.", + "", + "&a&lLEFT-CLICK &ato save changes" + )); + } + + @Override + public Material getMaterial(Player var1) { + return Material.WOOL; + } + + @Override + public byte getDamageValue(Player player) { + return 13; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + onSave(player, lines); + changed = false; + } + } + + private final class NoChangesButton extends Button { + + @Override + public String getName(Player var1) { + return Color.translate("&7&lNo Changes Made"); + } + + @Override + public List getDescription(Player var1) { + return Color.translate(Arrays.asList( + "", + "&7No changes have been made to the", + "&7lines of text." + )); + } + + @Override + public Material getMaterial(Player var1) { + return Material.WOOL; + } + + @Override + public byte getDamageValue(Player player) { + return 8; + } + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/menu/pagination/JumpToPageButton.java b/Network/eLib/src/main/java/com/elevatemc/elib/menu/pagination/JumpToPageButton.java new file mode 100644 index 0000000..58b14fa --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/menu/pagination/JumpToPageButton.java @@ -0,0 +1,47 @@ +package com.elevatemc.elib.menu.pagination; + +import com.elevatemc.elib.menu.Button; +import lombok.AllArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.List; + +@AllArgsConstructor +public class JumpToPageButton extends Button { + + private int page; + private PaginatedMenu menu; + + @Override + public String getName(Player player) { + return "§ePage " + this.page; + } + + @Override + public List getDescription(Player player) { + return null; + } + + @Override + public Material getMaterial(Player player) { + return Material.BOOK; + } + + @Override + public int getAmount(Player player) { + return this.page; + } + + @Override + public byte getDamageValue(Player player) { + return 0; + } + + @Override + public void clicked(Player player, int i, ClickType clickType) { + this.menu.modPage(player, this.page - this.menu.getPage()); + Button.playNeutral(player); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/menu/pagination/PageButton.java b/Network/eLib/src/main/java/com/elevatemc/elib/menu/pagination/PageButton.java new file mode 100644 index 0000000..0ac6cc0 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/menu/pagination/PageButton.java @@ -0,0 +1,64 @@ +package com.elevatemc.elib.menu.pagination; + +import com.elevatemc.elib.menu.Button; +import lombok.AllArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.ArrayList; +import java.util.List; + +@AllArgsConstructor +public class PageButton extends Button { + private int mod; + private PaginatedMenu menu; + + @Override + public void clicked(Player player,int i,ClickType clickType) { + + if (clickType == ClickType.RIGHT) { + (new ViewAllPagesMenu(this.menu)).openMenu(player); + playNeutral(player); + } else if (this.hasNext(player)) { + this.menu.modPage(player, this.mod); + Button.playNeutral(player); + } else { + Button.playFail(player); + } + + } + + private boolean hasNext(Player player) { + + final int pg = this.menu.getPage() + this.mod; + + return pg > 0 && this.menu.getPages(player) >= pg; + } + + @Override + public String getName(Player player) { + + if (!this.hasNext(player)) { + return this.mod > 0 ? "§7Last page" : "§7First page"; + } else { + return this.mod > 0 ? "§a⟶" : "§c⟵"; + } + + } + + @Override + public List getDescription(Player player) { + return new ArrayList(); + } + + @Override + public byte getDamageValue(Player player) { + return (byte)(this.hasNext(player) ? 11 : 7); + } + + @Override + public Material getMaterial(Player player) { + return Material.CARPET; + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/menu/pagination/PaginatedMenu.java b/Network/eLib/src/main/java/com/elevatemc/elib/menu/pagination/PaginatedMenu.java new file mode 100644 index 0000000..45f9b40 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/menu/pagination/PaginatedMenu.java @@ -0,0 +1,104 @@ +package com.elevatemc.elib.menu.pagination; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import lombok.Getter; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; + +public abstract class PaginatedMenu extends Menu { + + @Getter private int page = 1; + + @Override + public String getTitle(Player player) { + return getPrePaginatedTitle(player) + " - " + page + "/" + getPages(player); + } + + /** + * Changes the page number + * + * @param player player viewing the inventory + * @param mod delta to modify the page number by + */ + public final void modPage(Player player, int mod) { + this.page += mod; + + this.getButtons().clear(); + this.openMenu(player); + } + + /** + * @param player player viewing the inventory + * @return + */ + public final int getPages(Player player) { + + final int buttonAmount = this.getAllPagesButtons(player).size(); + + return buttonAmount == 0 ? 1 : (int)Math.ceil((double)buttonAmount / (double)this.getMaxItemsPerPage(player)); + } + + @Override + public final Map getButtons(Player player) { + + final int minIndex = (int) ((double) (page - 1) * getMaxItemsPerPage(player)); + final int maxIndex = (int) ((double) (page) * getMaxItemsPerPage(player)); + + final HashMap buttons = new HashMap<>(); + + buttons.put(0, new PageButton(-1, this)); + buttons.put(8, new PageButton(1, this)); + + for (Map.Entry entry : getAllPagesButtons(player).entrySet()) { + + int ind = entry.getKey(); + + if (ind >= minIndex && ind < maxIndex) { + + ind -= (int) ((double) (getMaxItemsPerPage(player)) * (page - 1)) - 9; + buttons.put(ind, entry.getValue()); + } + + } + + final Map global = getGlobalButtons(player); + + if (global != null) { + + for (Map.Entry gent : global.entrySet()) { + buttons.put(gent.getKey(), gent.getValue()); + } + + } + + + return buttons; + } + + public int getMaxItemsPerPage(Player player) { + return 18; + } + + /** + * @param player player viewing the inventory + * @return a Map of buttons that returns items which will be present on all pages + */ + public Map getGlobalButtons(Player player) { + return null; + } + + /** + * @param player player viewing the inventory + * @return title of the inventory before the page number is added + */ + public abstract String getPrePaginatedTitle(Player player); + + /** + * @param player player viewing the inventory + * @return a map of buttons that will be paginated and spread across pages + */ + public abstract Map getAllPagesButtons(Player player); +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/menu/pagination/ViewAllPagesMenu.java b/Network/eLib/src/main/java/com/elevatemc/elib/menu/pagination/ViewAllPagesMenu.java new file mode 100644 index 0000000..ffedaff --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/menu/pagination/ViewAllPagesMenu.java @@ -0,0 +1,53 @@ +package com.elevatemc.elib.menu.pagination; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import com.elevatemc.elib.menu.buttons.BackButton; +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; + +@RequiredArgsConstructor +public class ViewAllPagesMenu extends Menu { + + @NonNull @Getter PaginatedMenu menu; + + + @Override + public String getTitle(Player player) { + return "Jump to page"; + } + + @Override + public Map getButtons(Player player) { + + HashMap buttons = new HashMap<>(); + + buttons.put(0, new BackButton(menu)); + + int index = 10; + + + for (int i = 1; i <= menu.getPages(player); i++) { + buttons.put(index++, new JumpToPageButton(i,this.menu)); + + if ((index - 8) % 9 == 0) { + index += 2; + } + + } + + + return buttons; + } + + @Override + public boolean isAutoUpdate() { + return true; + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/nametag/NameTagHandler.java b/Network/eLib/src/main/java/com/elevatemc/elib/nametag/NameTagHandler.java new file mode 100644 index 0000000..0895208 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/nametag/NameTagHandler.java @@ -0,0 +1,196 @@ +package com.elevatemc.elib.nametag; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.nametag.construct.NameTagComparator; +import com.elevatemc.elib.nametag.construct.NameTagInfo; +import com.elevatemc.elib.nametag.construct.NameTagUpdate; +import com.elevatemc.elib.nametag.listener.NameTagListener; +import com.elevatemc.elib.nametag.provider.DefaultNameTagProvider; +import com.elevatemc.elib.nametag.provider.NameTagProvider; +import com.elevatemc.elib.packet.ScoreboardTeamPacketMod; +import com.elevatemc.elib.util.PlayerUtils; +import lombok.Getter; +import lombok.Setter; +import org.bukkit.entity.Player; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public final class NameTagHandler { + + @Getter private Map> teamMap = new ConcurrentHashMap<>(); + @Getter private List registeredTeams = Collections.synchronizedList(new ArrayList<>()); + @Getter private int teamCreateIndex = 1; + @Getter private List providers = new ArrayList<>(); + @Getter private boolean nametagRestrictionEnabled = false; + @Getter private String nametagRestrictBypass = ""; + @Getter @Setter private boolean async = true; + @Getter @Setter private int updateInterval = 2; + @Getter @Setter private NameTagInfo INVISIBLE; + + public NameTagHandler() { + + if (eLib.getInstance().getConfig().getBoolean("disableNametags",false)) { + return; + } + + this.nametagRestrictionEnabled = eLib.getInstance().getConfig().getBoolean("NametagPacketRestriction.Enabled", false); + this.nametagRestrictBypass = eLib.getInstance().getConfig().getString("NametagPacketRestriction.BypassPrefix").replace("&", "§"); + + eLib.getInstance().getServer().getPluginManager().registerEvents(new NameTagListener(), eLib.getInstance()); + this.registerProvider(new DefaultNameTagProvider()); + + eLib.getInstance().getServer().getScheduler().runTaskLater(eLib.getInstance(), () -> { + setINVISIBLE(eLib.getInstance().getNameTagHandler().getOrCreate("aaa", "aaa")); + }, 10); + + new NameTagThread().start(); + } + + public void registerProvider(NameTagProvider newProvider) { + this.providers.add(newProvider); + Collections.sort(this.providers,new NameTagComparator()); + } + + public void reloadPlayer(Player toRefresh) { + + final NameTagUpdate update = new NameTagUpdate(toRefresh); + + if (this.async) { + NameTagThread.getPendingUpdates().put(update, true); + } else { + this.applyUpdate(update); + } + + } + + public void reloadOthersFor(Player refreshFor) { + + for (Player toRefresh : eLib.getInstance().getServer().getOnlinePlayers()) { + + if (refreshFor != toRefresh) { + this.reloadPlayer(toRefresh, refreshFor); + } + + } + + } + + public void reloadPlayer(Player toRefresh, Player refreshFor) { + + final NameTagUpdate update = new NameTagUpdate(toRefresh, refreshFor); + + if (this.async) { + NameTagThread.getPendingUpdates().put(update, true); + } else { + this.applyUpdate(update); + } + + } + + public void applyUpdate(NameTagUpdate nametagUpdate) { + + final Player toRefreshPlayer = eLib.getInstance().getServer().getPlayerExact(nametagUpdate.getToRefresh()); + + if (toRefreshPlayer != null) { + + if (nametagUpdate.getRefreshFor() == null) { + + for (Player refreshFor : eLib.getInstance().getServer().getOnlinePlayers()) { + reloadPlayerInternal(toRefreshPlayer,refreshFor); + } + + } else { + + final Player refreshForPlayer = eLib.getInstance().getServer().getPlayerExact(nametagUpdate.getRefreshFor()); + + if (refreshForPlayer != null) { + reloadPlayerInternal(toRefreshPlayer,refreshForPlayer); + } + } + } + } + + public void reloadPlayerInternal(Player toRefresh, Player refreshFor) { + if (refreshFor.hasMetadata("eLibNametag-LoggedIn")) { + + NameTagInfo provided = null; + + for (int i = 0; provided == null; provided = (this.providers.get(i++).fetchNameTag(toRefresh,refreshFor))) { + + } + + if (provided == INVISIBLE || provided.getPrefix().equalsIgnoreCase("aaa") && provided.getSuffix().equalsIgnoreCase("aaa")) { + if (PlayerUtils.getProtocol(refreshFor) > 5) { + final Map localTeamMap = teamMap.get(refreshFor.getName()); + + if (localTeamMap == null) { + return; + } + + final NameTagInfo nameTagInfo = teamMap.get(refreshFor.getName()).get(toRefresh.getName()); + + if (nameTagInfo == null) { + return; + } + + new ScoreboardTeamPacketMod(nameTagInfo.getName(), Collections.singletonList(toRefresh.getName()), 4).sendToPlayer(refreshFor); + } + return; + } + + if (PlayerUtils.getProtocol(refreshFor) > 5 && this.nametagRestrictionEnabled) { + + final String prefix = provided.getPrefix(); + + if (prefix != null && !prefix.equalsIgnoreCase(this.nametagRestrictBypass)) { + return; + } + } + + Map teamInfoMap = new HashMap(); + + if (this.teamMap.containsKey(refreshFor.getName())) { + teamInfoMap = this.teamMap.get(refreshFor.getName()); + } + + new ScoreboardTeamPacketMod(provided.getName(), Collections.singletonList(toRefresh.getName()), 3).sendToPlayer(refreshFor); + + teamInfoMap.put(toRefresh.getName(),provided); + + this.teamMap.put(refreshFor.getName(), teamInfoMap); + } + } + + public void initiatePlayer(Player player) { + + for (NameTagInfo teamInfo : this.registeredTeams) { + teamInfo.getTeamAddPacket().sendToPlayer(player); + } + + } + + public NameTagInfo getOrCreate(String prefix,String suffix) { + + for (NameTagInfo teamInfo : this.registeredTeams) { + + if (teamInfo.getPrefix().equals(prefix) && teamInfo.getSuffix().equals(suffix)) { + return teamInfo; + } + + } + + final NameTagInfo newTeam = new NameTagInfo(String.valueOf(this.teamCreateIndex++), prefix, suffix); + + this.registeredTeams.add(newTeam); + + final ScoreboardTeamPacketMod addPacket = newTeam.getTeamAddPacket(); + + for (Player player : eLib.getInstance().getServer().getOnlinePlayers()) { + addPacket.sendToPlayer(player); + } + + return newTeam; + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/nametag/NametagThread.java b/Network/eLib/src/main/java/com/elevatemc/elib/nametag/NametagThread.java new file mode 100644 index 0000000..808b091 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/nametag/NametagThread.java @@ -0,0 +1,45 @@ +package com.elevatemc.elib.nametag; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.nametag.construct.NameTagUpdate; +import lombok.Getter; + +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +final class NameTagThread extends Thread { + + @Getter private static final Map pendingUpdates = new ConcurrentHashMap<>(); + + NameTagThread() { + super("eLib - NameTag Thread"); + setDaemon(false); + } + + public void run() { + while (true) { + + final Iterator pendingUpdatesIterator = pendingUpdates.keySet().iterator(); + + while (pendingUpdatesIterator.hasNext()) { + + final NameTagUpdate pendingUpdate = pendingUpdatesIterator.next(); + + try { + eLib.getInstance().getNameTagHandler().applyUpdate(pendingUpdate); + pendingUpdatesIterator.remove(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + try { + Thread.sleep(eLib.getInstance().getNameTagHandler().getUpdateInterval() * 50L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/nametag/construct/NameTagComparator.java b/Network/eLib/src/main/java/com/elevatemc/elib/nametag/construct/NameTagComparator.java new file mode 100644 index 0000000..0f1c6da --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/nametag/construct/NameTagComparator.java @@ -0,0 +1,14 @@ +package com.elevatemc.elib.nametag.construct; + +import com.elevatemc.elib.nametag.provider.NameTagProvider; +import com.google.common.primitives.Ints; + +import java.util.Comparator; + +public class NameTagComparator implements Comparator { + + public int compare(NameTagProvider a,NameTagProvider b) { + return Ints.compare(b.getWeight(), a.getWeight()); + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/nametag/construct/NameTagInfo.java b/Network/eLib/src/main/java/com/elevatemc/elib/nametag/construct/NameTagInfo.java new file mode 100644 index 0000000..c4789ca --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/nametag/construct/NameTagInfo.java @@ -0,0 +1,37 @@ +package com.elevatemc.elib.nametag.construct; + +import com.elevatemc.elib.packet.ScoreboardTeamPacketMod; +import lombok.Getter; + +import java.util.ArrayList; + +public final class NameTagInfo { + + @Getter private String name; + @Getter private String prefix; + @Getter private String suffix; + + @Getter private ScoreboardTeamPacketMod teamAddPacket; + + public NameTagInfo(String name,String prefix,String suffix) { + this.name = name; + this.prefix = prefix; + this.suffix = suffix; + + this.teamAddPacket = new ScoreboardTeamPacketMod(name, prefix, suffix, new ArrayList(), 0); + } + + @Override + public boolean equals(Object other) { + + if (other instanceof NameTagInfo) { + + final NameTagInfo otherNametag = (NameTagInfo) other; + + return (this.name.equals(otherNametag.name) && this.prefix.equals(otherNametag.prefix) && this.suffix.equals(otherNametag.suffix)); + } + + return false; + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/nametag/construct/NameTagUpdate.java b/Network/eLib/src/main/java/com/elevatemc/elib/nametag/construct/NameTagUpdate.java new file mode 100644 index 0000000..8c82873 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/nametag/construct/NameTagUpdate.java @@ -0,0 +1,35 @@ +package com.elevatemc.elib.nametag.construct; + +import lombok.Getter; +import org.bukkit.entity.Player; + +/** + * A nametag update that is queued to happen. + * Commonly the update is queued from a sync. thread. + */ +public final class NameTagUpdate { + + @Getter private String toRefresh; + @Getter private String refreshFor; + + /** + * Refreshes one player for all players online. + * + * @param toRefresh The player to refresh. + */ + public NameTagUpdate(Player toRefresh) { + this.toRefresh = toRefresh.getName(); + } + + /** + * Refreshes one player for another player only. + * + * @param toRefresh The player to refresh. + * @param refreshFor The player to refresh toRefresh for. + */ + public NameTagUpdate(Player toRefresh,Player refreshFor) { + this.toRefresh = toRefresh.getName(); + this.refreshFor = refreshFor.getName(); + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/nametag/listener/NameTagListener.java b/Network/eLib/src/main/java/com/elevatemc/elib/nametag/listener/NameTagListener.java new file mode 100644 index 0000000..58cb582 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/nametag/listener/NameTagListener.java @@ -0,0 +1,27 @@ +package com.elevatemc.elib.nametag.listener; + +import com.elevatemc.elib.eLib; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.metadata.FixedMetadataValue; + +public final class NameTagListener implements Listener { + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + event.getPlayer().setMetadata("eLibNametag-LoggedIn", new FixedMetadataValue(eLib.getInstance(), true)); + + eLib.getInstance().getNameTagHandler().initiatePlayer(event.getPlayer()); + eLib.getInstance().getNameTagHandler().reloadPlayer(event.getPlayer()); + eLib.getInstance().getNameTagHandler().reloadOthersFor(event.getPlayer()); + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + event.getPlayer().removeMetadata("eLibNametag-LoggedIn", eLib.getInstance()); + eLib.getInstance().getNameTagHandler().getTeamMap().remove(event.getPlayer().getName()); + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/nametag/provider/DefaultNameTagProvider.java b/Network/eLib/src/main/java/com/elevatemc/elib/nametag/provider/DefaultNameTagProvider.java new file mode 100644 index 0000000..bbf5fb9 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/nametag/provider/DefaultNameTagProvider.java @@ -0,0 +1,17 @@ +package com.elevatemc.elib.nametag.provider; + +import com.elevatemc.elib.nametag.construct.NameTagInfo; +import org.bukkit.entity.Player; + +public class DefaultNameTagProvider extends NameTagProvider { + + public DefaultNameTagProvider() { + super("Default Provider", 0); + } + + @Override + public NameTagInfo fetchNameTag(Player toRefresh, Player refreshFor) { + return (createNameTag(toRefresh.getDisplayName().replace(toRefresh.getName(),""), "")); + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/nametag/provider/NameTagProvider.java b/Network/eLib/src/main/java/com/elevatemc/elib/nametag/provider/NameTagProvider.java new file mode 100644 index 0000000..1a3e61f --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/nametag/provider/NameTagProvider.java @@ -0,0 +1,21 @@ +package com.elevatemc.elib.nametag.provider; + +import com.elevatemc.elib.eLib; +import lombok.AllArgsConstructor; +import lombok.Getter; +import com.elevatemc.elib.nametag.construct.NameTagInfo; +import org.bukkit.entity.Player; + +@AllArgsConstructor +public abstract class NameTagProvider { + + @Getter private String name; + @Getter private int weight; + + public abstract NameTagInfo fetchNameTag(Player toRefresh, Player refreshFor); + + public final NameTagInfo createNameTag(String prefix,String suffix) { + return eLib.getInstance().getNameTagHandler().getOrCreate(prefix, suffix); + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/npc/NPC.java b/Network/eLib/src/main/java/com/elevatemc/elib/npc/NPC.java new file mode 100644 index 0000000..3b9e2da --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/npc/NPC.java @@ -0,0 +1,249 @@ +package com.elevatemc.elib.npc; + +import com.elevatemc.elib.fake.impl.player.FakePlayerEntity; +import com.elevatemc.elib.skin.MojangSkin; +import com.elevatemc.elib.hologram.Hologram; +import com.elevatemc.elib.hologram.HologramLine; +import com.elevatemc.elib.npc.entry.NPCEntry; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import net.minecraft.server.v1_8_R3.*; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_8_R3.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +public class NPC extends FakePlayerEntity { + + private static final double HOLOGRAM_OFFSET = 0.86D; + + private MojangSkin mojangSkin; + + private final ItemStack[] inventory; + private final Hologram hologram; + + @Getter + private boolean sitting; + + private final PacketPlayOutAttachEntity detachPacket; + private PacketPlayOutSpawnEntity spawnArrow; + private PacketPlayOutEntityDestroy destroyArrow; + private int previousArrowId = -1; + + @Setter(AccessLevel.PROTECTED) + private PacketPlayOutAttachEntity attachPacket; + + @Setter(AccessLevel.PROTECTED) + private EntityArrow entityArrow; + + public NPC(NPCEntry entry) { + super(entry.getId(), entry.getDisplayName(), entry.getLocation()); + this.inventory = entry.getInventory(); + this.hologram = new Hologram(entry.getLocation().clone().add(0, HOLOGRAM_OFFSET, 0)); + this.setMojangSkin(entry.getMojangSkin()); + entry.getHologramLines().forEach(this::addLine); + this.sitting = entry.isSitting(); + + if (this.sitting) { + this.entityArrow = new EntityArrow(this.getEntityPlayer().getWorld()); + this.entityArrow.setLocation(this.getLocation().getX(), this.getLocation().getY(), this.getLocation().getZ(), 0, 90); + this.spawnArrow = new PacketPlayOutSpawnEntity(this.entityArrow, 60); + this.previousArrowId = this.entityArrow.getId(); + this.destroyArrow = new PacketPlayOutEntityDestroy(this.entityArrow.getId()); + } + + this.detachPacket = new PacketPlayOutAttachEntity(); + this.detachPacket.a = 0; + this.detachPacket.b = this.getEntityId(); + this.detachPacket.c = -1; + } + + @Override + public boolean show(Player player) { + super.show(player); + + if (this.isSitting()) { + this.updateRide(player); + } + + for (int i = 0; i < this.inventory.length; i++) { + ItemStack itemStack = this.inventory[i]; + if (itemStack == null) { + continue; + } + + int id = this.getEntityPlayer().getId(); + net.minecraft.server.v1_8_R3.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack); + PacketPlayOutEntityEquipment equipmentPacket = new PacketPlayOutEntityEquipment(id, i, nmsItemStack); + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(equipmentPacket); + } + + if (!this.hologram.isEmpty()) { + this.hologram.setup(player); + } + + return true; + } + + @Override + public boolean hide(Player player) { + if (this.isSitting()) { + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(this.destroyArrow); + } + this.hologram.hide(player); + return super.hide(player); + } + + @Override + public void teleport(Location location) { + super.teleport(location); + this.hologram.updateLocation(location.clone().add(0, HOLOGRAM_OFFSET, 0)); + + if (this.sitting) { + this.entityArrow.setLocation(getLocation().getX(), getLocation().getY(), getLocation().getZ(), 0, 90); + this.spawnArrow.b = MathHelper.floor(this.entityArrow.locX * 32.0D); + this.spawnArrow.c = MathHelper.floor(this.entityArrow.locY * 32.0D); + this.spawnArrow.d = MathHelper.floor(this.entityArrow.locZ * 32.0D); + PacketPlayOutEntityTeleport packet = new PacketPlayOutEntityTeleport(this.entityArrow); + for (UUID uuid : this.getCurrentlyViewing()) { + Player player = Bukkit.getPlayer(uuid); + + if (player == null) { + continue; + } + + if (!player.getWorld().getName().equals(location.getWorld().getName())) { + continue; + } + + this.updateRide(player); + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } + } + } + + public void setSitting(boolean sitting) { + this.sitting = sitting; + + if (!sitting) { + this.entityArrow = null; + this.showToAll(); + return; + } + + if (this.entityArrow == null) { + this.entityArrow = new EntityArrow(this.getEntityPlayer().getWorld()); + this.entityArrow.setLocation(this.getLocation().getX(), this.getLocation().getY(), this.getLocation().getZ(), 0, 90); + this.spawnArrow = new PacketPlayOutSpawnEntity(this.entityArrow, 60); + this.previousArrowId = this.entityArrow.getId(); + this.destroyArrow = new PacketPlayOutEntityDestroy(this.entityArrow.getId()); + this.attachPacket = new PacketPlayOutAttachEntity(); + this.attachPacket.a = 0; + this.attachPacket.b = this.getEntityId(); + this.attachPacket.c = this.entityArrow.getId(); + } + + this.showToAll(); + } + + public void setEquipment(int slot, ItemStack itemStack) { + this.inventory[slot] = itemStack; + this.showToAll(); + } + + public void addLine(String line) { + this.hologram.addLine(line); + for (UUID uuid : this.getCurrentlyViewing()) { + Player player = Bukkit.getPlayer(uuid); + if (player == null) { + continue; + } + this.hologram.setup(player); + } + } + + public void updateRideForViewers() { + for (UUID uuid : this.getCurrentlyViewing()) { + Player player = Bukkit.getPlayer(uuid); + if (player == null) { + continue; + } + this.updateRide(player); + } + } + + private void updateRide(Player player) { + if (!this.sitting) { + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(this.detachPacket); + return; + } + + boolean removeLast = false; + + if (this.entityArrow == null) { + this.entityArrow = new EntityArrow(this.getEntityPlayer().getWorld()); + this.entityArrow.setLocation(getLocation().getX(), getLocation().getY(), getLocation().getZ(), 0, 90); + this.spawnArrow = new PacketPlayOutSpawnEntity(this.entityArrow, 60); + this.destroyArrow = new PacketPlayOutEntityDestroy(this.entityArrow.getId()); + removeLast = (previousArrowId != -1); + } + + if (this.attachPacket == null) { + this.attachPacket = new PacketPlayOutAttachEntity(); + this.attachPacket.a = 0; + this.attachPacket.b = this.getEntityId(); + this.attachPacket.c = this.entityArrow.getId(); + } + + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(this.spawnArrow); + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(this.attachPacket); + + if (removeLast) { + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutEntityDestroy(this.previousArrowId)); + } + + this.previousArrowId = this.entityArrow.getId(); + } + + public void addLine(int index, String line) { + this.hologram.addLine(index, line); + for (UUID uuid : this.getCurrentlyViewing()) { + Player player = Bukkit.getPlayer(uuid); + if (player == null) { + continue; + } + this.hologram.setup(player); + } + } + + public void removeLine(int index) { + this.hologram.removeLine(index); + for (UUID uuid : this.getCurrentlyViewing()) { + Player player = Bukkit.getPlayer(uuid); + if (player == null) { + continue; + } + this.hologram.update(player); + } + } + + public void setMojangSkin(MojangSkin skin) { + this.mojangSkin = skin; + if (skin != null) { + this.updateSkin(skin.getValue(), skin.getSignature()); + } + } + + public NPCEntry toEntry() { + List hologramLines = this.hologram.getLines().stream().map(HologramLine::getLine).collect(Collectors.toList()); + return new NPCEntry(this.getId(), this.getDisplayName(), this.getCommand(), this.getLocation(), this.inventory, this.mojangSkin, hologramLines, sitting); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/npc/NPCManager.java b/Network/eLib/src/main/java/com/elevatemc/elib/npc/NPCManager.java new file mode 100644 index 0000000..85ce12a --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/npc/NPCManager.java @@ -0,0 +1,85 @@ +package com.elevatemc.elib.npc; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.npc.command.NPCCommand; +import com.elevatemc.elib.npc.command.NPCContextResolver; +import com.elevatemc.elib.npc.entry.NPCEntry; +import com.elevatemc.elib.npc.entry.NPCEntryMap; +import com.elevatemc.elib.util.TaskUtil; +import com.elevatemc.elib.util.json.GsonProvider; +import lombok.Getter; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class NPCManager { + + @Getter + private static NPCManager instance; + + public NPCManager() { + instance = this; + TaskUtil.scheduleAtFixedRateOnPool(new NPCUpdateArrowTask(), 45, 45, TimeUnit.SECONDS); + registerAllNPCs(); + eLib.getInstance().getCommandHandler().registerParameterType(NPC.class, new NPCContextResolver()); + eLib.getInstance().getCommandHandler().registerClass(NPCCommand.class); + + } + + public void registerAllNPCs() { + eLib.getInstance().getLogger().info("Registering all NPCs..."); + try { + String json = new String(Files.readAllBytes(this.getNPCFilePath())); + NPCEntryMap npcEntryMap = GsonProvider.fromJson(json, NPCEntryMap.class); + npcEntryMap.getAllEntries().forEach(this::registerNPC); + eLib.getInstance().getLogger().info(String.format("Registered %d NPCs", npcEntryMap.getAllEntries().size())); + } catch (NoSuchFileException e) { + eLib.getInstance().getLogger().warning("Couldn't find npcs.json"); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void saveAllNPCs() { + eLib.getInstance().getLogger().info("Saving all NPCs..."); + try { + List npcList = new ArrayList<>(); + eLib.getInstance().getFakeEntityHandler().getEntities().iterator().forEachRemaining(entity -> { + if (entity instanceof NPC) { + npcList.add(((NPC) entity).toEntry()); + } + }); + + NPCEntryMap entryMap = new NPCEntryMap(); + entryMap.putAllEntries(npcList); + Files.write(this.getNPCFilePath(), GsonProvider.toJsonPretty(entryMap).getBytes()); + eLib.getInstance().getLogger().info(String.format("Saved %d NPCs.", entryMap.getAllEntries().size())); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void registerNPC(NPCEntry npcEntry) { + eLib.getInstance().getLogger().info(String.format("Registering NPC %d @%s", npcEntry.getId(), npcEntry.getLocation())); + NPC npc = new NPC(npcEntry); + npc.setCommand(npcEntry.getCommand()); + eLib.getInstance().getFakeEntityHandler().registerFakeEntity(npc); + npc.showToAll(); + for (Player target : Bukkit.getOnlinePlayers()) { + npc.addToTeam(target); + } + } + + private Path getNPCFilePath() { + return new File(eLib.getInstance().getDataFolder(), "npcs.json").toPath(); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/npc/NPCUpdateArrowTask.java b/Network/eLib/src/main/java/com/elevatemc/elib/npc/NPCUpdateArrowTask.java new file mode 100644 index 0000000..6930c38 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/npc/NPCUpdateArrowTask.java @@ -0,0 +1,25 @@ +package com.elevatemc.elib.npc; + + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.fake.FakeEntity; + +public class NPCUpdateArrowTask implements Runnable { + @Override + public void run() { + for (FakeEntity entity : eLib.getInstance().getFakeEntityHandler().getEntities()) { + if (!(entity instanceof NPC)) { + continue; + } + NPC npc = (NPC) entity; + + if (!npc.isSitting()) { + continue; + } + + npc.setEntityArrow(null); + npc.setAttachPacket(null); + npc.updateRideForViewers(); + } + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/npc/command/NPCCommand.java b/Network/eLib/src/main/java/com/elevatemc/elib/npc/command/NPCCommand.java new file mode 100644 index 0000000..24deeb6 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/npc/command/NPCCommand.java @@ -0,0 +1,142 @@ +package com.elevatemc.elib.npc.command; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import com.elevatemc.elib.npc.NPC; +import com.elevatemc.elib.npc.NPCManager; +import com.elevatemc.elib.skin.MojangSkin; +import com.elevatemc.elib.util.UUIDFetcher; +import com.elevatemc.elib.npc.entry.NPCEntry; +import com.elevatemc.elib.util.message.MessageBuilder; +import com.elevatemc.elib.util.message.MessageColor; +import com.elevatemc.elib.util.message.MessageTranslator; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + + +import java.util.ArrayList; +import java.util.UUID; + +public class NPCCommand { + + + @Command(names = {"npc create"}, permission = "core.command.npc", async = true, description = "Create an npc") + public static void npcCreateCommand(Player player, + @Parameter(name = "id") int id, + @Parameter(name = "Display Name", wildcard = true) String displayName) { + if (displayName.length() > 16) { + player.sendMessage(MessageColor.RED + "That name is too long!"); + return; + } + + if (eLib.getInstance().getFakeEntityHandler().getEntityById(id) != null) { + player.sendMessage(MessageColor.RED + "There is already a fake entity with that id registered"); + return; + } + + NPCEntry npcEntry = new NPCEntry(id, MessageTranslator.translate(displayName), + null, player.getLocation(), new ItemStack[5], null, new ArrayList<>(), false); + NPCManager.getInstance().registerNPC(npcEntry); + + String message = MessageBuilder.standard("You have successfully created an npc named {} with the id {}.") + .element(displayName) + .element(id) + .build(); + player.sendMessage(message); + } + + + @Command(names = {"npc remove"}, permission = "core.command.npc", async = true, description = "Remove / Delete an npc") + public static void npcRemoveCommand(Player player, @Parameter(name = "id") NPC npc) { + eLib.getInstance().getFakeEntityHandler().removeFakeEntity(npc.getEntityId()); + npc.hideFromAll(); + player.sendMessage(MessageBuilder.construct("You have successfully removed the npc with the id {}.", npc.getId())); + } + + + @Command(names = {"npc sethelditem"}, permission = "core.command.npc", async = true, description = "Set the held item of the npc") + public static void npcSetHeldItemCommand(Player player, @Parameter(name = "id") NPC npc) { + npc.setEquipment(0, player.getItemInHand()); + player.sendMessage(MessageBuilder.construct("NPC {} is now holding a {}.", npc.getId(), player.getItemInHand())); + } + + + @Command(names = {"npc sethelmet"}, permission = "core.command.npc", async = true, description = "Set the helmet item of the npc") + public static void setHelmetCommand(Player player, @Parameter(name = "id") NPC npc) { + npc.setEquipment(4, player.getItemInHand()); + player.sendMessage(MessageBuilder.construct("NPC {} is now wearing a {}.", npc.getId(), player.getItemInHand())); + } + + + @Command(names = {"npc setchestplate"}, permission = "core.command.npc", async = true, description = "Set the chestplate item of the npc") + public static void setChestplateCommand(Player player, @Parameter(name = "id") NPC npc) { + npc.setEquipment(3, player.getItemInHand()); + player.sendMessage(MessageBuilder.construct("NPC {} is now wearing a {}.", npc.getId(), player.getItemInHand())); + } + + + @Command(names = {"npc setleggings"}, permission = "core.command.npc", async = true, description = "Set the leggings item of the npc") + public static void setLegginsCommand(Player player, @Parameter(name = "id") NPC npc) { + npc.setEquipment(2, player.getItemInHand()); + player.sendMessage(MessageBuilder.construct("NPC {} is now wearing a {}.", npc.getId(), player.getItemInHand())); + } + + + @Command(names = {"npc setboots"}, permission = "core.command.npc", async = true, description = "Set the boots item of the npc") + public static void setBootsCommand(Player player, @Parameter(name = "id") NPC npc) { + npc.setEquipment(1, player.getItemInHand()); + player.sendMessage(MessageBuilder.construct("NPC {} is now wearing a {}.", npc.getId(), player.getItemInHand())); + } + + + @Command(names = {"npc setcommand"}, permission = "core.command.npc", async = true, description = "Setup the command executed when interacted with the npc") + public static void npcSetCommand(Player player, @Parameter(name = "id") NPC npc, @Parameter(name = "Command", wildcard = true) String command) { + npc.setCommand(command); + player.sendMessage(MessageBuilder.construct("You have updated the command of npc {}.", npc.getId())); + } + + + @Command(names = {"npc setskin"}, permission = "core.command.npc", async = true, description = "Update npcs skin") + public static void npcSetSkinCommand(Player player, @Parameter(name = "id") NPC npc, @Parameter(name = "Skin Name") String skinName) { + UUID uuid = UUIDFetcher.getUUID(skinName); + player.sendMessage(MessageBuilder.construct("Attempting to fetch skin with name {}.", skinName)); + MojangSkin skin = eLib.getInstance().getMojangSkinHandler().getMojangSkin(uuid); + + if (skin == null) { + player.sendMessage(MessageBuilder.construct("Failed to find a skin with the name {}.", skinName)); + return; + } + + npc.setMojangSkin(skin); + npc.showToAll(); + player.sendMessage(MessageBuilder.construct("Successfully set npc {} skin to {}.", npc.getId(), skinName)); + } + + @Command(names = "npc teleport", permission = "core.command.npc", async = true, description = "Teleport an npc to you") + public static void npcTeleportCommand(Player player, @Parameter(name = "id") NPC npc) { + npc.teleport(player.getLocation()); + player.sendMessage(MessageBuilder.construct("You have teleported npc {} to your location.", npc.getId())); + } + + + @Command(names = "npc addline", permission = "core.command.npc", async = true, description = "Add a hologram line to an npc") + public static void npcAddLineCommand(Player player, @Parameter(name = "id") NPC npc, @Parameter(name = "Line", wildcard = true) String line) { + npc.addLine(line); + player.sendMessage(MessageBuilder.construct("You have added a hologram line to npc {}.", npc.getId())); + } + + + @Command(names = "npc removeline", permission = "core.command.npc", async = true, description = "Remove a hologram line from an npc") + public static void npcRemoveLineCommand(Player player, @Parameter(name = "id") NPC npc, @Parameter(name = "index") int index) { + npc.removeLine(index); + player.sendMessage(MessageBuilder.construct("You have removed a hologram line from npc {}.", npc.getId())); + } + + + @Command(names = "npc sit", permission = "core.command.npc", async = true, description = "Make an npc sitdown or standup") + public static void sitCommand(Player player, @Parameter(name = "id") NPC npc) { + npc.setSitting(!npc.isSitting()); + player.sendMessage(MessageBuilder.construct("Successfully {} this npc.", (npc.isSitting() ? "sat" : "stood"))); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/npc/command/NPCContextResolver.java b/Network/eLib/src/main/java/com/elevatemc/elib/npc/command/NPCContextResolver.java new file mode 100644 index 0000000..6a9c2ce --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/npc/command/NPCContextResolver.java @@ -0,0 +1,35 @@ +package com.elevatemc.elib.npc.command; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.command.param.ParameterType; +import com.elevatemc.elib.fake.FakeEntity; +import com.elevatemc.elib.npc.NPC; +import com.elevatemc.elib.util.message.MessageBuilder; +import org.bukkit.command.CommandSender; + +public class NPCContextResolver implements ParameterType { + + + + + @Override + public NPC transform(CommandSender sender, String source) { + int id; + + try { + id = Integer.parseInt(source); + } catch (NumberFormatException e) { + sender.sendMessage(MessageBuilder.constructError("That is not a valid id")); + return null; + } + + FakeEntity fakeEntity = eLib.getInstance().getFakeEntityHandler().getEntityById(id); + + if (!(fakeEntity instanceof NPC)) { + sender.sendMessage(MessageBuilder.constructError("That is not an npc")); + return null; + } + + return (NPC) fakeEntity; + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/npc/entry/NPCEntry.java b/Network/eLib/src/main/java/com/elevatemc/elib/npc/entry/NPCEntry.java new file mode 100644 index 0000000..0c00a0c --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/npc/entry/NPCEntry.java @@ -0,0 +1,25 @@ +package com.elevatemc.elib.npc.entry; + +import com.elevatemc.elib.skin.MojangSkin; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.bukkit.Location; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class NPCEntry { + + private int id; + private String displayName, command; + private Location location; + private ItemStack[] inventory; + private MojangSkin mojangSkin; + private List hologramLines; + private boolean sitting; + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/npc/entry/NPCEntryAdapter.java b/Network/eLib/src/main/java/com/elevatemc/elib/npc/entry/NPCEntryAdapter.java new file mode 100644 index 0000000..86c7438 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/npc/entry/NPCEntryAdapter.java @@ -0,0 +1,79 @@ +package com.elevatemc.elib.npc.entry; + +import com.elevatemc.elib.skin.MojangSkin; +import com.google.gson.*; +import org.bukkit.Location; +import org.bukkit.inventory.ItemStack; +import com.elevatemc.elib.util.json.GsonProvider; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +/** + * @author ImHacking + * @date 6/7/2022 + */ +public class NPCEntryAdapter implements JsonSerializer, JsonDeserializer { + + @Override + public NPCEntry deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + JsonObject object = jsonElement.getAsJsonObject(); + int id = object.get("id").getAsInt(); + String command = null; + if(object.has("command")) { + command = object.get("command").getAsString(); + } + String displayName = object.get("displayName").getAsString(); + Location location = GsonProvider.fromJson(object.getAsJsonObject("location").toString(), Location.class); + ItemStack[] inventory = new ItemStack[5]; + if (object.has("inventory")) { + inventory = GsonProvider.fromJson(object.getAsJsonArray("inventory").toString(), ItemStack[].class); + } + MojangSkin skin = null; + if (object.has("mojangSkin")) { + skin = GsonProvider.fromJson(object.getAsJsonObject("mojangSkin").toString(), MojangSkin.class); + } + + + List lines = new ArrayList<>(); + for (JsonElement element : object.getAsJsonArray("lines")) { + lines.add(element.getAsString()); + } + boolean sitting = object.get("sitting").getAsBoolean(); + return new NPCEntry(id, displayName, command, location, inventory, skin, lines, sitting); + } + + @Override + public JsonElement serialize(NPCEntry entry, Type type, JsonSerializationContext jsonSerializationContext) { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("id", entry.getId()); + jsonObject.addProperty("command", entry.getCommand()); + jsonObject.addProperty("displayName", entry.getDisplayName()); + jsonObject.add("location", GsonProvider.fromJson(GsonProvider.toJson(entry.getLocation()), JsonObject.class)); + if (entry.getInventory() != null) { + JsonArray inventory = new JsonArray(); + + for (ItemStack itemStack : entry.getInventory()) { + if (itemStack == null) { + inventory.add(JsonNull.INSTANCE); + } else { + inventory.add(GsonProvider.fromJson(GsonProvider.toJson(itemStack), JsonObject.class)); + } + + } + jsonObject.add("inventory", inventory); + } + if (entry.getMojangSkin() != null) { + jsonObject.add("mojangSkin", GsonProvider.fromJson(GsonProvider.toJson(entry.getMojangSkin()), JsonObject.class)); + } + JsonArray lines = new JsonArray(); + for (String hologramLine : entry.getHologramLines()) { + lines.add(new JsonPrimitive(hologramLine)); + } + jsonObject.add("lines", lines); + jsonObject.addProperty("sitting", entry.isSitting()); + return jsonObject; + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/npc/entry/NPCEntryMap.java b/Network/eLib/src/main/java/com/elevatemc/elib/npc/entry/NPCEntryMap.java new file mode 100644 index 0000000..9698e3c --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/npc/entry/NPCEntryMap.java @@ -0,0 +1,20 @@ +package com.elevatemc.elib.npc.entry; + +import java.util.*; + +public class NPCEntryMap { + + private final Map entryMap = new HashMap<>(); + + public Collection getAllEntries() { + return Collections.unmodifiableCollection(entryMap.values()); + } + + public void putAllEntries(List entries) { + entries.forEach(this::putEntry); + } + + private void putEntry(NPCEntry entry) { + this.entryMap.put(entry.getId(), entry); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/packet/PlayerInfoPacketMod.java b/Network/eLib/src/main/java/com/elevatemc/elib/packet/PlayerInfoPacketMod.java new file mode 100644 index 0000000..2d739c8 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/packet/PlayerInfoPacketMod.java @@ -0,0 +1,40 @@ +package com.elevatemc.elib.packet; + +import net.minecraft.server.v1_8_R3.PacketPlayOutPlayerInfo; +import com.mojang.authlib.GameProfile; +import net.minecraft.server.v1_8_R3.WorldSettings; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_8_R3.util.CraftChatMessage; +import org.bukkit.entity.Player; + +import java.lang.reflect.Field; + +public class PlayerInfoPacketMod { + + private PacketPlayOutPlayerInfo packet = new PacketPlayOutPlayerInfo(); + + public PlayerInfoPacketMod(String name,int ping,GameProfile profile,int action) { + packet.setA(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.values()[action]); + packet.getB().add(new PacketPlayOutPlayerInfo.PlayerInfoData(profile, ping, WorldSettings.EnumGamemode.SURVIVAL, CraftChatMessage.fromString(name)[0])); + } + + public void setField(String field, Object value) { + + try { + + final Field fieldObject = this.packet.getClass().getDeclaredField(field); + + fieldObject.setAccessible(true); + fieldObject.set(this.packet, value); + } catch (Exception ex) { + ex.printStackTrace(); + } + + } + + public void sendToPlayer(Player player) { + ((CraftPlayer)player).getHandle().playerConnection.sendPacket(this.packet); + } + + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/packet/ScoreboardTeamPacketMod.java b/Network/eLib/src/main/java/com/elevatemc/elib/packet/ScoreboardTeamPacketMod.java new file mode 100644 index 0000000..9a549cc --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/packet/ScoreboardTeamPacketMod.java @@ -0,0 +1,61 @@ +package com.elevatemc.elib.packet; + +import net.minecraft.server.v1_8_R3.PacketPlayOutScoreboardTeam; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import org.bukkit.entity.Player; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; + +public final class ScoreboardTeamPacketMod { + + private PacketPlayOutScoreboardTeam packet; + + public ScoreboardTeamPacketMod(String name,String prefix,String suffix,Collection players,int mode) { + + this.packet = new PacketPlayOutScoreboardTeam(); + packet.a = (name); + packet.h = (mode); //this.setField(fieldParamInt,paramInt); + if (mode == 0 || mode == 2) { + packet.b = (name); //this.setField(fieldDisplayName,name); + packet.c = (prefix); //this.setField(fieldPrefix,prefix); + packet.d = (suffix); //this.setField(fieldSuffix,suffix); + packet.i = (3); //this.setField(fieldPackOption,3); + + } + if (mode == 0) { + this.addAll(players); + } + + } + + public ScoreboardTeamPacketMod(String name, Collection players, int mode) { + + this.packet = new PacketPlayOutScoreboardTeam(); + + if (players == null) { + players = new ArrayList<>(); + } + packet.a = (name); //this.setField(fieldTeamName, name); + packet.h = (mode); //this.setField(fieldParamInt, paramInt); + + + this.addAll(players); + } + + public void sendToPlayer(Player bukkitPlayer) { + ((CraftPlayer) bukkitPlayer).getHandle().playerConnection.sendPacket(this.packet); + } + + private void addAll(Collection col) { + + try { + packet.g.addAll(col); + } catch (Exception e) { + e.printStackTrace(); + } + + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/pidgin/PidginHandler.java b/Network/eLib/src/main/java/com/elevatemc/elib/pidgin/PidginHandler.java new file mode 100644 index 0000000..54e3ed7 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/pidgin/PidginHandler.java @@ -0,0 +1,139 @@ +package com.elevatemc.elib.pidgin; + +import com.elevatemc.elib.pidgin.packet.Packet; +import com.elevatemc.elib.pidgin.packet.handler.IncomingPacketHandler; +import com.elevatemc.elib.pidgin.packet.handler.PacketExceptionHandler; +import com.elevatemc.elib.pidgin.packet.listener.PacketListener; +import com.elevatemc.elib.pidgin.packet.listener.PacketListenerData; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import lombok.Getter; + +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ForkJoinPool; + +//Credits: joeleoli +public class PidginHandler { + + public static JsonParser PARSER = new JsonParser(); + + @Getter private final String channel; + + @Getter private JedisPool pool; + @Getter private PidginPubSub pubSub; + + @Getter private List listeners; + @Getter private Map idToType; + @Getter private Map typeToId; + + public PidginHandler(String channel,JedisPool jedisPool) { + this.channel = channel; + this.listeners = new ArrayList<>(); + this.idToType = new HashMap<>(); + this.typeToId = new HashMap<>(); + this.pool = jedisPool; + this.pubSub = new PidginPubSub(channel); + + ForkJoinPool.commonPool().execute(() -> { + + try (Jedis jedis = this.pool.getResource()) { + jedis.subscribe(this.pubSub,channel); + } + + }); + } + + public void sendPacket(Packet packet) { + this.sendPacket(packet,null); + } + + public void sendPacket(Packet packet, PacketExceptionHandler exceptionHandler) { + + try { + + final JsonObject object = packet.serialize(); + + if (object == null) { + throw new IllegalStateException("Packet cannot generate null serialized data"); + } + + try (Jedis jedis = this.pool.getResource()) { + jedis.publish(this.channel, packet.id() + ";" + object.toString()); + } + + } catch (Exception e) { + + if (exceptionHandler != null) { + exceptionHandler.onException(e); + } + + } + } + + public Packet buildPacket(int id) { + + if (!this.idToType.containsKey(id)) { + throw new IllegalStateException("A packet with that ID does not exist (ID: " + id + ")"); + } + + try { + return (Packet) this.idToType.get(id).newInstance(); + } catch (Exception e) { + e.printStackTrace(); + } + + throw new IllegalStateException("Could not create new instance of packet type"); + } + + public void registerPacket(Class clazz) { + try { + + final int id = (int) clazz.getDeclaredMethod("id").invoke(clazz.newInstance(), null); + + if (this.idToType.containsKey(id) || this.typeToId.containsKey(clazz)) { + throw new IllegalStateException("A packet with that ID has already been registered"); + } + + this.idToType.put(id, clazz); + this.typeToId.put(clazz, id); + } catch (Exception e) { + e.printStackTrace(); + } + + } + + public void registerListener(PacketListener packetListener) { + + methodLoop: + for (Method method : packetListener.getClass().getDeclaredMethods()) { + + if (method.getDeclaredAnnotation(IncomingPacketHandler.class) == null) { + continue; + } + + Class packetClass = null; + + if (method.getParameters().length > 0) { + + if (!Packet.class.isAssignableFrom(method.getParameters()[0].getType())) { + continue; + } + + packetClass = method.getParameters()[0].getType(); + } + + if (packetClass != null) { + this.listeners.add(new PacketListenerData(packetListener,method,packetClass)); + } + + } + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/pidgin/PidginPubSub.java b/Network/eLib/src/main/java/com/elevatemc/elib/pidgin/PidginPubSub.java new file mode 100644 index 0000000..a0c9c53 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/pidgin/PidginPubSub.java @@ -0,0 +1,46 @@ +package com.elevatemc.elib.pidgin; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.pidgin.packet.Packet; +import com.elevatemc.elib.pidgin.packet.listener.PacketListenerData; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.bukkit.Bukkit; +import redis.clients.jedis.JedisPubSub; + +@AllArgsConstructor +public class PidginPubSub extends JedisPubSub { + + @Getter private String channel; + + @Override + public void onMessage(String channel,String message) { + + if (!channel.equalsIgnoreCase(this.channel)) { + return; + } + + try { + final String[] args = message.split(";"); + final Integer id = Integer.valueOf(args[0]); + final Packet packet = eLib.getInstance().getPidginHandler().buildPacket(id); + if (packet == null) { + return; + } + + packet.deserialize(PidginHandler.PARSER.parse(args[1]).getAsJsonObject()); + + for (PacketListenerData listener : eLib.getInstance().getPidginHandler().getListeners()) { + + if (!listener.matches(packet)) { + continue; + } + + listener.getMethod().invoke(listener.getInstance(),packet); + } + } catch (Exception ex) { + ex.printStackTrace(); + } + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/pidgin/PidginUser.java b/Network/eLib/src/main/java/com/elevatemc/elib/pidgin/PidginUser.java new file mode 100644 index 0000000..7613658 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/pidgin/PidginUser.java @@ -0,0 +1,17 @@ +package com.elevatemc.elib.pidgin; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +public class PidginUser { + + @Getter private String host; + @Getter private Integer port; + @Getter private String password; + + + public boolean requiresAuth() { + return this.password != null && !this.password.equals(""); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/pidgin/packet/Packet.java b/Network/eLib/src/main/java/com/elevatemc/elib/pidgin/packet/Packet.java new file mode 100644 index 0000000..21b25be --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/pidgin/packet/Packet.java @@ -0,0 +1,13 @@ +package com.elevatemc.elib.pidgin.packet; + +import com.google.gson.JsonObject; + +public interface Packet { + + int id(); + + JsonObject serialize(); + + void deserialize(JsonObject object); + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/pidgin/packet/handler/IncomingPacketHandler.java b/Network/eLib/src/main/java/com/elevatemc/elib/pidgin/packet/handler/IncomingPacketHandler.java new file mode 100644 index 0000000..3b9c791 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/pidgin/packet/handler/IncomingPacketHandler.java @@ -0,0 +1,17 @@ +package com.elevatemc.elib.pidgin.packet.handler; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Identifies a method and packet type assigned to the method. + * The only valid parameter types for a method that is annotated + * by this annotation are String and Packet. + */ +@Target({ ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface IncomingPacketHandler { + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/pidgin/packet/handler/PacketExceptionHandler.java b/Network/eLib/src/main/java/com/elevatemc/elib/pidgin/packet/handler/PacketExceptionHandler.java new file mode 100644 index 0000000..b4dd91a --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/pidgin/packet/handler/PacketExceptionHandler.java @@ -0,0 +1,10 @@ +package com.elevatemc.elib.pidgin.packet.handler; + +public class PacketExceptionHandler { + + public void onException(Exception e) { + System.out.println("Failed to send packet"); + e.printStackTrace(); + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/pidgin/packet/listener/PacketListener.java b/Network/eLib/src/main/java/com/elevatemc/elib/pidgin/packet/listener/PacketListener.java new file mode 100644 index 0000000..4daeac1 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/pidgin/packet/listener/PacketListener.java @@ -0,0 +1,5 @@ +package com.elevatemc.elib.pidgin.packet.listener; + +public interface PacketListener { + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/pidgin/packet/listener/PacketListenerData.java b/Network/eLib/src/main/java/com/elevatemc/elib/pidgin/packet/listener/PacketListenerData.java new file mode 100644 index 0000000..80e97cf --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/pidgin/packet/listener/PacketListenerData.java @@ -0,0 +1,26 @@ +package com.elevatemc.elib.pidgin.packet.listener; + +import com.elevatemc.elib.pidgin.packet.Packet; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.lang.reflect.Method; + +/** + * A wrapper class that holds all the information needed to + * identify and execute a message function. + * + */ +@AllArgsConstructor +@Getter +public class PacketListenerData { + + private Object instance; + private Method method; + private Class packetClass; + + public boolean matches(Packet packet) { + return this.packetClass == packet.getClass(); + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/prompt/PromptBuilder.java b/Network/eLib/src/main/java/com/elevatemc/elib/prompt/PromptBuilder.java new file mode 100644 index 0000000..071f330 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/prompt/PromptBuilder.java @@ -0,0 +1,4 @@ +package com.elevatemc.elib.prompt; + +public class PromptBuilder { +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/redis/RedisCommand.java b/Network/eLib/src/main/java/com/elevatemc/elib/redis/RedisCommand.java new file mode 100644 index 0000000..2bb5d11 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/redis/RedisCommand.java @@ -0,0 +1,9 @@ +package com.elevatemc.elib.redis; + +import redis.clients.jedis.Jedis; + +public interface RedisCommand { + + T execute(Jedis redis); + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/scoreboard/Scoreboard.java b/Network/eLib/src/main/java/com/elevatemc/elib/scoreboard/Scoreboard.java new file mode 100644 index 0000000..c1c2f4b --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/scoreboard/Scoreboard.java @@ -0,0 +1,236 @@ +package com.elevatemc.elib.scoreboard; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.packet.ScoreboardTeamPacketMod; +import net.minecraft.server.v1_8_R3.Packet; +import net.minecraft.server.v1_8_R3.PacketPlayOutScoreboardScore; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.UnmodifiableIterator; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.scoreboard.DisplaySlot; +import org.bukkit.scoreboard.Objective; + +import java.lang.reflect.Field; +import java.util.*; + +final class Scoreboard { + + private Player player; + private Objective objective; + private Map displayedScores = new HashMap<>(); + private Map scorePrefixes = new HashMap<>(); + private Map scoreSuffixes = new HashMap<>(); + private Set sentTeamCreates = new HashSet<>(); + private final Set recentlyUpdatedScores = new HashSet<>(); + private final Set usedBaseScores = new HashSet<>(); + private final ThreadLocal> localList = ThreadLocal.withInitial(LinkedList::new); + + + public Scoreboard(Player player) { + this.player = player; + + final org.bukkit.scoreboard.Scoreboard board = eLib.getInstance().getServer().getScoreboardManager().getNewScoreboard(); + + this.objective = board.registerNewObjective("eLib", "dummy"); + this.objective.setDisplaySlot(DisplaySlot.SIDEBAR); + + player.setScoreboard(board); + } + + public void update() { + + final String untranslatedTitle = eLib.getInstance().getScoreboardHandler().getConfiguration().getTitleGetter().getTitle(this.player); + final String title = ChatColor.translateAlternateColorCodes('&', untranslatedTitle); + final List lines = this.localList.get(); + + if (!lines.isEmpty()) { + lines.clear(); + } + + eLib.getInstance().getScoreboardHandler().getConfiguration().getScoreGetter().getScores(this.localList.get(), this.player); + + this.recentlyUpdatedScores.clear(); + this.usedBaseScores.clear(); + + int nextValue = lines.size(); + + Preconditions.checkArgument(lines.size() < 16, "Too many lines passed!"); + Preconditions.checkArgument(title.length() < 32, "Title is too long!"); + + if (!this.objective.getDisplayName().equals(title)) { + this.objective.setDisplayName(title); + } + + String displayedScore; + + for(Iterator var5 = lines.iterator(); var5.hasNext(); --nextValue) { + + displayedScore = var5.next(); + + if (48 <= displayedScore.length()) { + throw new IllegalArgumentException("Line is too long! Offending line: " + displayedScore); + } + + final String[] separated = this.separate(displayedScore, this.usedBaseScores); + final String prefix = separated[0]; + final String score = separated[1]; + final String suffix = separated[2]; + + this.recentlyUpdatedScores.add(score); + + if (!this.sentTeamCreates.contains(score)) { + this.createAndAddMember(score); + } + + if (!this.displayedScores.containsKey(score) || this.displayedScores.get(score) != nextValue) { + this.setScore(score, nextValue); + } + + if (!this.scorePrefixes.containsKey(score) || !(this.scorePrefixes.get(score)).equals(prefix) || !((String)this.scoreSuffixes.get(score)).equals(suffix)) { + this.updateScore(score, prefix, suffix); + } + } + + UnmodifiableIterator unmodifiableIterator = ImmutableSet.copyOf(this.displayedScores.keySet()).iterator(); + + while(unmodifiableIterator.hasNext()) { + + displayedScore = (String)unmodifiableIterator.next(); + + if (!this.recentlyUpdatedScores.contains(displayedScore)) { + this.removeScore(displayedScore); + } + } + } + + private void setField(Packet packet, String field, Object value) { + try { + Field fieldObject = packet.getClass().getDeclaredField(field); + + fieldObject.setAccessible(true); + fieldObject.set(packet, value); + } catch (Exception e) { + e.printStackTrace(); + } + } + + // This is here so that the score joins itself, this way + // #updateScore will work as it should (that works on a 'player'), which technically we are adding to ourselves + private void createAndAddMember(String scoreTitle) { + + final ScoreboardTeamPacketMod scoreboardTeamAdd = new ScoreboardTeamPacketMod(scoreTitle, "_", "_", new ArrayList(), 0); + final ScoreboardTeamPacketMod scoreboardTeamAddMember = new ScoreboardTeamPacketMod(scoreTitle, Arrays.asList(scoreTitle), 3); + + scoreboardTeamAdd.sendToPlayer(player); + scoreboardTeamAddMember.sendToPlayer(player); + + sentTeamCreates.add(scoreTitle); + } + + private void setScore(String score, int value) { + + final PacketPlayOutScoreboardScore scoreboardScorePacket = new PacketPlayOutScoreboardScore(); + + setField(scoreboardScorePacket, "a", score); + setField(scoreboardScorePacket, "b", objective.getName()); + setField(scoreboardScorePacket, "c", value); + setField(scoreboardScorePacket, "d", PacketPlayOutScoreboardScore.EnumScoreboardAction.CHANGE); + + this.displayedScores.put(score, value); + + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(scoreboardScorePacket); + } + + private void removeScore(String score) { + displayedScores.remove(score); + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutScoreboardScore(score)); + } + + private void updateScore(String score, String prefix, String suffix) { + ScoreboardTeamPacketMod scoreboardTeamModify = new ScoreboardTeamPacketMod(score, prefix, suffix, null, 2); + scoreboardTeamModify.sendToPlayer(player); + } + + // Here be dragons. + // Good luck maintaining this code. + private String[] separate(String line, Collection usedBaseScores) { + line = ChatColor.translateAlternateColorCodes('&', line); + String prefix = ""; + String score = ""; + String suffix = ""; + + List working = new ArrayList<>(); + StringBuilder workingStr = new StringBuilder(); + + for (char c : line.toCharArray()) { + if (c == '*' || (workingStr.length() == 16 && working.size() < 3)) { + working.add(workingStr.toString()); + workingStr = new StringBuilder(); + + if (c == '*') { + continue; + } + } + + workingStr.append(c); + } + + working.add(workingStr.toString()); + + switch (working.size()) { + case 1: + score = working.get(0); + break; + case 2: + score = working.get(0); + suffix = working.get(1); + break; + case 3: + prefix = working.get(0); + score = working.get(1); + suffix = working.get(2); + break; + default: + eLib.getInstance().getLogger().warning("Failed to separate scoreboard line. Input: " + line); + break; + } + + if (usedBaseScores.contains(score)) { + if (score.length() <= 14) { + for (ChatColor chatColor : ChatColor.values()) { + String possibleScore = chatColor + score; + + if (!usedBaseScores.contains(possibleScore)) { + score = possibleScore; + break; + } + } + + if (usedBaseScores.contains(score)) { + eLib.getInstance().getLogger().warning("Failed to find alternate color code for: " + score); + } + } else { + eLib.getInstance().getLogger().warning("Found a scoreboard base collision to shift: " + score); + } + } + + if (prefix.length() > 16) { + prefix = ChatColor.DARK_RED.toString() + ChatColor.BOLD + ">16"; + } + + if (score.length() > 16) { + score = ChatColor.DARK_RED.toString() + ChatColor.BOLD + ">16"; + } + + if (suffix.length() > 16) { + suffix = ChatColor.DARK_RED.toString() + ChatColor.BOLD + ">16"; + } + + usedBaseScores.add(score); + return (new String[]{prefix, score, suffix}); + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/scoreboard/ScoreboardHandler.java b/Network/eLib/src/main/java/com/elevatemc/elib/scoreboard/ScoreboardHandler.java new file mode 100644 index 0000000..b00f21c --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/scoreboard/ScoreboardHandler.java @@ -0,0 +1,45 @@ +package com.elevatemc.elib.scoreboard; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.scoreboard.config.ScoreboardConfiguration; +import com.elevatemc.elib.scoreboard.listener.ScoreboardListener; +import lombok.Getter; +import lombok.Setter; +import org.bukkit.entity.Player; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public final class ScoreboardHandler { + + @Getter private Map boards = new ConcurrentHashMap<>(); + @Getter @Setter private ScoreboardConfiguration configuration = null; + + public ScoreboardHandler() { + new ScoreboardThread().start(); + eLib.getInstance().getServer().getPluginManager().registerEvents(new ScoreboardListener(), eLib.getInstance()); + } + + public void create(Player player) { + if (configuration != null) { + boards.put(player.getName(), new Scoreboard(player)); + } + + } + + public void updateScoreboard(Player player) { + + final Scoreboard board = boards.get(player.getName()); + + if (board != null) { + board.update(); + } + + } + + + public void remove(Player player) { + boards.remove(player.getName()); + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/scoreboard/ScoreboardThread.java b/Network/eLib/src/main/java/com/elevatemc/elib/scoreboard/ScoreboardThread.java new file mode 100644 index 0000000..5ee4749 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/scoreboard/ScoreboardThread.java @@ -0,0 +1,36 @@ +package com.elevatemc.elib.scoreboard; + +import com.elevatemc.elib.eLib; +import org.bukkit.entity.Player; + +final class ScoreboardThread extends Thread { + + public static Integer UPDATE_INTERVAL = 2; + + public ScoreboardThread() { + super("eLib - Scoreboard Thread"); + setDaemon(false); + } + + public void run() { + while (true) { + + for (Player online : eLib.getInstance().getServer().getOnlinePlayers()) { + + try { + eLib.getInstance().getScoreboardHandler().updateScoreboard(online); + } catch (Exception e) { + e.printStackTrace(); + } + + } + + try { + Thread.sleep(UPDATE_INTERVAL * 50L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/scoreboard/config/ScoreboardConfiguration.java b/Network/eLib/src/main/java/com/elevatemc/elib/scoreboard/config/ScoreboardConfiguration.java new file mode 100644 index 0000000..85d8dfa --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/scoreboard/config/ScoreboardConfiguration.java @@ -0,0 +1,21 @@ +package com.elevatemc.elib.scoreboard.config; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import com.elevatemc.elib.scoreboard.construct.ScoreGetter; +import com.elevatemc.elib.scoreboard.construct.TitleGetter; + +/** + * Scoreboard Configuration class. This class can be used to + * create scoreboard objects. This configuration object provides + * the title/scores, along with some other settings. This should be passed to + * FrozenScoreboardHandler#setConfiguration. + */ +@NoArgsConstructor +public final class ScoreboardConfiguration { + + @Getter @Setter private TitleGetter titleGetter; + @Getter @Setter private ScoreGetter scoreGetter; + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/scoreboard/construct/ScoreFunction.java b/Network/eLib/src/main/java/com/elevatemc/elib/scoreboard/construct/ScoreFunction.java new file mode 100644 index 0000000..94966a6 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/scoreboard/construct/ScoreFunction.java @@ -0,0 +1,21 @@ +package com.elevatemc.elib.scoreboard.construct; + +import com.elevatemc.elib.util.TimeUtils; + +public interface ScoreFunction { + + ScoreFunction TIME_FANCY = (value) -> { + + if (value >= 60) { + return (TimeUtils.formatIntoMMSS(value.intValue())); + } else { + return (Math.round(10.0D * value) / 10.0D + "s"); + } + + }; + + ScoreFunction TIME_SIMPLE = (value) -> (TimeUtils.formatIntoMMSS(value.intValue())); + + String apply(T value); + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/scoreboard/construct/ScoreGetter.java b/Network/eLib/src/main/java/com/elevatemc/elib/scoreboard/construct/ScoreGetter.java new file mode 100644 index 0000000..f0619fa --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/scoreboard/construct/ScoreGetter.java @@ -0,0 +1,11 @@ +package com.elevatemc.elib.scoreboard.construct; + +import org.bukkit.entity.Player; + +import java.util.LinkedList; + +public interface ScoreGetter { + + void getScores(LinkedList linkedList,Player player); + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/scoreboard/construct/TitleGetter.java b/Network/eLib/src/main/java/com/elevatemc/elib/scoreboard/construct/TitleGetter.java new file mode 100644 index 0000000..02079cc --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/scoreboard/construct/TitleGetter.java @@ -0,0 +1,20 @@ +package com.elevatemc.elib.scoreboard.construct; + +import lombok.NoArgsConstructor; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +@NoArgsConstructor +public class TitleGetter { + + private String defaultTitle; + + public TitleGetter(String defaultTitle) { + this.defaultTitle = ChatColor.translateAlternateColorCodes('&', defaultTitle); + } + + public String getTitle(Player player) { + return defaultTitle; + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/scoreboard/listener/ScoreboardListener.java b/Network/eLib/src/main/java/com/elevatemc/elib/scoreboard/listener/ScoreboardListener.java new file mode 100644 index 0000000..f85904b --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/scoreboard/listener/ScoreboardListener.java @@ -0,0 +1,22 @@ +package com.elevatemc.elib.scoreboard.listener; + +import com.elevatemc.elib.eLib; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +public final class ScoreboardListener implements Listener { + + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerJoin(PlayerJoinEvent event) { + eLib.getInstance().getScoreboardHandler().create(event.getPlayer()); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerQuit(PlayerQuitEvent event) { + eLib.getInstance().getScoreboardHandler().remove(event.getPlayer()); + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/serialization/BlockVectorAdapter.java b/Network/eLib/src/main/java/com/elevatemc/elib/serialization/BlockVectorAdapter.java new file mode 100644 index 0000000..bb61f16 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/serialization/BlockVectorAdapter.java @@ -0,0 +1,47 @@ +package com.elevatemc.elib.serialization; + +import com.google.gson.*; +import org.bukkit.util.BlockVector; + +import java.lang.reflect.Type; + +public class BlockVectorAdapter implements JsonDeserializer, JsonSerializer { + + @Override + public BlockVector deserialize(JsonElement src, Type type, JsonDeserializationContext context) throws JsonParseException { + return fromJson(src); + } + + @Override + public JsonElement serialize(BlockVector src, Type type, JsonSerializationContext context) { + return toJson(src); + } + + public static JsonObject toJson(BlockVector src) { + if (src == null) { + return null; + } + + final JsonObject object = new JsonObject(); + + object.addProperty("x", src.getX()); + object.addProperty("y", src.getY()); + object.addProperty("z", src.getZ()); + + return object; + } + + public static BlockVector fromJson(JsonElement src) { + if (src == null || !src.isJsonObject()) { + return null; + } + final JsonObject json = src.getAsJsonObject(); + + final double x = json.get("x").getAsDouble(); + final double y = json.get("y").getAsDouble(); + final double z = json.get("z").getAsDouble(); + + return new BlockVector(x, y, z); + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/serialization/ItemStackAdapter.java b/Network/eLib/src/main/java/com/elevatemc/elib/serialization/ItemStackAdapter.java new file mode 100644 index 0000000..0f5bb4c --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/serialization/ItemStackAdapter.java @@ -0,0 +1,544 @@ +package com.elevatemc.elib.serialization; + +import com.elevatemc.elib.eLib; +import com.google.gson.*; +import org.bukkit.Color; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.*; +import org.bukkit.potion.PotionEffect; + +import java.lang.reflect.Type; +import java.util.*; +import java.util.Map.Entry; + +@SuppressWarnings("deprecation") +public class ItemStackAdapter implements JsonDeserializer, JsonSerializer { + + public static final String ID = "id"; + public static final String COUNT = "count"; + public static final String DAMAGE = "damage"; + + public static final String NAME = "name"; + public static final String LORE = "lore"; + + public static final String ENCHANTS = "enchants"; + + public static final String REPAIRCOST = "repaircost"; + + public static final String BOOK_TITLE = "title"; + public static final String BOOK_AUTHOR = "author"; + public static final String BOOK_PAGES = "pages"; + + public static final String LEATHER_ARMOR_COLOR = "color"; + + public static final String MAP_SCALING = "scaling"; + + public static final String SKULL_OWNER = "skull"; + + public static final String POTION_EFFECTS_OLD = "effects"; + public static final String POTION_EFFECTS = "potion-effects"; + + public static final String STORED_ENCHANTS = "stored-enchants"; + + // -------------------------------------------- // + // OTHER CONSTANTS + // -------------------------------------------- // + + public static final int DEFAULT_ID; + public static final int DEFAULT_COUNT; + public static final int DEFAULT_DAMAGE; + + static { + ItemStack stack = createItemStack(); + DEFAULT_ID = stack.getTypeId(); + DEFAULT_COUNT = stack.getAmount(); + DEFAULT_DAMAGE = stack.getDurability(); + } + + // -------------------------------------------- // + // GSON INTERFACE IMPLEMENTATION + // -------------------------------------------- // + + @Override + public JsonElement serialize(ItemStack src, Type typeOfSrc, JsonSerializationContext context) { + return erialize(src); + } + + @Override + public ItemStack deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + return erialize(json); + } + + // -------------------------------------------- // + // WRITE + // -------------------------------------------- // + + public static JsonObject erialize(ItemStack stack) { + // Check for "nothing" + if (stack == null) + return null; + if (stack.getTypeId() == 0) + return null; + if (stack.getAmount() == 0) + return null; + + // Create a new JsonObject + JsonObject json = new JsonObject(); + + // Transfer data from stack to json + transferAll(stack, json, true); + + return json; + } + + public static ItemStack erialize(JsonElement jsonElement) { + // Check for "nothing" + if (jsonElement == null) + return null; + + // Must be a JsonObject + if (jsonElement.isJsonObject() == false) + return null; + JsonObject json = jsonElement.getAsJsonObject(); + + // Create a new ItemStack + ItemStack stack = createItemStack(); + + // Transfer data from json to stack + transferAll(stack, json, false); + + return stack; + } + + // -------------------------------------------- // + // NOARG STACK CONSTRUCTOR + // -------------------------------------------- // + + public static ItemStack createItemStack() { + return new ItemStack(0); + } + + // -------------------------------------------- // + // ALL + // -------------------------------------------- // + + public static void transferAll(ItemStack stack, JsonObject json, boolean stack2json) { + transferBasic(stack, json, stack2json); + + ItemMeta meta = stack.getItemMeta(); + transferMeta(meta, json, stack2json); + + if (stack2json == false) { + stack.setItemMeta(meta); + } + } + + // -------------------------------------------- // + // BASIC + // -------------------------------------------- // + + public static void transferBasic(ItemStack stack, JsonObject json, boolean stack2json) { + transferId(stack, json, stack2json); + transferCount(stack, json, stack2json); + transferDamage(stack, json, stack2json); + } + + // -------------------------------------------- // + // BASIC: ID + // -------------------------------------------- // + + public static void transferId(ItemStack stack, JsonObject json, boolean stack2json) { + if (stack2json) { + int id = stack.getTypeId(); + if (id == DEFAULT_ID) + return; + json.addProperty(ID, id); + } else { + JsonElement element = json.get(ID); + if (element == null) + return; + stack.setTypeId(element.getAsInt()); + } + } + + // -------------------------------------------- // + // BASIC: COUNT + // -------------------------------------------- // + + public static void transferCount(ItemStack stack, JsonObject json, boolean stack2json) { + if (stack2json) { + int count = stack.getAmount(); + if (count == DEFAULT_COUNT) + return; + json.addProperty(COUNT, count); + } else { + JsonElement element = json.get(COUNT); + if (element == null) + return; + stack.setAmount(element.getAsInt()); + } + } + + // -------------------------------------------- // + // BASIC: DAMAGE + // -------------------------------------------- // + + public static void transferDamage(ItemStack stack, JsonObject json, boolean stack2json) { + // Durability is a weird name since it is the amount of damage. + if (stack2json) { + int damage = stack.getDurability(); + if (damage == DEFAULT_DAMAGE) + return; + json.addProperty(DAMAGE, damage); + } else { + JsonElement element = json.get(DAMAGE); + if (element == null) + return; + stack.setDurability(element.getAsShort()); + } + } + + // -------------------------------------------- // + // META + // -------------------------------------------- // + + public static void transferMeta(ItemMeta meta, JsonObject json, boolean meta2json) { + transferUnspecificMeta(meta, json, meta2json); + transferSpecificMeta(meta, json, meta2json); + } + + // -------------------------------------------- // + // UNSPECIFIC META + // -------------------------------------------- // + + public static void transferUnspecificMeta(ItemMeta meta, JsonObject json, boolean meta2json) { + transferName(meta, json, meta2json); + transferLore(meta, json, meta2json); + transferEnchants(meta, json, meta2json); + transferRepaircost(meta, json, meta2json); + } + + // -------------------------------------------- // + // UNSPECIFIC META: NAME + // -------------------------------------------- // + + public static void transferName(ItemMeta meta, JsonObject json, boolean meta2json) { + if (meta2json) { + if (!meta.hasDisplayName()) + return; + json.addProperty(NAME, meta.getDisplayName()); + } else { + JsonElement element = json.get(NAME); + if (element == null) + return; + meta.setDisplayName(element.getAsString()); + } + } + + // -------------------------------------------- // + // UNSPECIFIC META: LORE + // -------------------------------------------- // + + public static void transferLore(ItemMeta meta, JsonObject json, boolean meta2json) { + if (meta2json) { + if (!meta.hasLore()) + return; + json.add(LORE, convertStringList(meta.getLore())); + } else { + JsonElement element = json.get(LORE); + if (element == null) + return; + meta.setLore(convertStringList(element)); + } + } + + // -------------------------------------------- // + // UNSPECIFIC META: ENCHANTS + // -------------------------------------------- // + + public static void transferEnchants(ItemMeta meta, JsonObject json, boolean meta2json) { + if (meta2json) { + if (!meta.hasEnchants()) + return; + json.add(ENCHANTS, convertEnchantLevelMap(meta.getEnchants())); + } else { + JsonElement element = json.get(ENCHANTS); + if (element == null) + return; + for (Entry entry : convertEnchantLevelMap(element).entrySet()) { + meta.addEnchant(entry.getKey(), entry.getValue(), true); + } + } + } + + // -------------------------------------------- // + // UNSPECIFIC META: REPAIRCOST + // -------------------------------------------- // + + public static void transferRepaircost(ItemMeta meta, JsonObject json, boolean meta2json) { + if (!(meta instanceof Repairable)) + return; + Repairable repairable = (Repairable) meta; + + if (meta2json) { + if (!repairable.hasRepairCost()) + return; + json.addProperty(REPAIRCOST, repairable.getRepairCost()); + } else { + JsonElement element = json.get(REPAIRCOST); + if (element == null) + return; + + repairable.setRepairCost(element.getAsInt()); + } + } + + // -------------------------------------------- // + // SPECIFIC META + // -------------------------------------------- // + + public static void transferSpecificMeta(ItemMeta meta, JsonObject json, boolean meta2json) { + if (meta instanceof BookMeta) { + transferBookMeta((BookMeta) meta, json, meta2json); + } else if (meta instanceof LeatherArmorMeta) { + transferLeatherArmorMeta((LeatherArmorMeta) meta, json, meta2json); + } else if (meta instanceof MapMeta) { + transferMapMeta((MapMeta) meta, json, meta2json); + } else if (meta instanceof PotionMeta) { + transferPotionMeta((PotionMeta) meta, json, meta2json); + } else if (meta instanceof SkullMeta) { + transferSkullMeta((SkullMeta) meta, json, meta2json); + } else if (meta instanceof EnchantmentStorageMeta) { + transferEnchantmentStorageMeta((EnchantmentStorageMeta) meta, json, meta2json); + } + } + + // -------------------------------------------- // + // SPECIFIC META: BOOK + // -------------------------------------------- // + + public static void transferBookMeta(BookMeta meta, JsonObject json, boolean meta2json) { + transferTitle(meta, json, meta2json); + transferAuthor(meta, json, meta2json); + transferPages(meta, json, meta2json); + } + + public static void transferTitle(BookMeta meta, JsonObject json, boolean meta2json) { + if (meta2json) { + if (!meta.hasTitle()) + return; + json.addProperty(BOOK_TITLE, meta.getTitle()); + } else { + JsonElement element = json.get(BOOK_TITLE); + if (element == null) + return; + meta.setTitle(element.getAsString()); + } + } + + public static void transferAuthor(BookMeta meta, JsonObject json, boolean meta2json) { + if (meta2json) { + if (!meta.hasTitle()) + return; + json.addProperty(BOOK_AUTHOR, meta.getAuthor()); + } else { + JsonElement element = json.get(BOOK_AUTHOR); + if (element == null) + return; + meta.setAuthor(element.getAsString()); + } + } + + public static void transferPages(BookMeta meta, JsonObject json, boolean meta2json) { + if (meta2json) { + if (!meta.hasTitle()) + return; + json.add(BOOK_PAGES, convertStringList(meta.getPages())); + } else { + JsonElement element = json.get(BOOK_PAGES); + if (element == null) + return; + meta.setPages(convertStringList(element)); + } + } + + // -------------------------------------------- // + // SPECIFIC META: LEATHER ARMOR + // -------------------------------------------- // + + public static void transferLeatherArmorMeta(LeatherArmorMeta meta, JsonObject json, boolean meta2json) { + if (meta2json) { + Color color = meta.getColor(); + + if (eLib.getInstance().getServer().getItemFactory().getDefaultLeatherColor().equals(color)) + return; + + json.addProperty(LEATHER_ARMOR_COLOR, color.asRGB()); + } else { + JsonElement element = json.get(LEATHER_ARMOR_COLOR); + if (element == null) + return; + meta.setColor(Color.fromRGB(element.getAsInt())); + } + } + + // -------------------------------------------- // + // SPECIFIC META: MAP + // -------------------------------------------- // + + public static void transferMapMeta(MapMeta meta, JsonObject json, boolean meta2json) { + if (meta2json) { + if (!meta.isScaling()) + return; + json.addProperty(MAP_SCALING, true); + } else { + JsonElement element = json.get(MAP_SCALING); + if (element == null) + return; + + meta.setScaling(element.getAsBoolean()); + } + } + + // -------------------------------------------- // + // SPECIFIC META: POTION + // -------------------------------------------- // + + public static void transferPotionMeta(PotionMeta meta, JsonObject json, boolean meta2json) { + if (meta2json) { + if (!meta.hasCustomEffects()) + return; + json.add(POTION_EFFECTS, convertPotionEffectList(meta.getCustomEffects())); + } else { + JsonElement element = json.get(POTION_EFFECTS); + if (element == null) + element = json.get(POTION_EFFECTS_OLD); + if (element == null) + return; + + meta.clearCustomEffects(); + for (PotionEffect pe : convertPotionEffectList(element)) { + meta.addCustomEffect(pe, false); + } + } + } + + // -------------------------------------------- // + // SPECIFIC META: SKULL + // -------------------------------------------- // + + public static void transferSkullMeta(SkullMeta meta, JsonObject json, boolean meta2json) { + if (meta2json) { + if (!meta.hasOwner()) + return; + json.addProperty(SKULL_OWNER, meta.getOwner()); + } else { + JsonElement element = json.get(SKULL_OWNER); + if (element == null) + return; + meta.setOwner(element.getAsString()); + } + } + + public static void transferEnchantmentStorageMeta(EnchantmentStorageMeta meta, JsonObject json, boolean meta2json) { + if (meta2json) { + if (!meta.hasStoredEnchants()) + return; + json.add(STORED_ENCHANTS, convertEnchantLevelMap(meta.getStoredEnchants())); + } else { + JsonElement element = json.get(STORED_ENCHANTS); + if (element == null) + return; + for (Entry entry : convertEnchantLevelMap(element).entrySet()) { + meta.addStoredEnchant(entry.getKey(), entry.getValue(), true); + } + } + } + + // -------------------------------------------- // + // MINI UTILS + // -------------------------------------------- // + + // String List + public static JsonArray convertStringList(Collection strings) { + JsonArray ret = new JsonArray(); + for (String string : strings) { + ret.add(new JsonPrimitive(string)); + } + return ret; + } + + public static List convertStringList(JsonElement jsonElement) { + JsonArray array = jsonElement.getAsJsonArray(); + List ret = new ArrayList(); + + Iterator iter = array.iterator(); + while (iter.hasNext()) { + JsonElement element = iter.next(); + ret.add(element.getAsString()); + } + + return ret; + } + + // PotionEffect List + public static JsonArray convertPotionEffectList(Collection potionEffects) { + JsonArray ret = new JsonArray(); + for (PotionEffect e : potionEffects) { + ret.add(PotionEffectAdapter.toJson(e)); + } + return ret; + } + + public static List convertPotionEffectList(JsonElement jsonElement) { + if (jsonElement == null) + return null; + if (!jsonElement.isJsonArray()) + return null; + JsonArray array = jsonElement.getAsJsonArray(); + + List ret = new ArrayList(); + + Iterator iter = array.iterator(); + while (iter.hasNext()) { + PotionEffect e = PotionEffectAdapter.fromJson(iter.next()); + if (e == null) + continue; + ret.add(e); + } + + return ret; + } + + // EnchantLevelMap + public static JsonObject convertEnchantLevelMap(Map enchantLevelMap) { + + final JsonObject toReturn = new JsonObject(); + + enchantLevelMap.forEach((key,value) -> toReturn.addProperty(""+key.getId(),value)); + + return toReturn; + } + + public static Map convertEnchantLevelMap(JsonElement jsonElement) { + + final JsonObject json = jsonElement.getAsJsonObject(); + + final Map toReturn = new HashMap<>(); + + json.entrySet().forEach(entry -> { + Enchantment enchantment = null; + try { + enchantment = Enchantment.getById(Integer.valueOf(entry.getKey())); + } catch (Exception ex) { + enchantment = Enchantment.getByName(entry.getKey()); + } + toReturn.put(enchantment, entry.getValue().getAsInt()); + }); + + return toReturn; + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/serialization/ItemStackSerializer.java b/Network/eLib/src/main/java/com/elevatemc/elib/serialization/ItemStackSerializer.java new file mode 100644 index 0000000..97d8afd --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/serialization/ItemStackSerializer.java @@ -0,0 +1,86 @@ +package com.elevatemc.elib.serialization; + +import com.mongodb.BasicDBList; +import com.mongodb.BasicDBObject; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.ArrayList; +import java.util.Map; + +public final class ItemStackSerializer { + + public static final BasicDBObject AIR = new BasicDBObject(); + + static { + AIR.put("type", "AIR"); + AIR.put("amount", 1); + AIR.put("data", 0); + } + + // Static utility class -- cannot be created. + private ItemStackSerializer() { + } + + // TODO: Cleanup and make lore get serialized + public static BasicDBObject serialize(ItemStack itemStack) { + if (itemStack == null || itemStack.getType() == Material.AIR) { + return (AIR); + } + + BasicDBObject item = new BasicDBObject("type", itemStack.getType().toString()).append("amount", itemStack.getAmount()).append("data", itemStack.getDurability()); + BasicDBList enchants = new BasicDBList(); + for (Map.Entry entry : itemStack.getEnchantments().entrySet()) { + enchants.add(new BasicDBObject("enchantment", entry.getKey().getName()).append("level", entry.getValue())); + } + if (itemStack.getEnchantments().size() > 0) + item.append("enchants", enchants); + if (itemStack.hasItemMeta()) { + ItemMeta m = itemStack.getItemMeta(); + BasicDBObject meta = new BasicDBObject("displayName", m.getDisplayName()); + if (m.getLore() != null) { + + } + item.append("meta", meta); + } + return item; + } + + // TODO: Cleanup and make lore get deserialized + public static ItemStack deserialize(BasicDBObject dbObject) { + if (dbObject == null || dbObject.isEmpty()) { + return (new ItemStack(Material.AIR)); + } + + Material type = Material.valueOf(dbObject.getString("type")); + ItemStack item = new ItemStack(type, dbObject.getInt("amount")); + item.setDurability(Short.parseShort(dbObject.getString("data"))); + + if (dbObject.containsField("enchants")) { + BasicDBList enchs = (BasicDBList) dbObject.get("enchants"); + for (Object o : enchs) { + BasicDBObject enchant = (BasicDBObject) o; + item.addUnsafeEnchantment(Enchantment.getByName(enchant.getString("enchantment")), enchant.getInt("level")); + } + } + + if (dbObject.containsField("meta")) { + BasicDBObject meta = (BasicDBObject) dbObject.get("meta"); + ItemMeta m = item.getItemMeta(); + if (meta.containsField("displayName")) { + m.setDisplayName(meta.getString("displayName")); + } + if (meta.containsField("lore")) { + m.setLore(new ArrayList() { + private static final long serialVersionUID = -765088419932829612L; + }); + } + item.setItemMeta(m); + } + + return (item); + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/serialization/LocationAdapter.java b/Network/eLib/src/main/java/com/elevatemc/elib/serialization/LocationAdapter.java new file mode 100644 index 0000000..9bdfb69 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/serialization/LocationAdapter.java @@ -0,0 +1,56 @@ +package com.elevatemc.elib.serialization; + +import com.elevatemc.elib.eLib; +import com.google.gson.*; +import org.bukkit.Location; +import org.bukkit.World; + +import java.lang.reflect.Type; + +public class LocationAdapter implements JsonDeserializer, JsonSerializer { + + @Override + public JsonElement serialize(Location src, Type typeOfSrc, JsonSerializationContext context) { + return (toJson(src)); + } + + @Override + public Location deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + return (fromJson(json)); + } + + public static JsonObject toJson(Location location) { + if (location == null) { + return (null); + } + + JsonObject jsonObject = new JsonObject(); + + jsonObject.addProperty("world", location.getWorld().getName()); + jsonObject.addProperty("x", location.getX()); + jsonObject.addProperty("y", location.getY()); + jsonObject.addProperty("z", location.getZ()); + jsonObject.addProperty("yaw", location.getYaw()); + jsonObject.addProperty("pitch", location.getPitch()); + + return (jsonObject); + } + + public static Location fromJson(JsonElement jsonElement) { + if (jsonElement == null || !jsonElement.isJsonObject()) { + return (null); + } + + JsonObject jsonObject = jsonElement.getAsJsonObject(); + + World world = eLib.getInstance().getServer().getWorld(jsonObject.get("world").getAsString()); + double x = jsonObject.get("x").getAsDouble(); + double y = jsonObject.get("y").getAsDouble(); + double z = jsonObject.get("z").getAsDouble(); + float yaw = jsonObject.get("yaw").getAsFloat(); + float pitch = jsonObject.get("pitch").getAsFloat(); + + return (new Location(world, x, y, z, yaw, pitch)); + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/serialization/LocationSerializer.java b/Network/eLib/src/main/java/com/elevatemc/elib/serialization/LocationSerializer.java new file mode 100644 index 0000000..99bd8cd --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/serialization/LocationSerializer.java @@ -0,0 +1,46 @@ +package com.elevatemc.elib.serialization; + +import com.elevatemc.elib.eLib; +import com.mongodb.BasicDBObject; +import org.bukkit.Location; +import org.bukkit.World; + +public final class LocationSerializer { + + // Static utility class -- cannot be created. + private LocationSerializer() { + } + + public static BasicDBObject serialize(Location location) { + if (location == null) { + return (new BasicDBObject()); + } + + BasicDBObject dbObject = new BasicDBObject(); + + dbObject.put("world", location.getWorld().getName()); + dbObject.put("x", location.getX()); + dbObject.put("y", location.getY()); + dbObject.put("z", location.getZ()); + dbObject.append("yaw", location.getYaw()); + dbObject.append("pitch", location.getPitch()); + + return (dbObject); + } + + public static Location deserialize(BasicDBObject dbObject) { + if (dbObject == null || dbObject.isEmpty()) { + return (null); + } + + World world = eLib.getInstance().getServer().getWorld(dbObject.getString("world")); + double x = dbObject.getDouble("x"); + double y = dbObject.getDouble("y"); + double z = dbObject.getDouble("z"); + int yaw = dbObject.getInt("yaw"); + int pitch = dbObject.getInt("pitch"); + + return (new Location(world, x, y, z, yaw, pitch)); + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/serialization/PlayerInventorySerializer.java b/Network/eLib/src/main/java/com/elevatemc/elib/serialization/PlayerInventorySerializer.java new file mode 100644 index 0000000..39cf4b6 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/serialization/PlayerInventorySerializer.java @@ -0,0 +1,86 @@ +package com.elevatemc.elib.serialization; + +import com.elevatemc.elib.eLib; +import lombok.AllArgsConstructor; +import com.mongodb.BasicDBObject; +import com.mongodb.util.JSON; +import lombok.Getter; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; + +public class PlayerInventorySerializer { + + public static String serialize(Player player) { + return eLib.PLAIN_GSON.toJson(new PlayerInventoryWrapper(player)); + } + public static String serialize(PlayerInventoryWrapper wrapper) { + return eLib.PLAIN_GSON.toJson(wrapper); + } + + public static PlayerInventoryWrapper deserialize(String json) { + return eLib.PLAIN_GSON.fromJson(json, PlayerInventoryWrapper.class); + } + + public static BasicDBObject getInsertableObject(Player player) { + return (BasicDBObject)JSON.parse(serialize(player)); + } + + @AllArgsConstructor + public static class PlayerInventoryWrapper { + + @Getter private final PotionEffect[] effects; + @Getter private final ItemStack[] contents; + @Getter private final ItemStack[] armor; + @Getter private final int health; + @Getter private final int hunger; + + public PlayerInventoryWrapper(Player player) { + this.contents = player.getInventory().getContents(); + + int i; + ItemStack stack; + + for(i = 0; i < this.contents.length; ++i) { + + stack = this.contents[i]; + + if (stack == null) { + this.contents[i] = new ItemStack(Material.AIR, 0, (short)0); + } + + } + + this.armor = player.getInventory().getArmorContents(); + + for(i = 0; i < this.armor.length; ++i) { + + stack = this.armor[i]; + + if (stack == null) { + this.armor[i] = new ItemStack(Material.AIR, 0, (short)0); + } + + } + + this.effects = player.getActivePotionEffects().toArray(new PotionEffect[player.getActivePotionEffects().size()]); + this.health = (int)player.getHealth(); + this.hunger = player.getFoodLevel(); + } + + public void apply(Player player) { + + player.getInventory().setContents(this.contents); + player.getInventory().setArmorContents(this.armor); + player.getActivePotionEffects().forEach(potionEffect -> player.removePotionEffect(potionEffect.getType())); + + for (int i = 0; i < this.effects.length; i++) { + + final PotionEffect potionEffect = this.effects[i]; + + player.addPotionEffect(potionEffect); + } + } + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/serialization/PotionEffectAdapter.java b/Network/eLib/src/main/java/com/elevatemc/elib/serialization/PotionEffectAdapter.java new file mode 100644 index 0000000..05082de --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/serialization/PotionEffectAdapter.java @@ -0,0 +1,51 @@ +package com.elevatemc.elib.serialization; + +import com.google.gson.*; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.lang.reflect.Type; + +public class PotionEffectAdapter implements JsonDeserializer, JsonSerializer { + + @Override + public JsonElement serialize(PotionEffect src, Type typeOfSrc, JsonSerializationContext context) { + return (toJson(src)); + } + + @Override + public PotionEffect deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + return (fromJson(json)); + } + + public static JsonObject toJson(PotionEffect potionEffect) { + if (potionEffect == null) { + return (null); + } + + JsonObject jsonObject = new JsonObject(); + + jsonObject.addProperty("id", potionEffect.getType().getId()); + jsonObject.addProperty("duration", potionEffect.getDuration()); + jsonObject.addProperty("amplifier", potionEffect.getAmplifier()); + jsonObject.addProperty("ambient", potionEffect.isAmbient()); + + return (jsonObject); + } + + public static PotionEffect fromJson(JsonElement jsonElement) { + if (jsonElement == null || !jsonElement.isJsonObject()) { + return (null); + } + + JsonObject jsonObject = jsonElement.getAsJsonObject(); + + PotionEffectType effectType = PotionEffectType.getById(jsonObject.get("id").getAsInt()); + int duration = jsonObject.get("duration").getAsInt(); + int amplifier = jsonObject.get("amplifier").getAsInt(); + boolean ambient = jsonObject.get("ambient").getAsBoolean(); + + return (new PotionEffect(effectType, duration, amplifier, ambient)); + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/serialization/VectorAdapter.java b/Network/eLib/src/main/java/com/elevatemc/elib/serialization/VectorAdapter.java new file mode 100644 index 0000000..be50cee --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/serialization/VectorAdapter.java @@ -0,0 +1,49 @@ +package com.elevatemc.elib.serialization; + +import com.google.gson.*; +import org.bukkit.util.Vector; + +import java.lang.reflect.Type; + +public class VectorAdapter implements JsonDeserializer, JsonSerializer { + + @Override + public Vector deserialize(JsonElement src, Type type, JsonDeserializationContext context) + throws JsonParseException { + return fromJson(src); + } + + + @Override + public JsonElement serialize(Vector src, Type type, JsonSerializationContext context) { + return toJson(src); + } + + + public static JsonObject toJson(Vector src) { + if (src == null) { + return null; + } + + final JsonObject object = new JsonObject(); + + object.addProperty("x", src.getX()); + object.addProperty("y", src.getY()); + object.addProperty("z", src.getZ()); + + return object; + } + + public static Vector fromJson(JsonElement src) { + if (src == null || !src.isJsonObject()) { + return null; + } + final JsonObject json = src.getAsJsonObject(); + + final double x = json.get("x").getAsDouble(); + final double y = json.get("y").getAsDouble(); + final double z = json.get("z").getAsDouble(); + + return new Vector(x, y, z); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/skin/MojangSkin.java b/Network/eLib/src/main/java/com/elevatemc/elib/skin/MojangSkin.java new file mode 100644 index 0000000..2024ec5 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/skin/MojangSkin.java @@ -0,0 +1,19 @@ +package com.elevatemc.elib.skin; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import com.mojang.authlib.properties.Property; + +import java.util.UUID; + +@SuppressWarnings("DuplicateStringLiteralInspection") +@Data @AllArgsConstructor @NoArgsConstructor +public class MojangSkin { + private UUID uuid; + private String value; + private String signature; + public Property toProperty() { + return new Property("textures", this.value, this.signature); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/skin/MojangSkinHandler.java b/Network/eLib/src/main/java/com/elevatemc/elib/skin/MojangSkinHandler.java new file mode 100644 index 0000000..e28e4e1 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/skin/MojangSkinHandler.java @@ -0,0 +1,226 @@ +package com.elevatemc.elib.skin; + +import com.elevatemc.elib.eLib; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import com.elevatemc.elib.util.FileConfig; +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 org.bukkit.entity.Player; +import org.bukkit.plugin.PluginManager; + + +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.util.*; + +public class MojangSkinHandler { + + private static final JsonParser jsonParser = new JsonParser(); + private static final String mojangURL = "https://sessionserver.mojang.com/session/minecraft/profile/%s?unsigned=false"; + + private final Map uuidToMojangSkinMap; + private final Map tempUuidToMojangSkinMap; + private final Map preSetKeyToMojangSkinMap; + + private final FileConfig cacheFile; + + public MojangSkinHandler(eLib eLib) { + this.uuidToMojangSkinMap = new HashMap<>(); + this.tempUuidToMojangSkinMap = new HashMap<>(); + this.preSetKeyToMojangSkinMap = new HashMap<>(); + this.cacheFile = new FileConfig(eLib, "skinsCache"); + + this.loadUUIDSkinCache(); + this.loadSkinCache("/skins/premadeskins.json", "premade"); + this.loadSkinCache("/skins/chars/blue.json", "blue"); + this.loadSkinCache("/skins/chars/cyan.json", "cyan"); + this.loadSkinCache("/skins/chars/darkgreen.json", "darkgreen"); + this.loadSkinCache("/skins/chars/gray.json", "gray"); + this.loadSkinCache("/skins/chars/green.json", "green"); + this.loadSkinCache("/skins/chars/magenta.json", "magenta"); + this.loadSkinCache("/skins/chars/orange.json", "orange"); + this.loadSkinCache("/skins/chars/purple.json", "purple"); + this.loadSkinCache("/skins/chars/red.json", "red"); + this.loadSkinCache("/skins/chars/white.json", "white"); + this.loadSkinCache("/skins/chars/yellow.json", "yellow"); + + PluginManager pluginManager = eLib.getServer().getPluginManager(); + pluginManager.registerEvents(new MojangSkinListener(this), eLib); + } + + public MojangSkin getPremadeSkin(String string) { + return this.preSetKeyToMojangSkinMap.get(string); + } + + public MojangSkin getLetterSkin(String mapKey, char character) { + return this.getPremadeSkin((mapKey + "-" + character).toLowerCase()); + } + + public MojangSkin getCachedMojangSkin(UUID uuid) { + return this.uuidToMojangSkinMap.get(uuid); + } + + public void removeTemporarySkinEntry(UUID uuid) { + this.tempUuidToMojangSkinMap.remove(uuid); + } + + public MojangSkin getMojangSkinFromPlayer(Player player) { + if (player == null) { + return null; + } + + MojangSkin tempSkin = this.tempUuidToMojangSkinMap.get(player.getUniqueId()); + if (tempSkin != null) { + return tempSkin; + } + + GameProfile profile = ((CraftPlayer) player).getHandle().getProfile(); + Collection properties = profile.getProperties().get("textures"); + for (Property proper : properties) { + String sig = proper.getSignature(); + String val = proper.getValue(); + MojangSkin skin = new MojangSkin(player.getUniqueId(), val, sig); + this.tempUuidToMojangSkinMap.put(skin.getUuid(), skin); + return skin; + } + + return null; + } + + public MojangSkin getMojangSkin(UUID uuid) { + MojangSkin skin = this.getCachedMojangSkin(uuid); + + if (skin != null) { + return skin; + } + + try { + MojangSkin mojangSkin = this.fetchSkin(uuid); + if (mojangSkin != null) { + this.uuidToMojangSkinMap.put(uuid, mojangSkin); + } + return mojangSkin; + } catch (Exception e) { + System.out.println("Failed to fetch mojang skin for " + uuid.toString()); + return null; + } + } + + public void saveUUIDSkinCache() { + if (this.cacheFile == null) { + return; + } + + JsonObject jsonObject = new JsonObject(); + + this.uuidToMojangSkinMap.entrySet().iterator().forEachRemaining(entry -> { + UUID uuid = entry.getKey(); + MojangSkin skin = entry.getValue(); + String encoded = eLib.GSON.toJson(skin); + jsonObject.addProperty(uuid.toString(), encoded); + }); + + this.cacheFile.getConfiguration().set("cache", jsonObject.toString()); + this.cacheFile.save(); + } + + private MojangSkin fetchSkin(UUID uuid) throws IOException { + HttpURLConnection connection = this.createHttpURLConnection(uuid); + JsonElement jsonElement = this.readHttpURLConnection(connection); + connection.disconnect(); + + if (jsonElement == null || !jsonElement.isJsonObject()) { + return null; + } + + JsonObject jsonObject = jsonElement.getAsJsonObject(); + JsonObject propertyObject = jsonObject.get("properties").getAsJsonArray().get(0).getAsJsonObject(); + String value = propertyObject.get("value").getAsString(); + String signature = propertyObject.get("signature").getAsString(); + return new MojangSkin(uuid, value, signature); + } + + private void loadSkinCache(String resource, String mapKey) { + InputStream stream = eLib.class.getResourceAsStream(resource); + + if (stream == null) { + return; + } + + Scanner scanner = new Scanner(stream); + StringBuilder builder = new StringBuilder(); + + while (scanner.hasNextLine()) { + builder.append(scanner.nextLine()); + } + + JsonObject jsonObject = jsonParser.parse(builder.toString()).getAsJsonObject(); + jsonObject.entrySet().forEach(entry -> { + String key = entry.getKey(); + JsonObject object = entry.getValue().getAsJsonObject(); + String val = object.get("value").getAsString(); + String sig = object.get("signature").getAsString(); + MojangSkin skin = new MojangSkin(null, val, sig); + this.preSetKeyToMojangSkinMap.put(mapKey + "-" + key.toLowerCase(), skin); + }); + } + + private void loadUUIDSkinCache() { + if (this.cacheFile == null || this.cacheFile.getConfiguration() == null) { + return; + } + + if (!this.cacheFile.getConfiguration().contains("cache")) { + return; + } + + this.uuidToMojangSkinMap.clear(); + + String jsonString = this.cacheFile.getString("cache"); + JsonElement jsonElement = jsonParser.parse(jsonString); + + if (jsonElement == null || !jsonElement.isJsonObject()) { + return; + } + + JsonObject jsonObject = jsonElement.getAsJsonObject(); + jsonObject.entrySet().iterator().forEachRemaining(entry -> { + String key = entry.getKey(); + UUID uuid = UUID.fromString(key); + String value = entry.getValue().getAsString(); + MojangSkin mojangSkin = eLib.GSON.fromJson(value, MojangSkin.class); + this.uuidToMojangSkinMap.put(uuid, mojangSkin); + }); + } + + private JsonElement readHttpURLConnection(HttpURLConnection connection) throws IOException { + StringBuilder output = new StringBuilder(); + BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + + String line; + while ((line = reader.readLine()) != null) { + output.append(line); + } + + reader.close(); + return jsonParser.parse(output.toString()); + } + + private HttpURLConnection createHttpURLConnection(UUID uuid) throws IOException { + String shortUUID = uuid.toString().replace("-", ""); + HttpURLConnection connection = (HttpURLConnection) new URL(String.format(mojangURL, shortUUID)).openConnection(); + connection.setRequestMethod("GET"); + connection.setRequestProperty("User-Agent", "ElevateMC eLib Mojang Fetcher"); + connection.setConnectTimeout(5000); + connection.setReadTimeout(5000); + connection.setDoOutput(true); + return connection; + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/skin/MojangSkinListener.java b/Network/eLib/src/main/java/com/elevatemc/elib/skin/MojangSkinListener.java new file mode 100644 index 0000000..6395aa1 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/skin/MojangSkinListener.java @@ -0,0 +1,16 @@ +package com.elevatemc.elib.skin; + +import lombok.AllArgsConstructor; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; + +@AllArgsConstructor +public class MojangSkinListener implements Listener { + private MojangSkinHandler skinHandler; + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerQuit(PlayerQuitEvent event) { + this.skinHandler.removeTemporarySkinEntry(event.getPlayer().getUniqueId()); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/skinfix/SkinFixCommand.java b/Network/eLib/src/main/java/com/elevatemc/elib/skinfix/SkinFixCommand.java new file mode 100644 index 0000000..c4e51e1 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/skinfix/SkinFixCommand.java @@ -0,0 +1,21 @@ +package com.elevatemc.elib.skinfix; + + +import com.elevatemc.elib.command.Command; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public class SkinFixCommand { + @Command(names = {"disableskinfix"}, permission = "op", hidden = true) + public static void execute(Player player) { + if (SkinFixHandler.skinFix) { + SkinFixHandler.skinFix = false; + player.sendMessage(ChatColor.RED + "Disabled Skin Fix"); + } else { + SkinFixHandler.skinFix = true; + player.sendMessage(ChatColor.GREEN + "Enabled Skin Fix"); + } + + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/skinfix/SkinFixHandler.java b/Network/eLib/src/main/java/com/elevatemc/elib/skinfix/SkinFixHandler.java new file mode 100644 index 0000000..e1a431f --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/skinfix/SkinFixHandler.java @@ -0,0 +1,100 @@ +package com.elevatemc.elib.skinfix; + + +import com.elevatemc.elib.util.TaskUtil; +import com.elevatemc.spigot.handler.PacketHandler; +import com.mojang.authlib.GameProfile; +import net.minecraft.server.v1_8_R3.Packet; +import net.minecraft.server.v1_8_R3.PacketPlayOutPlayerInfo; +import net.minecraft.server.v1_8_R3.PlayerConnection; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerChangedWorldEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +public class SkinFixHandler implements Listener, PacketHandler { + public static boolean skinFix = true; + + private final Map> sentInfo = new HashMap<>(); + public void handleSentPacket(PlayerConnection connection, Packet incoming) { + if(!(incoming instanceof PacketPlayOutPlayerInfo)) { + return; + } + if (!skinFix) { + return; + } + + PacketPlayOutPlayerInfo packet = (PacketPlayOutPlayerInfo) incoming; + Player player = connection.getPlayer(); + + PacketPlayOutPlayerInfo.EnumPlayerInfoAction action = packet.getA(); + GameProfile updated = packet.getPlayer(); + + if (action == PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER) { // add + Player updatedPlayer = Bukkit.getPlayer(updated.getId()); + if (updatedPlayer == null) { + return; + } + + if (!sentInfo.containsKey(player.getUniqueId())) { + sentInfo.put(player.getUniqueId(), new HashSet<>()); + } + + Set sent = sentInfo.get(player.getUniqueId()); + + if (sent.add(updated.getId())) { + TaskUtil.scheduleOnPool(() -> sendUpdate(player, updatedPlayer), 500, TimeUnit.MILLISECONDS); + } + } else if (action == PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER) { // remove + if (sentInfo.containsKey(player.getUniqueId())) { + sentInfo.get(player.getUniqueId()).remove(updated.getId()); + } + } + } + + + @EventHandler + public void onPlayerJoinEvent(PlayerJoinEvent event) { + if (!skinFix) { + return; + } + + TaskUtil.scheduleOnPool(() -> + sendUpdate(event.getPlayer(), event.getPlayer()), 500, TimeUnit.MILLISECONDS); + } + @EventHandler + public void onPlayerWorld(PlayerChangedWorldEvent event) { + if (!skinFix) { + return; + } + + TaskUtil.scheduleOnPool(() -> + sendUpdate(event.getPlayer(), event.getPlayer()), 500, TimeUnit.MILLISECONDS); + } + @EventHandler + public void onPlayerQuitEvent(PlayerQuitEvent event) { + sentInfo.remove(event.getPlayer().getUniqueId()); + } + + public void sendUpdate(Player viewer, Player player) { + if (!skinFix) { + return; + } + + PacketPlayOutPlayerInfo remove = new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, ((CraftPlayer) player).getHandle()); + PacketPlayOutPlayerInfo add = new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, ((CraftPlayer) player).getHandle()); + + ((CraftPlayer) viewer).getHandle().playerConnection.sendPacket(remove); + ((CraftPlayer) viewer).getHandle().playerConnection.sendPacket(add); + } + + + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/tab/TabListManager.java b/Network/eLib/src/main/java/com/elevatemc/elib/tab/TabListManager.java new file mode 100644 index 0000000..8c063d5 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/tab/TabListManager.java @@ -0,0 +1,119 @@ +package com.elevatemc.elib.tab; + + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.tab.data.TabList; +import lombok.Getter; +import lombok.Setter; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * @author ImHacking + * @date 4/10/2022 + */ +public class TabListManager implements Listener { + + @Getter + @Setter + private TabList tabList; + + private List players = new ArrayList<>(); + + public TabListManager() { + eLib.getInstance().getServer().getPluginManager().registerEvents(this, eLib.getInstance()); + new TabRunnable().runTaskTimerAsynchronously(eLib.getInstance(), 2L, 20L); + } + + /** + * Update the players slot in a given row/column with value + * + * @param player the player we are updating + * @param row the row of the slot we are updating + * @param column the column of the slot we are updating + * @param value the value we want put in the slot. Use color codes we don't translate + */ + public void updateSlot(Player player, int row, int column, String value) { + if (players.contains(player.getUniqueId())) { + tabList.updateSlot(player, row, column, value); + } + } + + /** + * Update the players slot with a given UUID in the given row/column with value + * + * @param uuid the uuid of the player we are updating + * @param row the row of the slot we are updating + * @param column the column of the slot we are updating + * @param value the value we want put in the slot. Use color codes we don't translate + */ + public void updateSlot(UUID uuid, int row, int column, String value) { + if (players.contains(uuid)) { + Player player = eLib.getInstance().getServer().getPlayer(uuid); + if (player != null) { + tabList.updateSlot(player, row, column, value); + } + } + } + + /** + * Update every player that has their TabList setup with the value in the given row/column + * + * @param row the row of the slot we ar updating + * @param column the column of the slot we are updating + * @param value the value we want put in the slot. Use color codes we don't translate + */ + public void updateGlobally(int row, int column, String value) { + for (UUID uuid : players) { + updateSlot(uuid, row, column, value); + } + } + + /** + * Clear the slot of the player at the given row/column + * + * @param player the player we are clearing the slot of + * @param row the row of the slot we are clearing + * @param column the column of the slot we are clearing + */ + public void clearSlot(Player player, int row, int column) { + if (players.contains(player.getUniqueId())) { + tabList.updateSlot(player, row, column, ""); + } + } + + /** + * Clear the slot of the player at the given row/column + * + * @param uuid the uuid of the player we are clearing the slot of + * @param row the row of the slot we are clearing + * @param column the column of the slot we are clearing + */ + public void clearSlot(UUID uuid, int row, int column) { + Player player = eLib.getInstance().getServer().getPlayer(uuid); + if (player != null) { + clearSlot(player, row, column); + } + } + + /** + * Clear the slot of every player with the given row/column + * + * @param row the row of the slot we are clearing + * @param column the column of the slot we are clearing + */ + public void clearGlobally(int row, int column) { + for (UUID uuid : players) { + clearSlot(uuid, row, column); + } + } + + public void removePlayer(UUID uuid) { + this.players.remove(uuid); + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/tab/TabRunnable.java b/Network/eLib/src/main/java/com/elevatemc/elib/tab/TabRunnable.java new file mode 100644 index 0000000..d8a04ea --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/tab/TabRunnable.java @@ -0,0 +1,67 @@ +package com.elevatemc.elib.tab; + + + +import com.elevatemc.elib.eLib; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Table; +import net.md_5.bungee.api.chat.BaseComponent; +import net.minecraft.server.v1_8_R3.PacketPlayOutPlayerListHeaderFooter; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; +import com.elevatemc.elib.tab.data.TabList; +import com.elevatemc.elib.util.ChatUtils; + +/** + * @author ImHacking + * @date 4/10/2022 + */ +public class TabRunnable extends BukkitRunnable { + + private Table oldValues = HashBasedTable.create(); + + @Override + public void run() { + + TabList tabList = eLib.getInstance().getTabHandler().getTabList(); + + if (tabList != null) { + for (Player player : tabList.getPlugin().getServer().getOnlinePlayers()) { + String header = tabList.getProvider().getHeader(player); + String footer = tabList.getProvider().getFooter(player); + sendHeaderFooter(player, header, footer); + Table table = tabList.getProvider().provide(player); + + for (Table.Cell cell : table.cellSet()) { + tabList.updateSlot(player, cell.getRowKey(), cell.getColumnKey(), cell.getValue()); + } + + for (Table.Cell cell : oldValues.cellSet()) { + if (table.get(cell.getRowKey(), cell.getColumnKey()) == null || table.get(cell.getRowKey(), cell.getColumnKey()).isEmpty()) { + tabList.updateSlot(player, cell.getRowKey(), cell.getColumnKey(), ""); + } + } + + oldValues = table; + + } + } + + + + } + private void sendHeaderFooter(Player player, String header, String footer) { + PacketPlayOutPlayerListHeaderFooter packet = new PacketPlayOutPlayerListHeaderFooter(); + packet.header = new BaseComponent[1]; + if(header != null) + packet.header[0] = ChatUtils.colorizeTextComponent(header); + packet.footer = new BaseComponent[1]; + if(footer != null) + packet.footer[0] = ChatUtils.colorizeTextComponent(footer); + if (header != null && footer != null) { + ((CraftPlayer)player).getHandle().playerConnection.sendPacket(packet); + } + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/tab/data/TabList.java b/Network/eLib/src/main/java/com/elevatemc/elib/tab/data/TabList.java new file mode 100644 index 0000000..6d28ae7 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/tab/data/TabList.java @@ -0,0 +1,325 @@ +package com.elevatemc.elib.tab.data; + +import com.elevatemc.elib.tab.provider.TabProvider; +import com.elevatemc.spigot.handler.PacketHandler; +import com.google.common.collect.Multimap; +import com.google.common.collect.Table; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.HttpAuthenticationService; +import com.mojang.authlib.properties.Property; +import com.mojang.authlib.properties.PropertyMap; +import com.mojang.util.UUIDTypeAdapter; +import lombok.Getter; +import net.minecraft.server.v1_8_R3.*; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.plugin.java.JavaPlugin; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; +import com.elevatemc.elib.eLib; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.Field; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.*; + +public class TabList implements Listener, PacketHandler { + + @Getter private JavaPlugin plugin; + @Getter private TabProvider provider; + + public static Field info_action, info_profile_list; + + public static Field team_name, team_display, team_prefix, team_suffix, team_players, team_mode, team_color, team_nametag; + + public static Field network_channel; + + public static Field propertymap; + public static Property head = new Property("textures", "ewogICJ0aW1lc3RhbXAiIDogMTY1OTM2NjA2MDExNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zNTU3MzgyZDU4MWE5NzZkYTc2NmI0NGNiNDEwNWMzNjQzMWEzNjIwYzE3MjUwMTczYjI2YzY2MmNlNGY4M2NmIgogICAgfQogIH0KfQ==", "lTAFCWBk/YTBheTDwnkYsMj35DvJqy2BFGHcsQJxSN41Jin0wq5Z9ZRbL1lx2Vryijq4SbCcEgrRQvG8iz5vxcX8p4EFOFeQjEz4oaAlXSVGSfscOKeHSUA0xmPTx1JmPOTyuq1KvZoTsYa9ctx3oZMOuhXUdQmX5ZoBo9gdvXIiEfLv6+LoBoqkp9MQ8G8hcxw6FKkQ6sgKaCkRqQZ4lLxrnflm/CMwlcXD99DGs5VTlYAtYBKRl5bcoutEXBebz4Lw093yz7Z2I1jJJAGrRtyExkXkGxXOKL/w+Cnnu0mxjcZvFS2enKf++6FL3KPybZ5JtHmTLyXRVqWZ5OoekZHnLQmto8CaqvSCTDt0uLdMBfwocWJZdIoFy7EecpsjBdhS1UojkT6rkrB8Rf/mPz0xGktOwdOZn9ReYUcilU+p+x3+Q9a6ODlf9km2nK+27TY2pEnneL9LFK9mepR6gXGvkx11VjmtJNp9EPflPiE0WVSSeazPFFu2olyMCzJ1Clyzw2T5wgusSjSnhoQvKExsvjVtATbybN97diamzjzz3I5cgANbXOYFB3Z0Uey5tpcJdtHE9VKYHauaTQSqf8VXJYEDT0oOnLPoTMe11xtsmz/riWkfgGzp+cCoGpW8DyqyB9BVn9jSi9z99YCRiXYDgZyY+vCGwklrSnpECKI="); + + /** + * @param plugin + */ + public TabList(JavaPlugin plugin, TabProvider provider) { + this.plugin = plugin; + this.provider = provider; + + this.plugin.getServer().getPluginManager().registerEvents(this, this.plugin); + + + } + static { + try { + info_action = PacketPlayOutPlayerInfo.class.getDeclaredField("a"); + info_action.setAccessible(true); + info_profile_list = PacketPlayOutPlayerInfo.class.getDeclaredField("b"); + info_profile_list.setAccessible(true); + + team_name = PacketPlayOutScoreboardTeam.class.getDeclaredField("a"); + team_name.setAccessible(true); + team_display = PacketPlayOutScoreboardTeam.class.getDeclaredField("b"); + team_display.setAccessible(true); + team_prefix = PacketPlayOutScoreboardTeam.class.getDeclaredField("c"); + team_prefix.setAccessible(true); + team_suffix = PacketPlayOutScoreboardTeam.class.getDeclaredField("d"); + team_suffix.setAccessible(true); + team_players = PacketPlayOutScoreboardTeam.class.getDeclaredField("g"); + team_players.setAccessible(true); + team_color = PacketPlayOutScoreboardTeam.class.getDeclaredField("f"); + team_color.setAccessible(true); + team_mode = PacketPlayOutScoreboardTeam.class.getDeclaredField("h"); + team_mode.setAccessible(true); + team_nametag = PacketPlayOutScoreboardTeam.class.getDeclaredField("e"); + team_nametag.setAccessible(true); + + network_channel = NetworkManager.class.getDeclaredField("channel"); + network_channel.setAccessible(true); + + propertymap = PropertyMap.class.getDeclaredField("properties"); + propertymap.setAccessible(true); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * You can set the default head for the players in the tab. + * Warning due to mojang rate limit this can only be accessed once every minute. + * so basically if you restart the server make sure it was a minute before the last start. + * Will be a good idea if you turn this off for testing plugins or if you need to restart often. + * + * @param head the uuid of the players head you want + * @return this + */ + public TabList setHead(UUID head) { + try { + URL url = HttpAuthenticationService + .constantURL("https://sessionserver.mojang.com/session/minecraft/profile/" + + UUIDTypeAdapter.fromUUID(head) + "?unsigned=false"); + + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.connect(); + + if (connection.getResponseCode() == 429) { + this.plugin.getLogger().severe("Tab List could not get the players head for the tab please wait 1 minute and restart"); + return this; + } + + JSONObject obj = (JSONObject) new JSONParser().parse(new BufferedReader(new InputStreamReader(connection.getInputStream()))); + JSONObject props = (JSONObject) ((JSONArray) obj.get("properties")).get(0); + this.head = new Property("textures", props.get("value").toString(), props.get("signature").toString()); + } catch (IOException | ParseException e) { + e.printStackTrace(); + } + return this; + } + + @EventHandler(priority = EventPriority.LOWEST) + private void onQuit(PlayerQuitEvent event) { + Player player = event.getPlayer(); + + if (eLib.getInstance().getTabHandler() != null) { + eLib.getInstance().getTabHandler().removePlayer(player.getUniqueId()); + } + } + + @EventHandler(priority = EventPriority.LOWEST) + private void onLogin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + + HashMap heads = new HashMap<>(); + + for (Table.Cell cell : provider.getHeads(player).cellSet()) { + heads.put(rowColumnToSlot(cell.getRowKey(), cell.getColumnKey()), cell.getValue()); + } + + setupBoard(((CraftPlayer) player).getHandle(), heads); + + try { + PacketPlayOutScoreboardTeam a = new PacketPlayOutScoreboardTeam(); + team_mode.set(a, 3); + team_name.set(a, "eLib"); + team_display.set(a, "eLib"); + team_color.set(a, -1); + team_players.set(a, Arrays.asList(player.getName())); + + for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { + ((CraftPlayer) onlinePlayer).getHandle().playerConnection.sendPacket(a); + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + private void setupBoard(EntityPlayer player, HashMap heads) { + PacketPlayOutPlayerInfo player_packet = new PacketPlayOutPlayerInfo();; + PacketPlayOutScoreboardTeam t; + GameProfile profile; + + try { + List players_in_packet = (List) info_profile_list.get(player_packet); + info_action.set(player_packet, PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER); + for (int r = 0; r < 20; r++) {//slot 0-59 for 1.7 players + for (int c = 0; c < 3l; c++) { + int slot = rowColumnToSlot(r, c); + String name = playerName(slot); + + profile = new GameProfile(UUID.randomUUID(), name); + + Property customHead = heads.get(slot); + + Multimap prop = (Multimap) propertymap.get(profile.getProperties()); + if (customHead != null) { + prop.put("textures", customHead); + } else if (head != null) { + prop.put("textures", head); + } + + players_in_packet.add(new PacketPlayOutPlayerInfo.PlayerInfoData(profile, 0, WorldSettings.EnumGamemode.SURVIVAL, null)); + } + } + for (int i = 60; i < 80; i++) {//slot 60-79 for 1.8 players + String name = playerName(i); + profile = new GameProfile(UUID.randomUUID(), name); + + Property customHead = heads.get(i); + + Multimap prop = (Multimap) propertymap.get(profile.getProperties()); + if (customHead != null) { + prop.put("textures", customHead); + } else if (head != null) { + prop.put("textures", head); + } + + players_in_packet.add(new PacketPlayOutPlayerInfo.PlayerInfoData(profile, 0, WorldSettings.EnumGamemode.SURVIVAL, null)); + } + + player.playerConnection.sendPacket(player_packet); + + t = new PacketPlayOutScoreboardTeam(); + team_name.set(t, "eLib"); + team_display.set(t, "eLib"); + team_mode.set(t, 0); + team_color.set(t, -1); + team_nametag.set(t, "always"); + Collection players = new ArrayList<>(); + for (Player other : Bukkit.getOnlinePlayers()) { + players.add(other.getName()); + } + team_players.set(t, players); + player.playerConnection.sendPacket(t); + + for (int r = 0; r < 20; r++) { + for (int c = 0; c < 4; c++) { + String name = playerName(rowColumnToSlot(r, c)); + String teamName = "$" + name; + t = new PacketPlayOutScoreboardTeam(); + team_name.set(t, teamName); + team_display.set(t, teamName); + team_mode.set(t, 0); + team_color.set(t, -1); + team_nametag.set(t, "always"); + team_players.set(t, Arrays.asList(name)); + player.playerConnection.sendPacket(t); + } + } + } catch (Exception e) { + e.printStackTrace(); + this.plugin.getLogger().warning("Error setting up player board for " + player.getName()); + } + } + + private int rowColumnToSlot(int r, int c) { + return r + c * 20; + } + + private String playerName(int slot) { + return ChatColor.BOLD + ChatColor.GREEN.toString() + ChatColor.UNDERLINE + + ChatColor.YELLOW + + (slot >= 10 ? ChatColor.COLOR_CHAR + String.valueOf(slot / 10) + + ChatColor.COLOR_CHAR + slot % 10 + : ChatColor.BLACK.toString() + + ChatColor.COLOR_CHAR + slot) + ChatColor.RESET; + } + + public void updateSlot(Player player, int row, int column, String value) { + if (row > 19) { + throw new RuntimeException("Row is above 19 " + row); + } + if (column > 4) { + throw new RuntimeException("Column is above 4 " + column); + } + String prefix = value; + String suffix = ""; + if (value.length() > 16) { + prefix = value.substring(0, 16); + suffix = ChatColor.getLastColors(prefix) + value.substring(16); + if (suffix.length() > 16) { + suffix = suffix.substring(0, 16); + } + } + String teamName = "$" + playerName(rowColumnToSlot(row, column)); + PacketPlayOutScoreboardTeam t = new PacketPlayOutScoreboardTeam(); + try { + team_name.set(t, teamName); + team_display.set(t, teamName); + team_prefix.set(t, prefix); + team_suffix.set(t, suffix); + team_mode.set(t, 2); + team_nametag.set(t, "always"); + team_color.set(t, -1); + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(t); + } catch (IllegalAccessException e) { + e.printStackTrace(); + this.plugin.getLogger().warning(String.format("Error setting slot %s, %d, %d, %s", player.getName(), row, + column, value)); + } + } + + + @Override + public void handleSentPacket(PlayerConnection playerConnection, Packet packet) { + if (packet instanceof PacketPlayOutScoreboardTeam) { + PacketPlayOutScoreboardTeam p = (PacketPlayOutScoreboardTeam) packet; + try { + int mode = team_mode.getInt(p); + String teamname = (String) team_name.get(p); + /* + * if the packet is a remove player packet and the team isn't eLib this happens + * if a plugin is removing a player from a team and not adding it to another we + * need to check this for 1.8 players consistency as players not in a team will + * be displayed first on the scoreboard in front of all of our slots we set the + * mode of the packet to a add_player packet and change the team name and + * display name to our team "eLib" the only time a player would be removed from + * eLib is if we actually did it so dont check for eLib also changing it to a + * addplayer packet if it was eLib would mean adding a player to a team they + * are already in which would crash 1.7 players + */ + if (mode == 4 && !teamname.equals("eLib")) { + team_mode.set(p, 3); + team_name.set(p, "eLib"); + team_display.set(p, "eLib"); + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + + + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/tab/provider/TabProvider.java b/Network/eLib/src/main/java/com/elevatemc/elib/tab/provider/TabProvider.java new file mode 100644 index 0000000..599f2d1 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/tab/provider/TabProvider.java @@ -0,0 +1,26 @@ +package com.elevatemc.elib.tab.provider; + +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Table; +import com.mojang.authlib.properties.Property; +import org.bukkit.entity.Player; + +import java.util.HashMap; + +public interface TabProvider { + // row, column, string + default Table provide(Player player) { + return null; + } + default String getHeader(Player player) { + return null; + } + + default String getFooter(Player player) { + return null; + } + + default Table getHeads(Player player) { + return HashBasedTable.create(); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/ArrayWrapper.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/ArrayWrapper.java new file mode 100644 index 0000000..a61e86b --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/ArrayWrapper.java @@ -0,0 +1,111 @@ +package com.elevatemc.elib.util; + +import org.apache.commons.lang.Validate; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Collection; + +/** + * Represents a wrapper around an array class of an arbitrary reference type, + * which properly implements "value" hash code and equality functions. + *

+ * This class is intended for use as a key to a map. + *

+ * + * @param The type of elements in the array. + * @author Glen Husman + * @see Arrays + */ +public final class ArrayWrapper { + + /** + * Creates an array wrapper with some elements. + * + * @param elements The elements of the array. + */ + public ArrayWrapper(E... elements) { + setArray(elements); + } + + private E[] _array; + + /** + * Retrieves a reference to the wrapped array instance. + * + * @return The array wrapped by this instance. + */ + public E[] getArray() { + return _array; + } + + /** + * Set this wrapper to wrap a new array instance. + * + * @param array The new wrapped array. + */ + public void setArray(E[] array) { + Validate.notNull(array, "The array must not be null."); + _array = array; + } + + /** + * Determines if this object has a value equivalent to another object. + * + * @see Arrays#equals(Object[], Object[]) + */ + @SuppressWarnings("rawtypes") + @Override + public boolean equals(Object other) { + if (!(other instanceof ArrayWrapper)) { + return false; + } + return Arrays.equals(_array, ((ArrayWrapper) other)._array); + } + + /** + * Gets the hash code represented by this objects value. + * + * @return This object's hash code. + * @see Arrays#hashCode(Object[]) + */ + @Override + public int hashCode() { + return Arrays.hashCode(_array); + } + + /** + * Converts an iterable element collection to an array of elements. + * The iteration order of the specified object will be used as the array element order. + * + * @param list The iterable of objects which will be converted to an array. + * @param c The type of the elements of the array. + * @return An array of elements in the specified iterable. + */ + @SuppressWarnings("unchecked") + public static T[] toArray(Iterable list, Class c) { + int size = -1; + if (list instanceof Collection) { + @SuppressWarnings("rawtypes") + Collection coll = (Collection) list; + size = coll.size(); + } + + + if (size < 0) { + size = 0; + // Ugly hack: Count it ourselves + for (@SuppressWarnings("unused") T element : list) { + size++; + } + } + + T[] result = (T[]) Array.newInstance(c, size); + int i = 0; + for (T element : list) { // Assumes iteration order is consistent + result[i++] = element; // Assign array element at index THEN increment counter + } + return result; + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/BlockUtils.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/BlockUtils.java new file mode 100644 index 0000000..75eca99 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/BlockUtils.java @@ -0,0 +1,132 @@ +package com.elevatemc.elib.util; + +import com.google.common.collect.ImmutableSet; +import net.minecraft.server.v1_8_R3.*; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.v1_8_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import org.bukkit.entity.Player; + +import java.util.Set; + +public class BlockUtils { + + private static final Set INTERACTABLE; + + public static boolean isInteractable(Block block) { + return isInteractable(block.getType()); + } + + public static boolean isInteractable(Material material) { + return INTERACTABLE.contains(material); + } + + public static boolean setBlockFast(World world,int x,int y,int z,int blockId,byte data) { + net.minecraft.server.v1_8_R3.World w = ((CraftWorld)world).getHandle(); + + final Chunk chunk = w.getChunkAt(x >> 4, z >> 4); + BlockPosition position = new BlockPosition(x, y, z); + return chunk.a(position, chunk.getBlockData(position)) != null; + //return a(chunk, x & 15, y, z & 15, net.minecraft.server.v1_8_R3.Block.getById(blockId), data); + } + + private static void queueChunkForUpdate(Player player,int cx,int cz) { + ((CraftPlayer)player).getHandle().chunkCoordIntPairQueue.add(new ChunkCoordIntPair(cx, cz)); + } + + /* + private static boolean a(Chunk chunk,int xPos,int yPos,int zPos,net.minecraft.server.v1_8_R3.Block block,int data) { + int i1 = zPos << 4 | xPos; + + if (yPos >= chunk.b[i1] - 1) { + chunk.b[i1] = -999; + } + + int j1 = chunk.heightMap[i1]; + net.minecraft.server.v1_8_R3.Block block1 = chunk.getType(xPos, yPos, zPos); + IBlockData blockData = chunk.getBlockData(new BlockPosition(xPos, yPos, zPos)); + if (block1 == block) { + return false; + } else { + boolean flag = false; + ChunkSection chunksection = chunk.getSections()[yPos >> 4]; + if (chunksection == null) { + if (block == Blocks.AIR) { + return false; + } + + chunksection = chunk.getSections()[yPos >> 4] = new ChunkSection(yPos >> 4 << 4, !chunk.world.worldProvider.g); + flag = yPos >= j1; + } + + int xLoc = chunk.locX * 16 + xPos; + int yLoc = chunk.locZ * 16 + zPos; + if (!chunk.world.isClientSide) { + block1.remove(chunk.world, new BlockPosition(xLoc, yPos, yLoc), blockData); + } + + if (!(block1 instanceof IContainer)) { + chunksection.setType(xPos, yPos & 15, zPos, blockData); + } + + if (!chunk.world.isClientSide) { + block1.remove(chunk.world, new BlockPosition(xLoc, yPos, yLoc), blockData); + } else if (block1 instanceof IContainer) { + chunk.world.t(new BlockPosition(xLoc, yPos, yLoc)); + } + + if (block1 instanceof IContainer) { + chunksection.setTypeId(xPos, yPos & 15, zPos, block); + } + + if (chunksection.getTypeId(xPos, yPos & 15, zPos) != block) { + return false; + } else { + chunksection.setData(xPos, yPos & 15, zPos, data); + if (flag) { + chunk.initLighting(); + } + + TileEntity tileentity; + if (block1 instanceof IContainer) { + tileentity = chunk.e(xPos, yPos, zPos); + if (tileentity != null) { + tileentity.u(); + } + } + + if (!chunk.world.isStatic && (!chunk.world.captureBlockStates || block instanceof BlockContainer)) { + block.onPlace(chunk.world, xLoc, yPos, yLoc); + } + + if (block instanceof IContainer) { + if (chunk.getType(xPos, yPos, zPos) != block) { + return false; + } + + tileentity = chunk.e(xPos, yPos, zPos); + if (tileentity == null) { + tileentity = ((IContainer)block).a(chunk.world, data); + chunk.world.setTileEntity(xLoc, yPos, yLoc, tileentity); + } + + if (tileentity != null) { + tileentity.u(); + } + } + + chunk.n = true; + return true; + } + } + } + + */ + static { + INTERACTABLE = ImmutableSet.of(Material.FENCE_GATE, Material.FURNACE, Material.BURNING_FURNACE, Material.BREWING_STAND, Material.CHEST, Material.HOPPER, new Material[]{Material.DISPENSER, Material.WOODEN_DOOR, Material.STONE_BUTTON, Material.WOOD_BUTTON, Material.TRAPPED_CHEST, Material.TRAP_DOOR, Material.LEVER, Material.DROPPER, Material.ENCHANTMENT_TABLE, Material.BED_BLOCK, Material.ANVIL, Material.BEACON}); + } + + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/BungeeUtil.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/BungeeUtil.java new file mode 100644 index 0000000..b9c9c5e --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/BungeeUtil.java @@ -0,0 +1,15 @@ +package com.elevatemc.elib.util; + +import com.elevatemc.elib.eLib; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; +import org.bukkit.entity.Player; + +public class BungeeUtil { + public static void sendToServer(Player player, String server) { + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + out.writeUTF("Connect"); + out.writeUTF(server.toLowerCase()); + player.sendPluginMessage(eLib.getInstance(), "BungeeCord", out.toByteArray()); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/Callback.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/Callback.java new file mode 100644 index 0000000..163ccf6 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/Callback.java @@ -0,0 +1,13 @@ +package com.elevatemc.elib.util; + +import java.io.Serializable; + +public interface Callback extends Serializable { + + /** + * Called when the request is successfully completed + * + * @param data the data received from the call + */ + void callback(T data); +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/ChatUtils.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/ChatUtils.java new file mode 100644 index 0000000..26befc3 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/ChatUtils.java @@ -0,0 +1,37 @@ +package com.elevatemc.elib.util; + +import lombok.NonNull; +import net.md_5.bungee.api.chat.TextComponent; +import net.minecraft.server.v1_8_R3.IChatBaseComponent; +import org.bukkit.ChatColor; + +/** + * @author ImHacking + * @date 5/22/2022 + */ +public class ChatUtils { + private static final char AMPERSAND = '&'; + + private ChatUtils() { + throw new AssertionError("Utility classes cannot be instantiated."); // seal + } + + @NonNull + public static String colorize(@NonNull String s) { + return ChatColor.translateAlternateColorCodes(AMPERSAND, s); + } + + @NonNull + public static IChatBaseComponent colorizeBaseComponent(@NonNull String s) { + return IChatBaseComponent.ChatSerializer.a(colorize(s)); + } + + @NonNull + public static TextComponent colorizeTextComponent(@NonNull String s) { + return new TextComponent(colorize(s)); + } + @NonNull + public static String fromBaseComponent(@NonNull IChatBaseComponent cbc) { + return colorize(cbc.getText()); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/ClassUtils.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/ClassUtils.java new file mode 100644 index 0000000..061473b --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/ClassUtils.java @@ -0,0 +1,77 @@ +package com.elevatemc.elib.util; + +import com.google.common.collect.ImmutableSet; +import lombok.experimental.UtilityClass; +import org.bukkit.plugin.Plugin; + +import java.io.IOException; +import java.net.URL; +import java.security.CodeSource; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +@UtilityClass +public final class ClassUtils { + /** + * Gets all the classes in a the provided package. + * + * @param plugin The plugin who owns the package + * @param packageName The package to scan classes in. + * @return The classes in the package packageName. + */ + //TODO: Make this not require a Plugin object. + public static Collection> getClassesInPackage(Plugin plugin, String packageName) { + Collection> classes = new ArrayList<>(); + + CodeSource codeSource = plugin.getClass().getProtectionDomain().getCodeSource(); + URL resource = codeSource.getLocation(); + String relPath = packageName.replace('.', '/'); + String resPath = resource.getPath().replace("%20", " "); + String jarPath = resPath.replaceFirst("[.]jar[!].*", ".jar").replaceFirst("file:", ""); + JarFile jarFile; + + try { + jarFile = new JarFile(jarPath); + } catch (IOException e) { + throw (new RuntimeException("Unexpected IOException reading JAR File '" + jarPath + "'", e)); + } + + Enumeration entries = jarFile.entries(); + + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String entryName = entry.getName(); + String className = null; + + if (entryName.endsWith(".class") && entryName.startsWith(relPath) && entryName.length() > (relPath.length() + "/".length())) { + className = entryName.replace('/', '.').replace('\\', '.').replace(".class", ""); + } + + if (className != null) { + Class clazz = null; + + try { + clazz = Class.forName(className); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + + if (clazz != null) { + classes.add(clazz); + } + } + } + + try { + jarFile.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + return (ImmutableSet.copyOf(classes)); + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/ComponentBuilder.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/ComponentBuilder.java new file mode 100644 index 0000000..5e6cf40 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/ComponentBuilder.java @@ -0,0 +1,124 @@ +package com.elevatemc.elib.util; + + +import com.elevatemc.elib.util.message.MessageTranslator; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.TextComponent; +import org.bukkit.entity.Player; + +import java.lang.reflect.Field; +import java.util.List; + +public class ComponentBuilder extends net.md_5.bungee.api.chat.ComponentBuilder { + private static Field partsField; + private static Field currField; + + static { + try { + currField = net.md_5.bungee.api.chat.ComponentBuilder.class.getDeclaredField("current"); + partsField = net.md_5.bungee.api.chat.ComponentBuilder.class.getDeclaredField("parts"); + currField.setAccessible(true); + partsField.setAccessible(true); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } + } + + public ComponentBuilder(String text) { + super(text); + } + + public TextComponent getCurrent() { + try { + return (TextComponent) currField.get(this); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return null; + } + + public void setCurrent(TextComponent tc) { + try { + currField.set(this, tc); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + public List getParts() { + try { + return (List) partsField.get(this); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return null; + } + + public ComponentBuilder color(org.bukkit.ChatColor chatColor) { + super.color(ChatColor.getByChar(chatColor.getChar())); + return this; + } + + public ComponentBuilder tooltip(String text) { + this.hover(text); + return this; + } + + public ComponentBuilder hover(String text) { + this.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(MessageTranslator.translate(text)).create())); + return this; + } + + public ComponentBuilder command(String command) { + this.event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, command)); + return this; + } + + public ComponentBuilder then(ComponentBuilder builder) { + return this.then(builder.getCurrent()); + } + + public ComponentBuilder then(String text) { + return this.then(new ComponentBuilder(text).getCurrent()); + } + + public ComponentBuilder then(TextComponent textComponent) { + return this.append(textComponent); + } + + public ComponentBuilder append(TextComponent textComponent) { + String text = textComponent.getText(); + ChatColor color = textComponent.getColor(); + boolean bold = textComponent.isBold(); + boolean underline = textComponent.isUnderlined(); + boolean italic = textComponent.isUnderlined(); + boolean strike = textComponent.isStrikethrough(); + HoverEvent he = textComponent.getHoverEvent(); + ClickEvent ce = textComponent.getClickEvent(); + + append(text); + color(color); + underlined(underline); + italic(italic); + strikethrough(strike); + event(he); + event(ce); + + if (textComponent.getExtra() != null) { + for (BaseComponent bc : textComponent.getExtra()) { + if (bc instanceof TextComponent) { + append((TextComponent) bc); + } + } + } + + return this; + } + + public void send(Player player) { + player.spigot().sendMessage(this.create()); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/DirectionUtils.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/DirectionUtils.java new file mode 100644 index 0000000..a6d70a3 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/DirectionUtils.java @@ -0,0 +1,97 @@ +package com.elevatemc.elib.util; + +import org.bukkit.block.BlockFace; + +public class DirectionUtils { + + public static float directionToYaw(BlockFace direction) { + if (direction == null) { + return 0.0F; + } else { + switch(direction.getOppositeFace().ordinal()) { + case 1: + return 0.0F; + case 2: + return 22.5F; + case 3: + return 45.0F; + case 4: + return 67.5F; + case 5: + return 90.0F; + case 6: + return 112.5F; + case 7: + return 135.0F; + case 8: + return 157.5F; + case 9: + return 180.0F; + case 10: + return -157.5F; + case 11: + return -135.0F; + case 12: + return -112.5F; + case 13: + return -90.0F; + case 14: + return -67.5F; + case 15: + return -45.0F; + case 16: + return -22.5F; + default: + return 0.0F; + } + } + } + + public static BlockFace yawToDirection(float yaw) { + while(yaw > 180.0F) { + yaw -= 360.0F; + } + + while(yaw <= -180.0F) { + yaw += 360.0F; + } + + if ((double)yaw < -168.75D) { + return BlockFace.NORTH; + } else if ((double)yaw < -146.25D) { + return BlockFace.NORTH_NORTH_EAST; + } else if ((double)yaw < -123.75D) { + return BlockFace.NORTH_EAST; + } else if ((double)yaw < -101.25D) { + return BlockFace.EAST_NORTH_EAST; + } else if ((double)yaw < -78.75D) { + return BlockFace.EAST; + } else if ((double)yaw < -56.25D) { + return BlockFace.EAST_SOUTH_EAST; + } else if ((double)yaw < -33.75D) { + return BlockFace.SOUTH_EAST; + } else if ((double)yaw < -11.25D) { + return BlockFace.SOUTH_SOUTH_EAST; + } else if ((double)yaw < 11.25D) { + return BlockFace.SOUTH; + } else if ((double)yaw < 33.75D) { + return BlockFace.SOUTH_SOUTH_WEST; + } else if ((double)yaw < 56.25D) { + return BlockFace.SOUTH_WEST; + } else if ((double)yaw < 78.75D) { + return BlockFace.WEST_SOUTH_WEST; + } else if ((double)yaw < 101.25D) { + return BlockFace.WEST; + } else if ((double)yaw < 123.75D) { + return BlockFace.WEST_NORTH_WEST; + } else if ((double)yaw < 146.25D) { + return BlockFace.NORTH_WEST; + } else if ((double)yaw < 168.75D) { + return BlockFace.NORTH_NORTH_WEST; + } else { + return BlockFace.NORTH; + } + } + + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/EnchantUtil.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/EnchantUtil.java new file mode 100644 index 0000000..8448827 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/EnchantUtil.java @@ -0,0 +1,193 @@ +package com.elevatemc.elib.util; + +import org.bukkit.enchantments.Enchantment; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +public class EnchantUtil { + + public static final Map ENCHANTMENTS = new HashMap<>(); + public static final Map ALIAS_ENCHANTMENTS = new HashMap<>(); + + static { + ENCHANTMENTS.put("alldamage", Enchantment.DAMAGE_ALL); + ALIAS_ENCHANTMENTS.put("alldmg", Enchantment.DAMAGE_ALL); + ENCHANTMENTS.put("sharpness", Enchantment.DAMAGE_ALL); + ALIAS_ENCHANTMENTS.put("sharp", Enchantment.DAMAGE_ALL); + ALIAS_ENCHANTMENTS.put("dal", Enchantment.DAMAGE_ALL); + + ENCHANTMENTS.put("ardmg", Enchantment.DAMAGE_ARTHROPODS); + ENCHANTMENTS.put("baneofarthropods", Enchantment.DAMAGE_ARTHROPODS); + ALIAS_ENCHANTMENTS.put("baneofarthropod", Enchantment.DAMAGE_ARTHROPODS); + ALIAS_ENCHANTMENTS.put("arthropod", Enchantment.DAMAGE_ARTHROPODS); + ALIAS_ENCHANTMENTS.put("dar", Enchantment.DAMAGE_ARTHROPODS); + + ENCHANTMENTS.put("undeaddamage", Enchantment.DAMAGE_UNDEAD); + ENCHANTMENTS.put("smite", Enchantment.DAMAGE_UNDEAD); + ALIAS_ENCHANTMENTS.put("du", Enchantment.DAMAGE_UNDEAD); + + ENCHANTMENTS.put("digspeed", Enchantment.DIG_SPEED); + ENCHANTMENTS.put("efficiency", Enchantment.DIG_SPEED); + ALIAS_ENCHANTMENTS.put("minespeed", Enchantment.DIG_SPEED); + ALIAS_ENCHANTMENTS.put("cutspeed", Enchantment.DIG_SPEED); + ALIAS_ENCHANTMENTS.put("ds", Enchantment.DIG_SPEED); + ALIAS_ENCHANTMENTS.put("eff", Enchantment.DIG_SPEED); + + ENCHANTMENTS.put("durability", Enchantment.DURABILITY); + ALIAS_ENCHANTMENTS.put("dura", Enchantment.DURABILITY); + ENCHANTMENTS.put("unbreaking", Enchantment.DURABILITY); + ALIAS_ENCHANTMENTS.put("d", Enchantment.DURABILITY); + + ENCHANTMENTS.put("thorns", Enchantment.THORNS); + ENCHANTMENTS.put("highcrit", Enchantment.THORNS); + ALIAS_ENCHANTMENTS.put("thorn", Enchantment.THORNS); + ALIAS_ENCHANTMENTS.put("highercrit", Enchantment.THORNS); + ALIAS_ENCHANTMENTS.put("t", Enchantment.THORNS); + + ENCHANTMENTS.put("fireaspect", Enchantment.FIRE_ASPECT); + ENCHANTMENTS.put("fire", Enchantment.FIRE_ASPECT); + ALIAS_ENCHANTMENTS.put("meleefire", Enchantment.FIRE_ASPECT); + ALIAS_ENCHANTMENTS.put("meleeflame", Enchantment.FIRE_ASPECT); + ALIAS_ENCHANTMENTS.put("fa", Enchantment.FIRE_ASPECT); + + ENCHANTMENTS.put("knockback", Enchantment.KNOCKBACK); + ALIAS_ENCHANTMENTS.put("kback", Enchantment.KNOCKBACK); + ALIAS_ENCHANTMENTS.put("kb", Enchantment.KNOCKBACK); + ALIAS_ENCHANTMENTS.put("k", Enchantment.KNOCKBACK); + + ALIAS_ENCHANTMENTS.put("blockslootbonus", Enchantment.LOOT_BONUS_BLOCKS); + ENCHANTMENTS.put("fortune", Enchantment.LOOT_BONUS_BLOCKS); + ALIAS_ENCHANTMENTS.put("fort", Enchantment.LOOT_BONUS_BLOCKS); + ALIAS_ENCHANTMENTS.put("lbb", Enchantment.LOOT_BONUS_BLOCKS); + + ALIAS_ENCHANTMENTS.put("mobslootbonus", Enchantment.LOOT_BONUS_MOBS); + ENCHANTMENTS.put("mobloot", Enchantment.LOOT_BONUS_MOBS); + ENCHANTMENTS.put("looting", Enchantment.LOOT_BONUS_MOBS); + ALIAS_ENCHANTMENTS.put("lbm", Enchantment.LOOT_BONUS_MOBS); + + ALIAS_ENCHANTMENTS.put("oxygen", Enchantment.OXYGEN); + ENCHANTMENTS.put("respiration", Enchantment.OXYGEN); + ALIAS_ENCHANTMENTS.put("breathing", Enchantment.OXYGEN); + ENCHANTMENTS.put("breath", Enchantment.OXYGEN); + ALIAS_ENCHANTMENTS.put("o", Enchantment.OXYGEN); + + ENCHANTMENTS.put("protection", Enchantment.PROTECTION_ENVIRONMENTAL); + ALIAS_ENCHANTMENTS.put("prot", Enchantment.PROTECTION_ENVIRONMENTAL); + ENCHANTMENTS.put("protect", Enchantment.PROTECTION_ENVIRONMENTAL); + ALIAS_ENCHANTMENTS.put("p", Enchantment.PROTECTION_ENVIRONMENTAL); + + ALIAS_ENCHANTMENTS.put("explosionsprotection", Enchantment.PROTECTION_EXPLOSIONS); + ALIAS_ENCHANTMENTS.put("explosionprotection", Enchantment.PROTECTION_EXPLOSIONS); + ALIAS_ENCHANTMENTS.put("expprot", Enchantment.PROTECTION_EXPLOSIONS); + ALIAS_ENCHANTMENTS.put("blastprotection", Enchantment.PROTECTION_EXPLOSIONS); + ALIAS_ENCHANTMENTS.put("bprotection", Enchantment.PROTECTION_EXPLOSIONS); + ALIAS_ENCHANTMENTS.put("bprotect", Enchantment.PROTECTION_EXPLOSIONS); + ENCHANTMENTS.put("blastprotect", Enchantment.PROTECTION_EXPLOSIONS); + ALIAS_ENCHANTMENTS.put("pe", Enchantment.PROTECTION_EXPLOSIONS); + + ALIAS_ENCHANTMENTS.put("fallprotection", Enchantment.PROTECTION_FALL); + ENCHANTMENTS.put("fallprot", Enchantment.PROTECTION_FALL); + ENCHANTMENTS.put("featherfall", Enchantment.PROTECTION_FALL); + ALIAS_ENCHANTMENTS.put("featherfalling", Enchantment.PROTECTION_FALL); + ALIAS_ENCHANTMENTS.put("pfa", Enchantment.PROTECTION_FALL); + + ALIAS_ENCHANTMENTS.put("fireprotection", Enchantment.PROTECTION_FIRE); + ALIAS_ENCHANTMENTS.put("flameprotection", Enchantment.PROTECTION_FIRE); + ENCHANTMENTS.put("fireprotect", Enchantment.PROTECTION_FIRE); + ALIAS_ENCHANTMENTS.put("flameprotect", Enchantment.PROTECTION_FIRE); + ENCHANTMENTS.put("fireprot", Enchantment.PROTECTION_FIRE); + ALIAS_ENCHANTMENTS.put("flameprot", Enchantment.PROTECTION_FIRE); + ALIAS_ENCHANTMENTS.put("pf", Enchantment.PROTECTION_FIRE); + + ENCHANTMENTS.put("projectileprotection", Enchantment.PROTECTION_PROJECTILE); + ENCHANTMENTS.put("projprot", Enchantment.PROTECTION_PROJECTILE); + ALIAS_ENCHANTMENTS.put("pp", Enchantment.PROTECTION_PROJECTILE); + + ENCHANTMENTS.put("silktouch", Enchantment.SILK_TOUCH); + ALIAS_ENCHANTMENTS.put("softtouch", Enchantment.SILK_TOUCH); + ALIAS_ENCHANTMENTS.put("st", Enchantment.SILK_TOUCH); + + ENCHANTMENTS.put("waterworker", Enchantment.WATER_WORKER); + ENCHANTMENTS.put("aquaaffinity", Enchantment.WATER_WORKER); + ALIAS_ENCHANTMENTS.put("watermine", Enchantment.WATER_WORKER); + ALIAS_ENCHANTMENTS.put("ww", Enchantment.WATER_WORKER); + + ALIAS_ENCHANTMENTS.put("firearrow", Enchantment.ARROW_FIRE); + ENCHANTMENTS.put("flame", Enchantment.ARROW_FIRE); + ENCHANTMENTS.put("flamearrow", Enchantment.ARROW_FIRE); + ALIAS_ENCHANTMENTS.put("af", Enchantment.ARROW_FIRE); + + ENCHANTMENTS.put("arrowdamage", Enchantment.ARROW_DAMAGE); + ENCHANTMENTS.put("power", Enchantment.ARROW_DAMAGE); + ALIAS_ENCHANTMENTS.put("arrowpower", Enchantment.ARROW_DAMAGE); + ALIAS_ENCHANTMENTS.put("ad", Enchantment.ARROW_DAMAGE); + + ENCHANTMENTS.put("arrowknockback", Enchantment.ARROW_KNOCKBACK); + ALIAS_ENCHANTMENTS.put("arrowkb", Enchantment.ARROW_KNOCKBACK); + ENCHANTMENTS.put("punch", Enchantment.ARROW_KNOCKBACK); + ALIAS_ENCHANTMENTS.put("arrowpunch", Enchantment.ARROW_KNOCKBACK); + ALIAS_ENCHANTMENTS.put("ak", Enchantment.ARROW_KNOCKBACK); + + ALIAS_ENCHANTMENTS.put("infinitearrows", Enchantment.ARROW_INFINITE); + ENCHANTMENTS.put("infarrows", Enchantment.ARROW_INFINITE); + ENCHANTMENTS.put("infinity", Enchantment.ARROW_INFINITE); + ALIAS_ENCHANTMENTS.put("infinite", Enchantment.ARROW_INFINITE); + ALIAS_ENCHANTMENTS.put("unlimited", Enchantment.ARROW_INFINITE); + ALIAS_ENCHANTMENTS.put("unlimitedarrows", Enchantment.ARROW_INFINITE); + ALIAS_ENCHANTMENTS.put("ai", Enchantment.ARROW_INFINITE); + + ENCHANTMENTS.put("luck", Enchantment.LUCK); + ALIAS_ENCHANTMENTS.put("luckofsea", Enchantment.LUCK); + ALIAS_ENCHANTMENTS.put("luckofseas", Enchantment.LUCK); + ALIAS_ENCHANTMENTS.put("rodluck", Enchantment.LUCK); + + ENCHANTMENTS.put("lure", Enchantment.LURE); + ALIAS_ENCHANTMENTS.put("rodlure", Enchantment.LURE); + } + + public static Enchantment getByName(String name) { + + Enchantment enchantment; + + if (isInt(name)) { + enchantment = Enchantment.getById(Integer.parseInt(name)); + } else { + enchantment = Enchantment.getByName(name.toUpperCase(Locale.ENGLISH)); + } + + if (enchantment == null) { + enchantment = ENCHANTMENTS.get(name.toLowerCase(Locale.ENGLISH)); + } + + if (enchantment == null) { + enchantment = ALIAS_ENCHANTMENTS.get(name.toLowerCase(Locale.ENGLISH)); + } + + return enchantment; + } + + private static boolean isInt(String string) { + try { + Integer.parseInt(string); + + return true; + } catch(Exception ex) { + return false; + } + } + public static void registerEnchantments() { + try { + Field f = Enchantment.class.getDeclaredField("acceptingNew"); + f.setAccessible(true); + f.set(null, true); + } catch (Exception e) { + e.printStackTrace(); + } + Enchantment.registerEnchantment(new EnchantmentGlow(80)); + + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/EnchantmentGlow.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/EnchantmentGlow.java new file mode 100644 index 0000000..dd71ebe --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/EnchantmentGlow.java @@ -0,0 +1,46 @@ +package com.elevatemc.elib.util; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.inventory.ItemStack; + +public class EnchantmentGlow extends org.bukkit.enchantments.Enchantment { + + public EnchantmentGlow(int id) { + super(id); + } + + @Override + public String getName() { + return ""; + } + + @Override + public int getMaxLevel() { + return 0; + } + + @Override + public int getStartLevel() { + return 0; + } + + @Override + public EnchantmentTarget getItemTarget() { + return null; + } + + @Override + public boolean conflictsWith(Enchantment other) { + return false; + } + + + @Override + public boolean canEnchantItem(ItemStack item) { + return false; + } + + + + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/EnchantmentWrapper.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/EnchantmentWrapper.java new file mode 100644 index 0000000..cd8d7ac --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/EnchantmentWrapper.java @@ -0,0 +1,119 @@ +package com.elevatemc.elib.util; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.inventory.ItemStack; + +@AllArgsConstructor +public enum EnchantmentWrapper { + + PROTECTION_ENVIRONMENTAL("Protection", new String[]{"p", "prot", "protect"}), + PROTECTION_FIRE("Fire Protection", new String[]{"fp", "fprot", "fireprot", "fireprotection", "firep"}), + PROTECTION_FALL("Feather Falling", new String[]{"ff", "featherf", "ffalling","featherfalling"}), + PROTECTION_EXPLOSIONS("Blast Protection", new String[]{"explosionsprotection", "explosionprotection", "bprotection", "bprotect", "blastprotect", "pe", "bp"}), + PROTECTION_PROJECTILE("Projectile Protection", new String[]{"pp", "projprot", "projprotection", "projp", "pprot"}), + THORNS("Thorns", new String[0]), + DURABILITY("Unbreaking", new String[]{"unbr", "unb", "dur", "dura"}), + DAMAGE_ALL("Sharpness", new String[]{"s", "sharp"}), + DAMAGE_UNDEAD("Smite", new String[]{"du", "dz"}), + DAMAGE_ARTHROPODS("Bane of Arthropods", new String[]{"bane", "ardmg", "baneofarthropod", "arthropod", "dar", "dspider"}), + KNOCKBACK("Knockback", new String[]{"k", "knock", "kb"}), + FIRE_ASPECT("Fire Aspect", new String[]{"fire", "fa"}), + OXYGEN("Respiration", new String[]{"oxygen", "breathing", "o", "breath"}), + WATER_WORKER("Aqua Affinity", new String[]{"aa"}), + LOOT_BONUS_MOBS("Looting", new String[]{"moblooting", "ml", "loot"}), + DIG_SPEED("Efficiency", new String[]{"e", "eff", "digspeed", "ds"}), + SILK_TOUCH("Silk Touch", new String[]{"silk", "st"}), + LOOT_BONUS_BLOCKS("Fortune", new String[]{"fort", "lbm"}), + ARROW_DAMAGE("Power", new String[]{"apower", "adamage", "admg"}), + ARROW_KNOCKBACK("Punch", new String[]{"akb", "arrowkb", "arrowknockback", "aknockback"}), + ARROW_FIRE("Fire", new String[]{"afire", "arrowfire"}), + ARROW_INFINITE("Infinity", new String[]{"infinitearrows", "infinite", "inf", "infarrows", "unlimitedarrows", "ai", "uarrows", "unlimited"}), + LUCK("Luck of the Sea", new String[]{"rodluck", "luckofsea", "los"}), + LURE("Lure", new String[]{"rodlure"}); + + @Getter private String friendlyName; + @Getter private String[] parse; + + public void enchant(ItemStack item,int level) { + item.addUnsafeEnchantment(this.getBukkitEnchantment(), level); + } + + public int getMaxLevel() { + return this.getBukkitEnchantment().getMaxLevel(); + } + + public int getStartLevel() { + return this.getBukkitEnchantment().getStartLevel(); + } + + public EnchantmentTarget getItemTarget() { + return this.getBukkitEnchantment().getItemTarget(); + } + + public boolean conflictsWith(Enchantment enchantment) { + return this.getBukkitEnchantment().conflictsWith(enchantment); + } + + public boolean canEnchantItem(ItemStack item) { + return this.getBukkitEnchantment().canEnchantItem(item); + } + + public String toString() { + return this.getBukkitEnchantment().toString(); + } + + public Enchantment getBukkitEnchantment() { + return Enchantment.getByName(this.name()); + } + + public static EnchantmentWrapper parse(String input) { + + for (int i = 0; i < values().length; i++) { + + final EnchantmentWrapper enchantment = values()[i]; + + for (int j = 0; j < enchantment.getParse().length; j++) { + + final String string = enchantment.getParse()[j]; + + if (string.equalsIgnoreCase(input)) { + return enchantment; + } + } + + if (enchantment.getBukkitEnchantment().getName().replace("_", "").equalsIgnoreCase(input)) { + return enchantment; + } + + if (enchantment.getBukkitEnchantment().getName().equalsIgnoreCase(input)) { + return enchantment; + } + + if (enchantment.getFriendlyName().equalsIgnoreCase(input)) { + return enchantment; + } + + } + + return null; + } + + public static EnchantmentWrapper parse(Enchantment enchantment) { + + for (int i = 0; i < values().length; ++i) { + + final EnchantmentWrapper possible = values()[i]; + + if (possible.getBukkitEnchantment() == enchantment) { + return possible; + } + + } + + throw new IllegalArgumentException("Invalid enchantment given for parsing."); + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/EntityUtils.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/EntityUtils.java new file mode 100644 index 0000000..4386d8e --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/EntityUtils.java @@ -0,0 +1,110 @@ +package com.elevatemc.elib.util; + +import org.bukkit.entity.EntityType; + +import java.util.EnumMap; +import java.util.Map; + +public final class EntityUtils { + + private static Map displayNames = new EnumMap<>(EntityType.class); + private static int currentFakeEntityId = -1; + + public static int getFakeEntityId() { + return currentFakeEntityId--; + } + + static { + displayNames.put(EntityType.ARROW, "Arrow"); + displayNames.put(EntityType.BAT, "Bat"); + displayNames.put(EntityType.BLAZE, "Blaze"); + displayNames.put(EntityType.BOAT, "Boat"); + displayNames.put(EntityType.CAVE_SPIDER, "Cave Spider"); + displayNames.put(EntityType.CHICKEN, "Chicken"); + displayNames.put(EntityType.COMPLEX_PART, "Complex Part"); + displayNames.put(EntityType.COW, "Cow"); + displayNames.put(EntityType.CREEPER, "Creeper"); + displayNames.put(EntityType.DROPPED_ITEM, "Item"); + displayNames.put(EntityType.EGG, "Egg"); + displayNames.put(EntityType.ENDER_CRYSTAL, "Ender Crystal"); + displayNames.put(EntityType.ENDER_DRAGON, "Ender Dragon"); + displayNames.put(EntityType.ENDER_PEARL, "Ender Pearl"); + displayNames.put(EntityType.ENDER_SIGNAL, "Ender Signal"); + displayNames.put(EntityType.ENDERMAN, "Enderman"); + displayNames.put(EntityType.EXPERIENCE_ORB, "Experience Orb"); + displayNames.put(EntityType.FALLING_BLOCK, "Falling Block"); + displayNames.put(EntityType.FIREBALL, "Fireball"); + displayNames.put(EntityType.FIREWORK, "Firework"); + displayNames.put(EntityType.FISHING_HOOK, "Fishing Rod Hook"); + displayNames.put(EntityType.GHAST, "Ghast"); + displayNames.put(EntityType.GIANT, "Giant"); + displayNames.put(EntityType.HORSE, "Horse"); + displayNames.put(EntityType.IRON_GOLEM, "Iron Golem"); + displayNames.put(EntityType.ITEM_FRAME, "Item Frame"); + displayNames.put(EntityType.LEASH_HITCH, "Lead Hitch"); + displayNames.put(EntityType.LIGHTNING, "Lightning"); + displayNames.put(EntityType.MAGMA_CUBE, "Magma Cube"); + displayNames.put(EntityType.MINECART, "Minecart"); + displayNames.put(EntityType.MINECART_CHEST, "Chest Minecart"); + displayNames.put(EntityType.MINECART_FURNACE, "Furnace Minecart"); + displayNames.put(EntityType.MINECART_HOPPER, "Hopper Minecart"); + displayNames.put(EntityType.MINECART_MOB_SPAWNER, "Spawner Minecart"); + displayNames.put(EntityType.MINECART_TNT, "TNT Minecart"); + displayNames.put(EntityType.OCELOT, "Ocelot"); + displayNames.put(EntityType.PAINTING, "Painting"); + displayNames.put(EntityType.PIG, "Pig"); + displayNames.put(EntityType.PIG_ZOMBIE, "Zombie Pigman"); + displayNames.put(EntityType.PLAYER, "Player"); + displayNames.put(EntityType.PRIMED_TNT, "TNT"); + displayNames.put(EntityType.SHEEP, "Sheep"); + displayNames.put(EntityType.SILVERFISH, "Silverfish"); + displayNames.put(EntityType.SKELETON, "Skeleton"); + displayNames.put(EntityType.SLIME, "Slime"); + displayNames.put(EntityType.SMALL_FIREBALL, "Fireball"); + displayNames.put(EntityType.SNOWBALL, "Snowball"); + displayNames.put(EntityType.SNOWMAN, "Snowman"); + displayNames.put(EntityType.SPIDER, "Spider"); + displayNames.put(EntityType.SPLASH_POTION, "Potion"); + displayNames.put(EntityType.SQUID, "Squid"); + displayNames.put(EntityType.THROWN_EXP_BOTTLE, "Experience Bottle"); + displayNames.put(EntityType.UNKNOWN, "Custom"); + displayNames.put(EntityType.VILLAGER, "Villager"); + displayNames.put(EntityType.WEATHER, "Weather"); + displayNames.put(EntityType.WITCH, "Witch"); + displayNames.put(EntityType.WITHER, "Wither"); + displayNames.put(EntityType.WITHER_SKULL, "Wither Skull"); + displayNames.put(EntityType.WOLF, "Wolf"); + displayNames.put(EntityType.ZOMBIE, "Zombie"); + } + + // Static utility class -- cannot be created. + private EntityUtils() { + } + + /** + * Gets the display name of a given entity type. + * + * @param type The entity type to lookup. + * @return The display name of the provided entity type. + */ + public static String getName(EntityType type) { + return (displayNames.get(type)); + } + + public static EntityType parse(String input) { + for (Map.Entry entry : displayNames.entrySet()) { + if (entry.getValue().replace(" ", "").equalsIgnoreCase(input)) { + return entry.getKey(); + } + } + + for (EntityType type : EntityType.values()) { + if (input.equalsIgnoreCase(type.toString())) { + return type; + } + } + + return null; + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/FileConfig.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/FileConfig.java new file mode 100644 index 0000000..db3de6c --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/FileConfig.java @@ -0,0 +1,80 @@ +package com.elevatemc.elib.util; + + +import org.bukkit.ChatColor; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +public class FileConfig { + + public static final String FILE_EXTENSION = ".yml"; + private final JavaPlugin plugin; + private final String name; + private final File file; + private final YamlConfiguration configuration; + + public FileConfig(JavaPlugin plugin, String name, boolean overwrite) { + this.plugin = plugin; + this.name = name; + this.file = new File(plugin.getDataFolder(), name + ".yml"); + plugin.saveResource(name + ".yml", overwrite); + this.configuration = YamlConfiguration.loadConfiguration(this.file); + } + + public FileConfig(JavaPlugin plugin, String name) { + this(plugin, name, false); + } + + public String getString(String path) { + return this.configuration.contains(path) ? ChatColor.translateAlternateColorCodes('&', this.configuration.getString(path)) : null; + } + + public void save() { + try { + configuration.save(file); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public String getStringOrDefault(String path, String or) { + String toReturn = this.getString(path); + return toReturn == null ? or : toReturn; + } + + public int getInteger(String path) { + return this.configuration.contains(path) ? this.configuration.getInt(path) : 0; + } + + public boolean getBoolean(String path) { + return getBoolean(path, false); + } + + public boolean getBoolean(String path, boolean def) { + return this.configuration.getBoolean(path, def); + } + + public double getDouble(String path) { + return this.configuration.contains(path) ? this.configuration.getDouble(path) : 0.0D; + } + + public Object get(String path) { + return this.configuration.contains(path) ? this.configuration.get(path) : null; + } + + public List getStringList(String path) { + return this.configuration.contains(path) ? this.configuration.getStringList(path) : null; + } + + public File getFile() { + return this.file; + } + + public YamlConfiguration getConfiguration() { + return this.configuration; + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/InventoryAdapter.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/InventoryAdapter.java new file mode 100644 index 0000000..afbcb3a --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/InventoryAdapter.java @@ -0,0 +1,43 @@ +package com.elevatemc.elib.util; + +import com.elevatemc.spigot.handler.PacketHandler; +import net.minecraft.server.v1_8_R3.*; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** +<<<<<<< HEAD + * @author ImHacking + * @date 5/10/2022 + */ + + +public class InventoryAdapter implements PacketHandler +{ + private static final Set currentlyOpen = new HashSet<>(); + + @Override + public void handleReceivedPacket(PlayerConnection connection, Packet packet) { + if (packet instanceof PacketPlayInClientCommand) { + PacketPlayInClientCommand packetPlayInClientCommand = (PacketPlayInClientCommand) packet; + if (packetPlayInClientCommand.getCommand().equals(PacketPlayInClientCommand.EnumClientCommand.OPEN_INVENTORY_ACHIEVEMENT)) { + InventoryAdapter.currentlyOpen.add(connection.getPlayer().getUniqueId()); + } + } + + if (packet instanceof PacketPlayInCloseWindow) { + InventoryAdapter.currentlyOpen.remove(connection.getPlayer().getUniqueId()); + } + } + + @Override + public void handleSentPacket(PlayerConnection connection, Packet packet) { + } + + public static Set getCurrentlyOpen() { + return InventoryAdapter.currentlyOpen; + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/InventoryUtils.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/InventoryUtils.java new file mode 100644 index 0000000..da4dd0f --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/InventoryUtils.java @@ -0,0 +1,43 @@ +package com.elevatemc.elib.util; + +import org.bukkit.Material; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +public class InventoryUtils { + + public static boolean fits(ItemStack item,Inventory target) { + + int leftToAdd = item.getAmount(); + + if (target.getMaxStackSize() == 2147483647) { + return true; + } else { + + final ItemStack[] contents = target.getContents(); + + for (int i = 0; i < contents.length; i++) { + + final ItemStack itemStack = contents[i]; + + if (leftToAdd <= 0) { + return true; + } + + if (itemStack != null && itemStack.getType() != Material.AIR) { + + if (itemStack.isSimilar(item)) { + leftToAdd -= item.getMaxStackSize() - itemStack.getAmount(); + } + + } else { + leftToAdd -= item.getMaxStackSize(); + } + } + + + return leftToAdd <= 0; + } + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/ItemBuilder.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/ItemBuilder.java new file mode 100644 index 0000000..d7253c6 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/ItemBuilder.java @@ -0,0 +1,120 @@ +package com.elevatemc.elib.util; + +import com.elevatemc.elib.eLib; +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import org.bukkit.ChatColor; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.LeatherArmorMeta; + +import java.util.*; +import java.util.stream.Collectors; + +public class ItemBuilder { + + private final ItemStack item; + + public static ItemBuilder of(Material material) { + return new ItemBuilder(material, 1); + } + + public static ItemBuilder of(Material material, int amount) { + return new ItemBuilder(material, amount); + } + + public static ItemBuilder copyOf(ItemBuilder builder) { + return new ItemBuilder(builder.build()); + } + + public static ItemBuilder copyOf(ItemStack item) { + return new ItemBuilder(item); + } + + private ItemBuilder(Material material, int amount) { + Preconditions.checkArgument(amount > 0, "Amount cannot be lower than 0."); + this.item = new ItemStack(material, amount); + } + + private ItemBuilder(ItemStack item) { + this.item = item; + } + + public ItemBuilder amount(int amount) { + this.item.setAmount(amount); + return this; + } + + public ItemBuilder data(short data) { + this.item.setDurability(data); + return this; + } + + public ItemBuilder enchant(Enchantment enchantment, int level) { + this.item.addUnsafeEnchantment(enchantment, level); + return this; + } + + public ItemBuilder unenchant(Enchantment enchantment) { + this.item.removeEnchantment(enchantment); + return this; + } + + public ItemBuilder name(String displayName) { + ItemMeta meta = this.item.getItemMeta(); + meta.setDisplayName(displayName == null ? null : ChatColor.translateAlternateColorCodes('&', displayName)); + this.item.setItemMeta(meta); + return this; + } + + public ItemBuilder addToLore(String... parts) { + ItemMeta meta = this.item.getItemMeta(); + if (meta == null) { + meta = eLib.getInstance().getServer().getItemFactory().getItemMeta(this.item.getType()); + } + + List lore = meta.getLore(); + if (lore == null) { + lore = Lists.newArrayList(); + } + + (lore).addAll(Arrays.stream(parts).map((part) -> ChatColor.translateAlternateColorCodes('&', part)).collect(Collectors.toList())); + meta.setLore(lore); + this.item.setItemMeta(meta); + return this; + } + + public ItemBuilder setLore(Collection l) { + List lore = new ArrayList(); + ItemMeta meta = this.item.getItemMeta(); + lore.addAll(l.stream().map((part) -> ChatColor.translateAlternateColorCodes('&', part)).collect(Collectors.toList())); + meta.setLore(lore); + this.item.setItemMeta(meta); + return this; + } + + public ItemBuilder color(Color color) { + ItemMeta meta = this.item.getItemMeta(); + if (!(meta instanceof LeatherArmorMeta)) { + throw new UnsupportedOperationException("Cannot set color of a non-leather armor item."); + } else { + ((LeatherArmorMeta)meta).setColor(color); + this.item.setItemMeta(meta); + return this; + } + } + + public ItemBuilder setUnbreakable(boolean unbreakable) { + ItemMeta meta = this.item.getItemMeta(); + meta.spigot().setUnbreakable(unbreakable); + this.item.setItemMeta(meta); + return this; + } + + public ItemStack build() { + return this.item.clone(); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/ItemUtils.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/ItemUtils.java new file mode 100644 index 0000000..fb940da --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/ItemUtils.java @@ -0,0 +1,200 @@ +package com.elevatemc.elib.util; + +import com.elevatemc.elib.eLib; +import org.bukkit.inventory.meta.ItemMeta; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.WordUtils; +import org.bukkit.Material; +import org.bukkit.craftbukkit.v1_8_R3.inventory.CraftItemStack; +import org.bukkit.inventory.ItemStack; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ItemUtils { + + private static final Map NAME_MAP = new HashMap<>(); + +// public static ItemStack makeTexturedSkull(String texture) { +// return applySkullTexture(new ItemStack(Material.SKULL_ITEM, 1, (short)3), texture); +// } +// +// public static ItemStack applySkullTexture(ItemStack itemStack, String texture) { +// final CraftItemStack nmsCopy = CraftItemStack.asCraftCopy(itemStack); +// +// } + + public static ItemData[] repeat(Material material, int times) { + return repeat(material, (byte) 0, times); + } + + public static ItemData[] repeat(Material material, byte data, int times) { + ItemData[] itemData = new ItemData[times]; + + for (int i = 0; i < times; i++) { + itemData[i] = new ItemData(material, data); + } + + return itemData; + + } + + public static void setDisplayName(final ItemStack itemStack, String name) { + ItemMeta itemMeta = itemStack.getItemMeta(); + itemMeta.setDisplayName(name); + itemStack.setItemMeta(itemMeta); + } + + public static void setLore(final ItemStack itemStack, List lore) { + ItemMeta itemMeta = itemStack.getItemMeta(); + itemMeta.setLore(lore); + itemStack.setItemMeta(itemMeta); + } + + public static ItemData[] armorOf(ArmorPart part) { + List data = new ArrayList<>(); + + for (ArmorType at : ArmorType.values()) { + data.add(new ItemData(Material.valueOf(at.name() + "_" + part.name()), (short) 0)); + } + + return data.toArray(new ItemData[data.size()]); + } + + public static ItemData[] swords() { + List data = new ArrayList<>(); + + for (SwordType at : SwordType.values()) { + data.add(new ItemData(Material.valueOf(at.name() + "_SWORD"), (short) 0)); + } + + return data.toArray(new ItemData[data.size()]); + } + + public static void load() { + NAME_MAP.clear(); + + List lines = readLines(); + + for (String line : lines) { + String[] parts = line.split(","); + + NAME_MAP.put(parts[0], new ItemData(Material.getMaterial(Integer.parseInt(parts[1])), Short.parseShort(parts[2]))); + } + } + + public static ItemStack get(String input, int amount) { + ItemStack item = get(input); + + if (item != null) item.setAmount(amount); + + return item; + } + + public static ItemStack get(String input) { + if (NumberUtils.isInteger(input)) { + return new ItemStack(Material.getMaterial(Integer.parseInt(input))); + } + + if (input.contains(":")) { + if (NumberUtils.isShort(input.split(":")[1])) { + if (NumberUtils.isInteger(input.split(":")[0])) { + return new ItemStack(Material.getMaterial(Integer.parseInt(input.split(":")[0])), 1, Short.parseShort(input.split(":")[1])); + } else { + if (!NAME_MAP.containsKey(input.split(":")[0].toLowerCase())) { + return null; + } + + ItemData data = NAME_MAP.get(input.split(":")[0].toLowerCase()); + return new ItemStack(data.getMaterial(), 1, Short.parseShort(input.split(":")[1])); + } + } else { + return null; + } + } + + if (!NAME_MAP.containsKey(input)) { + return null; + } + + return NAME_MAP.get(input).toItemStack(); + } + + public static String getName(ItemStack item) { + if (item.getDurability() != 0) { + net.minecraft.server.v1_8_R3.ItemStack nmsStack = CraftItemStack.asNMSCopy(item); + + if (nmsStack != null) { + String name = nmsStack.getName(); + + if (name.contains(".")) { + name = WordUtils.capitalize(item.getType().toString().toLowerCase().replace("_", " ")); + } + + return name; + } + } + + String string = item.getType().toString().replace("_", " "); + char[] chars = string.toLowerCase().toCharArray(); + boolean found = false; + for (int i = 0; i < chars.length; i++) { + if (!found && Character.isLetter(chars[i])) { + chars[i] = Character.toUpperCase(chars[i]); + found = true; + } else if (Character.isWhitespace(chars[i]) || chars[i] == '.' || chars[i] == '\'') { + found = false; + } + } + return String.valueOf(chars); + } + + private static List readLines() { + try { + return IOUtils.readLines(eLib.class.getClassLoader().getResourceAsStream("items.csv")); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + @Getter + @AllArgsConstructor + public static class ItemData { + + private final Material material; + private final short data; + + public String getName() { + return ItemUtils.getName(toItemStack()); + } + + public boolean matches(ItemStack item) { + return item != null && item.getType() == material && item.getDurability() == data; + } + + public ItemStack toItemStack() { + return new ItemStack(material, 1, data); + } + + } + + public enum ArmorPart { + HELMET, CHESTPLATE, LEGGINGS, BOOTS + } + + public enum ArmorType { + DIAMOND, IRON, GOLD, LEATHER + } + + public enum SwordType { + DIAMOND, IRON, GOLD, STONE + } + + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/LoreUtil.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/LoreUtil.java new file mode 100644 index 0000000..b03c409 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/LoreUtil.java @@ -0,0 +1,26 @@ +package com.elevatemc.elib.util; + +import org.bukkit.inventory.ItemStack; + +/** + * @author ImHacking + * @date 5/5/2022 + */ +public class LoreUtil { + + public static String getFirstLoreLine(ItemStack itemStack) { + return getLoreLine(itemStack, 0); + } + + public static String getLoreLine(ItemStack itemStack, int index) { + if (!itemStack.hasItemMeta() || !itemStack.getItemMeta().hasLore()) { + return null; + } + + if (index >= itemStack.getItemMeta().getLore().size()) { + return null; + } + + return itemStack.getItemMeta().getLore().get(index); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/MojangUtil.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/MojangUtil.java new file mode 100644 index 0000000..c82bccc --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/MojangUtil.java @@ -0,0 +1,50 @@ +package com.elevatemc.elib.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.net.URLConnection; +import java.util.UUID; + +public class MojangUtil { + + public static UUID getFromMojang(String name) throws IOException { + + final URL url = new URL("https://api.mojang.com/users/profiles/minecraft/" + name); + final URLConnection conn = url.openConnection(); + + conn.setDoOutput(true); + + final BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); + final String line = reader.readLine(); + + if (line == null) { + return null; + } + + final String[] id = line.split(","); + + String part = id[1]; + part = part.substring(6,38); + + return UUID.fromString(part.replaceFirst("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5")); + } + + public static String getFromMojang(UUID uuid) throws IOException { + + final URL url = new URL("https://api.mojang.com/users/profiles/minecraft/" + uuid.toString().replace("-","")); + final URLConnection conn = url.openConnection(); + + conn.setDoOutput(true); + + final BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); + final String line = reader.readLine(); + + if (line == null) { + return null; + } + + return line; + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/NBTUtil.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/NBTUtil.java new file mode 100644 index 0000000..5a51ad7 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/NBTUtil.java @@ -0,0 +1,24 @@ +//package com.elevatemc.elib.util; +// +//import com.elevatemc.spigot.viaversion.bukkit.util.NMSUtil; +// +//import java.lang.reflect.Method; +// +//public class NBTUtil { +// +// private final Class NBT_BASE_CLASS = NMSUtil.nms("NBTBase"); +//// private final Class NBT_NUMBER_CLASS = NMSUtil.nms("NBTBase\$NBTNumber"); +// private final Class NBT_INT_CLASS = NMSUtil.nms("NBTTagInt"); +// private final Class NBT_LONG_CLASS = NMSUtil.nms("NBTTagLong"); +// private final Class NBT_DOUBLE_CLASS = NMSUtil.nms("NBTTagDouble"); +// private final Class NBT_FLOAT_CLASS = NMSUtil.nms("NBTTagFloat"); +// +// private final Class NBT_TAG_LIST_CLASS = NMSUtil.nms("NBTTagList"); +// private final Method NBT_TAG_LIST_ADD_METHOD = NBT_TAG_LIST_CLASS.getDeclaredMethod("add", NBT_BASE_CLASS); +// +// +// +// public NBTUtil() throws ClassNotFoundException, NoSuchMethodException { +// throw new RuntimeException("Failed to load NBTUtil"); +// } +//} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/NetworkUtils.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/NetworkUtils.java new file mode 100644 index 0000000..a156448 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/NetworkUtils.java @@ -0,0 +1,74 @@ +package com.elevatemc.elib.util; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +// Borrowed from kohi. +public class NetworkUtils { + + // Static utility class -- cannot be created. + private NetworkUtils() { + } + + public static void writeVarInt(DataOutputStream out, int value) throws IOException { + for (int i = 0; i < 3; i++) { + if ((value & 0xFFFFFF80) == 0) { + out.write(value); + + return; + } + + out.write(value & 0x7F | 0x80); + value >>>= 7; + } + } + + public static void writeString(DataOutputStream out, String str) throws IOException { + + final byte[] bytes = str.getBytes("UTF-8"); + + writeVarInt(out, bytes.length); + out.write(bytes); + } + + public static void writePacket(DataOutputStream out, byte[] bytes) throws IOException { + writeVarInt(out, bytes.length); + out.write(bytes); + } + + public static int readVarInt(DataInputStream in) throws IOException { + int value = 0; + + for (int i = 0; i < 3; i++) { + int b = in.read(); + + value |= (b & 0x7F) << i * 7; + + if ((b & 0x80) == 0) { + break; + } + } + + return (value); + } + + public static String readString(DataInputStream in) throws IOException { + int len = readVarInt(in); + byte[] bytes = new byte[len]; + + in.readFully(bytes); + + return (new String(bytes, "UTF-8")); + } + + public static byte[] readPacket(DataInputStream in) throws IOException { + int len = readVarInt(in); + byte[] bytes = new byte[len]; + + in.readFully(bytes); + + return (bytes); + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/NumberUtils.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/NumberUtils.java new file mode 100644 index 0000000..75c374e --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/NumberUtils.java @@ -0,0 +1,62 @@ +package com.elevatemc.elib.util; + +import java.math.BigInteger; +import java.text.NumberFormat; +import java.util.TreeMap; + +public class NumberUtils { + + public static TreeMap map = new TreeMap<>(); + private static final NumberFormat BALANCE_FORMAT = NumberFormat.getInstance(); + + static { + map.put(1, "I"); + map.put(4, "IV"); + map.put(5, "V"); + map.put(9, "IX"); + map.put(10, "X"); + map.put(40, "XL"); + map.put(50, "L"); + map.put(90, "XC"); + map.put(100, "C"); + map.put(400, "CD"); + map.put(500, "D"); + map.put(900, "CM"); + map.put(1000, "M"); + } + public static double round(double value, int places) { + double scale = Math.pow(10, places); + return Math.round(value * scale) / scale; + } + + public static String formatBalance(BigInteger balance) { + return BALANCE_FORMAT.format(balance); + } + + public static String formatBalance(int balance) { + return BALANCE_FORMAT.format((long)balance); + } + public static String formatBalance(double balance) { + return BALANCE_FORMAT.format((long)balance); + } + public static boolean isInteger(String input) { + try { + Integer.parseInt(input); + return true; + } catch (NumberFormatException e) { + return false; + } + } + + public static boolean isShort(String input) { + try { + Short.parseShort(input); + return true; + } catch (NumberFormatException e) { + return false; + } + } + static { + BALANCE_FORMAT.setGroupingUsed(true); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/Pair.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/Pair.java new file mode 100644 index 0000000..3a000c2 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/Pair.java @@ -0,0 +1,12 @@ +package com.elevatemc.elib.util; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +public class Pair{ + + @Getter private K key; + @Getter private V value; + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/ParticleEffect.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/ParticleEffect.java new file mode 100644 index 0000000..5f965e4 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/ParticleEffect.java @@ -0,0 +1,1583 @@ +package com.elevatemc.elib.util; + + +import net.minecraft.server.v1_8_R3.EnumParticle; +import net.minecraft.server.v1_8_R3.PacketPlayOutWorldParticles; +import org.bukkit.Bukkit; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * ParticleEffect Library + *

+ * This library was created by @DarkBlade12 and allows you to display all Minecraft particle effects on a Bukkit server + *

+ * You are welcome to use it, modify it and redistribute it under the following conditions: + *

    + *
  • Don't claim this class as your own + *
  • Don't remove this disclaimer + *
+ *

+ * Special thanks: + *

    + *
  • @microgeek (original idea, names and packet parameters) + *
  • @ShadyPotato (1.8 names, ids and packet parameters) + *
  • @RingOfStorms (particle behavior) + *
  • @Cybermaxke (particle behavior) + *
  • @JamieSinn (hosting a jenkins server and documentation for particleeffect) + *
+ *

+ * It would be nice if you provide credit to me if you use this class in a published project + * + * @author DarkBlade12 + * @version 1.7 + */ +public enum ParticleEffect { + /** + * A particle effect which is displayed by exploding tnt and creepers: + *

    + *
  • It looks like a white cloud + *
  • The speed value influences the velocity at which the particle flies off + *
+ */ + EXPLOSION_NORMAL("explode", 0, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed by exploding ghast fireballs and wither skulls: + *
    + *
  • It looks like a gray ball which is fading away + *
  • The speed value slightly influences the size of this particle effect + *
+ */ + EXPLOSION_LARGE("largeexplode", 1, -1), + /** + * A particle effect which is displayed by exploding tnt and creepers: + *
    + *
  • It looks like a crowd of gray balls which are fading away + *
  • The speed value has no influence on this particle effect + *
+ */ + EXPLOSION_HUGE("hugeexplosion", 2, -1), + /** + * A particle effect which is displayed by launching fireworks: + *
    + *
  • It looks like a white star which is sparkling + *
  • The speed value influences the velocity at which the particle flies off + *
+ */ + FIREWORKS_SPARK("fireworksSpark", 3, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed by swimming entities and arrows in water: + *
    + *
  • It looks like a bubble + *
  • The speed value influences the velocity at which the particle flies off + *
+ */ + WATER_BUBBLE("bubble", 4, -1, ParticleProperty.DIRECTIONAL, ParticleProperty.REQUIRES_WATER), + /** + * A particle effect which is displayed by swimming entities and shaking wolves: + *
    + *
  • It looks like a blue drop + *
  • The speed value has no influence on this particle effect + *
+ */ + WATER_SPLASH("splash", 5, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed on water when fishing: + *
    + *
  • It looks like a blue droplet + *
  • The speed value influences the velocity at which the particle flies off + *
+ */ + WATER_WAKE("wake", 6, 7, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed by water: + *
    + *
  • It looks like a tiny blue square + *
  • The speed value has no influence on this particle effect + *
+ */ + SUSPENDED("suspended", 7, -1, ParticleProperty.REQUIRES_WATER), + /** + * A particle effect which is displayed by air when close to bedrock and the in the void: + *
    + *
  • It looks like a tiny gray square + *
  • The speed value has no influence on this particle effect + *
+ */ + SUSPENDED_DEPTH("depthSuspend", 8, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed when landing a critical hit and by arrows: + *
    + *
  • It looks like a light brown cross + *
  • The speed value influences the velocity at which the particle flies off + *
+ */ + CRIT("crit", 9, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed when landing a hit with an enchanted weapon: + *
    + *
  • It looks like a cyan star + *
  • The speed value influences the velocity at which the particle flies off + *
+ */ + CRIT_MAGIC("magicCrit", 10, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed by primed tnt, torches, droppers, dispensers, end portals, brewing stands and monster spawners: + *
    + *
  • It looks like a little gray cloud + *
  • The speed value influences the velocity at which the particle flies off + *
+ */ + SMOKE_NORMAL("smoke", 11, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed by fire, minecarts with furnace and blazes: + *
    + *
  • It looks like a large gray cloud + *
  • The speed value influences the velocity at which the particle flies off + *
+ */ + SMOKE_LARGE("largesmoke", 12, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed when splash potions or bottles o' enchanting hit something: + *
    + *
  • It looks like a white swirl + *
  • The speed value causes the particle to only move upwards when set to 0 + *
  • Only the motion on the y-axis can be controlled, the motion on the x- and z-axis are multiplied by 0.1 when setting the values to 0 + *
+ */ + SPELL("spell", 13, -1), + /** + * A particle effect which is displayed when instant splash potions hit something: + *
    + *
  • It looks like a white cross + *
  • The speed value causes the particle to only move upwards when set to 0 + *
  • Only the motion on the y-axis can be controlled, the motion on the x- and z-axis are multiplied by 0.1 when setting the values to 0 + *
+ */ + SPELL_INSTANT("instantSpell", 14, -1), + /** + * A particle effect which is displayed by entities with active potion effects: + *
    + *
  • It looks like a colored swirl + *
  • The speed value causes the particle to be colored black when set to 0 + *
  • The particle color gets lighter when increasing the speed and darker when decreasing the speed + *
+ */ + SPELL_MOB("mobSpell", 15, -1, ParticleProperty.COLORABLE), + /** + * A particle effect which is displayed by entities with active potion effects applied through a beacon: + *
    + *
  • It looks like a transparent colored swirl + *
  • The speed value causes the particle to be always colored black when set to 0 + *
  • The particle color gets lighter when increasing the speed and darker when decreasing the speed + *
+ */ + SPELL_MOB_AMBIENT("mobSpellAmbient", 16, -1, ParticleProperty.COLORABLE), + /** + * A particle effect which is displayed by witches: + *
    + *
  • It looks like a purple cross + *
  • The speed value causes the particle to only move upwards when set to 0 + *
  • Only the motion on the y-axis can be controlled, the motion on the x- and z-axis are multiplied by 0.1 when setting the values to 0 + *
+ */ + SPELL_WITCH("witchMagic", 17, -1), + /** + * A particle effect which is displayed by blocks beneath a water source: + *
    + *
  • It looks like a blue drip + *
  • The speed value has no influence on this particle effect + *
+ */ + DRIP_WATER("dripWater", 18, -1), + /** + * A particle effect which is displayed by blocks beneath a lava source: + *
    + *
  • It looks like an orange drip + *
  • The speed value has no influence on this particle effect + *
+ */ + DRIP_LAVA("dripLava", 19, -1), + /** + * A particle effect which is displayed when attacking a villager in a village: + *
    + *
  • It looks like a cracked gray heart + *
  • The speed value has no influence on this particle effect + *
+ */ + VILLAGER_ANGRY("angryVillager", 20, -1), + /** + * A particle effect which is displayed when using bone meal and trading with a villager in a village: + *
    + *
  • It looks like a green star + *
  • The speed value has no influence on this particle effect + *
+ */ + VILLAGER_HAPPY("happyVillager", 21, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed by mycelium: + *
    + *
  • It looks like a tiny gray square + *
  • The speed value has no influence on this particle effect + *
+ */ + TOWN_AURA("townaura", 22, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed by note blocks: + *
    + *
  • It looks like a colored note + *
  • The speed value causes the particle to be colored green when set to 0 + *
+ */ + NOTE("note", 23, -1, ParticleProperty.COLORABLE), + /** + * A particle effect which is displayed by nether portals, endermen, ender pearls, eyes of ender, ender chests and dragon eggs: + *
    + *
  • It looks like a purple cloud + *
  • The speed value influences the spread of this particle effect + *
+ */ + PORTAL("portal", 24, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed by enchantment tables which are nearby bookshelves: + *
    + *
  • It looks like a cryptic white letter + *
  • The speed value influences the spread of this particle effect + *
+ */ + ENCHANTMENT_TABLE("enchantmenttable", 25, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed by torches, active furnaces, magma cubes and monster spawners: + *
    + *
  • It looks like a tiny flame + *
  • The speed value influences the velocity at which the particle flies off + *
+ */ + FLAME("flame", 26, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed by lava: + *
    + *
  • It looks like a spark + *
  • The speed value has no influence on this particle effect + *
+ */ + LAVA("lava", 27, -1), + /** + * A particle effect which is currently unused: + *
    + *
  • It looks like a transparent gray square + *
  • The speed value has no influence on this particle effect + *
+ */ + FOOTSTEP("footstep", 28, -1), + /** + * A particle effect which is displayed when a mob dies: + *
    + *
  • It looks like a large white cloud + *
  • The speed value influences the velocity at which the particle flies off + *
+ */ + CLOUD("cloud", 29, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed by redstone ore, powered redstone, redstone torches and redstone repeaters: + *
    + *
  • It looks like a tiny colored cloud + *
  • The speed value causes the particle to be colored red when set to 0 + *
+ */ + REDSTONE("reddust", 30, -1, ParticleProperty.COLORABLE), + /** + * A particle effect which is displayed when snowballs hit a block: + *
    + *
  • It looks like a little piece with the snowball texture + *
  • The speed value has no influence on this particle effect + *
+ */ + SNOWBALL("snowballpoof", 31, -1), + /** + * A particle effect which is currently unused: + *
    + *
  • It looks like a tiny white cloud + *
  • The speed value influences the velocity at which the particle flies off + *
+ */ + SNOW_SHOVEL("snowshovel", 32, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed by slimes: + *
    + *
  • It looks like a tiny part of the slimeball icon + *
  • The speed value has no influence on this particle effect + *
+ */ + SLIME("slime", 33, -1), + /** + * A particle effect which is displayed when breeding and taming animals: + *
    + *
  • It looks like a red heart + *
  • The speed value has no influence on this particle effect + *
+ */ + HEART("heart", 34, -1), + /** + * A particle effect which is displayed by barriers: + *
    + *
  • It looks like a red box with a slash through it + *
  • The speed value has no influence on this particle effect + *
+ */ + BARRIER("barrier", 35, 8), + /** + * A particle effect which is displayed when breaking a tool or eggs hit a block: + *
    + *
  • It looks like a little piece with an item texture + *
+ */ + ITEM_CRACK("iconcrack", 36, -1, ParticleProperty.DIRECTIONAL, ParticleProperty.REQUIRES_DATA), + /** + * A particle effect which is displayed when breaking blocks or sprinting: + *
    + *
  • It looks like a little piece with a block texture + *
  • The speed value has no influence on this particle effect + *
+ */ + BLOCK_CRACK("blockcrack", 37, -1, ParticleProperty.REQUIRES_DATA), + /** + * A particle effect which is displayed when falling: + *
    + *
  • It looks like a little piece with a block texture + *
+ */ + BLOCK_DUST("blockdust", 38, 7, ParticleProperty.DIRECTIONAL, ParticleProperty.REQUIRES_DATA), + /** + * A particle effect which is displayed when rain hits the ground: + *
    + *
  • It looks like a blue droplet + *
  • The speed value has no influence on this particle effect + *
+ */ + WATER_DROP("droplet", 39, 8), + /** + * A particle effect which is currently unused: + *
    + *
  • It has no visual effect + *
+ */ + ITEM_TAKE("take", 40, 8), + /** + * A particle effect which is displayed by elder guardians: + *
    + *
  • It looks like the shape of the elder guardian + *
  • The speed value has no influence on this particle effect + *
  • The offset values have no influence on this particle effect + *
+ */ + MOB_APPEARANCE("mobappearance", 41, 8); + + private static final Map NAME_MAP = new HashMap(); + private static final Map ID_MAP = new HashMap(); + private final String name; + private final int id; + private final int requiredVersion; + private final List properties; + + // Initialize map for quick name and id lookup + static { + for (ParticleEffect effect : values()) { + NAME_MAP.put(effect.name, effect); + ID_MAP.put(effect.id, effect); + } + } + + /** + * Construct a new particle effect + * + * @param name Name of this particle effect + * @param id Id of this particle effect + * @param requiredVersion Version which is required (1.x) + * @param properties Properties of this particle effect + */ + private ParticleEffect(String name, int id, int requiredVersion, ParticleProperty... properties) { + this.name = name; + this.id = id; + this.requiredVersion = requiredVersion; + this.properties = Arrays.asList(properties); + } + + /** + * Returns the name of this particle effect + * + * @return The name + */ + public String getName() { + return name; + } + + /** + * Returns the id of this particle effect + * + * @return The id + */ + public int getId() { + return id; + } + + /** + * Returns the required version for this particle effect (1.x) + * + * @return The required version + */ + public int getRequiredVersion() { + return requiredVersion; + } + + /** + * Determine if this particle effect has a specific property + * + * @return Whether it has the property or not + */ + public boolean hasProperty(ParticleProperty property) { + return properties.contains(property); + } + + /** + * Determine if this particle effect is supported by your current server version + * + * @return Whether the particle effect is supported or not + */ + public boolean isSupported() { + if (requiredVersion == -1) { + return true; + } + return ParticlePacket.getVersion() >= requiredVersion; + } + + /** + * Returns the particle effect with the given name + * + * @param name Name of the particle effect + * @return The particle effect + */ + public static ParticleEffect fromName(String name) { + for (Entry entry : NAME_MAP.entrySet()) { + if (!entry.getKey().equalsIgnoreCase(name)) { + continue; + } + return entry.getValue(); + } + return null; + } + + /** + * Returns the particle effect with the given id + * + * @param id Id of the particle effect + * @return The particle effect + */ + public static ParticleEffect fromId(int id) { + for (Entry entry : ID_MAP.entrySet()) { + if (entry.getKey() != id) { + continue; + } + return entry.getValue(); + } + return null; + } + + /** + * Determine if water is at a certain location + * + * @param location Location to check + * @return Whether water is at this location or not + */ + private static boolean isWater(Location location) { + Material material = location.getBlock().getType(); + return material == Material.WATER || material == Material.STATIONARY_WATER; + } + + /** + * Determine if the distance between @param location and one of the players exceeds 256 + * + * @param location Location to check + * @return Whether the distance exceeds 256 or not + */ + private static boolean isLongDistance(Location location, List players) { + String world = location.getWorld().getName(); + for (Player player : players) { + Location playerLocation = player.getLocation(); + if (!world.equals(playerLocation.getWorld().getName()) || playerLocation.distanceSquared(location) < 65536) { + continue; + } + return true; + } + return false; + } + + /** + * Determine if the data type for a particle effect is correct + * + * @param effect Particle effect + * @param data Particle data + * @return Whether the data type is correct or not + */ + private static boolean isDataCorrect(ParticleEffect effect, ParticleData data) { + return ((effect == BLOCK_CRACK || effect == BLOCK_DUST) && data instanceof BlockData) || (effect == ITEM_CRACK && data instanceof ItemData); + } + + /** + * Determine if the color type for a particle effect is correct + * + * @param effect Particle effect + * @param color Particle color + * @return Whether the color type is correct or not + */ + private static boolean isColorCorrect(ParticleEffect effect, ParticleColor color) { + return ((effect == SPELL_MOB || effect == SPELL_MOB_AMBIENT || effect == REDSTONE) && color instanceof OrdinaryColor) || (effect == NOTE && color instanceof NoteColor); + } + + /** + * Displays a particle effect which is only visible for all players within a certain range in the world of @param center + * + * @param offsetX Maximum distance particles can fly away from the center on the x-axis + * @param offsetY Maximum distance particles can fly away from the center on the y-axis + * @param offsetZ Maximum distance particles can fly away from the center on the z-axis + * @param speed Display speed of the particles + * @param amount Amount of particles + * @param center Center location of the effect + * @param range Range of the visibility + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleDataException If the particle effect requires additional data + * @throws IllegalArgumentException If the particle effect requires water and none is at the center location + * @see ParticlePacket + * @see ParticlePacket#sendTo(Location, double) + */ + public void display(float offsetX, float offsetY, float offsetZ, float speed, int amount, Location center, double range) throws ParticleVersionException, ParticleDataException, IllegalArgumentException { + if (!isSupported()) { + throw new ParticleVersionException("This particle effect is not supported by your server version"); + } + if (hasProperty(ParticleProperty.REQUIRES_DATA)) { + throw new ParticleDataException("This particle effect requires additional data"); + } + if (hasProperty(ParticleProperty.REQUIRES_WATER) && !isWater(center)) { + throw new IllegalArgumentException("There is no water at the center location"); + } + new ParticlePacket(this, offsetX, offsetY, offsetZ, speed, amount, range > 256, null).sendTo(center, range); + } + + /** + * Displays a particle effect which is only visible for the specified players + * + * @param offsetX Maximum distance particles can fly away from the center on the x-axis + * @param offsetY Maximum distance particles can fly away from the center on the y-axis + * @param offsetZ Maximum distance particles can fly away from the center on the z-axis + * @param speed Display speed of the particles + * @param amount Amount of particles + * @param center Center location of the effect + * @param players Receivers of the effect + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleDataException If the particle effect requires additional data + * @throws IllegalArgumentException If the particle effect requires water and none is at the center location + * @see ParticlePacket + * @see ParticlePacket#sendTo(Location, List) + */ + public void display(float offsetX, float offsetY, float offsetZ, float speed, int amount, Location center, List players) throws ParticleVersionException, ParticleDataException, IllegalArgumentException { + if (!isSupported()) { + throw new ParticleVersionException("This particle effect is not supported by your server version"); + } + if (hasProperty(ParticleProperty.REQUIRES_DATA)) { + throw new ParticleDataException("This particle effect requires additional data"); + } + if (hasProperty(ParticleProperty.REQUIRES_WATER) && !isWater(center)) { + throw new IllegalArgumentException("There is no water at the center location"); + } + new ParticlePacket(this, offsetX, offsetY, offsetZ, speed, amount, isLongDistance(center, players), null).sendTo(center, players); + } + + /** + * Displays a particle effect which is only visible for the specified players + * + * @param offsetX Maximum distance particles can fly away from the center on the x-axis + * @param offsetY Maximum distance particles can fly away from the center on the y-axis + * @param offsetZ Maximum distance particles can fly away from the center on the z-axis + * @param speed Display speed of the particles + * @param amount Amount of particles + * @param center Center location of the effect + * @param players Receivers of the effect + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleDataException If the particle effect requires additional data + * @throws IllegalArgumentException If the particle effect requires water and none is at the center location + * @see #display(float, float, float, float, int, Location, List) + */ + public void display(float offsetX, float offsetY, float offsetZ, float speed, int amount, Location center, Player... players) throws ParticleVersionException, ParticleDataException, IllegalArgumentException { + display(offsetX, offsetY, offsetZ, speed, amount, center, Arrays.asList(players)); + } + + /** + * Displays a single particle which flies into a determined direction and is only visible for all players within a certain range in the world of @param center + * + * @param direction Direction of the particle + * @param speed Display speed of the particle + * @param center Center location of the effect + * @param range Range of the visibility + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleDataException If the particle effect requires additional data + * @throws IllegalArgumentException If the particle effect is not directional or if it requires water and none is at the center location + * @see ParticlePacket#ParticlePacket(ParticleEffect, Vector, float, boolean, ParticleData) + * @see ParticlePacket#sendTo(Location, double) + */ + public void display(Vector direction, float speed, Location center, double range) throws ParticleVersionException, ParticleDataException, IllegalArgumentException { + if (!isSupported()) { + throw new ParticleVersionException("This particle effect is not supported by your server version"); + } + if (hasProperty(ParticleProperty.REQUIRES_DATA)) { + throw new ParticleDataException("This particle effect requires additional data"); + } + if (!hasProperty(ParticleProperty.DIRECTIONAL)) { + throw new IllegalArgumentException("This particle effect is not directional"); + } + if (hasProperty(ParticleProperty.REQUIRES_WATER) && !isWater(center)) { + throw new IllegalArgumentException("There is no water at the center location"); + } + new ParticlePacket(this, direction, speed, range > 256, null).sendTo(center, range); + } + + /** + * Displays a single particle which flies into a determined direction and is only visible for the specified players + * + * @param direction Direction of the particle + * @param speed Display speed of the particle + * @param center Center location of the effect + * @param players Receivers of the effect + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleDataException If the particle effect requires additional data + * @throws IllegalArgumentException If the particle effect is not directional or if it requires water and none is at the center location + * @see ParticlePacket#ParticlePacket(ParticleEffect, Vector, float, boolean, ParticleData) + * @see ParticlePacket#sendTo(Location, List) + */ + public void display(Vector direction, float speed, Location center, List players) throws ParticleVersionException, ParticleDataException, IllegalArgumentException { + if (!isSupported()) { + throw new ParticleVersionException("This particle effect is not supported by your server version"); + } + if (hasProperty(ParticleProperty.REQUIRES_DATA)) { + throw new ParticleDataException("This particle effect requires additional data"); + } + if (!hasProperty(ParticleProperty.DIRECTIONAL)) { + throw new IllegalArgumentException("This particle effect is not directional"); + } + if (hasProperty(ParticleProperty.REQUIRES_WATER) && !isWater(center)) { + throw new IllegalArgumentException("There is no water at the center location"); + } + new ParticlePacket(this, direction, speed, isLongDistance(center, players), null).sendTo(center, players); + } + + /** + * Displays a single particle which flies into a determined direction and is only visible for the specified players + * + * @param direction Direction of the particle + * @param speed Display speed of the particle + * @param center Center location of the effect + * @param players Receivers of the effect + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleDataException If the particle effect requires additional data + * @throws IllegalArgumentException If the particle effect is not directional or if it requires water and none is at the center location + * @see #display(Vector, float, Location, List) + */ + public void display(Vector direction, float speed, Location center, Player... players) throws ParticleVersionException, ParticleDataException, IllegalArgumentException { + display(direction, speed, center, Arrays.asList(players)); + } + + /** + * Displays a single particle which is colored and only visible for all players within a certain range in the world of @param center + * + * @param color Color of the particle + * @param center Center location of the effect + * @param range Range of the visibility + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleColorException If the particle effect is not colorable or the color type is incorrect + * @see ParticlePacket#ParticlePacket(ParticleEffect, ParticleColor, boolean) + * @see ParticlePacket#sendTo(Location, double) + */ + public void display(ParticleColor color, Location center, double range) throws ParticleVersionException, ParticleColorException { + if (!isSupported()) { + throw new ParticleVersionException("This particle effect is not supported by your server version"); + } + if (!hasProperty(ParticleProperty.COLORABLE)) { + throw new ParticleColorException("This particle effect is not colorable"); + } + if (!isColorCorrect(this, color)) { + throw new ParticleColorException("The particle color type is incorrect"); + } + new ParticlePacket(this, color, range > 256).sendTo(center, range); + } + + /** + * Displays a single particle which is colored and only visible for the specified players + * + * @param color Color of the particle + * @param center Center location of the effect + * @param players Receivers of the effect + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleColorException If the particle effect is not colorable or the color type is incorrect + * @see ParticlePacket#ParticlePacket(ParticleEffect, ParticleColor, boolean) + * @see ParticlePacket#sendTo(Location, List) + */ + public void display(ParticleColor color, Location center, List players) throws ParticleVersionException, ParticleColorException { + if (!isSupported()) { + throw new ParticleVersionException("This particle effect is not supported by your server version"); + } + if (!hasProperty(ParticleProperty.COLORABLE)) { + throw new ParticleColorException("This particle effect is not colorable"); + } + if (!isColorCorrect(this, color)) { + throw new ParticleColorException("The particle color type is incorrect"); + } + new ParticlePacket(this, color, isLongDistance(center, players)).sendTo(center, players); + } + + /** + * Displays a single particle which is colored and only visible for the specified players + * + * @param color Color of the particle + * @param center Center location of the effect + * @param players Receivers of the effect + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleColorException If the particle effect is not colorable or the color type is incorrect + * @see #display(ParticleColor, Location, List) + */ + public void display(ParticleColor color, Location center, Player... players) throws ParticleVersionException, ParticleColorException { + display(color, center, Arrays.asList(players)); + } + + /** + * Displays a particle effect which requires additional data and is only visible for all players within a certain range in the world of @param center + * + * @param data Data of the effect + * @param offsetX Maximum distance particles can fly away from the center on the x-axis + * @param offsetY Maximum distance particles can fly away from the center on the y-axis + * @param offsetZ Maximum distance particles can fly away from the center on the z-axis + * @param speed Display speed of the particles + * @param amount Amount of particles + * @param center Center location of the effect + * @param range Range of the visibility + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleDataException If the particle effect does not require additional data or if the data type is incorrect + * @see ParticlePacket + * @see ParticlePacket#sendTo(Location, double) + */ + public void display(ParticleData data, float offsetX, float offsetY, float offsetZ, float speed, int amount, Location center, double range) throws ParticleVersionException, ParticleDataException { + if (!isSupported()) { + throw new ParticleVersionException("This particle effect is not supported by your server version"); + } + if (!hasProperty(ParticleProperty.REQUIRES_DATA)) { + throw new ParticleDataException("This particle effect does not require additional data"); + } + if (!isDataCorrect(this, data)) { + throw new ParticleDataException("The particle data type is incorrect"); + } + new ParticlePacket(this, offsetX, offsetY, offsetZ, speed, amount, range > 256, data).sendTo(center, range); + } + + /** + * Displays a particle effect which requires additional data and is only visible for the specified players + * + * @param data Data of the effect + * @param offsetX Maximum distance particles can fly away from the center on the x-axis + * @param offsetY Maximum distance particles can fly away from the center on the y-axis + * @param offsetZ Maximum distance particles can fly away from the center on the z-axis + * @param speed Display speed of the particles + * @param amount Amount of particles + * @param center Center location of the effect + * @param players Receivers of the effect + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleDataException If the particle effect does not require additional data or if the data type is incorrect + * @see ParticlePacket + * @see ParticlePacket#sendTo(Location, List) + */ + public void display(ParticleData data, float offsetX, float offsetY, float offsetZ, float speed, int amount, Location center, List players) throws ParticleVersionException, ParticleDataException { + if (!isSupported()) { + throw new ParticleVersionException("This particle effect is not supported by your server version"); + } + if (!hasProperty(ParticleProperty.REQUIRES_DATA)) { + throw new ParticleDataException("This particle effect does not require additional data"); + } + if (!isDataCorrect(this, data)) { + throw new ParticleDataException("The particle data type is incorrect"); + } + new ParticlePacket(this, offsetX, offsetY, offsetZ, speed, amount, isLongDistance(center, players), data).sendTo(center, players); + } + + /** + * Displays a particle effect which requires additional data and is only visible for the specified players + * + * @param data Data of the effect + * @param offsetX Maximum distance particles can fly away from the center on the x-axis + * @param offsetY Maximum distance particles can fly away from the center on the y-axis + * @param offsetZ Maximum distance particles can fly away from the center on the z-axis + * @param speed Display speed of the particles + * @param amount Amount of particles + * @param center Center location of the effect + * @param players Receivers of the effect + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleDataException If the particle effect does not require additional data or if the data type is incorrect + * @see #display(ParticleData, float, float, float, float, int, Location, List) + */ + public void display(ParticleData data, float offsetX, float offsetY, float offsetZ, float speed, int amount, Location center, Player... players) throws ParticleVersionException, ParticleDataException { + display(data, offsetX, offsetY, offsetZ, speed, amount, center, Arrays.asList(players)); + } + + /** + * Displays a single particle which requires additional data that flies into a determined direction and is only visible for all players within a certain range in the world of @param center + * + * @param data Data of the effect + * @param direction Direction of the particle + * @param speed Display speed of the particles + * @param center Center location of the effect + * @param range Range of the visibility + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleDataException If the particle effect does not require additional data or if the data type is incorrect + * @see ParticlePacket + * @see ParticlePacket#sendTo(Location, double) + */ + public void display(ParticleData data, Vector direction, float speed, Location center, double range) throws ParticleVersionException, ParticleDataException { + if (!isSupported()) { + throw new ParticleVersionException("This particle effect is not supported by your server version"); + } + if (!hasProperty(ParticleProperty.REQUIRES_DATA)) { + throw new ParticleDataException("This particle effect does not require additional data"); + } + if (!isDataCorrect(this, data)) { + throw new ParticleDataException("The particle data type is incorrect"); + } + new ParticlePacket(this, direction, speed, range > 256, data).sendTo(center, range); + } + + /** + * Displays a single particle which requires additional data that flies into a determined direction and is only visible for the specified players + * + * @param data Data of the effect + * @param direction Direction of the particle + * @param speed Display speed of the particles + * @param center Center location of the effect + * @param players Receivers of the effect + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleDataException If the particle effect does not require additional data or if the data type is incorrect + * @see ParticlePacket + * @see ParticlePacket#sendTo(Location, List) + */ + public void display(ParticleData data, Vector direction, float speed, Location center, List players) throws ParticleVersionException, ParticleDataException { + if (!isSupported()) { + throw new ParticleVersionException("This particle effect is not supported by your server version"); + } + if (!hasProperty(ParticleProperty.REQUIRES_DATA)) { + throw new ParticleDataException("This particle effect does not require additional data"); + } + if (!isDataCorrect(this, data)) { + throw new ParticleDataException("The particle data type is incorrect"); + } + new ParticlePacket(this, direction, speed, isLongDistance(center, players), data).sendTo(center, players); + } + + /** + * Displays a single particle which requires additional data that flies into a determined direction and is only visible for the specified players + * + * @param data Data of the effect + * @param direction Direction of the particle + * @param speed Display speed of the particles + * @param center Center location of the effect + * @param players Receivers of the effect + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleDataException If the particle effect does not require additional data or if the data type is incorrect + * @see #display(ParticleData, Vector, float, Location, List) + */ + public void display(ParticleData data, Vector direction, float speed, Location center, Player... players) throws ParticleVersionException, ParticleDataException { + display(data, direction, speed, center, Arrays.asList(players)); + } + + /** + * Represents the property of a particle effect + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.7 + */ + public static enum ParticleProperty { + /** + * The particle effect requires water to be displayed + */ + REQUIRES_WATER, + /** + * The particle effect requires block or item data to be displayed + */ + REQUIRES_DATA, + /** + * The particle effect uses the offsets as direction values + */ + DIRECTIONAL, + /** + * The particle effect uses the offsets as color values + */ + COLORABLE; + } + + /** + * Represents the particle data for effects like {@link ParticleEffect#ITEM_CRACK}, {@link ParticleEffect#BLOCK_CRACK} and {@link ParticleEffect#BLOCK_DUST} + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.6 + */ + public static abstract class ParticleData { + private final Material material; + private final byte data; + private final int[] packetData; + + /** + * Construct a new particle data + * + * @param material Material of the item/block + * @param data Data value of the item/block + */ + @SuppressWarnings("deprecation") + public ParticleData(Material material, byte data) { + this.material = material; + this.data = data; + this.packetData = new int[] { material.getId(), data }; + } + + /** + * Returns the material of this data + * + * @return The material + */ + public Material getMaterial() { + return material; + } + + /** + * Returns the data value of this data + * + * @return The data value + */ + public byte getData() { + return data; + } + + /** + * Returns the data as an int array for packet construction + * + * @return The data for the packet + */ + public int[] getPacketData() { + return packetData; + } + + /** + * Returns the data as a string for pre 1.8 versions + * + * @return The data string for the packet + */ + public String getPacketDataString() { + return "_" + packetData[0] + "_" + packetData[1]; + } + } + + /** + * Represents the item data for the {@link ParticleEffect#ITEM_CRACK} effect + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.6 + */ + public static final class ItemData extends ParticleData { + /** + * Construct a new item data + * + * @param material Material of the item + * @param data Data value of the item + * @see ParticleData#ParticleData(Material, byte) + */ + public ItemData(Material material, byte data) { + super(material, data); + } + } + + /** + * Represents the block data for the {@link ParticleEffect#BLOCK_CRACK} and {@link ParticleEffect#BLOCK_DUST} effects + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.6 + */ + public static final class BlockData extends ParticleData { + /** + * Construct a new block data + * + * @param material Material of the block + * @param data Data value of the block + * @throws IllegalArgumentException If the material is not a block + * @see ParticleData#ParticleData(Material, byte) + */ + public BlockData(Material material, byte data) throws IllegalArgumentException { + super(material, data); + if (!material.isBlock()) { + throw new IllegalArgumentException("The material is not a block"); + } + } + } + + /** + * Represents the color for effects like {@link ParticleEffect#SPELL_MOB}, {@link ParticleEffect#SPELL_MOB_AMBIENT}, {@link ParticleEffect#REDSTONE} and {@link ParticleEffect#NOTE} + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.7 + */ + public static abstract class ParticleColor { + /** + * Returns the value for the offsetX field + * + * @return The offsetX value + */ + public abstract float getValueX(); + + /** + * Returns the value for the offsetY field + * + * @return The offsetY value + */ + public abstract float getValueY(); + + /** + * Returns the value for the offsetZ field + * + * @return The offsetZ value + */ + public abstract float getValueZ(); + } + + /** + * Represents the color for effects like {@link ParticleEffect#SPELL_MOB}, {@link ParticleEffect#SPELL_MOB_AMBIENT} and {@link ParticleEffect#NOTE} + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.7 + */ + public static final class OrdinaryColor extends ParticleColor { + private final int red; + private final int green; + private final int blue; + + /** + * Construct a new ordinary color + * + * @param red Red value of the RGB format + * @param green Green value of the RGB format + * @param blue Blue value of the RGB format + * @throws IllegalArgumentException If one of the values is lower than 0 or higher than 255 + */ + public OrdinaryColor(int red, int green, int blue) throws IllegalArgumentException { + if (red < 0) { + throw new IllegalArgumentException("The red value is lower than 0"); + } + if (red > 255) { + throw new IllegalArgumentException("The red value is higher than 255"); + } + this.red = red; + if (green < 0) { + throw new IllegalArgumentException("The green value is lower than 0"); + } + if (green > 255) { + throw new IllegalArgumentException("The green value is higher than 255"); + } + this.green = green; + if (blue < 0) { + throw new IllegalArgumentException("The blue value is lower than 0"); + } + if (blue > 255) { + throw new IllegalArgumentException("The blue value is higher than 255"); + } + this.blue = blue; + } + + /** + * Construct a new ordinary color + * + * @param color Bukkit color + */ + public OrdinaryColor(Color color) { + this(color.getRed(), color.getGreen(), color.getBlue()); + } + + /** + * Returns the red value of the RGB format + * + * @return The red value + */ + public int getRed() { + return red; + } + + /** + * Returns the green value of the RGB format + * + * @return The green value + */ + public int getGreen() { + return green; + } + + /** + * Returns the blue value of the RGB format + * + * @return The blue value + */ + public int getBlue() { + return blue; + } + + /** + * Returns the red value divided by 255 + * + * @return The offsetX value + */ + @Override + public float getValueX() { + return (float) red / 255F; + } + + /** + * Returns the green value divided by 255 + * + * @return The offsetY value + */ + @Override + public float getValueY() { + return (float) green / 255F; + } + + /** + * Returns the blue value divided by 255 + * + * @return The offsetZ value + */ + @Override + public float getValueZ() { + return (float) blue / 255F; + } + } + + /** + * Represents the color for the {@link ParticleEffect#NOTE} effect + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.7 + */ + public static final class NoteColor extends ParticleColor { + private final int note; + + /** + * Construct a new note color + * + * @param note Note id which determines color + * @throws IllegalArgumentException If the note value is lower than 0 or higher than 24 + */ + public NoteColor(int note) throws IllegalArgumentException { + if (note < 0) { + throw new IllegalArgumentException("The note value is lower than 0"); + } + if (note > 24) { + throw new IllegalArgumentException("The note value is higher than 24"); + } + this.note = note; + } + + /** + * Returns the note value divided by 24 + * + * @return The offsetX value + */ + @Override + public float getValueX() { + return (float) note / 24F; + } + + /** + * Returns zero because the offsetY value is unused + * + * @return zero + */ + @Override + public float getValueY() { + return 0; + } + + /** + * Returns zero because the offsetZ value is unused + * + * @return zero + */ + @Override + public float getValueZ() { + return 0; + } + + } + + /** + * Represents a runtime exception that is thrown either if the displayed particle effect requires data and has none or vice-versa or if the data type is incorrect + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.6 + */ + private static final class ParticleDataException extends RuntimeException { + private static final long serialVersionUID = 3203085387160737484L; + + /** + * Construct a new particle data exception + * + * @param message Message that will be logged + */ + public ParticleDataException(String message) { + super(message); + } + } + + /** + * Represents a runtime exception that is thrown either if the displayed particle effect is not colorable or if the particle color type is incorrect + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.7 + */ + private static final class ParticleColorException extends RuntimeException { + private static final long serialVersionUID = 3203085387160737484L; + + /** + * Construct a new particle color exception + * + * @param message Message that will be logged + */ + public ParticleColorException(String message) { + super(message); + } + } + + /** + * Represents a runtime exception that is thrown if the displayed particle effect requires a newer version + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.6 + */ + private static final class ParticleVersionException extends RuntimeException { + private static final long serialVersionUID = 3203085387160737484L; + + /** + * Construct a new particle version exception + * + * @param message Message that will be logged + */ + public ParticleVersionException(String message) { + super(message); + } + } + + /** + * Represents a particle effect packet with all attributes which is used for sending packets to the players + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.5 + */ + public static final class ParticlePacket { + private static int version; + private static boolean initialized; + private final ParticleEffect effect; + private float offsetX; + private final float offsetY; + private final float offsetZ; + private final float speed; + private final int amount; + private final boolean longDistance; + private final ParticleData data; + private PacketPlayOutWorldParticles packet; + + /** + * Construct a new particle packet + * + * @param effect Particle effect + * @param offsetX Maximum distance particles can fly away from the center on the x-axis + * @param offsetY Maximum distance particles can fly away from the center on the y-axis + * @param offsetZ Maximum distance particles can fly away from the center on the z-axis + * @param speed Display speed of the particles + * @param amount Amount of particles + * @param longDistance Indicates whether the maximum distance is increased from 256 to 65536 + * @param data Data of the effect + * @throws IllegalArgumentException If the speed or amount is lower than 0 + * @see #initialize() + */ + public ParticlePacket(ParticleEffect effect, float offsetX, float offsetY, float offsetZ, float speed, int amount, boolean longDistance, ParticleData data) throws IllegalArgumentException { + initialize(); + if (speed < 0) { + throw new IllegalArgumentException("The speed is lower than 0"); + } + if (amount < 0) { + throw new IllegalArgumentException("The amount is lower than 0"); + } + this.effect = effect; + this.offsetX = offsetX; + this.offsetY = offsetY; + this.offsetZ = offsetZ; + this.speed = speed; + this.amount = amount; + this.longDistance = longDistance; + this.data = data; + } + + /** + * Construct a new particle packet of a single particle flying into a determined direction + * + * @param effect Particle effect + * @param direction Direction of the particle + * @param speed Display speed of the particle + * @param longDistance Indicates whether the maximum distance is increased from 256 to 65536 + * @param data Data of the effect + * @throws IllegalArgumentException If the speed is lower than 0 + */ + public ParticlePacket(ParticleEffect effect, Vector direction, float speed, boolean longDistance, ParticleData data) throws IllegalArgumentException { + this(effect, (float) direction.getX(), (float) direction.getY(), (float) direction.getZ(), speed, 0, longDistance, data); + } + + /** + * Construct a new particle packet of a single colored particle + * + * @param effect Particle effect + * @param color Color of the particle + * @param longDistance Indicates whether the maximum distance is increased from 256 to 65536 + */ + public ParticlePacket(ParticleEffect effect, ParticleColor color, boolean longDistance) { + this(effect, color.getValueX(), color.getValueY(), color.getValueZ(), 1, 0, longDistance, null); + if (effect == ParticleEffect.REDSTONE && color instanceof OrdinaryColor && ((OrdinaryColor) color).getRed() == 0) { + offsetX = Float.MIN_NORMAL; + } + } + + /** + * Initializes and sets {@link #initialized} to true if it succeeds + *

+ * Note: These fields only have to be initialized once, so it will return if {@link #initialized} is already set to true + * + * @throws VersionIncompatibleException if your bukkit version is not supported by this library + */ + public static void initialize() throws VersionIncompatibleException { + if (initialized) { + return; + } + try { + version = Integer.parseInt(Character.toString(Bukkit.getServer().getClass().getPackage().getName().substring(23).charAt(3))); + } catch (Exception exception) { + throw new VersionIncompatibleException("Your current bukkit version seems to be incompatible with this library", exception); + } + initialized = true; + } + + /** + * Returns the version of your server (1.x) + * + * @return The version number + */ + public static int getVersion() { + if (!initialized) { + initialize(); + } + return version; + } + + /** + * Determine this is initialized + * + * @return Whether these fields are initialized or not + * @see #initialize() + */ + public static boolean isInitialized() { + return initialized; + } + + /** + * Initializes {@link #packet} with all set values + * + * @param center Center location of the effect + * @throws PacketInstantiationException If instantion fails due to an unknown error + */ + private void initializePacket(Location center) throws PacketInstantiationException { + if (packet != null) { + return; + } + try { + packet = new PacketPlayOutWorldParticles(); + + packet.a = EnumParticle.values()[effect.getId()]; + packet.j = longDistance; + if (data != null) { + int[] packetData = data.getPacketData(); + packet.k = effect == ParticleEffect.ITEM_CRACK ? packetData : new int[] { packetData[0] | (packetData[1] << 12) }; + } + packet.b = (float) center.getX(); + packet.c = (float) center.getY(); + packet.d = (float) center.getZ(); + packet.e = offsetX; + packet.f = offsetY; + packet.g = offsetZ; + packet.h = speed; + packet.i = amount; + } catch (Exception exception) { + throw new PacketInstantiationException("Packet instantiation failed", exception); + } + } + + /** + * Sends the packet to a single player and caches it + * + * @param center Center location of the effect + * @param player Receiver of the packet + * @throws PacketInstantiationException If instantion fails due to an unknown error + * @throws PacketSendingException If sending fails due to an unknown error + * @see #initializePacket(Location) + */ + public void sendTo(Location center, Player player) throws PacketInstantiationException, PacketSendingException { + initializePacket(center); + try { + ((CraftPlayer)player).getHandle().playerConnection.sendPacket(packet); + } catch (Exception exception) { + throw new PacketSendingException("Failed to send the packet to player '" + player.getName() + "'", exception); + } + } + + /** + * Sends the packet to all players in the list + * + * @param center Center location of the effect + * @param players Receivers of the packet + * @throws IllegalArgumentException If the player list is empty + * @see #sendTo(Location center, Player player) + */ + public void sendTo(Location center, List players) throws IllegalArgumentException { + if (players.isEmpty()) { + throw new IllegalArgumentException("The player list is empty"); + } + for (Player player : players) { + sendTo(center, player); + } + } + + /** + * Sends the packet to all players in a certain range + * + * @param center Center location of the effect + * @param range Range in which players will receive the packet (Maximum range for particles is usually 16, but it can differ for some types) + * @throws IllegalArgumentException If the range is lower than 1 + * @see #sendTo(Location center, Player player) + */ + public void sendTo(Location center, double range) throws IllegalArgumentException { + if (range < 1) { + throw new IllegalArgumentException("The range is lower than 1"); + } + String worldName = center.getWorld().getName(); + double squared = range * range; + for (Player player : Bukkit.getOnlinePlayers()) { + if (!player.getWorld().getName().equals(worldName) || player.getLocation().distanceSquared(center) > squared) { + continue; + } + sendTo(center, player); + } + } + + /** + * Represents a runtime exception that is thrown if a bukkit version is not compatible with this library + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.5 + */ + private static final class VersionIncompatibleException extends RuntimeException { + private static final long serialVersionUID = 3203085387160737484L; + + /** + * Construct a new version incompatible exception + * + * @param message Message that will be logged + * @param cause Cause of the exception + */ + public VersionIncompatibleException(String message, Throwable cause) { + super(message, cause); + } + } + + /** + * Represents a runtime exception that is thrown if packet instantiation fails + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.4 + */ + private static final class PacketInstantiationException extends RuntimeException { + private static final long serialVersionUID = 3203085387160737484L; + + /** + * Construct a new packet instantiation exception + * + * @param message Message that will be logged + * @param cause Cause of the exception + */ + public PacketInstantiationException(String message, Throwable cause) { + super(message, cause); + } + } + + /** + * Represents a runtime exception that is thrown if packet sending fails + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.4 + */ + private static final class PacketSendingException extends RuntimeException { + private static final long serialVersionUID = 3203085387160737484L; + + /** + * Construct a new packet sending exception + * + * @param message Message that will be logged + * @param cause Cause of the exception + */ + public PacketSendingException(String message, Throwable cause) { + super(message, cause); + } + } + } +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/ParticleMath.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/ParticleMath.java new file mode 100644 index 0000000..8052337 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/ParticleMath.java @@ -0,0 +1,50 @@ +package com.elevatemc.elib.util; + + +/** + * @author ImHacking + * @date 5/8/2022 + */ +/* + * http://www.java-gaming.org/topics/extremely-fast-sine-cosine/36469/view.html + * Dont want to rape the cpu + */ +public class ParticleMath { + private static final int SIN_BITS, SIN_MASK, SIN_COUNT; + private static final double radFull, radToIndex; + private static final double degFull, degToIndex; + private static final double[] sin, cos; + + static { + SIN_BITS = 12; + SIN_MASK = ~(-1 << SIN_BITS); + SIN_COUNT = SIN_MASK + 1; + + radFull = Math.PI * 2.0; + degFull = 360.0; + radToIndex = SIN_COUNT / radFull; + degToIndex = SIN_COUNT / degFull; + + sin = new double[SIN_COUNT]; + cos = new double[SIN_COUNT]; + + for (int i = 0; i < SIN_COUNT; i++) { + sin[i] = Math.sin((i + 0.5f) / SIN_COUNT * radFull); + cos[i] = Math.cos((i + 0.5f) / SIN_COUNT * radFull); + } + + for (int i = 0; i < 360; i += 90) { + sin[(int) (i * degToIndex) & SIN_MASK] = Math.sin(i * Math.PI / 180.0); + cos[(int) (i * degToIndex) & SIN_MASK] = Math.cos(i * Math.PI / 180.0); + } + } + + public static double sin(double rad) { + return sin[(int) (rad * radToIndex) & SIN_MASK]; + } + + public static double cos(double rad) { + return cos[(int) (rad * radToIndex) & SIN_MASK]; + } +} + diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/PingUtils.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/PingUtils.java new file mode 100644 index 0000000..f7de7b2 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/PingUtils.java @@ -0,0 +1,115 @@ +package com.elevatemc.elib.util; + +import com.elevatemc.elib.eLib; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; + +public final class PingUtils { + + // Static utility class -- cannot be created. + private PingUtils() { + } + + /** + * Pings a remote Minecraft server. + * + * @param host The host of the server to ping. Can be either a hostname (ex MineHQ.com) or an IP (ex 127.0.0.1) + * @param port The port of the server to ping. The default Minecraft port is 25565. + * @param callback The callback to call with the results of pinging. + */ + public static void ping(String host, int port, Callback callback) { + (new PingTask(host, port, callback)).run(); + } + + public static class PingResponse { + + @Getter private Version version; + @Getter private Players players; + @Getter private String description; + @Getter private String favicon; + + public static class Players { + + @Getter private int max; + @Getter private int online; + + } + + public static class Version { + + @Getter private String name; + @Getter private int protocol; + + } + } + + @AllArgsConstructor + private static class PingTask implements Runnable { + + private String host; + private int port; + private Callback callback; + + @Override + public void run() { + try (Socket socket = new Socket()) { + SocketAddress address = new InetSocketAddress(host, port); + + socket.connect(address, 5000); // 5 second timeout on connect and read + socket.setSoTimeout(5000); + + DataOutputStream out = new DataOutputStream(socket.getOutputStream()); + + // construct a handshake packet + ByteArrayOutputStream handshake = new ByteArrayOutputStream(); + DataOutputStream handshakeOut = new DataOutputStream(handshake); + + NetworkUtils.writeVarInt(handshakeOut, 0x00); // handshake ID = 0x00 + NetworkUtils.writeVarInt(handshakeOut, 4); // 1.7.2 - 1.7.5 = 4 + NetworkUtils.writeString(handshakeOut, host); + handshakeOut.writeShort(port); + NetworkUtils.writeVarInt(handshakeOut, 1); // status protocol = 1 + NetworkUtils.writePacket(out, handshake.toByteArray()); + + ByteArrayOutputStream status = new ByteArrayOutputStream(); + DataOutputStream statusOut = new DataOutputStream(status); + + NetworkUtils.writeVarInt(statusOut, 0x00); // request status = 0 + NetworkUtils.writePacket(out, status.toByteArray()); + + DataInputStream in = new DataInputStream(socket.getInputStream()); + byte[] response = NetworkUtils.readPacket(in); + DataInputStream responseIn = new DataInputStream(new ByteArrayInputStream(response)); + int id = NetworkUtils.readVarInt(responseIn); + + if (id != 0x00) { + throw new Exception("Unexpected packet ID"); + } + + String jsonResponse = NetworkUtils.readString(responseIn); + + callback.success(eLib.GSON.fromJson(jsonResponse, PingResponse.class)); + } catch (Exception e) { + callback.failure(e); + } + } + + } + + public interface Callback { + + void success(PingResponse response); + + void failure(Exception e); + + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/PlayerTeamUtils.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/PlayerTeamUtils.java new file mode 100644 index 0000000..1961a3e --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/PlayerTeamUtils.java @@ -0,0 +1,83 @@ +package com.elevatemc.elib.util; + +import net.md_5.bungee.api.chat.TextComponent; +import net.minecraft.server.v1_8_R3.Packet; +import net.minecraft.server.v1_8_R3.PacketPlayOutPlayerInfo; +import net.minecraft.server.v1_8_R3.PacketPlayOutScoreboardTeam; +import com.mojang.authlib.GameProfile; +import net.minecraft.server.v1_8_R3.WorldSettings; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_8_R3.util.CraftChatMessage; +import org.bukkit.entity.Player; +import java.util.ArrayList; +import java.util.Collection; + + +public class PlayerTeamUtils { + + public static void sendPlayerInfoPacket(Player player, int ping, String username, GameProfile profile, int mode) { + PacketPlayOutPlayerInfo playerInfo = new PacketPlayOutPlayerInfo(); + PacketPlayOutPlayerInfo.EnumPlayerInfoAction action = PacketPlayOutPlayerInfo.EnumPlayerInfoAction.values()[mode]; + + playerInfo.setA(action); + playerInfo.getB().add(new PacketPlayOutPlayerInfo.PlayerInfoData(profile, ping, WorldSettings.EnumGamemode.SURVIVAL, CraftChatMessage.fromString(username)[0])); + sendPacket(player, playerInfo); + } + + public static void sendUpdatePlayers(Player viewer, String teamName, Collection players, int mode) { + PacketPlayOutScoreboardTeam teamPacket = new PacketPlayOutScoreboardTeam(); + teamPacket.a = (teamName); + teamPacket.b = (teamName); + teamPacket.c = (""); + teamPacket.d = (""); + + if (mode == 0 || mode == 3 || mode == 4) { + teamPacket.g = (players); + } + + teamPacket.h = (mode); + teamPacket.i = (0); + + sendPacket(viewer, teamPacket); + } + + //a = name + //b = display //16 + //c = prefix //16 + //d = suffix //16 + //e = players + //f = mode + //g = friendlyFire 1 = on 0 = off + //mode 0 = create, 1 = remove, 2 = update, 3 = new players, 4 = remove players + public static void sendTeamPacket(Player viewer, String targetName, String teamName, String prefix, String suffix, boolean friendly, int mode) { + PacketPlayOutScoreboardTeam teamPacket = new PacketPlayOutScoreboardTeam(); + teamPacket.a = (teamName); + teamPacket.b = (teamName); + + if (mode == 0 || mode == 2) { + teamPacket.c = (prefix); + teamPacket.d = (suffix); + } + + if (mode == 0 || mode == 3 || mode == 4) { + if (mode == 0 || mode == 3) { + teamPacket.g = (new ArrayList<>()); + teamPacket.g.add(targetName); + } else { + teamPacket.g.add(targetName); + } + } + + teamPacket.h = (mode); + + if (mode == 0 || mode == 2) { + teamPacket.i = (friendly ? 3 : 0); + } + + sendPacket(viewer, teamPacket); + } + + public static void sendPacket(Player player, Packet packet) { + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/PlayerUtils.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/PlayerUtils.java new file mode 100644 index 0000000..c8e47e1 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/PlayerUtils.java @@ -0,0 +1,203 @@ +package com.elevatemc.elib.util; + +import com.elevatemc.elib.eLib; + +import com.elevatemc.spigot.viaversion.api.Via; +import com.lunarclient.bukkitapi.LunarClientAPI; +import com.lunarclient.bukkitapi.nethandler.client.LCPacketTitle; +import net.md_5.bungee.api.chat.BaseComponent; +import net.minecraft.server.v1_8_R3.MinecraftServer; +import net.minecraft.server.v1_8_R3.PacketPlayOutEntityDestroy; +import net.minecraft.server.v1_8_R3.PacketPlayOutEntityStatus; +import net.minecraft.server.v1_8_R3.PacketPlayOutNamedEntitySpawn; + +import org.bukkit.GameMode; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.potion.PotionEffect; +import org.github.paperspigot.Title; + +import java.lang.reflect.Field; +import java.util.*; + + +public final class PlayerUtils { + + private static Field STATUS_PACKET_ID_FIELD; + private static Field STATUS_PACKET_STATUS_FIELD; + private static Field SPAWN_PACKET_ID_FIELD; + + private static Map cpsCount = new HashMap<>(); + + // Static utility class -- cannot be created. + private PlayerUtils() { + } + + /** + * Resets a player's inventory (and other associated data, such as health, food, etc) to their default state. + * + * @param player The player to reset + */ + public static void resetInventory(Player player) { + resetInventory(player, null); + } + public static int getProtocol(Player player) { + return 47; + } + public static boolean hasOpenInventory(Player player) { + return hasOwnInventoryOpen(player) || hasOtherInventoryOpen(player); + } + + public static boolean hasOwnInventoryOpen(Player player) { + return InventoryAdapter.getCurrentlyOpen().contains(player.getUniqueId()); + } + + + /** + * Resets a player's inventory (and other associated data, such as health, food, etc) to their default state. + * + * @param player The player to reset + * @param gameMode The gamemode to reset the player to. null if their current gamemode should be kept. + */ + public static void resetInventory(Player player, GameMode gameMode) { + ((CraftPlayer) player).getHandle().getDataWatcher().watch(9, (byte) 0); + player.setHealth(player.getMaxHealth()); + player.setFallDistance(0F); + player.setFoodLevel(20); + player.setSaturation(10F); + player.setLevel(0); + player.setExp(0F); + player.getInventory().clear(); + player.getInventory().setArmorContents(null); + player.setFireTicks(0); + + for (PotionEffect potionEffect : player.getActivePotionEffects()) { + player.removePotionEffect(potionEffect.getType()); + } + + if (gameMode != null && player.getGameMode() != gameMode) { + player.setGameMode(gameMode); + } + } + + public static Player getDamageSource(Entity damager) { + + Player playerDamager = null; + + if (damager instanceof Player) { + playerDamager = (Player) damager; + } else if (damager instanceof Projectile) { + + final Projectile projectile = (Projectile) damager; + + if (projectile.getShooter() instanceof Player) { + playerDamager = (Player) projectile.getShooter(); + } + } + + return playerDamager; + } + + public static boolean hasOtherInventoryOpen(Player player) { + return ((CraftPlayer) player).getHandle().activeContainer.windowId != 0; + } + + public static int getPing(Player player) { + return ((CraftPlayer) player).getHandle().ping; + } + + public static void animateDeath(Player player) { + + final int entityId = EntityUtils.getFakeEntityId(); + final PacketPlayOutNamedEntitySpawn spawnPacket = new PacketPlayOutNamedEntitySpawn(((CraftPlayer) player).getHandle()); + final PacketPlayOutEntityStatus statusPacket = new PacketPlayOutEntityStatus(); + + try { + SPAWN_PACKET_ID_FIELD.set(spawnPacket, entityId); + STATUS_PACKET_ID_FIELD.set(statusPacket, entityId); + STATUS_PACKET_STATUS_FIELD.set(statusPacket, (byte) 3); + + final int radius = MinecraftServer.getServer().getPlayerList().d(); + final Set sentTo = new HashSet<>(); + + for (Entity entity : player.getNearbyEntities(radius, radius, radius)) { + + if (!(entity instanceof Player)) { + continue; + } + + final Player watcher = (Player) entity; + + if (watcher.getUniqueId().equals(player.getUniqueId())) { + continue; + } + + ((CraftPlayer) watcher).getHandle().playerConnection.sendPacket(spawnPacket); + ((CraftPlayer) watcher).getHandle().playerConnection.sendPacket(statusPacket); + + sentTo.add(watcher); + } + + eLib.getInstance().getServer().getScheduler().runTaskLater(eLib.getInstance(), () -> { + + for (Player watcher : sentTo) { + ((CraftPlayer) watcher).getHandle().playerConnection.sendPacket(new PacketPlayOutEntityDestroy(entityId)); + } + + }, 40L); + } catch (Exception ex) { + ex.printStackTrace(); + } + + } + + public static void animateDeath(Player player, Player watcher) { + + final int entityId = EntityUtils.getFakeEntityId(); + final PacketPlayOutNamedEntitySpawn spawnPacket = new PacketPlayOutNamedEntitySpawn(((CraftPlayer) player).getHandle()); + final PacketPlayOutEntityStatus statusPacket = new PacketPlayOutEntityStatus(); + + try { + SPAWN_PACKET_ID_FIELD.set(spawnPacket, entityId); + STATUS_PACKET_ID_FIELD.set(statusPacket, entityId); + STATUS_PACKET_STATUS_FIELD.set(statusPacket, (byte) 3); + + ((CraftPlayer) watcher).getHandle().playerConnection.sendPacket(spawnPacket); + ((CraftPlayer) watcher).getHandle().playerConnection.sendPacket(statusPacket); + + eLib.getInstance().getServer().getScheduler().runTaskLater(eLib.getInstance(), () -> ((CraftPlayer) watcher).getHandle().playerConnection.sendPacket(new PacketPlayOutEntityDestroy(new int[]{entityId})), 40L); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + public static void sendTitle(Player player, Title title) { + // If the player is on 1.7 we try to send the LunarClient title packet + if (Via.getAPI().getPlayerVersion(player.getUniqueId()) == 5) { + if (title.getTitle() != null) { + LunarClientAPI.getInstance().sendPacket(player, new LCPacketTitle("title", BaseComponent.toLegacyText(title.getTitle()), title.getStay() * 50L, title.getFadeIn() * 50L, title.getFadeOut() * 50L)); + } + if (title.getSubtitle() != null) { + LunarClientAPI.getInstance().sendPacket(player, new LCPacketTitle("subtitle", BaseComponent.toLegacyText(title.getSubtitle()), title.getStay() * 50L, title.getFadeIn() * 50L, title.getFadeOut() * 50L)); + } + } else { + player.sendTitle(title); + } + } + + static { + try { + STATUS_PACKET_ID_FIELD = PacketPlayOutEntityStatus.class.getDeclaredField("a"); + STATUS_PACKET_ID_FIELD.setAccessible(true); + STATUS_PACKET_STATUS_FIELD = PacketPlayOutEntityStatus.class.getDeclaredField("b"); + STATUS_PACKET_STATUS_FIELD.setAccessible(true); + SPAWN_PACKET_ID_FIELD = PacketPlayOutNamedEntitySpawn.class.getDeclaredField("a"); + SPAWN_PACKET_ID_FIELD.setAccessible(true); + } catch (NoSuchFieldException ex) { + ex.printStackTrace(); + } + + } +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/ReflectionUtil.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/ReflectionUtil.java new file mode 100644 index 0000000..8be2c37 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/ReflectionUtil.java @@ -0,0 +1,117 @@ +package com.elevatemc.elib.util; + +import org.bukkit.Bukkit; +import org.bukkit.Server; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.List; + +//https://github.com/joeleoli/BukkitReflection/blob/master/BukkitReflection.java + +public class ReflectionUtil { + + private static final String CRAFT_BUKKIT_PACKAGE; + private static final String NET_MINECRAFT_SERVER_PACKAGE; + + private static final Class CRAFT_SERVER_CLASS; + private static final Method CRAFT_SERVER_GET_HANDLE_METHOD; + + private static final Class PLAYER_LIST_CLASS; + private static final Field PLAYER_LIST_MAX_PLAYERS_FIELD; + + private static final Class CRAFT_PLAYER_CLASS; + private static final Method CRAFT_PLAYER_GET_HANDLE_METHOD; + + private static final Class ENTITY_PLAYER_CLASS; + private static final Field ENTITY_PLAYER_PING_FIELD; + + private static final Class CRAFT_ITEM_STACK_CLASS; + private static final Method CRAFT_ITEM_STACK_AS_NMS_COPY_METHOD; + + static { + try { + String version = Bukkit.getServer().getClass().getPackage().getName().replace(".",",").split(",")[3]; + + CRAFT_BUKKIT_PACKAGE = "org.bukkit.craftbukkit."+version+"."; + NET_MINECRAFT_SERVER_PACKAGE = "net.minecraft.server."+version+"."; + + CRAFT_SERVER_CLASS = Class.forName(CRAFT_BUKKIT_PACKAGE+"CraftServer"); + CRAFT_SERVER_GET_HANDLE_METHOD = CRAFT_SERVER_CLASS.getDeclaredMethod("getHandle"); + CRAFT_SERVER_GET_HANDLE_METHOD.setAccessible(true); + + PLAYER_LIST_CLASS = Class.forName(NET_MINECRAFT_SERVER_PACKAGE+"PlayerList"); + PLAYER_LIST_MAX_PLAYERS_FIELD = PLAYER_LIST_CLASS.getDeclaredField("maxPlayers"); + PLAYER_LIST_MAX_PLAYERS_FIELD.setAccessible(true); + + CRAFT_PLAYER_CLASS = Class.forName(CRAFT_BUKKIT_PACKAGE+"entity.CraftPlayer"); + CRAFT_PLAYER_GET_HANDLE_METHOD = CRAFT_PLAYER_CLASS.getDeclaredMethod("getHandle"); + CRAFT_PLAYER_GET_HANDLE_METHOD.setAccessible(true); + + ENTITY_PLAYER_CLASS = Class.forName(NET_MINECRAFT_SERVER_PACKAGE+"EntityPlayer"); + ENTITY_PLAYER_PING_FIELD = ENTITY_PLAYER_CLASS.getDeclaredField("ping"); + ENTITY_PLAYER_PING_FIELD.setAccessible(true); + + CRAFT_ITEM_STACK_CLASS = Class.forName(CRAFT_BUKKIT_PACKAGE+"inventory.CraftItemStack"); + CRAFT_ITEM_STACK_AS_NMS_COPY_METHOD = CRAFT_ITEM_STACK_CLASS.getDeclaredMethod("asNMSCopy",ItemStack.class); + CRAFT_ITEM_STACK_AS_NMS_COPY_METHOD.setAccessible(true); + } catch (Exception e) { + e.printStackTrace(); + + throw new RuntimeException("Failed to initialize Bukkit/NMS Reflection"); + } + } + + public static int getPing(Player player) { + try { + int ping = ENTITY_PLAYER_PING_FIELD.getInt(CRAFT_PLAYER_GET_HANDLE_METHOD.invoke(player)); + + return ping > 0 ? ping : 0; + } catch (Exception e) { + return 1; + } + } + + public static void setMaxPlayers(Server server,int slots) { + try { + PLAYER_LIST_MAX_PLAYERS_FIELD.set(CRAFT_SERVER_GET_HANDLE_METHOD.invoke(server),slots); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static String getItemStackName(ItemStack itemStack) { + try { + return (String)CRAFT_ITEM_STACK_AS_NMS_COPY_METHOD.invoke(itemStack,itemStack); + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + } + + public static List> toParamTypes(Object... params) { + return null; + } + + public static Method getDeclaredMethod(Class clazz, String methodName, Class... params) { + try { + final Method declaredMethod = clazz.getDeclaredMethod(methodName, params); + declaredMethod.setAccessible(true); + return declaredMethod; + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } + + return null; + } + + public static void callMethod(Object instance, String methodName, Object... params) { + try { +// final Method method = instance.getClass().getMethod(methodName, toParamTypes(params)); + } catch(Exception ex) { + ex.printStackTrace(); + } + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/SimpleText.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/SimpleText.java new file mode 100644 index 0000000..5aff06f --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/SimpleText.java @@ -0,0 +1,58 @@ +package com.elevatemc.elib.util; + + +import com.elevatemc.elib.util.message.MessageTranslator; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.HoverEvent; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + + +public class SimpleText { + + private final ComponentBuilder builder; + + public SimpleText(String string) { + builder = new ComponentBuilder(MessageTranslator.translate(string)); + } + + public SimpleText add(String string) { + builder.append(MessageTranslator.translate(string)); + return this; + } + + public SimpleText add(SimpleText simpleText) { + builder.append(simpleText.builder.getCurrent()); + return this; + } + + public SimpleText click(String command) { + builder.event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, command)); + return this; + } + + public SimpleText hover(String string) { + HoverEvent event = new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(MessageTranslator.translate(string)).create()); + builder.event(event); + return this; + } + + public BaseComponent[] build() { + return builder.create(); + } + + public void send(CommandSender sender) { + if (sender instanceof Player) { + Player player = (Player) sender; + player.spigot().sendMessage(this.build()); + } else { + StringBuilder message = new StringBuilder(); + for (BaseComponent component : this.build()) { + message.append(component.toPlainText()).append(" "); + } + sender.sendMessage(message.toString()); + } + } +} + diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/SpawnerEntry.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/SpawnerEntry.java new file mode 100644 index 0000000..de0a51f --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/SpawnerEntry.java @@ -0,0 +1,59 @@ +package com.elevatemc.elib.util; + +import lombok.RequiredArgsConstructor; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.CreatureSpawner; +import org.bukkit.entity.EntityType; +import org.bukkit.inventory.ItemStack; +import com.elevatemc.elib.util.message.MessageColor; + +/** + * @author ImHacking + * @date 5/5/2022 + */ + +@RequiredArgsConstructor +public class SpawnerEntry { + private static final String FIRST_SPLIT = MessageColor.GOLD + "This is a " + MessageColor.YELLOW; + private static final String LAST_SPLIT = MessageColor.GOLD + " spawner."; + + private final EntityType entityType; + + public ItemStack toItemStack() { + return ItemBuilder.of(Material.MOB_SPAWNER) + .name(MessageColor.GOLD + EntityUtils.getName(entityType) + " Spawner") + .addToLore(FIRST_SPLIT + EntityUtils.getName(entityType) + LAST_SPLIT) + .build(); + } + + public void updateBlock(Block block) { + BlockState state = block.getState(); + if (state instanceof CreatureSpawner) { + CreatureSpawner creatureSpawner = (CreatureSpawner) state; + creatureSpawner.setSpawnedType(this.entityType); + } + } + + public static SpawnerEntry fromItemStack(ItemStack itemStack) { + String line = LoreUtil.getFirstLoreLine(itemStack); + if (line == null) { + return null; + } + String[] split = line.split(LAST_SPLIT); + if (split.length <= 0) { + return null; + } + split = split[0].split(FIRST_SPLIT); + if (split.length <= 1) { + return null; + } + EntityType entityType = EntityUtils.parse(split[1]); + if (entityType == null) { + return null; + } + return new SpawnerEntry(entityType); + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/TaskUtil.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/TaskUtil.java new file mode 100644 index 0000000..ae29fc2 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/TaskUtil.java @@ -0,0 +1,99 @@ +package com.elevatemc.elib.util; + + +import com.elevatemc.elib.eLib; +import org.bukkit.Bukkit; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.scheduler.BukkitTask; + + +import java.util.concurrent.*; +import java.util.function.Consumer; +import java.util.function.Function; + +@SuppressWarnings("ALL") +public class TaskUtil { + + private static final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(8); + private static final JavaPlugin javaPlugin = eLib.getInstance(); + private static final BukkitScheduler scheduler = Bukkit.getScheduler(); + + public static void scheduleAtFixedRateOnPool(Runnable runnable, long delay, long period, TimeUnit timeUnit) { + scheduledThreadPoolExecutor.scheduleAtFixedRate(runnable, delay, period, timeUnit); + } + + public static void scheduleOnPool(Runnable runnable, long delay, TimeUnit timeUnit) { + scheduledThreadPoolExecutor.schedule(runnable, delay, timeUnit); + } + + public static void executeWithPool(Runnable runnable) { + scheduledThreadPoolExecutor.execute(runnable); + } + + public static void executeWithPoolIfRequired(Runnable runnable) { + if (Bukkit.isPrimaryThread()) { + scheduledThreadPoolExecutor.execute(runnable); + return; + } + runnable.run(); + } + + public static void runSync(Runnable runnable) { + scheduler.runTask(javaPlugin, runnable); + } + + public BukkitTask runTask(Runnable runnable) { + return scheduler.runTask(javaPlugin, runnable); + } + + public static BukkitTask runTaskAsynchronously(Runnable runnable) { + return scheduler.runTaskAsynchronously(javaPlugin, runnable); + } + + public static BukkitTask runTaskTimer(Runnable runnable, int initialDelay, int scheduleDelay) { + return scheduler.runTaskTimer(javaPlugin, runnable, initialDelay, scheduleDelay); + } + + public static BukkitTask runTaskTimerAsynchronously(Runnable runnable, int initialDelay, int scheduleDelay) { + return scheduler.runTaskTimerAsynchronously(javaPlugin, runnable, initialDelay, scheduleDelay); + } + + public static BukkitTask runTaskLater(Runnable runnable, int delay) { + return scheduler.runTaskLater(javaPlugin, runnable, delay); + } + + public static BukkitTask runTaskLaterAsynchronously(Runnable runnable, int delay) { + return scheduler.runTaskLaterAsynchronously(javaPlugin, runnable, delay); + } + + public static Future callMethodSync(Callable callable) { + return scheduler.callSyncMethod(javaPlugin, callable); + } + + public static T callMethodSyncFetched(Callable callable) { + Future future = callMethodSync(callable); + try { + return future.get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + return null; + } + } + + public static CompletableFuture andThenSync(CompletableFuture input, Function function) { + return input.thenApply((T in) -> callMethodSyncFetched(() -> function.apply(in))); + } + + public static CompletableFuture andThenSync(CompletableFuture input, Consumer consumer) { + return input.thenAccept((T in) -> callMethodSyncFetched(() -> { + consumer.accept(in); + return in; + })); + } + + public static void shutdownThreadPoolExecutor() { + scheduledThreadPoolExecutor.shutdown(); + } +} + diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/TimeUtils.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/TimeUtils.java new file mode 100644 index 0000000..a0859f8 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/TimeUtils.java @@ -0,0 +1,171 @@ +package com.elevatemc.elib.util; + +import lombok.experimental.UtilityClass; + +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@UtilityClass +public final class TimeUtils { + private static final String HOUR_FORMAT = "%02d:%02d:%02d"; + private static final String MINUTE_FORMAT = "%02d:%02d"; + private static final SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm"); + + public static String millisToTimer(long millis) { + long seconds = millis / 1000L; + + if (seconds > 3600L) { + return String.format(HOUR_FORMAT, seconds / 3600L, seconds % 3600L / 60L, seconds % 60L); + } else { + return String.format(MINUTE_FORMAT, seconds / 60L, seconds % 60L); + } + } + + /** + * Return the amount of seconds from milliseconds. + * Note: We explicitly use 1000.0F (float) instead of 1000L (long). + * + * @param millis the amount of time in milliseconds + * @return the seconds + */ + public static String millisToSeconds(long millis) { + return new DecimalFormat("#0.0").format(millis / 1000.0F); + } + + + /** + * Delegate to TimeUtils#formatIntoMMSS for backwards compat + */ + public static String formatIntoHHMMSS(int secs) { + return formatIntoMMSS(secs); + } + + /** + * Formats the time into a format of HH:MM:SS. Example: 3600 (1 hour) displays as '01:00:00' + * + * @param secs The input time, in seconds. + * @return The HH:MM:SS formatted time. + */ + public static String formatIntoMMSS(int secs) { + // Calculate the seconds to display: + int seconds = secs % 60; + secs -= seconds; + + // Calculate the minutes: + long minutesCount = secs / 60; + long minutes = minutesCount % 60; + minutesCount -= minutes; + + long hours = minutesCount / 60; + return (hours > 0 ? (hours < 10 ? "0" : "") + hours + ":" : "") + (minutes < 10 ? "0" : "") + minutes + ":" + (seconds < 10 ? "0" : "") + seconds; + } + + + public static String formatLongIntoMMSS(final long secs) { + final int unconvertedSeconds = (int)secs; + return formatIntoMMSS(unconvertedSeconds); + } + + /** + * Formats time into a detailed format. Example: 600 seconds (10 minutes) displays as '10 minutes' + * + * @param secs The input time, in seconds. + * @return The formatted time. + */ + public static String formatIntoDetailedString(int secs) { + if (secs == 0) { + return "0 seconds"; + } + int remainder = secs % 86400; + + int days = secs / 86400; + int hours = remainder / 3600; + int minutes = (remainder / 60) - (hours * 60); + int seconds = (remainder % 3600) - (minutes * 60); + + String fDays = (days > 0 ? " " + days + " day" + (days > 1 ? "s" : "") : ""); + String fHours = (hours > 0 ? " " + hours + " hour" + (hours > 1 ? "s" : "") : ""); + String fMinutes = (minutes > 0 ? " " + minutes + " minute" + (minutes > 1 ? "s" : "") : ""); + String fSeconds = (seconds > 0 ? " " + seconds + " second" + (seconds > 1 ? "s" : "") : ""); + + return ((fDays + fHours + fMinutes + fSeconds).trim()); + } + + public static String formatLongIntoString(long millis) { + return formatIntoString((int) millis / 1000); + } + + public static String formatIntoString(int secs) { + if (secs == 0) { + return "0s"; + } + + int remainder = secs % 86400; + int days = secs / 86400; + int hours = remainder / 3600; + int minutes = (remainder / 60) - (hours * 60); + int seconds = (remainder % 3600) - (minutes * 60); + + String fDays = (days > 0 ? days + "d " : ""); + String fHours = (hours > 0 ? hours + "h " : ""); + String fMinutes = (minutes > 0 ? minutes + "m " : ""); + String fSeconds = (seconds > 0 ? seconds + "s " : ""); + + return ((fDays + fHours + fMinutes + fSeconds).trim()); + } + + /** + * Formats time into a format of MM/dd/yyyy HH:mm. + * + * @param date The Date instance to format. + * @return The formatted time. + */ + public static String formatIntoCalendarString(Date date) { + return (dateFormat.format(date)); + } + + /** + * Parses a string, such as '1h4m25s' into a number of seconds. + * + * @param time The string to attempt to parse. + * @return The number of seconds 'in' the given string. + */ + public static int parseTime(String time) { + + if (time.equals("0") || time.equals("")) { + return (0); + } + + String[] lifeMatch = new String[]{"w", "d", "h", "m", "s"}; + int[] lifeInterval = new int[]{604800, 86400, 3600, 60, 1}; + int seconds = 0; + + for (int i = 0; i < lifeMatch.length; i++) { + + final Matcher matcher = Pattern.compile("([0-9]*)" + lifeMatch[i]).matcher(time); + + while (matcher.find()) { + seconds += Integer.parseInt(matcher.group(1)) * lifeInterval[i]; + } + + } + + return (seconds); + } + + /** + * Gets the seconds between date A and date B. This will never return a negative number. + * + * @param a Date A + * @param b Date B + * @return The number of seconds between date A and date B. + */ + public static int getSecondsBetween(Date a, Date b) { + return (Math.abs((int) (a.getTime() - b.getTime()) / 1000)); + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/UUIDFetcher.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/UUIDFetcher.java new file mode 100644 index 0000000..f4de9ba --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/UUIDFetcher.java @@ -0,0 +1,136 @@ +package com.elevatemc.elib.util; + +import com.elevatemc.elib.eLib; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.UUID; + + +public class UUIDFetcher { + + private static final JsonParser PARSER = new JsonParser(); + private static final String UUID_URL = "https://api.mojang.com/users/profiles/minecraft/%s"; + + /** + * Fetches the uuid synchronously for a specified name and time + * + * @param name The name + */ + public static UUID getUUID(String name) { + if (name.length() > 16){ + return null; + } + + final UUID value = getCachedUUID(name); + + if (value != null){ + return value; + } + + try { + HttpURLConnection connection = (HttpURLConnection) new URL(String.format(UUID_URL, name)).openConnection(); + connection.setReadTimeout(5000); + + JsonElement jsonElement = PARSER.parse(new BufferedReader(new InputStreamReader(connection.getInputStream()))); + + if (jsonElement.isJsonObject()) { + JsonObject jsonObject = jsonElement.getAsJsonObject(); + String correctName = jsonObject.get("name").getAsString(); + UUID uuid = fromString(jsonObject.get("id").getAsString()); + if (correctName != null) { + updateCache(correctName, uuid); + return uuid; + } + } + + return null; + } catch (Exception e) { + e.printStackTrace(); + } + + return null; + } + + /** + * Fetches the uuid and checks if its cached synchronously for a specified name and time + * + * @param name the name of the player to get the uuid for + */ + public static UUID getCachedUUID(String name) { + if (name.length() > 16){ + return null; + } + return eLib.getInstance().getUuidCache().uuid(name); + } + + /** + * Fetches the name synchronously and returns it + * + * @param uuid The uuid + * @return The name + */ + public static String getName(UUID uuid) { + final String value = getCachedName(uuid); + + if (value != null){ + return value; + } + + try { + URL obj = new URL(String.format(UUID_URL, uuid.toString())); + HttpURLConnection con = (HttpURLConnection) obj.openConnection(); + con.setRequestProperty("User-Agent", "Mozilla/5.0"); + + BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream())); + String inputLine; + StringBuffer response = new StringBuffer(); + + while ((inputLine = in.readLine()) != null) { + response.append(inputLine); + } + + in.close(); + JsonElement object = PARSER.parse(response.toString()); + JsonObject parsedObject = object.getAsJsonObject(); + + String name = parsedObject.get("username").getAsString(); + updateCache(name, uuid); + return name; + } catch (Exception e) { + e.printStackTrace(); + } + + return null; + } + + /** + * Fetches the name in caches synchronously and returns it + * + * @param uuid The uuid + * @return The name + */ + public static String getCachedName(UUID uuid) { + return eLib.getInstance().getUuidCache().name(uuid); + } + + public static void updateCache(String playerName, UUID uuid){ + updateCache(playerName, uuid, true); + } + + public static void updateCache(String playerName, UUID uuid, boolean updateRedis) { + if(updateRedis) { + eLib.getInstance().getUuidCache().updateAll(uuid, playerName); + } + } + + public static UUID fromString(String input) { + return UUID.fromString(input.replaceFirst( + "(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5")); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/UUIDUtils.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/UUIDUtils.java new file mode 100644 index 0000000..829744b --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/UUIDUtils.java @@ -0,0 +1,86 @@ +package com.elevatemc.elib.util; + +import com.elevatemc.elib.eLib; +import lombok.experimental.UtilityClass; +import com.mongodb.BasicDBList; + +import java.util.Collection; +import java.util.UUID; + +@UtilityClass +public final class UUIDUtils { + + /** + * Gets the name associated with a UUID. + * + * @param uuid The UUID object to fetch the name for. + * @return The name associated with the UUID given. + */ + public static String name(UUID uuid) { + + final String toReturn = eLib.getInstance().getUuidCache().name(uuid); + + return toReturn == null ? "null":toReturn; + } + + /** + * Gets the UUID associated with a name. + * + * @param name The name to fetch the UUID for. + * @return The UUID associated with the name given. + */ + public static UUID uuid(String name) { + return eLib.getInstance().getUuidCache().uuid(name); + } + + /** + * Returns whether or not the uuid is cached inside the UUIDCache. + * + * @param uuid The uuid to fetch whether the uuid is cached. + * @return Boolean whether the uuid is cached. + */ + public static boolean cached(UUID uuid) { + return eLib.getInstance().getUuidCache().cached(uuid); + } + + /** + * Returns whether or not the name is cached inside the UUIDCache. + * + * @param name The name to fetch whether the name is cached. + * @return Boolean whether the name is cached. + */ + public static boolean cached(String name) { + return eLib.getInstance().getUuidCache().cached(name); + } + + /** + * Formats a UUID and its name. + * + * @param uuid The UUID to format. + * @return The formatted String. + */ + public static String formatPretty(UUID uuid) { + return (name(uuid) + " [" + uuid + "]"); + } + + /** + * Converts a Collection of UUIDs into a String-based BasicDBList (for storage in MongoDB) + * + * @param toConvert The UUIDs to convert. + * @return A BasicDBList containing the UUIDs in String form. + */ + public static BasicDBList uuidsToStrings(Collection toConvert) { + if (toConvert == null || toConvert.isEmpty()) { + return (new BasicDBList()); + } + + BasicDBList dbList = new BasicDBList(); + + for (UUID uuid : toConvert) { + dbList.add(uuid.toString()); + } + + return (dbList); + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/UnicodeUtils.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/UnicodeUtils.java new file mode 100644 index 0000000..cd5e141 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/UnicodeUtils.java @@ -0,0 +1,19 @@ +package com.elevatemc.elib.util; + +import org.apache.commons.lang.StringEscapeUtils; + +public class UnicodeUtils { + + public static final String HEART = StringEscapeUtils.unescapeJava("\u2764"); + + public static final String VERTICAL_BAR = StringEscapeUtils.unescapeJava("\u2503"); + + public static final String CAUTION = StringEscapeUtils.unescapeJava("\u26a0"); + + public static final String ARROW_LEFT = StringEscapeUtils.unescapeJava("\u25C0"); + public static final String ARROW_RIGHT = StringEscapeUtils.unescapeJava("\u25B6"); + + public static final String ARROWS_LEFT = StringEscapeUtils.unescapeJava("\u00AB"); + public static final String ARROWS_RIGHT = StringEscapeUtils.unescapeJava("\u00BB"); + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/cosmetics/ArmorUtil.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/cosmetics/ArmorUtil.java new file mode 100644 index 0000000..5415b70 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/cosmetics/ArmorUtil.java @@ -0,0 +1,40 @@ +package com.elevatemc.elib.util.cosmetics; + +import org.bukkit.Color; + +import java.util.ArrayList; +import java.util.List; + +public class ArmorUtil { + + public static final List COLORS = new ArrayList<>(); + + static { + for (int r = 0; r < 100; r++) COLORS.add(Color.fromRGB(r * 255 / 100, 255, 0)); + for (int g = 100; g > 0; g--) COLORS.add(Color.fromRGB(255, g * 255 / 100, 0)); + for (int b = 0; b < 100; b++) COLORS.add(Color.fromRGB(255, 0, b * 255 / 100)); + for (int r = 100; r > 0; r--) COLORS.add(Color.fromRGB(r * 255 / 100, 0, 255)); + for (int g = 0; g < 100; g++) COLORS.add(Color.fromRGB(0, g * 255 / 100, 255)); + for (int b = 100; b > 0; b--) COLORS.add(Color.fromRGB(0, 255, b * 255 / 100)); + + COLORS.add(Color.fromRGB(0, 255, 0)); + } + + public static int parseColor(Color color) { + if (color.equals(Color.YELLOW) || color.equals(Color.OLIVE)) return 4; + if (color.equals(Color.BLUE) || color.equals(Color.NAVY)) return 11; + if (color.equals(Color.GREEN)) return 13; + if (color.equals(Color.LIME)) return 5; + if (color.equals(Color.RED) || color.equals(Color.MAROON)) return 14; + if (color.equals(Color.SILVER)) return 8; + if (color.equals(Color.GRAY)) return 7; + if (color.equals(Color.AQUA)) return 3; + if (color.equals(Color.TEAL)) return 9; + if (color.equals(Color.PURPLE)) return 2; + if (color.equals(Color.FUCHSIA)) return 6; + if (color.equals(Color.ORANGE)) return 1; + if (color.equals(Color.BLACK)) return 15; + + return -1; + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/countdown/Countdown.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/countdown/Countdown.java new file mode 100644 index 0000000..cb7cd74 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/countdown/Countdown.java @@ -0,0 +1,84 @@ +package com.elevatemc.elib.util.countdown; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.util.TimeUtils; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; + +public class Countdown extends BukkitRunnable { + + private final String broadcastMessage; + private final int[] broadcastAt; + private final Runnable tickHandler; + private final Runnable broadcastHandler; + private final Runnable finishHandler; + private final Predicate messageFilter; + private int seconds; + private boolean first = true; + + public static CountdownBuilder of(int amount, TimeUnit unit) { + return new CountdownBuilder((int)unit.toSeconds((long)amount)); + } + + Countdown(int seconds, String broadcastMessage, Runnable tickHandler, Runnable broadcastHandler, Runnable finishHandler, Predicate messageFilter, int... broadcastAt) { + this.seconds = seconds; + this.broadcastMessage = ChatColor.translateAlternateColorCodes('&', broadcastMessage); + this.broadcastAt = broadcastAt; + this.tickHandler = tickHandler; + this.broadcastHandler = broadcastHandler; + this.finishHandler = finishHandler; + this.messageFilter = messageFilter; + this.runTaskTimer(eLib.getInstance(), 0L, 20L); + } + + public final void run() { + + if (!this.first) { + --this.seconds; + } else { + this.first = false; + } + + for (int i = 0; i < this.broadcastAt.length; i++) { + + final int index = this.broadcastAt[i]; + + if (this.seconds == index) { + + final String message = this.broadcastMessage.replace("{time}", TimeUtils.formatIntoDetailedString(this.seconds)); + + if (this.broadcastHandler != null) { + this.broadcastHandler.run(); + } + + for (Player loopPlayer : eLib.getInstance().getServer().getOnlinePlayers()) { + + if (this.messageFilter != null && !this.messageFilter.test(loopPlayer)) { + loopPlayer.sendMessage(message); + } + + } + } + } + + if (this.seconds == 0) { + + if (this.finishHandler != null) { + this.finishHandler.run(); + } + + this.cancel(); + } else if (this.tickHandler != null) { + this.tickHandler.run(); + } + + } + + public int getSecondsRemaining() { + return this.seconds; + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/countdown/CountdownBuilder.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/countdown/CountdownBuilder.java new file mode 100644 index 0000000..30ddb4f --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/countdown/CountdownBuilder.java @@ -0,0 +1,89 @@ +package com.elevatemc.elib.util.countdown; + +import com.google.common.base.Preconditions; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; + +public class CountdownBuilder { + + private final int seconds; + private String message; + private List broadcastAt = new ArrayList<>(); + private Runnable tickHandler; + private Runnable broadcastHandler; + private Runnable finishHandler; + private Predicate messageFilter; + + CountdownBuilder(int seconds) { + Preconditions.checkArgument(seconds >= 0, "Seconds cannot must be greater than or equal to 0!"); + this.seconds = seconds; + } + + public CountdownBuilder withMessage(String message) { + this.message = message; + return this; + } + + public CountdownBuilder broadcastAt(int amount, TimeUnit unit) { + this.broadcastAt.add((int)unit.toSeconds((long)amount)); + return this; + } + + public CountdownBuilder onTick(Runnable tickHandler) { + this.tickHandler = tickHandler; + return this; + } + + public CountdownBuilder onBroadcast(Runnable broadcastHandler) { + this.broadcastHandler = broadcastHandler; + return this; + } + + public CountdownBuilder onFinish(Runnable finishHandler) { + this.finishHandler = finishHandler; + return this; + } + + public CountdownBuilder withMessageFilter(Predicate messageFilter) { + this.messageFilter = messageFilter; + return this; + } + + public Countdown start() { + Preconditions.checkNotNull(this.message, "Message cannot be null!"); + if (this.broadcastAt.isEmpty()) { + this.broadcastAt(10, TimeUnit.MINUTES); + this.broadcastAt(5, TimeUnit.MINUTES); + this.broadcastAt(4, TimeUnit.MINUTES); + this.broadcastAt(3, TimeUnit.MINUTES); + this.broadcastAt(2, TimeUnit.MINUTES); + this.broadcastAt(1, TimeUnit.MINUTES); + this.broadcastAt(30, TimeUnit.SECONDS); + this.broadcastAt(15, TimeUnit.SECONDS); + this.broadcastAt(10, TimeUnit.SECONDS); + this.broadcastAt(5, TimeUnit.SECONDS); + this.broadcastAt(4, TimeUnit.SECONDS); + this.broadcastAt(3, TimeUnit.SECONDS); + this.broadcastAt(2, TimeUnit.SECONDS); + this.broadcastAt(1, TimeUnit.SECONDS); + } + + return new Countdown(this.seconds, this.message, this.tickHandler, this.broadcastHandler, this.finishHandler, this.messageFilter, this.convertIntegers(this.broadcastAt)); + } + + private int[] convertIntegers(List integers) { + int[] ret = new int[integers.size()]; + + for(int i = 0; i < ret.length; ++i) { + ret[i] = integers.get(i); + } + + return ret; + } + + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/eLibComponentBuilder.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/eLibComponentBuilder.java new file mode 100644 index 0000000..a6bfea5 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/eLibComponentBuilder.java @@ -0,0 +1,85 @@ +package com.elevatemc.elib.util; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.*; + +import java.lang.reflect.Field; +import java.util.List; + +public class eLibComponentBuilder extends com.elevatemc.elib.util.ComponentBuilder { + private static Field partsField; + private static Field currField; + + static { + try { + currField = com.elevatemc.elib.util.ComponentBuilder.class.getDeclaredField("current"); + partsField = com.elevatemc.elib.util.ComponentBuilder.class.getDeclaredField("parts"); + + currField.setAccessible(true); + partsField.setAccessible(true); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } + } + + public eLibComponentBuilder(String text) { + super(text); + } + + public TextComponent getCurrent() { + try { + return (TextComponent) currField.get(this); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return null; + } + + public void setCurrent(TextComponent tc) { + try { + currField.set(this, tc); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + public List getParts() { + try { + return (List) partsField.get(this); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return null; + } + + + public ComponentBuilder append(TextComponent textComponent) { + String text = textComponent.getText(); + ChatColor color = textComponent.getColor(); + boolean bold = textComponent.isBold(); + boolean underline = textComponent.isUnderlined(); + boolean italic = textComponent.isUnderlined(); + boolean strike = textComponent.isStrikethrough(); + HoverEvent he = textComponent.getHoverEvent(); + ClickEvent ce = textComponent.getClickEvent(); + + append(text); + color(color); + underlined(underline); + italic(italic); + strikethrough(strike); + event(he); + event(ce); + + if (textComponent.getExtra() != null) { + for (BaseComponent bc : textComponent.getExtra()) { + if (bc instanceof TextComponent) { + append((TextComponent) bc); + } + } + } + + return this; + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/event/BaseEvent.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/event/BaseEvent.java new file mode 100644 index 0000000..3f11550 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/event/BaseEvent.java @@ -0,0 +1,32 @@ +package com.elevatemc.elib.util.event; + +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.plugin.java.JavaPlugin; + +public class BaseEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + + public BaseEvent() { + this(false); + } + + public BaseEvent(boolean async) { + super(async); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public boolean call(JavaPlugin javaPlugin) { + javaPlugin.getServer().getPluginManager().callEvent(this); + return this instanceof Cancellable && ((Cancellable) this).isCancelled(); + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/event/PlayerEvent.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/event/PlayerEvent.java new file mode 100644 index 0000000..f769d94 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/event/PlayerEvent.java @@ -0,0 +1,28 @@ +package com.elevatemc.elib.util.event; + +import lombok.Getter; +import org.bukkit.entity.Player; + +import java.util.UUID; + +@Getter +public class PlayerEvent extends BaseEvent { + private Player player; + + public PlayerEvent(Player player) { + this(player, false); + } + + public PlayerEvent(Player player, boolean async) { + super(async); + this.player = player; + } + + public Player getPlayer() { + return player; + } + + public UUID getUniqueId() { + return player.getUniqueId(); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/imgur/ImgurException.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/imgur/ImgurException.java new file mode 100644 index 0000000..da832fd --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/imgur/ImgurException.java @@ -0,0 +1,73 @@ +package com.elevatemc.elib.util.imgur; + +/** + * Custom exception class that handles Web Exceptions. + * + * @author DV8FromTheWorld (Austin Keener) + * @version v1.0.0 July 16, 2014 + */ +@SuppressWarnings("serial") +public class ImgurException extends RuntimeException { + private ImgurStatusCode code; + + /** + * Creates a new instance of WebException containing + * the StatusCode and the original exception. + * + * @param code The StatusCode related to this exception. + * @param cause The Throwable that set off this exception. + */ + public ImgurException(ImgurStatusCode code, Throwable cause) { + super(cause); + this.code = code; + } + + /** + * Creates a new instance of WebException containing the StatusCode. + * + * @param code The StatusCode related to this exception. + */ + public ImgurException(ImgurStatusCode code) { + this(code, null); + } + + /** + * Creates a new instance of WebException containing + * the StatusCode(based on the provided httpCode) and the original exception. + * + * @param httpCode The httpCode related to this exception. + * @param cause The Throwable that set off this exception. + */ + public ImgurException(int httpCode, Throwable cause) { + this(ImgurStatusCode.getStatus(httpCode), cause); + } + + /** + * Creates a new instance of WebException containing + * the StatusCode(based on the provided httpCode). + * + * @param httpCode The httpCode related to this exception. + */ + public ImgurException(int httpCode) { + this(httpCode, null); + } + + /** + * Gets the Http StatusCode associated with this exception. + * + * @return The StatusCode that caused the exception. + */ + public ImgurStatusCode getStatusCode() { + return code; + } + + /** + * Gets the description of the exception based on the description of the StatusCode. + * + * @return Description of exception based on Http StatusCode. + */ + @Override + public String getMessage() { + return code.getDescription(); + } +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/imgur/ImgurStatusCode.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/imgur/ImgurStatusCode.java new file mode 100644 index 0000000..b7ed453 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/imgur/ImgurStatusCode.java @@ -0,0 +1,92 @@ +package com.elevatemc.elib.util.imgur; + +public enum ImgurStatusCode { + UNKNOWN_HOST("Couldn't find api.imgur.com, are you connected to the internet?", 1), + SUCCESS("The action was successful!", 200), + BAD_REQUEST("Upload interupted or corrupted.", 400), + UNAUTHORIZED("Action requires Auth. Credentials are invalid.", 401), + FORBIDDEN("You don't have access. Possible lack of API credits.", 403), + NOT_FOUND("The requested image or album does not exist.", 404), + FILE_TOO_BIG("The file that you tried to upload was too big!", 413), + UPLOAD_LIMITED("You are uploading to quickly! You've been rate limited.", 429), + INTERNAL_SERVER_ERROR("Imgur unexpected internal server error. Not our fault.", 500), + SERVICE_UNAVAILABLE("Imgur is unavailable currently. Most likely over capacity.", 502), + UNKNOWN_ERROR("An error occured, but we don't know what kind. Sorry!", -1); + + private String description; + private int httpCode; + + /** + * Creates a new StatusCode with provided description and http code. + * + * @param description A message that describes the status or what might have caused it. + * @param httpCode The Http response associated with this StatusCode. + */ + private ImgurStatusCode(String description, int httpCode) { + this.description = description; + this.httpCode = httpCode; + } + + /** + * Gets the StatusCode associated with this Http response code. + * + * @param code Http response code. + * @return The StatusCode associated with the provided code. + */ + public static ImgurStatusCode getStatus(int code) { + switch (code) { + case 1: + return UNKNOWN_HOST; + case 200: + return SUCCESS; + case 400: + return BAD_REQUEST; + case 401: + return UNAUTHORIZED; + case 403: + return FORBIDDEN; + case 404: + return NOT_FOUND; + case 413: + return FILE_TOO_BIG; + case 429: + return UPLOAD_LIMITED; + case 500: + return INTERNAL_SERVER_ERROR; + case 502: + return SERVICE_UNAVAILABLE; + default: + return UNKNOWN_ERROR; + } + } + + /** + * Gets the string description of this StatusCode. + * + * @return The description of this StatusCode. + */ + public String getDescription() { + return description; + } + + /** + * Gets the Http code associated with this StatusCode. + * + * @return The Http associated with this StatusCode. + */ + public int getHttpCode() { + return httpCode; + } + + /** + * Returns a string that represents this StatusCode. + * Format: StatusCode - Name: [name] - HttpCode: [httpCode] - Description: [description] + */ + @Override + public String toString() { + return String.format("StatusCode - %s: %s - %s: %d - %s: %s", + "Name", super.toString(), + "HttpCode", getHttpCode(), + "Description", getDescription()); + } +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/imgur/ImgurUtil.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/imgur/ImgurUtil.java new file mode 100644 index 0000000..4515f0b --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/imgur/ImgurUtil.java @@ -0,0 +1,121 @@ +package com.elevatemc.elib.util.imgur; + +import com.google.common.io.ByteStreams; +import org.bson.internal.Base64; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.net.UnknownHostException; + +/** + * Yoinked from https://github.com/DV8FromTheWorld/Imgur-Uploader-Java + * Reworked to just do what we need thats all + */ +public class ImgurUtil { + + public static final String UPLOAD_API_URL = "https://api.imgur.com/3/image"; + private static final String CLIENT_ID = "b6c07d0048fda79"; + + /** + * Takes a file and uploads it to Imgur. + * Does not check to see if the file is an image, this should be done + * before the file is passed to this method. + * + * @param file The image to be uploaded to Imgur. + * @return The JSON response from Imgur. + */ + public static String upload(File file) { + HttpURLConnection conn = getHttpConnection(UPLOAD_API_URL); + writeToConnection(conn, "image=" + toBase64(file)); + return getResponse(conn); + } + + /** + * Converts a file to a Base64 String. + * + * @param file The file to be converted. + * @return The file as a Base64 String. + */ + private static String toBase64(File file) { + try { + FileInputStream fs = new FileInputStream(file); + byte[] b = ByteStreams.toByteArray(fs); + fs.close(); + return URLEncoder.encode(Base64.encode(b), "UTF-8"); + } catch (IOException e) { + throw new ImgurException(ImgurStatusCode.UNKNOWN_ERROR, e); + } + } + + /** + * Creates and sets up an HttpURLConnection for use with the Imgur API. + * + * @param url The URL to connect to. (check Imgur API for correct URL). + * @return The newly created HttpURLConnection. + */ + private static HttpURLConnection getHttpConnection(String url) { + HttpURLConnection conn; + try { + conn = (HttpURLConnection) new URL(url).openConnection(); + conn.setDoInput(true); + conn.setDoOutput(true); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Authorization", "Client-ID " + CLIENT_ID); + conn.setReadTimeout(100000); + conn.connect(); + return conn; + } catch (UnknownHostException e) { + throw new ImgurException(ImgurStatusCode.UNKNOWN_HOST, e); + } catch (IOException e) { + throw new ImgurException(ImgurStatusCode.UNKNOWN_ERROR, e); + } + } + + /** + * Sends the provided message to the connection as uploaded data. + * + * @param conn The connection to send the data to. + * @param message The data to upload. + */ + private static void writeToConnection(HttpURLConnection conn, String message) { + OutputStreamWriter writer; + try { + writer = new OutputStreamWriter(conn.getOutputStream()); + writer.write(message); + writer.flush(); + writer.close(); + } catch (IOException e) { + throw new ImgurException(ImgurStatusCode.UNKNOWN_ERROR, e); + } + } + + /** + * Gets the response from the connection, Usually in the format of a JSON string. + * + * @param conn The connection to listen to. + * @return The response, usually as a JSON string. + */ + private static String getResponse(HttpURLConnection conn) { + StringBuilder str = new StringBuilder(); + BufferedReader reader; + try { + if (conn.getResponseCode() != ImgurStatusCode.SUCCESS.getHttpCode()) { + throw new ImgurException(conn.getResponseCode()); + } + reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); + String line; + while ((line = reader.readLine()) != null) { + str.append(line); + } + reader.close(); + } catch (IOException e) { + throw new ImgurException(ImgurStatusCode.UNKNOWN_ERROR, e); + } + if (str.toString().equals("")) { + throw new ImgurException(ImgurStatusCode.UNKNOWN_ERROR); + } + return str.toString(); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/json/AbstractClassAdapter.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/json/AbstractClassAdapter.java new file mode 100644 index 0000000..c8424b3 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/json/AbstractClassAdapter.java @@ -0,0 +1,33 @@ +package com.elevatemc.elib.util.json; + +import com.google.gson.*; + +import java.lang.reflect.Type; + +public class AbstractClassAdapter implements JsonSerializer, JsonDeserializer { + + private static final String CLASS_KEY = "@CLASS"; + private static final String DATA_KEY = "@DATA"; + + @Override + public Object deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + String className = jsonObject.get(CLASS_KEY).getAsString(); + try { + Class clazz = Class.forName(className); + return GsonProvider.getGsonHierarchyAbsent().fromJson(jsonObject.get(DATA_KEY), clazz); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public JsonElement serialize(Object src, Type type, JsonSerializationContext context) { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty(CLASS_KEY, src.getClass().getName()); + jsonObject.add(DATA_KEY, GsonProvider.getGsonHierarchyAbsent().toJsonTree(src)); + return jsonObject; + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/json/ClassAdapter.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/json/ClassAdapter.java new file mode 100644 index 0000000..9e99d32 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/json/ClassAdapter.java @@ -0,0 +1,27 @@ +package com.elevatemc.elib.util.json; + +import com.google.gson.*; + +import java.lang.reflect.Type; + +public class ClassAdapter implements JsonSerializer>, JsonDeserializer> { + + @Override + public Class deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + JsonObject jsonObject = jsonElement.getAsJsonObject(); + try { + return Class.forName(jsonObject.get("classname").getAsString()); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public JsonElement serialize(Class aClass, Type type, JsonSerializationContext jsonSerializationContext) { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("classname", aClass.getName()); + return jsonObject; + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/json/ColorAdapter.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/json/ColorAdapter.java new file mode 100644 index 0000000..8d10a1e --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/json/ColorAdapter.java @@ -0,0 +1,47 @@ +package com.elevatemc.elib.util.json; + +import com.google.gson.*; + +import java.awt.*; +import java.lang.reflect.Type; + +/** + * @author ImHacking + * @date 4/17/2022 + */ +public class ColorAdapter implements JsonDeserializer, JsonSerializer { + + @Override + public Color deserialize(JsonElement src, Type type, JsonDeserializationContext context) + throws JsonParseException { + return fromJson(src); + } + + + @Override + public JsonElement serialize(Color src, Type type, JsonSerializationContext context) { + return toJson(src); + } + + + public static JsonObject toJson(Color src) { + if (src == null) { + return null; + } + JsonObject json = new JsonObject(); + json.addProperty("r", src.getRed()); + json.addProperty("g", src.getGreen()); + json.addProperty("b", src.getBlue()); + return json; + } + public static Color fromJson(JsonElement src) { + if (src == null || !src.isJsonObject()) { + return null; + } + final JsonObject json = src.getAsJsonObject(); + int red = json.get("r").getAsInt(); + int green = json.get("g").getAsInt(); + int blue = json.get("b").getAsInt(); + return new Color(red, green, blue); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/json/GsonProvider.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/json/GsonProvider.java new file mode 100644 index 0000000..786ee08 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/json/GsonProvider.java @@ -0,0 +1,151 @@ +package com.elevatemc.elib.util.json; + +import com.elevatemc.elib.util.json.map.AllowNullConstructorConstructor; +import com.google.gson.*; +import com.google.gson.reflect.TypeToken; + +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +public class GsonProvider { + + private static final GsonBuilder gsonBuilder = new GsonBuilder() + .enableComplexMapKeySerialization() + .disableHtmlEscaping(); + private static final GsonBuilder prettyGsonBuilder = new GsonBuilder() + .enableComplexMapKeySerialization() + .disableHtmlEscaping() + .setPrettyPrinting(); + private static final GsonBuilder hierarchyAbsentBuilder = new GsonBuilder() + .enableComplexMapKeySerialization() + .disableHtmlEscaping() + .setPrettyPrinting(); + + private static final Map> instanceCreators = new HashMap<>(); + + private static boolean builderChanged = true; + private static Gson gson; + private static Gson prettyGson; + private static Gson hierarchyAbsentGson; + + private static void checkForChanges() { + if (!builderChanged) { + return; + } + gson = gsonBuilder.create(); + prettyGson = prettyGsonBuilder.create(); + hierarchyAbsentGson = hierarchyAbsentBuilder.create(); + builderChanged = false; + } + + public static byte[] toBinary(T element) { + return toJson(element).getBytes(StandardCharsets.UTF_8); + } + + public static T fromBinary(byte[] bytes, Type type) { + return fromJson(new String(bytes, StandardCharsets.UTF_8), type); + } + + public static T fromBinary(byte[] bytes, Class clazz) { + return fromJson(new String(bytes, StandardCharsets.UTF_8), clazz); + } + + public static String toJson(T element) { + checkForChanges(); + return gson.toJson(element); + } + + public static String toJsonPretty(T element) { + checkForChanges(); + return prettyGson.toJson(element); + } + + public static JsonElement toJsonTree(T element) { + checkForChanges(); + return gson.toJsonTree(element); + } + + public static JsonElement toJsonTreePretty(T element) { + checkForChanges(); + return prettyGson.toJsonTree(element); + } + + public static T fromJson(String json, Class type) { + checkForChanges(); + return gson.fromJson(json, type); + } + + public static T fromJson(String json, Type type) { + checkForChanges(); + return gson.fromJson(json, type); + } + + private static void apply(Consumer builderConsumer) { + apply(builderConsumer, false); + } + + public static void apply(Consumer builderConsumer, boolean skipHierarchyAbsent) { + builderConsumer.accept(gsonBuilder); + builderConsumer.accept(prettyGsonBuilder); + if (!skipHierarchyAbsent) { + builderConsumer.accept(hierarchyAbsentBuilder); + } + builderChanged = true; + } + + public static void registerInterface(Class clazz) { + registerTypeHierarchyAdapter(clazz, new AbstractClassAdapter()); + } + + public static void registerAbstractClass(Class clazz) { + registerTypeHierarchyAdapter(clazz, new AbstractClassAdapter()); + } + + public static void registerTypeAdapterFactory(TypeAdapterFactory factory) { + apply(builder -> builder.registerTypeAdapterFactory(factory)); + } + + public static void registerTypeAdapter(Type type, Object typeAdapter) { + apply(builder -> { + if (typeAdapter instanceof InstanceCreator) { + instanceCreators.put(type, (InstanceCreator) typeAdapter); + } + builder.registerTypeAdapter(type, typeAdapter); + }); + } + + public static Gson getGson() { + checkForChanges(); + return gson; + } + + public static Gson getGsonPretty() { + checkForChanges(); + return prettyGson; + } + + public static AllowNullConstructorConstructor createConstructor() { + return new AllowNullConstructorConstructor(instanceCreators, true); + } + + public static Gson getGsonHierarchyAbsent() { + checkForChanges(); + return hierarchyAbsentGson; + } + + public static void disableHtmlEscaping() { + apply(GsonBuilder::disableHtmlEscaping); + } + + public static void registerTypeHierarchyAdapter(Class type, Object adapterFactory) { + apply(builder -> builder.registerTypeHierarchyAdapter(type, adapterFactory), true); + } + + public static TypeAdapter getAdapter(TypeToken typeToken) { + checkForChanges(); + return gson.getAdapter(typeToken); + } +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/json/map/AllowNullConstructorConstructor.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/json/map/AllowNullConstructorConstructor.java new file mode 100644 index 0000000..8015403 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/json/map/AllowNullConstructorConstructor.java @@ -0,0 +1,199 @@ +package com.elevatemc.elib.util.json.map; + +import com.google.gson.InstanceCreator; +import com.google.gson.JsonIOException; +import com.google.gson.internal.LinkedTreeMap; +import com.google.gson.internal.ObjectConstructor; +import com.google.gson.internal.UnsafeAllocator; +import com.google.gson.reflect.TypeToken; + +import java.lang.reflect.*; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ConcurrentNavigableMap; +import java.util.concurrent.ConcurrentSkipListMap; + +@SuppressWarnings("ALL") +public final class AllowNullConstructorConstructor { + private final Map> instanceCreators; + private final boolean useJdkUnsafe; + + public AllowNullConstructorConstructor(Map> instanceCreators, boolean useJdkUnsafe) { + this.instanceCreators = instanceCreators; + this.useJdkUnsafe = useJdkUnsafe; + } + + public ObjectConstructor get(TypeToken typeToken) { + final Type type = typeToken.getType(); + Class rawType = typeToken.getRawType(); + final InstanceCreator typeCreator = (InstanceCreator)this.instanceCreators.get(type); + if (typeCreator != null) { + return () -> typeCreator.createInstance(type); + } else { + final InstanceCreator rawTypeCreator = (InstanceCreator)this.instanceCreators.get(rawType); + if (rawTypeCreator != null) { + return () -> rawTypeCreator.createInstance(type); + } else { + ObjectConstructor defaultConstructor = this.newDefaultConstructor(rawType); + if (defaultConstructor != null) { + return defaultConstructor; + } else { + ObjectConstructor defaultImplementation = this.newDefaultImplementationConstructor(type, rawType); + return defaultImplementation != null ? defaultImplementation : this.newUnsafeAllocator(rawType); + } + } + } + } + + /** + * Creates a string representation for a constructor. + * E.g.: {@code java.lang.String#String(char[], int, int)} + */ + private static String constructorToString(Constructor constructor) { + StringBuilder stringBuilder = new StringBuilder(constructor.getDeclaringClass().getName()) + .append('#') + .append(constructor.getDeclaringClass().getSimpleName()) + .append('('); + Class[] parameters = constructor.getParameterTypes(); + for (int i = 0; i < parameters.length; i++) { + if (i > 0) { + stringBuilder.append(", "); + } + stringBuilder.append(parameters[i].getSimpleName()); + } + + return stringBuilder.append(')').toString(); + } + + /** + * Tries making the constructor accessible, returning an exception message + * if this fails. + * + * @param constructor constructor to make accessible + * @return exception message; {@code null} if successful, non-{@code null} if + * unsuccessful + */ + public static String tryMakeAccessible(Constructor constructor) { + try { + constructor.setAccessible(true); + return null; + } catch (Exception exception) { + return "Failed making constructor '" + constructorToString(constructor) + "' accessible; " + + "either change its visibility or write a custom InstanceCreator or TypeAdapter for its declaring type: " + // Include the message since it might contain more detailed information + + exception.getMessage(); + } + } + + + private ObjectConstructor newDefaultConstructor(Class rawType) { + if (Modifier.isAbstract(rawType.getModifiers())) { + return null; + } else { + final Constructor constructor; + try { + constructor = rawType.getDeclaredConstructor(); + } catch (NoSuchMethodException var4) { + return null; + } + + final String exceptionMessage = tryMakeAccessible(constructor); + return exceptionMessage != null ? () -> { + throw new JsonIOException(exceptionMessage); + } : () -> { + try { + return (T) constructor.newInstance(); + } catch (InstantiationException var2) { + throw new RuntimeException("Failed to invoke " + constructor + " with no args", var2); + } catch (InvocationTargetException var3) { + throw new RuntimeException("Failed to invoke " + constructor + " with no args", var3.getTargetException()); + } catch (IllegalAccessException var4) { + throw new AssertionError(var4); + } + }; + } + } + + private ObjectConstructor newDefaultImplementationConstructor(final Type type, Class rawType) { + if (Collection.class.isAssignableFrom(rawType)) { + if (SortedSet.class.isAssignableFrom(rawType)) { + return () -> (T) new TreeSet<>(); + } else if (EnumSet.class.isAssignableFrom(rawType)) { + return () -> { + if (type instanceof ParameterizedType) { + Type elementType = ((ParameterizedType)type).getActualTypeArguments()[0]; + if (elementType instanceof Class) { + return (T) EnumSet.noneOf((Class)elementType); + } else { + throw new JsonIOException("Invalid EnumSet type: " + type.toString()); + } + } else { + throw new JsonIOException("Invalid EnumSet type: " + type.toString()); + } + }; + } else if (Set.class.isAssignableFrom(rawType)) { + return () -> (T) new LinkedHashSet<>(); + } else { + return Queue.class.isAssignableFrom(rawType) ? () -> (T) new ArrayDeque<>() : () -> (T) new ArrayList<>(); + } + } else if (Map.class.isAssignableFrom(rawType)) { + if (rawType == EnumMap.class) { + return () -> { + if (type instanceof ParameterizedType) { + Type elementType = ((ParameterizedType)type).getActualTypeArguments()[0]; + if (elementType instanceof Class) { + T map = (T) new EnumMap((Class)elementType); + return map; + } else { + throw new JsonIOException("Invalid EnumMap type: " + type.toString()); + } + } else { + throw new JsonIOException("Invalid EnumMap type: " + type.toString()); + } + }; + } else if (ConcurrentNavigableMap.class.isAssignableFrom(rawType)) { + return () -> (T) new ConcurrentSkipListMap<>(); + } else if (ConcurrentMap.class.isAssignableFrom(rawType)) { + return () -> (T) new ConcurrentHashMap<>(); + } else if (SortedMap.class.isAssignableFrom(rawType)) { + return () -> (T) new TreeMap<>(); + } else { + return type instanceof ParameterizedType && !String.class.isAssignableFrom(TypeToken.get(((ParameterizedType)type).getActualTypeArguments()[0]).getRawType()) ? new ObjectConstructor() { + public T construct() { + return (T) new LinkedHashMap<>(); + } + } : () -> (T) new LinkedTreeMap<>(); + } + } else { + return null; + } + } + + private ObjectConstructor newUnsafeAllocator(final Class rawType) { + if (this.useJdkUnsafe) { + return new ObjectConstructor() { + private final UnsafeAllocator unsafeAllocator = UnsafeAllocator.create(); + + public T construct() { + try { + return (T) this.unsafeAllocator.newInstance(rawType); + } catch (Exception var2) { + throw new RuntimeException("Unable to create instance of " + rawType + ". Registering an InstanceCreator or a TypeAdapter for this type, or adding a no-args constructor may fix this problem.", var2); + } + } + }; + } else { + final String exceptionMessage = "Unable to create instance of " + rawType + "; usage of JDK Unsafe is disabled. Registering an InstanceCreator or a TypeAdapter for this type, adding a no-args constructor, or enabling usage of JDK Unsafe may fix this problem."; + return new ObjectConstructor() { + public T construct() { + throw new JsonIOException(exceptionMessage); + } + }; + } + } + + public String toString() { + return this.instanceCreators.toString(); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/json/map/AllowNullMapTypeAdapterFactory.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/json/map/AllowNullMapTypeAdapterFactory.java new file mode 100644 index 0000000..8431d16 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/json/map/AllowNullMapTypeAdapterFactory.java @@ -0,0 +1,200 @@ +package com.elevatemc.elib.util.json.map; + +import com.google.gson.*; +import com.google.gson.internal.$Gson$Types; +import com.google.gson.internal.JsonReaderInternalAccess; +import com.google.gson.internal.ObjectConstructor; +import com.google.gson.internal.Streams; +import com.google.gson.internal.bind.TypeAdapters; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +@SuppressWarnings("ALL") +public final class AllowNullMapTypeAdapterFactory implements TypeAdapterFactory { + private final AllowNullConstructorConstructor constructorConstructor; + final boolean complexMapKeySerialization; + + public AllowNullMapTypeAdapterFactory(AllowNullConstructorConstructor constructorConstructor, boolean complexMapKeySerialization) { + this.constructorConstructor = constructorConstructor; + this.complexMapKeySerialization = complexMapKeySerialization; + } + + public TypeAdapter create(Gson gson, TypeToken typeToken) { + Type type = typeToken.getType(); + Class rawType = typeToken.getRawType(); + if (!Map.class.isAssignableFrom(rawType)) { + return null; + } else { + Class rawTypeOfSrc = $Gson$Types.getRawType(type); + Type[] keyAndValueTypes = $Gson$Types.getMapKeyAndValueTypes(type, rawTypeOfSrc); + TypeAdapter keyAdapter = this.getKeyAdapter(gson, keyAndValueTypes[0]); + TypeAdapter valueAdapter = gson.getAdapter(TypeToken.get(keyAndValueTypes[1])); + ObjectConstructor constructor = this.constructorConstructor.get(typeToken); + TypeAdapter result = new Adapter(gson, keyAndValueTypes[0], keyAdapter, keyAndValueTypes[1], valueAdapter, constructor); + return result; + } + } + + private TypeAdapter getKeyAdapter(Gson context, Type keyType) { + return keyType != Boolean.TYPE && keyType != Boolean.class ? context.getAdapter(TypeToken.get(keyType)) : TypeAdapters.BOOLEAN_AS_STRING; + } + + private final class Adapter extends TypeAdapter> { + private final TypeAdapter keyTypeAdapter; + private final TypeAdapter valueTypeAdapter; + private final ObjectConstructor> constructor; + + public Adapter(Gson context, Type keyType, TypeAdapter keyTypeAdapter, Type valueType, TypeAdapter valueTypeAdapter, ObjectConstructor> constructor) { + this.keyTypeAdapter = new AllowNullTypeAdapterRuntimeTypeWrapper<>(context, keyTypeAdapter, keyType); + this.valueTypeAdapter = new AllowNullTypeAdapterRuntimeTypeWrapper<>(context, valueTypeAdapter, valueType); + this.constructor = constructor; + } + + public Map read(JsonReader in) throws IOException { + JsonToken peek = in.peek(); + if (peek == JsonToken.NULL) { + in.nextNull(); + return null; + } else { + Map map = this.constructor.construct(); + if (peek == JsonToken.BEGIN_ARRAY) { + in.beginArray(); + + while(in.hasNext()) { + in.beginArray(); + + K key; + try { + key = this.keyTypeAdapter.read(in); + } catch (Exception e){ + key = null; + } + if (key == null || key.equals("null")) { + in.skipValue(); + continue; + } + V value = this.valueTypeAdapter.read(in); + Object replaced = map.put(key, value); + if (replaced != null) { + throw new JsonSyntaxException("duplicate key: " + key); + } + + in.endArray(); + } + + in.endArray(); + } else { + in.beginObject(); + + while(in.hasNext()) { + JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in); + + K key; + try { + key = this.keyTypeAdapter.read(in); + } catch (Exception e){ + key = null; + } + if (key == null || key.equals("null")) { + in.skipValue(); + continue; + } + V value = this.valueTypeAdapter.read(in); + Object replaced = map.put(key, value); + if (replaced != null) { + throw new JsonSyntaxException("duplicate key: " + key); + } + } + + in.endObject(); + } + + return map; + } + } + + public void write(JsonWriter out, Map map) throws IOException { + if (map == null) { + out.nullValue(); + } else if (!AllowNullMapTypeAdapterFactory.this.complexMapKeySerialization) { + out.beginObject(); + + for (Entry entry : map.entrySet()) { + out.name(String.valueOf(entry.getKey())); + this.valueTypeAdapter.write(out, entry.getValue()); + } + + out.endObject(); + } else { + boolean hasComplexKeys = false; + List keys = new ArrayList<>(map.size()); + List values = new ArrayList<>(map.size()); + + JsonElement keyElement; + for(Iterator> var6 = map.entrySet().iterator(); var6.hasNext(); hasComplexKeys |= keyElement.isJsonArray() || keyElement.isJsonObject()) { + Entry entry = var6.next(); + keyElement = this.keyTypeAdapter.toJsonTree(entry.getKey()); + keys.add(keyElement); + values.add(entry.getValue()); + } + + int i; + int size; + if (hasComplexKeys) { + out.beginArray(); + i = 0; + + for(size = keys.size(); i < size; ++i) { + out.beginArray(); + Streams.write(keys.get(i), out); + this.valueTypeAdapter.write(out, values.get(i)); + out.endArray(); + } + + out.endArray(); + } else { + out.beginObject(); + i = 0; + + for(size = keys.size(); i < size; ++i) { + keyElement = keys.get(i); + out.name(this.keyToString(keyElement)); + this.valueTypeAdapter.write(out, values.get(i)); + } + + out.endObject(); + } + + } + } + + private String keyToString(JsonElement keyElement) { + if (keyElement.isJsonPrimitive()) { + JsonPrimitive primitive = keyElement.getAsJsonPrimitive(); + if (primitive.isNumber()) { + return String.valueOf(primitive.getAsNumber()); + } else if (primitive.isBoolean()) { + return Boolean.toString(primitive.getAsBoolean()); + } else if (primitive.isString()) { + return primitive.getAsString(); + } else { + throw new AssertionError(); + } + } else if (keyElement.isJsonNull()) { + return "null"; + } else { + throw new AssertionError(); + } + } + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/json/map/AllowNullTypeAdapterRuntimeTypeWrapper.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/json/map/AllowNullTypeAdapterRuntimeTypeWrapper.java new file mode 100644 index 0000000..6367ce3 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/json/map/AllowNullTypeAdapterRuntimeTypeWrapper.java @@ -0,0 +1,54 @@ +package com.elevatemc.elib.util.json.map; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.Adapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; + +@SuppressWarnings("ALL") +final class AllowNullTypeAdapterRuntimeTypeWrapper extends TypeAdapter { + private final Gson context; + private final TypeAdapter delegate; + private final Type type; + + AllowNullTypeAdapterRuntimeTypeWrapper(Gson context, TypeAdapter delegate, Type type) { + this.context = context; + this.delegate = delegate; + this.type = type; + } + + public T read(JsonReader in) throws IOException { + return this.delegate.read(in); + } + + public void write(JsonWriter out, T value) throws IOException { + TypeAdapter chosen = this.delegate; + Type runtimeType = this.getRuntimeTypeIfMoreSpecific(this.type, value); + if (runtimeType != this.type) { + TypeAdapter runtimeTypeAdapter = this.context.getAdapter(TypeToken.get(runtimeType)); + if (!(runtimeTypeAdapter instanceof Adapter)) { + chosen = runtimeTypeAdapter; + } else if (!(this.delegate instanceof Adapter)) { + chosen = this.delegate; + } else { + chosen = runtimeTypeAdapter; + } + } + + chosen.write(out, value); + } + + private Type getRuntimeTypeIfMoreSpecific(Type type, Object value) { + if (value != null && (type instanceof TypeVariable || type instanceof Class)) { + type = value.getClass(); + } + + return (Type)type; + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/message/MessageBuilder.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/message/MessageBuilder.java new file mode 100644 index 0000000..983cd3b --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/message/MessageBuilder.java @@ -0,0 +1,125 @@ +package com.elevatemc.elib.util.message; + +import java.util.ArrayList; +import java.util.List; + +public class MessageBuilder { + + public static MessageBuilder standard(String message) { + return new MessageBuilder(MessageColor.GOLD, MessageColor.WHITE, MessageColor.YELLOW, MessageColor.GRAY).message(message); + } + + public static MessageBuilder error(String message) { + return new MessageBuilder(MessageColor.RED, MessageColor.WHITE, MessageColor.RED, MessageColor.RED).message(message); + } + + public static String construct(String message, Object... elements) { + MessageBuilder builder = MessageBuilder.standard(message); + for (Object element : elements) { + builder.element(element); + } + return builder.build(); + } + + public static String constructError(String message, Object... elements) { + MessageBuilder builder = MessageBuilder.error(message); + for (Object element : elements) { + builder.element(element); + } + return builder.build(); + } + + private final MessageColor primaryColor, secondaryColor, tertiaryColor, prefixColor; + private final List elements; + + private String message, prefix; + + public MessageBuilder(MessageColor primaryColor, MessageColor secondaryColor, MessageColor tertiaryColor, MessageColor prefixColor) { + this.primaryColor = primaryColor; + this.secondaryColor = secondaryColor; + this.tertiaryColor = tertiaryColor; + this.prefixColor = prefixColor; + this.elements = new ArrayList<>(); + this.message = null; + this.prefix = null; + } + + public MessageBuilder message(String message) { + this.message = MessageTranslator.translate(message); + return this; + } + + public MessageBuilder element(Object element) { + this.elements.add(element); + return this; + } + + public MessageBuilder prefix(String prefix) { + this.prefix = prefix; + return this; + } + + public String build() { + if (this.message == null) { + return ""; + } + + int currentElement = 0; + String coloredPrefix = this.prefix == null ? "" : this.prefixColor + "[" + this.prefix + "] "; + StringBuilder builder = new StringBuilder(coloredPrefix + this.primaryColor); + + for (int index = 0; index < this.message.length(); index++) { + char currentChar = this.message.charAt(index); + + int relativeIndex = index + 1; + + if (relativeIndex >= this.message.length()) { + builder.append(this.translateChar(currentChar, true)); + continue; + } + + char relativeChar = this.message.charAt(relativeIndex); + + if (!(currentChar == '{' && relativeChar == '}')) { + builder.append(this.translateChar(currentChar, false)); + continue; + } + + + if (currentElement >= this.elements.size()) { + builder.append(this.translateChar(currentChar, false)); + continue; + } + + builder.append(this.secondaryColor.toString()); + builder.append(this.elements.get(currentElement++)); + builder.append(this.primaryColor); + index++; + } + + return builder.toString(); + } + + private String translateChar(char currentChar, boolean ignorePrimary) { + StringBuilder builder = new StringBuilder(); + boolean flag = MessageConstants.ALPHANUMERIC_PATTERN.matcher(String.valueOf(currentChar)).find(); + + if (currentChar == MessageConstants.COLOR_SYMBOL || currentChar == ' ') { + flag = false; + } + + if (flag) { + builder.append(this.tertiaryColor.toString()); + builder.append(currentChar); + + if (!ignorePrimary) { + builder.append(this.primaryColor); + } + + return builder.toString(); + } + + builder.append(currentChar); + return builder.toString(); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/message/MessageColor.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/message/MessageColor.java new file mode 100644 index 0000000..9e12434 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/message/MessageColor.java @@ -0,0 +1,33 @@ +package com.elevatemc.elib.util.message; + +public enum MessageColor { + + BLACK('0'), + DARK_BLUE('1'), + DARK_GREEN('2'), + DARK_AQUA('3'), + DARK_RED('4'), + DARK_PURPLE('5'), + GOLD('6'), + GRAY('7'), + DARK_GRAY('8'), + BLUE('9'), + GREEN('a'), + AQUA('b'), + RED('c'), + LIGHT_PURPLE('d'), + YELLOW('e'), + WHITE('f'), + RESET('r'); + + private final String color; + + MessageColor(char code) { + this.color = String.valueOf(MessageConstants.COLOR_SYMBOL) + code; + } + + @Override + public String toString() { + return this.color; + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/message/MessageConstants.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/message/MessageConstants.java new file mode 100644 index 0000000..aa5c0b8 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/message/MessageConstants.java @@ -0,0 +1,26 @@ +package com.elevatemc.elib.util.message; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; + +public final class MessageConstants { + + public static final char COLOR_SYMBOL = '§'; + public static final Pattern ALPHANUMERIC_PATTERN = Pattern.compile("[^a-zA-Z0-9]"); + private static final List VOWELS = Arrays.asList("a", "e", "u", "i", "o"); + + public static String getAOrAn(String input) { + return ((VOWELS.contains(input.substring(0, 1).toLowerCase())) ? "an" : "a"); + } + + public static String getSorApostrophe(String in) { + return in.endsWith("s") ? "'" : "'s"; + } + + public static String completeWithSOrApostrophe(String in) { + return in + getSorApostrophe(in); + } + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/message/MessageTranslator.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/message/MessageTranslator.java new file mode 100644 index 0000000..e61fce6 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/message/MessageTranslator.java @@ -0,0 +1,35 @@ +package com.elevatemc.elib.util.message; + +import it.unimi.dsi.fastutil.chars.CharOpenHashSet; +import it.unimi.dsi.fastutil.chars.CharSet; + +import java.util.ArrayList; +import java.util.List; + +public final class MessageTranslator { + + private static final CharSet colorCodes = new CharOpenHashSet("0123456789AaBbCcDdEeFfKkLlMmNnOoRr".toCharArray()); + + public static String translate(String text) { + char[] chars = text.toCharArray(); + + for (int i = 0; i < chars.length - 1; ++i) { + if (chars[i] == '&' && colorCodes.contains(chars[i + 1])) { + chars[i] = MessageConstants.COLOR_SYMBOL; + chars[i + 1] = Character.toLowerCase(chars[i + 1]); + } + } + + return new String(chars); + } + + public static List translateLines(List lines) { + List toReturn = new ArrayList<>(); + + for (String line : lines) { + toReturn.add(translate(line)); + } + + return toReturn; + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/paginate/FancyPaginatedOutput.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/paginate/FancyPaginatedOutput.java new file mode 100644 index 0000000..36ad51a --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/paginate/FancyPaginatedOutput.java @@ -0,0 +1,50 @@ +package com.elevatemc.elib.util.paginate; + +import com.google.common.base.Preconditions; +import mkremins.fanciful.FancyMessage; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public abstract class FancyPaginatedOutput { + + private final int resultsPerPage; + + public FancyPaginatedOutput() { + this(9); + } + + public FancyPaginatedOutput(int resultsPerPage) { + Preconditions.checkArgument(resultsPerPage > 0); + this.resultsPerPage = resultsPerPage; + } + + public abstract FancyMessage getHeader(int var1,int var2); + + public abstract FancyMessage format(T var1, int var2); + + public final void display(CommandSender sender,int page,Collection results) { + this.display(sender, page, (List)(new ArrayList(results))); + } + + public final void display(CommandSender sender, int page, List results) { + if (results.size() == 0) { + sender.sendMessage(ChatColor.RED + "No entries found."); + } else { + int maxPages = results.size() / this.resultsPerPage + 1; + if (page > 0 && page <= maxPages) { + this.getHeader(page, maxPages).send(sender); + + for(int i = this.resultsPerPage * (page - 1); i < this.resultsPerPage * page && i < results.size(); ++i) { + this.format(results.get(i), i).send(sender); + } + + } else { + sender.sendMessage(ChatColor.RED + "Page " + ChatColor.YELLOW + page + ChatColor.RED + " is out of bounds. (" + ChatColor.YELLOW + "1 - " + maxPages + ChatColor.RED + ")"); + } + } + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/paginate/PaginatedOutput.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/paginate/PaginatedOutput.java new file mode 100644 index 0000000..e766bbb --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/paginate/PaginatedOutput.java @@ -0,0 +1,48 @@ +package com.elevatemc.elib.util.paginate; + +import com.google.common.base.Preconditions; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public abstract class PaginatedOutput { + private final int resultsPerPage; + + public PaginatedOutput() { + this(9); + } + + public PaginatedOutput(int resultsPerPage) { + Preconditions.checkArgument(resultsPerPage > 0); + this.resultsPerPage = resultsPerPage; + } + + public abstract String getHeader(int var1, int var2); + + public abstract String format(T var1, int var2); + + public final void display(CommandSender sender,int page,Collection results) { + this.display(sender, page, (new ArrayList<>(results))); + } + + public final void display(CommandSender sender, int page, List results) { + if (results.size() == 0) { + sender.sendMessage(ChatColor.RED + "No entries found."); + } else { + int maxPages = results.size() / this.resultsPerPage + 1; + if (page > 0 && page <= maxPages) { + sender.sendMessage(this.getHeader(page, maxPages)); + + for(int i = this.resultsPerPage * (page - 1); i < this.resultsPerPage * page && i < results.size(); ++i) { + sender.sendMessage(this.format(results.get(i), i)); + } + + } else { + sender.sendMessage(ChatColor.RED + "Page " + ChatColor.YELLOW + page + ChatColor.RED + " is out of bounds. (" + ChatColor.YELLOW + "1 - " + maxPages + ChatColor.RED + ")"); + } + } + } +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/qr/QRCodeMapRenderer.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/qr/QRCodeMapRenderer.java new file mode 100644 index 0000000..e200a87 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/qr/QRCodeMapRenderer.java @@ -0,0 +1,27 @@ +package com.elevatemc.elib.util.qr; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.bukkit.entity.Player; +import org.bukkit.map.MapCanvas; +import org.bukkit.map.MapRenderer; +import org.bukkit.map.MapView; + +import java.awt.image.BufferedImage; +import java.util.UUID; + +@AllArgsConstructor +public class QRCodeMapRenderer extends MapRenderer { + + @Getter private UUID uuid; + @Getter private BufferedImage image; + + public void render(MapView map,MapCanvas canvas,Player player) { + + if (player.getUniqueId().equals(this.uuid)) { + canvas.drawImage(0, 0, this.image); + this.image = null; + } + + } +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/util/qr/TotpUtil.java b/Network/eLib/src/main/java/com/elevatemc/elib/util/qr/TotpUtil.java new file mode 100644 index 0000000..08d70d3 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/util/qr/TotpUtil.java @@ -0,0 +1,247 @@ +package com.elevatemc.elib.util.qr; + +import lombok.experimental.UtilityClass; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.security.GeneralSecurityException; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Random; + +@UtilityClass +public class TotpUtil { + + public static final int DEFAULT_TIME_STEP_SECONDS = 30; + + private final static int NUM_DIGITS_OUTPUT = 6; + private static final String blockOfZeros; + + public static String generateBase32Secret() { + return generateBase32Secret(16); + } + + public static String generateBase32Secret(int length) { + + final Random random = new SecureRandom(); + + final StringBuilder stringBuilder = new StringBuilder(length); + + for(int i = 0; i < length; ++i) { + + int val = random.nextInt(32); + + if (val < 26) { + stringBuilder.append((char)(65 + val)); + } else { + stringBuilder.append((char)(50 + (val - 26))); + } + + } + + return stringBuilder.toString(); + } + + public static boolean validateCurrentNumber(String base32Secret, int authNumber, int windowMillis) throws GeneralSecurityException { + return validateCurrentNumber(base32Secret, authNumber, windowMillis, System.currentTimeMillis(), 30); + } + + public static boolean validateCurrentNumber(String base32Secret, int authNumber, int windowMillis, long timeMillis, int timeStepSeconds) throws GeneralSecurityException { + long from = timeMillis; + + long to = timeMillis; + + if (windowMillis > 0) { + from = timeMillis - (long)windowMillis; + to = timeMillis + (long)windowMillis; + } + + long timeStepMillis = (long)(timeStepSeconds * 1000); + + for(long millis = from; millis <= to; millis += timeStepMillis) { + + final long compare = generateNumber(base32Secret, millis, timeStepSeconds); + + if (compare == (long)authNumber) { + return true; + } + + } + + return false; + } + + public static String generateCurrentNumberString(String base32Secret) throws GeneralSecurityException { + return generateNumberString(base32Secret, System.currentTimeMillis(), 30); + } + + public static String generateNumberString(String base32Secret, long timeMillis, int timeStepSeconds) throws GeneralSecurityException { + + final long number = generateNumber(base32Secret, timeMillis, timeStepSeconds); + + return zeroPrepend(number, NUM_DIGITS_OUTPUT); + } + + public static long generateCurrentNumber(String base32Secret) throws GeneralSecurityException { + return generateNumber(base32Secret, System.currentTimeMillis(), 30); + } + + public static long generateNumber(String base32Secret, long timeMillis, int timeStepSeconds) throws GeneralSecurityException { + + final byte[] key = decodeBase32(base32Secret); + final byte[] data = new byte[8]; + + long value = timeMillis / 1000L / (long)timeStepSeconds; + + for(int i = 7; value > 0L; --i) { + data[i] = (byte)((int)(value & 255L)); + value >>= 8; + } + + final SecretKeySpec signKey = new SecretKeySpec(key, "HmacSHA1"); + final Mac mac = Mac.getInstance("HmacSHA1"); + + mac.init(signKey); + + final byte[] hash = mac.doFinal(data); + final int offset = hash[hash.length - 1] & 15; + + long truncatedHash = 0L; + + for(int i = offset; i < offset + 4; ++i) { + truncatedHash <<= 8; + truncatedHash |= (long)(hash[i] & 255); + } + + truncatedHash &= 2147483647L; + truncatedHash %= 1000000L; + + return truncatedHash; + } + + public static String qrImageUrl(String keyId, String secret) { + final StringBuilder sb = new StringBuilder(128); + sb.append("https://chart.googleapis.com/chart?chs=200x200&cht=qr&chl=200x200&chld=M|0&cht=qr&chl="); + addOtpAuthPart(keyId, secret, sb); + return sb.toString(); + } + + public static String generateOtpAuthUrl(String keyId, String secret) { + StringBuilder sb = new StringBuilder(64); + addOtpAuthPart(keyId, secret, sb); + return sb.toString(); + } + + private static void addOtpAuthPart(String keyId, String secret, StringBuilder sb) { + sb.append("otpauth://totp/").append(keyId).append("%3Fsecret%3D").append(secret); + } + + static String zeroPrepend(long num, int digits) { + final String numStr = Long.toString(num); + + if (numStr.length() >= digits) { + return numStr; + } else { + StringBuilder sb = new StringBuilder(digits); + int zeroCount = digits - numStr.length(); + sb.append(blockOfZeros, 0, zeroCount); + sb.append(numStr); + return sb.toString(); + } + } + + static byte[] decodeBase32(String str) { + int numBytes = (str.length() * 5 + 7) / 8; + byte[] result = new byte[numBytes]; + int resultIndex = 0; + int which = 0; + int working = 0; + + for(int i = 0; i < str.length(); ++i) { + char ch = str.charAt(i); + int val; + if (ch >= 'a' && ch <= 'z') { + val = ch - 97; + } else if (ch >= 'A' && ch <= 'Z') { + val = ch - 65; + } else { + if (ch < '2' || ch > '7') { + if (ch != '=') { + throw new IllegalArgumentException("Invalid base-32 character: " + ch); + } + + which = 0; + break; + } + + val = 26 + (ch - 50); + } + + switch(which) { + case 0: + working = (val & 31) << 3; + which = 1; + break; + case 1: + working |= (val & 28) >> 2; + result[resultIndex++] = (byte)working; + working = (val & 3) << 6; + which = 2; + break; + case 2: + working |= (val & 31) << 1; + which = 3; + break; + case 3: + working |= (val & 16) >> 4; + result[resultIndex++] = (byte)working; + working = (val & 15) << 4; + which = 4; + break; + case 4: + working |= (val & 30) >> 1; + result[resultIndex++] = (byte)working; + working = (val & 1) << 7; + which = 5; + break; + case 5: + working |= (val & 31) << 2; + which = 6; + break; + case 6: + working |= (val & 24) >> 3; + result[resultIndex++] = (byte)working; + working = (val & 7) << 5; + which = 7; + break; + case 7: + working |= val & 31; + result[resultIndex++] = (byte)working; + which = 0; + } + } + + if (which != 0) { + result[resultIndex++] = (byte)working; + } + + if (resultIndex != result.length) { + result = Arrays.copyOf(result, resultIndex); + } + + return result; + } + + static { + + final char[] chars = new char[NUM_DIGITS_OUTPUT]; + + for(int i = 0; i < chars.length; ++i) { + chars[i] = '0'; + } + + blockOfZeros = new String(chars); + } + + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/uuid/UUIDCache.java b/Network/eLib/src/main/java/com/elevatemc/elib/uuid/UUIDCache.java new file mode 100644 index 0000000..70ad380 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/uuid/UUIDCache.java @@ -0,0 +1,65 @@ +package com.elevatemc.elib.uuid; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.uuid.impl.redis.RedisUUIDCache; +import com.elevatemc.elib.uuid.impl.IUUIDCache; +import com.elevatemc.elib.uuid.listener.UUIDListener; +import lombok.Getter; +import org.bukkit.ChatColor; + +import java.util.*; + +public final class UUIDCache implements IUUIDCache { + + public static final UUID CONSOLE_UUID = UUID.fromString("00000000-0000-0000-0000-000000000000"); + public static final String UPDATE_PREFIX = ChatColor.BLUE + "[UUIDCache]"; + + public static final Map MONITOR_CACHE = new HashMap<>(); + + @Getter private IUUIDCache impl; + + public UUIDCache() { + + try { + this.impl = new RedisUUIDCache(); + } catch (Exception e) { + e.printStackTrace(); + } + + eLib.getInstance().getServer().getPluginManager().registerEvents(new UUIDListener(), eLib.getInstance()); + + this.update(CONSOLE_UUID,"CONSOLE"); + } + + public UUID uuid(String name) { + return this.impl.uuid(name); + } + + public String name(UUID uuid) { + return this.impl.name(uuid); + } + + public boolean cached(UUID uuid) { + return this.impl.cached(uuid); + } + + public boolean cached(String name) { + return this.impl.cached(name); + } + + public void ensure(UUID uuid) { + this.impl.ensure(uuid); + } + + public void update(UUID uuid, String name) { + this.impl.update(uuid, name); + } + + public void updateAll(UUID uuid,String name) { + this.impl.updateAll(uuid,name); + } + + public void monitor(String message) { + + } +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/uuid/impl/IUUIDCache.java b/Network/eLib/src/main/java/com/elevatemc/elib/uuid/impl/IUUIDCache.java new file mode 100644 index 0000000..3d10562 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/uuid/impl/IUUIDCache.java @@ -0,0 +1,21 @@ +package com.elevatemc.elib.uuid.impl; + +import java.util.UUID; + +public interface IUUIDCache { + + UUID uuid(String name); + + String name(UUID uuid); + + boolean cached(UUID uuid); + + boolean cached(String name); + + void ensure(UUID uuid); + + void update(UUID uuid,String name); + + void updateAll(UUID uuid,String name); + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/uuid/impl/bukkit/BukkitUUIDCache.java b/Network/eLib/src/main/java/com/elevatemc/elib/uuid/impl/bukkit/BukkitUUIDCache.java new file mode 100644 index 0000000..83673ee --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/uuid/impl/bukkit/BukkitUUIDCache.java @@ -0,0 +1,32 @@ +package com.elevatemc.elib.uuid.impl.bukkit; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.uuid.impl.IUUIDCache; + +import java.util.UUID; + +public final class BukkitUUIDCache implements IUUIDCache { + + public UUID uuid(String name) { + return (eLib.getInstance().getServer().getOfflinePlayer(name).getUniqueId()); + } + + public String name(UUID uuid) { + return (eLib.getInstance().getServer().getOfflinePlayer(uuid).getName()); + } + + public boolean cached(UUID uuid) { + return eLib.getInstance().getServer().getOfflinePlayer(uuid) != null; + } + + public boolean cached(String name) { + return eLib.getInstance().getServer().getOfflinePlayer(name) != null; + } + + public void ensure(UUID uuid) {} // Do nothing, as this class just delegates calls down to Bukkit. + + public void update(UUID uuid, String name) {} // We never need to update this, as this class just delegates calls down to Bukkit. + + public void updateAll(UUID uuid,String name) {} // We never need to update this, as this class just delegates calls down to Bukkit. + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/uuid/impl/redis/RedisUUIDCache.java b/Network/eLib/src/main/java/com/elevatemc/elib/uuid/impl/redis/RedisUUIDCache.java new file mode 100644 index 0000000..a19d3a5 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/uuid/impl/redis/RedisUUIDCache.java @@ -0,0 +1,106 @@ +package com.elevatemc.elib.uuid.impl.redis; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.util.TaskUtil; +import com.elevatemc.elib.util.UUIDUtils; +import com.elevatemc.elib.uuid.impl.IUUIDCache; +import lombok.Getter; +import com.elevatemc.elib.pidgin.packet.handler.IncomingPacketHandler; +import com.elevatemc.elib.pidgin.packet.listener.PacketListener; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public final class RedisUUIDCache implements IUUIDCache, PacketListener { + + @Getter private static Map uuidToName = new ConcurrentHashMap<>(); + @Getter private static Map nameToUuid = new ConcurrentHashMap<>(); + + public RedisUUIDCache() { + + eLib.getInstance().getPidginHandler().registerPacket(RedisUUIDUpdatePacket.class); + eLib.getInstance().getPidginHandler().registerListener(this); + + eLib.getInstance().runBackboneRedisCommand(redis -> { + + final Map cache = redis.hgetAll("UUIDCache"); + + for (Map.Entry cacheEntry : cache.entrySet()) { + + final UUID uuid = UUID.fromString(cacheEntry.getKey()); + final String name = cacheEntry.getValue(); + + uuidToName.put(uuid, name); + nameToUuid.put(name.toLowerCase(), uuid); + } + + return null; + }); + } + + public UUID uuid(String name) { + + if (nameToUuid.containsKey(name.toLowerCase())) { + return nameToUuid.get(name.toLowerCase()); + } + + return null; + } + + public String name(UUID uuid) { + return uuidToName.get(uuid); + } + + @Override + public boolean cached(UUID uuid) { + return uuidToName.containsKey(uuid); + } + + @Override + public boolean cached(String name) { + return nameToUuid.containsKey(name.toLowerCase()); + } + + public void ensure(UUID uuid) { + if (String.valueOf(name(uuid)).equals("null")) { + eLib.getInstance().getLogger().warning(uuid + " didn't have a cached name."); + } + } + + public void update(UUID uuid,String name) { + uuidToName.put(uuid,name); + + for (Map.Entry entry : (new HashMap<>(nameToUuid)).entrySet()) { + + if (entry.getValue().equals(uuid)) { + nameToUuid.remove(entry.getKey()); + } + + } + + nameToUuid.put(name.toLowerCase(),uuid); + } + + public void updateAll(UUID uuid,String name) { + this.update(uuid,name); + + eLib.getInstance().getUuidCache().monitor("UUID & name has been updated in the local cache. (" + UUIDUtils.formatPretty(uuid) + ")"); + + TaskUtil.executeWithPoolIfRequired(() -> eLib.getInstance().runBackboneRedisCommand(redis -> { + eLib.getInstance().getUuidCache().monitor("UUID & name has been updated in the redis cache. (" + UUIDUtils.formatPretty(uuid) + ")"); + redis.hset("UUIDCache", uuid.toString(), name); + return null; + })); + + eLib.getInstance().getPidginHandler().sendPacket(new RedisUUIDUpdatePacket(uuid,name)); + } + + @IncomingPacketHandler + public void onRedisUUIDUpdate(RedisUUIDUpdatePacket packet) { + this.update(packet.uuid(),packet.name()); + eLib.getInstance().getUuidCache().monitor("UUID & name has been updated across network. (" +UUIDUtils.formatPretty(packet.uuid()) + ")"); + } + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/uuid/impl/redis/RedisUUIDUpdatePacket.java b/Network/eLib/src/main/java/com/elevatemc/elib/uuid/impl/redis/RedisUUIDUpdatePacket.java new file mode 100644 index 0000000..de6d6f9 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/uuid/impl/redis/RedisUUIDUpdatePacket.java @@ -0,0 +1,45 @@ +package com.elevatemc.elib.uuid.impl.redis; + +import com.elevatemc.elib.pidgin.packet.Packet; +import com.google.gson.JsonObject; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +@NoArgsConstructor +@AllArgsConstructor +public class RedisUUIDUpdatePacket implements Packet { + + @Getter private JsonObject jsonObject; + + RedisUUIDUpdatePacket(UUID uuid,String name) { + this.jsonObject = new JsonObject(); + this.jsonObject.addProperty("uuid",uuid.toString()); + this.jsonObject.addProperty("name",name); + } + + @Override + public int id() { + return 999; + } + + @Override + public JsonObject serialize() { + return this.jsonObject; + } + + @Override + public void deserialize(JsonObject object) { + this.jsonObject = object; + } + + public UUID uuid() { + return UUID.fromString(this.jsonObject.get("uuid").getAsString()); + } + + public String name() { + return this.jsonObject.get("name").getAsString(); + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/uuid/listener/UUIDListener.java b/Network/eLib/src/main/java/com/elevatemc/elib/uuid/listener/UUIDListener.java new file mode 100644 index 0000000..cc07881 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/uuid/listener/UUIDListener.java @@ -0,0 +1,24 @@ +package com.elevatemc.elib.uuid.listener; + +import com.elevatemc.elib.eLib; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerPreLoginEvent; + +public final class UUIDListener implements Listener { + + @EventHandler(priority = EventPriority.MONITOR) + public void onAsyncPlayerPreLogin(AsyncPlayerPreLoginEvent event) { + + if (eLib.getInstance().getUuidCache().cached(event.getUniqueId())) { + eLib.getInstance().getUuidCache().update(event.getUniqueId(),event.getName()); + } else { + eLib.getInstance().getUuidCache().updateAll(event.getUniqueId(),event.getName()); + } + + } + + + +} \ No newline at end of file diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/visibility/VisibilityHandler.java b/Network/eLib/src/main/java/com/elevatemc/elib/visibility/VisibilityHandler.java new file mode 100644 index 0000000..5eecdd0 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/visibility/VisibilityHandler.java @@ -0,0 +1,90 @@ +package com.elevatemc.elib.visibility; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.visibility.action.OverrideAction; +import com.elevatemc.elib.visibility.action.VisibilityAction; +import com.elevatemc.elib.visibility.listener.VisibilityListener; +import com.elevatemc.elib.visibility.provider.OverrideProvider; +import com.elevatemc.elib.visibility.provider.VisibilityProvider; +import lombok.Getter; + +import org.bukkit.entity.Player; + +import java.util.*; + +public class VisibilityHandler { + + @Getter private final Map handlers = new LinkedHashMap<>(); + @Getter private final Map overrideHandlers = new LinkedHashMap<>(); + + public VisibilityHandler() { + eLib.getInstance().getServer().getPluginManager().registerEvents(new VisibilityListener(), eLib.getInstance()); + } + + public void registerHandler(String identifier, VisibilityProvider handler) { + this.handlers.put(identifier, handler); + } + + public void registerOverride(String identifier, OverrideProvider handler) { + this.overrideHandlers.put(identifier, handler); + } + + public void update(Player player) { + + if (!this.handlers.isEmpty() || !this.overrideHandlers.isEmpty()) { + this.updateAllTo(player); + this.updateToAll(player); + } + + } + + /** @deprecated */ + @Deprecated + public void updateAllTo(Player viewer) { + + for (Player target : eLib.getInstance().getServer().getOnlinePlayers()) { + + if (!this.shouldSee(target, viewer)) { + viewer.hidePlayer(target); + } else { + viewer.showPlayer(target); + } + + } + + } + + /** @deprecated */ + @Deprecated + public void updateToAll(Player target) { + + for (Player viewer : eLib.getInstance().getServer().getOnlinePlayers()) { + + if (!this.shouldSee(target, viewer)) { + viewer.hidePlayer(target); + } else { + viewer.showPlayer(target); + } + + } + + } + + public boolean treatAsOnline(Player target, Player viewer) { + return viewer.canSee(target) || !target.hasMetadata("invisible"); + } + + private boolean shouldSee(Player target,Player viewer) { + + for (VisibilityProvider visibilityProvider : this.handlers.values()) { + + for (OverrideProvider overrideProvider : this.overrideHandlers.values()) { + return overrideProvider.getAction(target,viewer) == OverrideAction.SHOW; + } + + return visibilityProvider.getAction(target,viewer) == VisibilityAction.NEUTRAL; + } + + return true; + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/visibility/action/OverrideAction.java b/Network/eLib/src/main/java/com/elevatemc/elib/visibility/action/OverrideAction.java new file mode 100644 index 0000000..5f50f8e --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/visibility/action/OverrideAction.java @@ -0,0 +1,8 @@ +package com.elevatemc.elib.visibility.action; + +public enum OverrideAction { + + SHOW, + NEUTRAL + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/visibility/action/VisibilityAction.java b/Network/eLib/src/main/java/com/elevatemc/elib/visibility/action/VisibilityAction.java new file mode 100644 index 0000000..8659213 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/visibility/action/VisibilityAction.java @@ -0,0 +1,8 @@ +package com.elevatemc.elib.visibility.action; + +public enum VisibilityAction { + + HIDE, + NEUTRAL + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/visibility/listener/VisibilityListener.java b/Network/eLib/src/main/java/com/elevatemc/elib/visibility/listener/VisibilityListener.java new file mode 100644 index 0000000..28726b7 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/visibility/listener/VisibilityListener.java @@ -0,0 +1,38 @@ +package com.elevatemc.elib.visibility.listener; + +import com.elevatemc.elib.eLib; +import org.apache.commons.lang.StringUtils; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerChatTabCompleteEvent; +import org.bukkit.event.player.PlayerJoinEvent; + +import java.util.Collection; + +public class VisibilityListener implements Listener { + + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerJoin(PlayerJoinEvent event) { + eLib.getInstance().getVisibilityHandler().update(event.getPlayer()); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onTabComplete(PlayerChatTabCompleteEvent event) { + + final String token = event.getLastToken(); + final Collection completions = event.getTabCompletions(); + + completions.clear(); + + for (Player target : eLib.getInstance().getServer().getOnlinePlayers()) { + + if (eLib.getInstance().getVisibilityHandler().treatAsOnline(target,event.getPlayer()) && StringUtils.startsWithIgnoreCase(target.getName(),token)) { + completions.add(target.getName()); + } + + } + + } +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/visibility/provider/OverrideProvider.java b/Network/eLib/src/main/java/com/elevatemc/elib/visibility/provider/OverrideProvider.java new file mode 100644 index 0000000..57d9e66 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/visibility/provider/OverrideProvider.java @@ -0,0 +1,10 @@ +package com.elevatemc.elib.visibility.provider; + +import com.elevatemc.elib.visibility.action.OverrideAction; +import org.bukkit.entity.Player; + +public interface OverrideProvider { + + OverrideAction getAction(Player target, Player viewer); + +} diff --git a/Network/eLib/src/main/java/com/elevatemc/elib/visibility/provider/VisibilityProvider.java b/Network/eLib/src/main/java/com/elevatemc/elib/visibility/provider/VisibilityProvider.java new file mode 100644 index 0000000..9329d58 --- /dev/null +++ b/Network/eLib/src/main/java/com/elevatemc/elib/visibility/provider/VisibilityProvider.java @@ -0,0 +1,10 @@ +package com.elevatemc.elib.visibility.provider; + +import com.elevatemc.elib.visibility.action.VisibilityAction; +import org.bukkit.entity.Player; + +public interface VisibilityProvider { + + VisibilityAction getAction(Player player,Player target); + +} diff --git a/Network/eLib/src/main/java/mkremins/fanciful/FancyMessage.java b/Network/eLib/src/main/java/mkremins/fanciful/FancyMessage.java new file mode 100644 index 0000000..5f467e9 --- /dev/null +++ b/Network/eLib/src/main/java/mkremins/fanciful/FancyMessage.java @@ -0,0 +1,661 @@ +package mkremins.fanciful; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonWriter; +import net.md_5.bungee.chat.ComponentSerializer; +import net.minecraft.server.v1_8_R3.ChatComponentUtils; +import net.minecraft.server.v1_8_R3.IChatBaseComponent; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.ConfigurationSerialization; +import org.bukkit.entity.Player; +import com.elevatemc.elib.util.ArrayWrapper; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; + +import static mkremins.fanciful.TextualComponent.rawText; + +/** + * Represents a formattable message. Such messages can use elements such as colors, formatting codes, hover and click data, and other features provided by the vanilla Minecraft JSON message formatter. + * This class allows plugins to emulate the functionality of the vanilla Minecraft tellraw command. + *

+ * This class follows the builder pattern, allowing for method chaining. + * It is set up such that invocations of property-setting methods will affect the current editing component, + * and a call to {@link #then()} or {@link #then(String)} will append a new editing component to the end of the message, + * optionally initializing it with text. Further property-setting method calls will affect that editing component. + *

+ */ +public class FancyMessage implements JsonRepresentedObject, Cloneable, Iterable, ConfigurationSerializable { + + static { + ConfigurationSerialization.registerClass(FancyMessage.class); + } + + private List messageParts; + private String jsonString; + private boolean dirty; + + @Override + public FancyMessage clone() throws CloneNotSupportedException { + FancyMessage instance = (FancyMessage) super.clone(); + instance.messageParts = new ArrayList(messageParts.size()); + for (int i = 0; i < messageParts.size(); i++) { + instance.messageParts.add(i, messageParts.get(i).clone()); + } + instance.dirty = false; + instance.jsonString = null; + return instance; + } + + /** + * Creates a JSON message with text. + * + * @param firstPartText The existing text in the message. + */ + public FancyMessage(final String firstPartText) { + this(rawText(firstPartText)); + } + + public FancyMessage(final TextualComponent firstPartText) { + messageParts = new ArrayList(); + messageParts.add(new MessagePart(firstPartText)); + jsonString = null; + dirty = false; + } + + /** + * Creates a JSON message without text. + */ + public FancyMessage() { + this((TextualComponent) null); + } + + /** + * Sets the text of the current editing component to a value. + * + * @param text The new text of the current editing component. + * @return This builder instance. + */ + public FancyMessage text(String text) { + MessagePart latest = latest(); + latest.text = rawText(text); + dirty = true; + return this; + } + + /** + * Sets the text of the current editing component to a value. + * + * @param text The new text of the current editing component. + * @return This builder instance. + */ + public FancyMessage text(TextualComponent text) { + MessagePart latest = latest(); + latest.text = text; + dirty = true; + return this; + } + + /** + * Sets the color of the current editing component to a value. + * + * @param color The new color of the current editing component. + * @return This builder instance. + * @throws IllegalArgumentException If the specified {@code ChatColor} enumeration value is not a color (but a format value). + */ + public FancyMessage color(final ChatColor color) { + if (!color.isColor()) { + throw new IllegalArgumentException(color.name() + " is not a color"); + } + latest().color = color; + dirty = true; + return this; + } + + /** + * Sets the stylization of the current editing component. + * + * @param styles The array of styles to apply to the editing component. + * @return This builder instance. + * @throws IllegalArgumentException If any of the enumeration values in the array do not represent formatters. + */ + public FancyMessage style(ChatColor... styles) { + for (final ChatColor style : styles) { + if (!style.isFormat()) { + throw new IllegalArgumentException(style.name() + " is not a style"); + } + } + latest().styles.addAll(Arrays.asList(styles)); + dirty = true; + return this; + } + + /** + * Set the behavior of the current editing component to instruct the client to open a file on the client side filesystem when the currently edited part of the {@code FancyMessage} is clicked. + * + * @param path The path of the file on the client filesystem. + * @return This builder instance. + */ + public FancyMessage file(final String path) { + onClick("open_file", path); + return this; + } + + /** + * Set the behavior of the current editing component to instruct the client to open a webpage in the client's web browser when the currently edited part of the {@code FancyMessage} is clicked. + * + * @param url The URL of the page to open when the link is clicked. + * @return This builder instance. + */ + public FancyMessage link(final String url) { + onClick("open_url", url); + return this; + } + + /** + * Set the behavior of the current editing component to instruct the client to replace the chat input box content with the specified string when the currently edited part of the {@code FancyMessage} is clicked. + * The client will not immediately send the command to the server to be executed unless the client player submits the command/chat message, usually with the enter key. + * + * @param command The text to display in the chat bar of the client. + * @return This builder instance. + */ + public FancyMessage suggest(final String command) { + onClick("suggest_command", command); + return this; + } + + /** + * Set the behavior of the current editing component to instruct the client to append the chat input box content with the specified string when the currently edited part of the {@code FancyMessage} is SHIFT-CLICKED. + * The client will not immediately send the command to the server to be executed unless the client player submits the command/chat message, usually with the enter key. + * + * @param command The text to append to the chat bar of the client. + * @return This builder instance. + */ + public FancyMessage insert(final String command) { + latest().insertionData = command; + dirty = true; + return this; + } + + /** + * Set the behavior of the current editing component to instruct the client to send the specified string to the server as a chat message when the currently edited part of the {@code FancyMessage} is clicked. + * The client will immediately send the command to the server to be executed when the editing component is clicked. + * + * @param command The text to display in the chat bar of the client. + * @return This builder instance. + */ + public FancyMessage command(final String command) { + onClick("run_command", command); + return this; + } + + /** + * Set the behavior of the current editing component to display information about an achievement when the client hovers over the text. + *

Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.

+ * + * @param name The name of the achievement to display, excluding the "achievement." prefix. + * @return This builder instance. + */ + public FancyMessage achievementTooltip(final String name) { + onHover("show_achievement", new JsonString("achievement." + name)); + return this; + } + + /** + * Set the behavior of the current editing component to display raw text when the client hovers over the text. + *

Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.

+ * + * @param text The text, which supports newlines, which will be displayed to the client upon hovering. + * @return This builder instance. + */ + public FancyMessage tooltip(final String text) { + onHover("show_text", new JsonString(text)); + return this; + } + + /** + * Set the behavior of the current editing component to display raw text when the client hovers over the text. + *

Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.

+ * + * @param lines The lines of text which will be displayed to the client upon hovering. The iteration order of this object will be the order in which the lines of the tooltip are created. + * @return This builder instance. + */ + public FancyMessage tooltip(final Iterable lines) { + tooltip(ArrayWrapper.toArray(lines, String.class)); + return this; + } + + /** + * Set the behavior of the current editing component to display raw text when the client hovers over the text. + *

Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.

+ * + * @param lines The lines of text which will be displayed to the client upon hovering. + * @return This builder instance. + */ + public FancyMessage tooltip(final String... lines) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < lines.length; i++) { + builder.append(lines[i]); + if (i != lines.length - 1) { + builder.append('\n'); + } + } + tooltip(builder.toString()); + return this; + } + + /** + * Set the behavior of the current editing component to display formatted text when the client hovers over the text. + *

Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.

+ * + * @param text The formatted text which will be displayed to the client upon hovering. + * @return This builder instance. + */ + public FancyMessage formattedTooltip(FancyMessage text) { + for (MessagePart component : text.messageParts) { + if (component.clickActionData != null && component.clickActionName != null) { + throw new IllegalArgumentException("The tooltip text cannot have click data."); + } else if (component.hoverActionData != null && component.hoverActionName != null) { + throw new IllegalArgumentException("The tooltip text cannot have a tooltip."); + } + } + onHover("show_text", text); + return this; + } + + /** + * Set the behavior of the current editing component to display the specified lines of formatted text when the client hovers over the text. + *

Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.

+ * + * @param lines The lines of formatted text which will be displayed to the client upon hovering. + * @return This builder instance. + */ + public FancyMessage formattedTooltip(FancyMessage... lines) { + if (lines.length < 1) { + onHover(null, null); // Clear tooltip + return this; + } + + FancyMessage result = new FancyMessage(); + result.messageParts.clear(); // Remove the one existing text component that exists by default, which destabilizes the object + + for (int i = 0; i < lines.length; i++) { + try { + for (MessagePart component : lines[i]) { + if (component.clickActionData != null && component.clickActionName != null) { + throw new IllegalArgumentException("The tooltip text cannot have click data."); + } else if (component.hoverActionData != null && component.hoverActionName != null) { + throw new IllegalArgumentException("The tooltip text cannot have a tooltip."); + } + if (component.hasText()) { + result.messageParts.add(component.clone()); + } + } + if (i != lines.length - 1) { + result.messageParts.add(new MessagePart(rawText("\n"))); + } + } catch (CloneNotSupportedException e) { + Bukkit.getLogger().log(Level.WARNING, "Failed to clone object", e); + return this; + } + } + return formattedTooltip(result.messageParts.isEmpty() ? null : result); // Throws NPE if size is 0, intended + } + + /** + * Set the behavior of the current editing component to display the specified lines of formatted text when the client hovers over the text. + *

Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.

+ * + * @param lines The lines of text which will be displayed to the client upon hovering. The iteration order of this object will be the order in which the lines of the tooltip are created. + * @return This builder instance. + */ + public FancyMessage formattedTooltip(final Iterable lines) { + return formattedTooltip(ArrayWrapper.toArray(lines, FancyMessage.class)); + } + + /** + * If the text is a translatable key, and it has replaceable values, this function can be used to set the replacements that will be used in the message. + * + * @param replacements The replacements, in order, that will be used in the language-specific message. + * @return This builder instance. + */ + public FancyMessage translationReplacements(final String... replacements) { + for (String str : replacements) { + latest().translationReplacements.add(new JsonString(str)); + } + dirty = true; + + return this; + } + /* + + /** + * If the text is a translatable key, and it has replaceable values, this function can be used to set the replacements that will be used in the message. + * @param replacements The replacements, in order, that will be used in the language-specific message. + * @return This builder instance. + */ /* ------------ + public FancyMessage translationReplacements(final Iterable replacements){ + for(CharSequence str : replacements){ + latest().translationReplacements.add(new JsonString(str)); + } + + return this; + } + + */ + + /** + * If the text is a translatable key, and it has replaceable values, this function can be used to set the replacements that will be used in the message. + * + * @param replacements The replacements, in order, that will be used in the language-specific message. + * @return This builder instance. + */ + public FancyMessage translationReplacements(final FancyMessage... replacements) { + for (FancyMessage str : replacements) { + latest().translationReplacements.add(str); + } + + dirty = true; + + return this; + } + + /** + * If the text is a translatable key, and it has replaceable values, this function can be used to set the replacements that will be used in the message. + * + * @param replacements The replacements, in order, that will be used in the language-specific message. + * @return This builder instance. + */ + public FancyMessage translationReplacements(final Iterable replacements) { + return translationReplacements(ArrayWrapper.toArray(replacements, FancyMessage.class)); + } + + /** + * Terminate construction of the current editing component, and begin construction of a new message component. + * After a successful call to this method, all setter methods will refer to a new message component, created as a result of the call to this method. + * + * @param text The text which will populate the new message component. + * @return This builder instance. + */ + public FancyMessage then(final String text) { + return then(rawText(text)); + } + + /** + * Terminate construction of the current editing component, and begin construction of a new message component. + * After a successful call to this method, all setter methods will refer to a new message component, created as a result of the call to this method. + * + * @param text The text which will populate the new message component. + * @return This builder instance. + */ + public FancyMessage then(final TextualComponent text) { + if (!latest().hasText()) { + throw new IllegalStateException("previous message part has no text"); + } + messageParts.add(new MessagePart(text)); + dirty = true; + return this; + } + + /** + * Terminate construction of the current editing component, and begin construction of a new message component. + * After a successful call to this method, all setter methods will refer to a new message component, created as a result of the call to this method. + * + * @return This builder instance. + */ + public FancyMessage then() { + if (!latest().hasText()) { + throw new IllegalStateException("previous message part has no text"); + } + messageParts.add(new MessagePart()); + dirty = true; + return this; + } + + @Override + public void writeJson(JsonWriter writer) throws IOException { + if (messageParts.size() == 1) { + latest().writeJson(writer); + } else { + writer.beginObject().name("text").value("").name("extra").beginArray(); + for (final MessagePart part : this) { + part.writeJson(writer); + } + writer.endArray().endObject(); + } + } + + /** + * Serialize this fancy message, converting it into syntactically-valid JSON using a {@link JsonWriter}. + * This JSON should be compatible with vanilla formatter commands such as {@code /tellraw}. + * + * @return The JSON string representing this object. + */ + public String toJSONString() { + if (!dirty && jsonString != null) { + return jsonString; + } + StringWriter string = new StringWriter(); + JsonWriter json = new JsonWriter(string); + try { + writeJson(json); + json.close(); + } catch (IOException e) { + throw new RuntimeException("invalid message"); + } + jsonString = string.toString(); + dirty = false; + return jsonString; + } + + /** + * Sends this message to a player. The player will receive the fully-fledged formatted display of this message. + * + * @param player The player who will receive the message. + */ + public void send(Player player) { + send(player, toJSONString()); + } + + private void send(CommandSender sender, String jsonString) { + if (!(sender instanceof Player)) { + sender.sendMessage(toOldMessageFormat()); + return; + } + Player player = (Player) sender; + + player.spigot().sendMessage(ComponentSerializer.parse(jsonString));//ChatComponentUtils.filterForDisplay(sender, chat, player)); + //Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "tellraw " + player.getName() + " " + jsonString); + } + + /** + * Sends this message to a command sender. + * If the sender is a player, they will receive the fully-fledged formatted display of this message. + * Otherwise, they will receive a version of this message with less formatting. + * + * @param sender The command sender who will receive the message. + * @see #toOldMessageFormat() + */ + public void send(CommandSender sender) { + send(sender, toJSONString()); + } + + /** + * Sends this message to multiple command senders. + * + * @param senders The command senders who will receive the message. + * @see #send(CommandSender) + */ + public void send(final Iterable senders) { + String string = toJSONString(); + for (final CommandSender sender : senders) { + send(sender, string); + } + } + + /** + * Convert this message to a human-readable string with limited formatting. + * This method is used to send this message to clients without JSON formatting support. + *

+ * Serialization of this message by using this message will include (in this order for each message part): + *

    + *
  1. The color of each message part.
  2. + *
  3. The applicable stylizations for each message part.
  4. + *
  5. The core text of the message part.
  6. + *
+ * The primary omissions are tooltips and clickable actions. Consequently, this method should be used only as a last resort. + *

+ *

+ * Color and formatting can be removed from the returned string by using {@link ChatColor#stripColor(String)}.

+ * + * @return A human-readable string representing limited formatting in addition to the core text of this message. + */ + public String toOldMessageFormat() { + StringBuilder result = new StringBuilder(); + for (MessagePart part : this) { + result.append(part.color == null ? "" : part.color); + for (ChatColor formatSpecifier : part.styles) { + result.append(formatSpecifier); + } + result.append(part.text); + } + return result.toString(); + } + + private MessagePart latest() { + return messageParts.get(messageParts.size() - 1); + } + + private void onClick(final String name, final String data) { + final MessagePart latest = latest(); + latest.clickActionName = name; + latest.clickActionData = data; + dirty = true; + } + + private void onHover(final String name, final JsonRepresentedObject data) { + final MessagePart latest = latest(); + latest.hoverActionName = name; + latest.hoverActionData = data; + dirty = true; + } + + // Doc copied from interface + public Map serialize() { + HashMap map = new HashMap(); + map.put("messageParts", messageParts); +// map.put("JSON", toJSONString()); + return map; + } + + /** + * Deserializes a JSON-represented message from a mapping of key-value pairs. + * This is called by the Bukkit serialization API. + * It is not intended for direct public API consumption. + * + * @param serialized The key-value mapping which represents a fancy message. + */ + @SuppressWarnings("unchecked") + public static FancyMessage deserialize(Map serialized) { + FancyMessage msg = new FancyMessage(); + msg.messageParts = (List) serialized.get("messageParts"); + msg.jsonString = serialized.containsKey("JSON") ? serialized.get("JSON").toString() : null; + msg.dirty = !serialized.containsKey("JSON"); + return msg; + } + + /** + * Internally called method. Not for API consumption. + */ + public Iterator iterator() { + return messageParts.iterator(); + } + + private static JsonParser _stringParser = new JsonParser(); + + /** + * Deserializes a fancy message from its JSON representation. This JSON representation is of the format of + * that returned by {@link #toJSONString()}, and is compatible with vanilla inputs. + * + * @param json The JSON string which represents a fancy message. + * @return A {@code FancyMessage} representing the parameterized JSON message. + */ + public static FancyMessage deserialize(String json) { + JsonObject serialized = _stringParser.parse(json).getAsJsonObject(); + JsonArray extra = serialized.getAsJsonArray("extra"); // Get the extra component + FancyMessage returnVal = new FancyMessage(); + returnVal.messageParts.clear(); + for (JsonElement mPrt : extra) { + MessagePart component = new MessagePart(); + JsonObject messagePart = mPrt.getAsJsonObject(); + for (Map.Entry entry : messagePart.entrySet()) { + // Deserialize text + if (TextualComponent.isTextKey(entry.getKey())) { + // The map mimics the YAML serialization, which has a "key" field and one or more "value" fields + Map serializedMapForm = new HashMap(); // Must be object due to Bukkit serializer API compliance + serializedMapForm.put("key", entry.getKey()); + if (entry.getValue().isJsonPrimitive()) { + // Assume string + serializedMapForm.put("value", entry.getValue().getAsString()); + } else { + // Composite object, but we assume each element is a string + for (Map.Entry compositeNestedElement : entry.getValue().getAsJsonObject().entrySet()) { + serializedMapForm.put("value." + compositeNestedElement.getKey(), compositeNestedElement.getValue().getAsString()); + } + } + component.text = TextualComponent.deserialize(serializedMapForm); + } else if (MessagePart.stylesToNames.inverse().containsKey(entry.getKey())) { + if (entry.getValue().getAsBoolean()) { + component.styles.add(MessagePart.stylesToNames.inverse().get(entry.getKey())); + } + } else if (entry.getKey().equals("color")) { + component.color = ChatColor.valueOf(entry.getValue().getAsString().toUpperCase()); + } else if (entry.getKey().equals("clickEvent")) { + JsonObject object = entry.getValue().getAsJsonObject(); + component.clickActionName = object.get("action").getAsString(); + component.clickActionData = object.get("value").getAsString(); + } else if (entry.getKey().equals("hoverEvent")) { + JsonObject object = entry.getValue().getAsJsonObject(); + component.hoverActionName = object.get("action").getAsString(); + if (object.get("value").isJsonPrimitive()) { + // Assume string + component.hoverActionData = new JsonString(object.get("value").getAsString()); + } else { + // Assume composite type + // The only composite type we currently store is another FancyMessage + // Therefore, recursion time! + component.hoverActionData = deserialize(object.get("value").toString() /* This should properly serialize the JSON object as a JSON string */); + } + } else if (entry.getKey().equals("insertion")) { + component.insertionData = entry.getValue().getAsString(); + } else if (entry.getKey().equals("with")) { + for (JsonElement object : entry.getValue().getAsJsonArray()) { + if (object.isJsonPrimitive()) { + component.translationReplacements.add(new JsonString(object.getAsString())); + } else { + // Only composite type stored in this array is - again - FancyMessages + // Recurse within this function to parse this as a translation replacement + component.translationReplacements.add(deserialize(object.toString())); + } + } + } + } + returnVal.messageParts.add(component); + } + return returnVal; + } + +} diff --git a/Network/eLib/src/main/java/mkremins/fanciful/JsonRepresentedObject.java b/Network/eLib/src/main/java/mkremins/fanciful/JsonRepresentedObject.java new file mode 100644 index 0000000..7264111 --- /dev/null +++ b/Network/eLib/src/main/java/mkremins/fanciful/JsonRepresentedObject.java @@ -0,0 +1,19 @@ +package mkremins.fanciful; + +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; + +/** + * Represents an object that can be serialized to a JSON writer instance. + */ +interface JsonRepresentedObject { + + /** + * Writes the JSON representation of this object to the specified writer. + * @param writer The JSON writer which will receive the object. + * @throws IOException If an error occurs writing to the stream. + */ + public void writeJson(JsonWriter writer) throws IOException; + +} diff --git a/Network/eLib/src/main/java/mkremins/fanciful/JsonString.java b/Network/eLib/src/main/java/mkremins/fanciful/JsonString.java new file mode 100644 index 0000000..67764c6 --- /dev/null +++ b/Network/eLib/src/main/java/mkremins/fanciful/JsonString.java @@ -0,0 +1,47 @@ +package mkremins.fanciful; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import com.google.gson.stream.JsonWriter; +import org.bukkit.configuration.serialization.ConfigurationSerializable; + +/** + * Represents a JSON string value. + * Writes by this object will not write name values nor begin/end objects in the JSON stream. + * All writes merely write the represented string value. + */ +final class JsonString implements JsonRepresentedObject, ConfigurationSerializable { + + private String _value; + + public JsonString(CharSequence value) { + _value = value == null ? null : value.toString(); + } + + @Override + public void writeJson(JsonWriter writer) throws IOException { + writer.value(getValue()); + } + + public String getValue() { + return _value; + } + + public Map serialize() { + HashMap theSingleValue = new HashMap(); + theSingleValue.put("stringValue", _value); + return theSingleValue; + } + + public static JsonString deserialize(Map map) { + return new JsonString(map.get("stringValue").toString()); + } + + @Override + public String toString() { + return _value; + } + +} diff --git a/Network/eLib/src/main/java/mkremins/fanciful/MessagePart.java b/Network/eLib/src/main/java/mkremins/fanciful/MessagePart.java new file mode 100644 index 0000000..037939a --- /dev/null +++ b/Network/eLib/src/main/java/mkremins/fanciful/MessagePart.java @@ -0,0 +1,155 @@ +package mkremins.fanciful; + +import com.google.common.collect.BiMap; +import com.google.common.collect.ImmutableBiMap; +import com.google.gson.stream.JsonWriter; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.ConfigurationSerialization; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; + +/** + * Internal class: Represents a component of a JSON-serializable {@link FancyMessage}. + */ +final class MessagePart implements JsonRepresentedObject, ConfigurationSerializable, Cloneable { + + ChatColor color = ChatColor.WHITE; + ArrayList styles = new ArrayList(); + String clickActionName = null, clickActionData = null, hoverActionName = null; + JsonRepresentedObject hoverActionData = null; + TextualComponent text = null; + String insertionData = null; + ArrayList translationReplacements = new ArrayList(); + + MessagePart(final TextualComponent text) { + this.text = text; + } + + MessagePart() { + this.text = null; + } + + boolean hasText() { + return text != null; + } + + @Override + @SuppressWarnings("unchecked") + public MessagePart clone() throws CloneNotSupportedException { + MessagePart obj = (MessagePart) super.clone(); + obj.styles = (ArrayList) styles.clone(); + if (hoverActionData instanceof JsonString) { + obj.hoverActionData = new JsonString(((JsonString) hoverActionData).getValue()); + } else if (hoverActionData instanceof FancyMessage) { + obj.hoverActionData = ((FancyMessage) hoverActionData).clone(); + } + obj.translationReplacements = (ArrayList) translationReplacements.clone(); + return obj; + + } + + static final BiMap stylesToNames; + + static { + ImmutableBiMap.Builder builder = ImmutableBiMap.builder(); + for (final ChatColor style : ChatColor.values()) { + if (!style.isFormat()) { + continue; + } + + String styleName; + switch (style) { + case MAGIC: + styleName = "obfuscated"; + break; + case UNDERLINE: + styleName = "underlined"; + break; + default: + styleName = style.name().toLowerCase(); + break; + } + + builder.put(style, styleName); + } + stylesToNames = builder.build(); + } + + public void writeJson(JsonWriter json) { + try { + json.beginObject(); + text.writeJson(json); + json.name("color").value(color.name().toLowerCase()); + for (final ChatColor style : styles) { + json.name(stylesToNames.get(style)).value(true); + } + if (clickActionName != null && clickActionData != null) { + json.name("clickEvent") + .beginObject() + .name("action").value(clickActionName) + .name("value").value(clickActionData) + .endObject(); + } + if (hoverActionName != null && hoverActionData != null) { + json.name("hoverEvent") + .beginObject() + .name("action").value(hoverActionName) + .name("value"); + hoverActionData.writeJson(json); + json.endObject(); + } + if (insertionData != null) { + json.name("insertion").value(insertionData); + } + if (translationReplacements.size() > 0 && text != null && TextualComponent.isTranslatableText(text)) { + json.name("with").beginArray(); + for (JsonRepresentedObject obj : translationReplacements) { + obj.writeJson(json); + } + json.endArray(); + } + json.endObject(); + } catch (IOException e) { + Bukkit.getLogger().log(Level.WARNING, "A problem occured during writing of JSON string", e); + } + } + + public Map serialize() { + HashMap map = new HashMap(); + map.put("text", text); + map.put("styles", styles); + map.put("color", color.getChar()); + map.put("hoverActionName", hoverActionName); + map.put("hoverActionData", hoverActionData); + map.put("clickActionName", clickActionName); + map.put("clickActionData", clickActionData); + map.put("insertion", insertionData); + map.put("translationReplacements", translationReplacements); + return map; + } + + @SuppressWarnings("unchecked") + public static MessagePart deserialize(Map serialized) { + MessagePart part = new MessagePart((TextualComponent) serialized.get("text")); + part.styles = (ArrayList) serialized.get("styles"); + part.color = ChatColor.getByChar(serialized.get("color").toString()); + part.hoverActionName = (String) serialized.get("hoverActionName"); + part.hoverActionData = (JsonRepresentedObject) serialized.get("hoverActionData"); + part.clickActionName = (String) serialized.get("clickActionName"); + part.clickActionData = (String) serialized.get("clickActionData"); + part.insertionData = (String) serialized.get("insertion"); + part.translationReplacements = (ArrayList) serialized.get("translationReplacements"); + return part; + } + + static { + ConfigurationSerialization.registerClass(MessagePart.class); + } + +} diff --git a/Network/eLib/src/main/java/mkremins/fanciful/TextualComponent.java b/Network/eLib/src/main/java/mkremins/fanciful/TextualComponent.java new file mode 100644 index 0000000..5e960eb --- /dev/null +++ b/Network/eLib/src/main/java/mkremins/fanciful/TextualComponent.java @@ -0,0 +1,297 @@ +package mkremins.fanciful; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import com.google.gson.stream.JsonWriter; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.ConfigurationSerialization; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * Represents a textual component of a message part. + * This can be used to not only represent string literals in a JSON message, + * but also to represent localized strings and other text values. + *

Different instances of this class can be created with static constructor methods.

+ */ +public abstract class TextualComponent implements Cloneable { + + static { + ConfigurationSerialization.registerClass(TextualComponent.ArbitraryTextTypeComponent.class); + ConfigurationSerialization.registerClass(TextualComponent.ComplexTextTypeComponent.class); + } + + @Override + public String toString() { + return getReadableString(); + } + + /** + * @return The JSON key used to represent text components of this type. + */ + public abstract String getKey(); + + /** + * @return A readable String + */ + public abstract String getReadableString(); + + /** + * Clones a textual component instance. + * The returned object should not reference this textual component instance, but should maintain the same key and value. + */ + @Override + public abstract TextualComponent clone() throws CloneNotSupportedException; + + /** + * Writes the text data represented by this textual component to the specified JSON writer object. + * A new object within the writer is not started. + * + * @param writer The object to which to write the JSON data. + * @throws IOException If an error occurs while writing to the stream. + */ + public abstract void writeJson(JsonWriter writer) throws IOException; + + static TextualComponent deserialize(Map map) { + if (map.containsKey("key") && map.size() == 2 && map.containsKey("value")) { + // Arbitrary text component + return ArbitraryTextTypeComponent.deserialize(map); + } else if (map.size() >= 2 && map.containsKey("key") && !map.containsKey("value") /* It contains keys that START WITH value */) { + // Complex JSON object + return ComplexTextTypeComponent.deserialize(map); + } + + return null; + } + + static boolean isTextKey(String key) { + return key.equals("translate") || key.equals("text") || key.equals("score") || key.equals("selector"); + } + + static boolean isTranslatableText(TextualComponent component) { + return component instanceof ComplexTextTypeComponent && ((ComplexTextTypeComponent) component).getKey().equals("translate"); + } + + /** + * Internal class used to represent all types of text components. + * Exception validating done is on keys and values. + */ + private static final class ArbitraryTextTypeComponent extends TextualComponent implements ConfigurationSerializable { + + public ArbitraryTextTypeComponent(String key, String value) { + setKey(key); + setValue(value); + } + + @Override + public String getKey() { + return _key; + } + + public void setKey(String key) { + Preconditions.checkArgument(key != null && !key.isEmpty(), "The key must be specified."); + _key = key; + } + + public String getValue() { + return _value; + } + + public void setValue(String value) { + Preconditions.checkArgument(value != null, "The value must be specified."); + _value = value; + } + + private String _key; + private String _value; + + @Override + public TextualComponent clone() throws CloneNotSupportedException { + // Since this is a private and final class, we can just reinstantiate this class instead of casting super.clone + return new ArbitraryTextTypeComponent(getKey(), getValue()); + } + + @Override + public void writeJson(JsonWriter writer) throws IOException { + writer.name(getKey()).value(getValue()); + } + + @SuppressWarnings("serial") + public Map serialize() { + return new HashMap() {{ + put("key", getKey()); + put("value", getValue()); + }}; + } + + public static ArbitraryTextTypeComponent deserialize(Map map) { + return new ArbitraryTextTypeComponent(map.get("key").toString(), map.get("value").toString()); + } + + @Override + public String getReadableString() { + return getValue(); + } + } + + /** + * Internal class used to represent a text component with a nested JSON value. + * Exception validating done is on keys and values. + */ + private static final class ComplexTextTypeComponent extends TextualComponent implements ConfigurationSerializable { + + public ComplexTextTypeComponent(String key, Map values) { + setKey(key); + setValue(values); + } + + @Override + public String getKey() { + return _key; + } + + public void setKey(String key) { + Preconditions.checkArgument(key != null && !key.isEmpty(), "The key must be specified."); + _key = key; + } + + public Map getValue() { + return _value; + } + + public void setValue(Map value) { + Preconditions.checkArgument(value != null, "The value must be specified."); + _value = value; + } + + private String _key; + private Map _value; + + @Override + public TextualComponent clone() throws CloneNotSupportedException { + // Since this is a private and final class, we can just reinstantiate this class instead of casting super.clone + return new ComplexTextTypeComponent(getKey(), getValue()); + } + + @Override + public void writeJson(JsonWriter writer) throws IOException { + writer.name(getKey()); + writer.beginObject(); + for (Map.Entry jsonPair : _value.entrySet()) { + writer.name(jsonPair.getKey()).value(jsonPair.getValue()); + } + writer.endObject(); + } + + @SuppressWarnings("serial") + public Map serialize() { + return new java.util.HashMap() {{ + put("key", getKey()); + for (Map.Entry valEntry : getValue().entrySet()) { + put("value." + valEntry.getKey(), valEntry.getValue()); + } + }}; + } + + public static ComplexTextTypeComponent deserialize(Map map) { + String key = null; + Map value = new HashMap(); + for (Map.Entry valEntry : map.entrySet()) { + if (valEntry.getKey().equals("key")) { + key = (String) valEntry.getValue(); + } else if (valEntry.getKey().startsWith("value.")) { + value.put(((String) valEntry.getKey()).substring(6) /* Strips out the value prefix */, valEntry.getValue().toString()); + } + } + return new ComplexTextTypeComponent(key, value); + } + + @Override + public String getReadableString() { + return getKey(); + } + } + + /** + * Create a textual component representing a string literal. + * This is the default type of textual component when a single string literal is given to a method. + * + * @param textValue The text which will be represented. + * @return The text component representing the specified literal text. + */ + public static TextualComponent rawText(String textValue) { + return new ArbitraryTextTypeComponent("text", textValue); + } + + + /** + * Create a textual component representing a localized string. + * The client will see this text component as their localized version of the specified string key, which can be overridden by a resource pack. + *

+ * If the specified translation key is not present on the client resource pack, the translation key will be displayed as a string literal to the client. + *

+ * + * @param translateKey The string key which maps to localized text. + * @return The text component representing the specified localized text. + */ + public static TextualComponent localizedText(String translateKey) { + return new ArbitraryTextTypeComponent("translate", translateKey); + } + + private static void throwUnsupportedSnapshot() { + throw new UnsupportedOperationException("This feature is only supported in snapshot releases."); + } + + /** + * Create a textual component representing a scoreboard value. + * The client will see their own score for the specified objective as the text represented by this component. + *

+ * This method is currently guaranteed to throw an {@code UnsupportedOperationException} as it is only supported on snapshot clients. + *

+ * + * @param scoreboardObjective The name of the objective for which to display the score. + * @return The text component representing the specified scoreboard score (for the viewing player), or {@code null} if an error occurs during JSON serialization. + */ + public static TextualComponent objectiveScore(String scoreboardObjective) { + return objectiveScore("*", scoreboardObjective); + } + + /** + * Create a textual component representing a scoreboard value. + * The client will see the score of the specified player for the specified objective as the text represented by this component. + *

+ * This method is currently guaranteed to throw an {@code UnsupportedOperationException} as it is only supported on snapshot clients. + *

+ * + * @param playerName The name of the player whos score will be shown. If this string represents the single-character sequence "*", the viewing player's score will be displayed. + * Standard minecraft selectors (@a, @p, etc) are not supported. + * @param scoreboardObjective The name of the objective for which to display the score. + * @return The text component representing the specified scoreboard score for the specified player, or {@code null} if an error occurs during JSON serialization. + */ + public static TextualComponent objectiveScore(String playerName, String scoreboardObjective) { + throwUnsupportedSnapshot(); // Remove this line when the feature is released to non-snapshot versions, in addition to updating ALL THE OVERLOADS documentation accordingly + + return new ComplexTextTypeComponent("score", ImmutableMap.builder() + .put("name", playerName) + .put("objective", scoreboardObjective) + .build()); + } + + /** + * Create a textual component representing a player name, retrievable by using a standard minecraft selector. + * The client will see the players or entities captured by the specified selector as the text represented by this component. + *

+ * This method is currently guaranteed to throw an {@code UnsupportedOperationException} as it is only supported on snapshot clients. + *

+ * + * @param selector The minecraft player or entity selector which will capture the entities whose string representations will be displayed in the place of this text component. + * @return The text component representing the name of the entities captured by the selector. + */ + public static TextualComponent selector(String selector) { + throwUnsupportedSnapshot(); // Remove this line when the feature is released to non-snapshot versions, in addition to updating ALL THE OVERLOADS documentation accordingly + + return new ArbitraryTextTypeComponent("selector", selector); + } +} diff --git a/Network/eLib/src/main/resources/config.yml b/Network/eLib/src/main/resources/config.yml new file mode 100644 index 0000000..62abe70 --- /dev/null +++ b/Network/eLib/src/main/resources/config.yml @@ -0,0 +1,13 @@ +Redis: + Host: 'localhost' + Pass: null + DbId: 5 + Port: 6379 +Backbone: + Host: 'localhost' + Pass: null + DbId: 0 + Port: 6379 +NametagPacketRestriction: + Enabled: false + BypassPrefix: '&a' \ No newline at end of file diff --git a/Network/eLib/src/main/resources/items.csv b/Network/eLib/src/main/resources/items.csv new file mode 100644 index 0000000..5c56f42 --- /dev/null +++ b/Network/eLib/src/main/resources/items.csv @@ -0,0 +1,7392 @@ +stone,1,0 +sstone,1,0 +smoothstone,1,0 +rock,1,0 +grass,2,0 +greendirt,2,0 +greenearth,2,0 +greenland,2,0 +dirt,3,0 +earth,3,0 +land,3,0 +cobblestone,4,0 +cstone,4,0 +cobble,4,0 +wood,5,0 +plank,5,0 +woodenplank,5,0 +woodplank,5,0 +wplank,5,0 +plankwooden,5,0 +plankwood,5,0 +plankw,5,0 +oakplank,5,0 +oakwoodenplank,5,0 +oakwoodplank,5,0 +oakwplank,5,0 +oakplankwooden,5,0 +oakplankwood,5,0 +oakplankw,5,0 +oplank,5,0 +owoodenplank,5,0 +owoodplank,5,0 +owplank,5,0 +oplankwooden,5,0 +oplankwood,5,0 +oplankw,5,0 +darkplank,5,1 +darkwoodenplank,5,1 +darkwoodplank,5,1 +darkwplank,5,1 +darkplankwooden,5,1 +darkplankwood,5,1 +darkplankw,5,1 +dplank,5,1 +dwoodenplank,5,1 +dwoodplank,5,1 +dwplank,5,1 +dplankwooden,5,1 +dplankwood,5,1 +dplankw,5,1 +spruceplank,5,1 +sprucewoodenplank,5,1 +sprucewoodplank,5,1 +sprucewplank,5,1 +spruceplankwooden,5,1 +spruceplankwood,5,1 +spruceplankw,5,1 +splank,5,1 +swoodenplank,5,1 +swoodplank,5,1 +swplank,5,1 +splankwooden,5,1 +splankwood,5,1 +splankw,5,1 +pineplank,5,1 +pinewoodenplank,5,1 +pinewoodplank,5,1 +pinewplank,5,1 +pineplankwooden,5,1 +pineplankwood,5,1 +pineplankw,5,1 +pplank,5,1 +pwoodenplank,5,1 +pwoodplank,5,1 +pwplank,5,1 +pplankwooden,5,1 +pplankwood,5,1 +pplankw,5,1 +lightplank,5,2 +lightwoodenplank,5,2 +lightwoodplank,5,2 +lightwplank,5,2 +lightplankwooden,5,2 +lightplankwood,5,2 +lightplankw,5,2 +lplank,5,2 +lwoodenplank,5,2 +lwoodplank,5,2 +lwplank,5,2 +lplankwooden,5,2 +lplankwood,5,2 +lplankw,5,2 +birchplank,5,2 +birchwoodenplank,5,2 +birchwoodplank,5,2 +birchwplank,5,2 +birchplankwooden,5,2 +birchplankwood,5,2 +birchplankw,5,2 +bplank,5,2 +bwoodenplank,5,2 +bwoodplank,5,2 +bwplank,5,2 +bplankwooden,5,2 +bplankwood,5,2 +bplankw,5,2 +whiteplank,5,2 +whitewoodenplank,5,2 +whitewoodplank,5,2 +whitewplank,5,2 +whiteplankwooden,5,2 +whiteplankwood,5,2 +whiteplankw,5,2 +wwoodenplank,5,2 +wwoodplank,5,2 +wwplank,5,2 +wplankwooden,5,2 +wplankwood,5,2 +wplankw,5,2 +jungleplank,5,3 +junglewoodenplank,5,3 +junglewoodplank,5,3 +junglewplank,5,3 +jungleplankwooden,5,3 +jungleplankwood,5,3 +jungleplankw,5,3 +jplank,5,3 +jwoodenplank,5,3 +jwoodplank,5,3 +jwplank,5,3 +jplankwooden,5,3 +jplankwood,5,3 +jplankw,5,3 +forestplank,5,3 +forestwoodenplank,5,3 +forestwoodplank,5,3 +forestwplank,5,3 +forestplankwooden,5,3 +forestplankwood,5,3 +forestplankw,5,3 +fplank,5,3 +fwoodenplank,5,3 +fwoodplank,5,3 +fwplank,5,3 +fplankwooden,5,3 +fplankwood,5,3 +fplankw,5,3 +sapling,6,0 +treesapling,6,0 +logsapling,6,0 +trunksapling,6,0 +woodsapling,6,0 +oaktreesapling,6,0 +oaklogsapling,6,0 +oaktrunksapling,6,0 +oakwoodsapling,6,0 +osapling,6,0 +otreesapling,6,0 +ologsapling,6,0 +otrunksapling,6,0 +owoodsapling,6,0 +darksapling,6,1 +darktreesapling,6,1 +darklogsapling,6,1 +darktrunksapling,6,1 +darkwoodsapling,6,1 +sprucesapling,6,1 +sprucetreesapling,6,1 +sprucelogsapling,6,1 +sprucetrunksapling,6,1 +sprucewoodsapling,6,1 +pinesapling,6,1 +pinetreesapling,6,1 +pinelogsapling,6,1 +pinetrunksapling,6,1 +pinewoodsapling,6,1 +dsapling,6,1 +dtreesapling,6,1 +dlogsapling,6,1 +dtrunksapling,6,1 +dwoodsapling,6,1 +ssapling,6,1 +streesapling,6,1 +slogsapling,6,1 +strunksapling,6,1 +swoodsapling,6,1 +psapling,6,1 +ptreesapling,6,1 +plogsapling,6,1 +ptrunksapling,6,1 +pwoodsapling,6,1 +birchsapling,6,2 +birchtreesapling,6,2 +birchlogsapling,6,2 +birchtrunksapling,6,2 +birchwoodsapling,6,2 +lightsapling,6,2 +lighttreesapling,6,2 +lightlogsapling,6,2 +lighttrunksapling,6,2 +lightwoodsapling,6,2 +whitesapling,6,2 +whitetreesapling,6,2 +whitelogsapling,6,2 +whitetrunksapling,6,2 +whitewoodsapling,6,2 +bsapling,6,2 +btreesapling,6,2 +blogsapling,6,2 +btrunksapling,6,2 +bwoodsapling,6,2 +lsapling,6,2 +ltreesapling,6,2 +llogsapling,6,2 +ltrunksapling,6,2 +lwoodsapling,6,2 +wsapling,6,2 +wtreesapling,6,2 +wlogsapling,6,2 +wtrunksapling,6,2 +wwoodsapling,6,2 +junglesapling,6,3 +jungletreesapling,6,3 +junglelogsapling,6,3 +jungletrunksapling,6,3 +junglewoodsapling,6,3 +forestsapling,6,3 +foresttreesapling,6,3 +forestlogsapling,6,3 +foresttrunksapling,6,3 +forestwoodsapling,6,3 +jsapling,6,3 +jtreesapling,6,3 +jlogsapling,6,3 +jtrunksapling,6,3 +jwoodsapling,6,3 +fsapling,6,3 +ftreesapling,6,3 +flogsapling,6,3 +ftrunksapling,6,3 +fwoodsapling,6,3 +bedrock,7,0 +oprock,7,0 +opblock,7,0 +adminblock,7,0 +adminrock,7,0 +adminium,7,0 +water,8,0 +stationarywater,9,0 +swater,9,0 +lava,10,0 +stationarylava,11,0 +slava,11,0 +sand,12,0 +gravel,13,0 +goldore,14,0 +oregold,14,0 +goldo,14,0 +ogold,14,0 +gore,14,0 +oreg,14,0 +ironore,15,0 +oreiron,15,0 +irono,15,0 +oiron,15,0 +steelore,15,0 +oresteel,15,0 +steelo,15,0 +osteel,15,0 +iore,15,0 +orei,15,0 +sore,15,0 +ores,15,0 +coalore,16,0 +orecoal,16,0 +coalo,16,0 +ocoal,16,0 +Proton,16,0 +tree,17,0 +log,17,0 +trunk,17,0 +oak,17,0 +oaktree,17,0 +oaklog,17,0 +oaktrunk,17,0 +oakwood,17,0 +otree,17,0 +olog,17,0 +otrunk,17,0 +owood,17,0 +darktree,17,1 +darklog,17,1 +darktrunk,17,1 +darkwood,17,1 +spruce,17,1 +sprucetree,17,1 +sprucelog,17,1 +sprucetrunk,17,1 +sprucewood,17,1 +pine,17,1 +pinetree,17,1 +pinelog,17,1 +pinetrunk,17,1 +pinewood,17,1 +dtree,17,1 +dlog,17,1 +dtrunk,17,1 +dwood,17,1 +stree,17,1 +slog,17,1 +strunk,17,1 +swood,17,1 +ptree,17,1 +plog,17,1 +ptrunk,17,1 +pwood,17,1 +birch,17,2 +birchtree,17,2 +birchlog,17,2 +birchtrunk,17,2 +birchwood,17,2 +whitetree,17,2 +whitelog,17,2 +whitetrunk,17,2 +whitewood,17,2 +lighttree,17,2 +lightlog,17,2 +lighttrunk,17,2 +lightwood,17,2 +btree,17,2 +blog,17,2 +btrunk,17,2 +bwood,17,2 +wtree,17,2 +wlog,17,2 +wtrunk,17,2 +wwood,17,2 +ltree,17,2 +llog,17,2 +ltrunk,17,2 +lwood,17,2 +jungle,17,3 +jungletree,17,3 +junglelog,17,3 +jungletrunk,17,3 +junglewood,17,3 +forest,17,3 +foresttree,17,3 +forestlog,17,3 +foresttrunk,17,3 +forestwood,17,3 +jtree,17,3 +jlog,17,3 +jtrunk,17,3 +jwood,17,3 +ftree,17,3 +flog,17,3 +ftrunk,17,3 +fwood,17,3 +leaves,18,0 +leaf,18,0 +treeleaves,18,0 +logleaves,18,0 +trunkleaves,18,0 +woodleaves,18,0 +oakleaves,18,0 +oakleaf,18,0 +oleaves,18,0 +oleaf,18,0 +oaktreeleaves,18,0 +oaklogleaves,18,0 +oaktrunkleaves,18,0 +oakwoodleaves,18,0 +otreeleaves,18,0 +ologleaves,18,0 +otrunkleaves,18,0 +owoodleaves,18,0 +treeleaf,18,0 +logleaf,18,0 +trunkleaf,18,0 +woodleaf,18,0 +oaktreeleaf,18,0 +oaklogleaf,18,0 +oaktrunkleaf,18,0 +oakwoodleaf,18,0 +otreeleaf,18,0 +ologleaf,18,0 +otrunkleaf,18,0 +owoodleaf,18,0 +spruceleaves,18,1 +spruceleaf,18,1 +sleaves,18,1 +sleaf,18,1 +sprucetreeleaves,18,1 +sprucelogleaves,18,1 +sprucetrunkleaves,18,1 +sprucewoodleaves,18,1 +streeleaves,18,1 +slogleaves,18,1 +strunkleaves,18,1 +swoodleaves,18,1 +pineleaves,18,1 +pineleaf,18,1 +pleaves,18,1 +pleaf,18,1 +pinetreeleaves,18,1 +pinelogleaves,18,1 +pinetrunkleaves,18,1 +pinewoodleaves,18,1 +ptreeleaves,18,1 +plogleaves,18,1 +ptrunkleaves,18,1 +pwoodleaves,18,1 +darkleaves,18,1 +darkleaf,18,1 +dleaves,18,1 +dleaf,18,1 +darktreeleaves,18,1 +darklogleaves,18,1 +darktrunkleaves,18,1 +darkwoodleaves,18,1 +dtreeleaves,18,1 +dlogleaves,18,1 +dtrunkleaves,18,1 +dwoodleaves,18,1 +sprucetreeleaf,18,1 +sprucelogleaf,18,1 +sprucetrunkleaf,18,1 +sprucewoodleaf,18,1 +streeleaf,18,1 +slogleaf,18,1 +strunkleaf,18,1 +swoodleaf,18,1 +pinetreeleaf,18,1 +pinelogleaf,18,1 +pinetrunkleaf,18,1 +pinewoodleaf,18,1 +ptreeleaf,18,1 +plogleaf,18,1 +ptrunkleaf,18,1 +pwoodleaf,18,1 +darktreeleaf,18,1 +darklogleaf,18,1 +darktrunkleaf,18,1 +darkwoodleaf,18,1 +dtreeleaf,18,1 +dlogleaf,18,1 +dtrunkleaf,18,1 +dwoodleaf,18,1 +birchleaves,18,2 +birchleaf,18,2 +bleaves,18,2 +bleaf,18,2 +birchtreeleaves,18,2 +birchlogleaves,18,2 +birchtrunkleaves,18,2 +birchwoodleaves,18,2 +btreeleaves,18,2 +blogleaves,18,2 +btrunkleaves,18,2 +bwoodleaves,18,2 +lightleaves,18,2 +lightleaf,18,2 +lleaves,18,2 +lleaf,18,2 +lighttreeleaves,18,2 +lightlogleaves,18,2 +lighttrunkleaves,18,2 +lightwoodleaves,18,2 +ltreeleaves,18,2 +llogleaves,18,2 +ltrunkleaves,18,2 +lwoodleaves,18,2 +whiteleaves,18,2 +whiteleaf,18,2 +wleaves,18,2 +wleaf,18,2 +whitetreeleaves,18,2 +whitelogleaves,18,2 +whitetrunkleaves,18,2 +whitewoodleaves,18,2 +wtreeleaves,18,2 +wlogleaves,18,2 +wtrunkleaves,18,2 +wwoodleaves,18,2 +birchtreeleaf,18,2 +birchlogleaf,18,2 +birchtrunkleaf,18,2 +birchwoodleaf,18,2 +btreeleaf,18,2 +blogleaf,18,2 +btrunkleaf,18,2 +bwoodleaf,18,2 +lighttreeleaf,18,2 +lightlogleaf,18,2 +lighttrunkleaf,18,2 +lightwoodleaf,18,2 +ltreeleaf,18,2 +llogleaf,18,2 +ltrunkleaf,18,2 +lwoodleaf,18,2 +whitetreeleaf,18,2 +whitelogleaf,18,2 +whitetrunkleaf,18,2 +whitewoodleaf,18,2 +wtreeleaf,18,2 +wlogleaf,18,2 +wtrunkleaf,18,2 +wwoodleaf,18,2 +jungleleaves,18,3 +jungleleaf,18,3 +jleaves,18,3 +jleaf,18,3 +jungletreeleaves,18,3 +junglelogleaves,18,3 +jungletrunkleaves,18,3 +junglewoodleaves,18,3 +jtreeleaves,18,3 +jlogleaves,18,3 +jtrunkleaves,18,3 +jwoodleaves,18,3 +forestleaves,18,3 +forestleaf,18,3 +fleaves,18,3 +fleaf,18,3 +foresttreeleaves,18,3 +forestlogleaves,18,3 +foresttrunkleaves,18,3 +forestwoodleaves,18,3 +ftreeleaves,18,3 +flogleaves,18,3 +ftrunkleaves,18,3 +fwoodleaves,18,3 +jungletreeleaf,18,3 +junglelogleaf,18,3 +jungletrunkleaf,18,3 +junglewoodleaf,18,3 +jtreeleaf,18,3 +jlogleaf,18,3 +jtrunkleaf,18,3 +jwoodleaf,18,3 +foresttreeleaf,18,3 +forestlogleaf,18,3 +foresttrunkleaf,18,3 +forestwoodleaf,18,3 +ftreeleaf,18,3 +flogleaf,18,3 +ftrunkleaf,18,3 +fwoodleaf,18,3 +sponge,19,0 +glass,20,0 +lapislazuliore,21,0 +lapislazulio,21,0 +orelapislazuli,21,0 +olapislazuli,21,0 +lapisore,21,0 +lapiso,21,0 +orelapis,21,0 +olapis,21,0 +lore,21,0 +orel,21,0 +lapislazuliblock,22,0 +blocklapislazuli,22,0 +lapisblock,22,0 +blocklapis,22,0 +lblock,22,0 +blockl,22,0 +dispenser,23,0 +dispense,23,0 +sandstone,24,0 +sastone,24,0 +csandstone,24,1 +csastone,24,1 +creepsandstone,24,1 +creepsastone,24,1 +creepersandstone,24,1 +creepersastone,24,1 +hieroglyphicsandstone,24,1 +hieroglyphicsastone,24,1 +hieroglyphsandstone,24,1 +hieroglyphsastone,24,1 +hsandstone,24,1 +hsastone,24,1 +pyramidsandstone,24,1 +pyramidsastone,24,1 +psandstone,24,1 +psastone,24,1 +smoothsandstone,24,2 +smoothsastone,24,2 +ssandstone,24,2 +smsastone,24,2 +ssastone,24,2 +noteblock,25,0 +musicblock,25,0 +nblock,25,0 +mblock,25,0 +bedblock,26,0 +poweredtrack,27,0 +poweredrails,27,0 +poweredrail,27,0 +boostertrack,27,0 +boosterrails,27,0 +boosterrail,27,0 +powertrack,27,0 +powerrails,27,0 +powerrail,27,0 +boosttrack,27,0 +boostrails,27,0 +boostrail,27,0 +ptrack,27,0 +prails,27,0 +prail,27,0 +btrack,27,0 +brails,27,0 +brail,27,0 +detectortrack,28,0 +detectorrails,28,0 +detectorrail,28,0 +detectingtrack,28,0 +detectingrails,28,0 +detectingrail,28,0 +detecttrack,28,0 +detectrails,28,0 +detectrail,28,0 +dtrack,28,0 +drails,28,0 +drail,28,0 +stickypistonbase,29,0 +stickypiston,29,0 +stickpistonbase,29,0 +stickpiston,29,0 +stickyp,29,0 +spistonbase,29,0 +spiston,29,0 +pistonstickybase,29,0 +pistonsticky,29,0 +pistonstickbase,29,0 +pistonstick,29,0 +pistonsbase,29,0 +pistons,29,0 +psticky,29,0 +pstick,29,0 +spiderweb,30,0 +sweb,30,0 +web,30,0 +longgrass,31,1 +tallgrass,31,1 +wildgrass,31,1 +grasslong,31,1 +grasstall,31,1 +grasswild,31,1 +lgrass,31,1 +tgrass,31,1 +wgrass,31,1 +fern,31,2 +bush,31,2 +deadshrub,32,0 +dshrub,32,0 +deadbush,32,0 +dbush,32,0 +deadsapling,32,0 +normalpistonbase,33,0 +normalpiston,33,0 +normpistonbase,33,0 +normpiston,33,0 +npistonbase,33,0 +npiston,33,0 +pistonnormalbase,33,0 +pistonnormal,33,0 +pistonnormbase,33,0 +pistonnorm,33,0 +pistonnbase,33,0 +pistonn,33,0 +pistonbase,33,0 +piston,33,0 +pistonblock,33,0 +pistonextensionnormal,34,0 +pistonextension,34,0 +pistonextnormal,34,0 +pistonext,34,0 +pistonenormal,34,0 +pistone,34,0 +extensionpistonnormal,34,0 +extensionpiston,34,0 +extpistonnormal,34,0 +extpiston,34,0 +epistonnormal,34,0 +episton,34,0 +whitecloth,35,0 +whitewool,35,0 +whitecotton,35,0 +wcloth,35,0 +wwool,35,0 +wcotton,35,0 +cloth,35,0 +wool,35,0 +cotton,35,0 +orangecloth,35,1 +orangewool,35,1 +orangecotton,35,1 +ocloth,35,1 +owool,35,1 +ocotton,35,1 +magentacloth,35,2 +magentawool,35,2 +magentacotton,35,2 +mcloth,35,2 +mwool,35,2 +mcotton,35,2 +lightbluecloth,35,3 +lightbluewool,35,3 +lightbluecotton,35,3 +lbluecloth,35,3 +lbluewool,35,3 +lbluecotton,35,3 +lightblucloth,35,3 +lightbluwool,35,3 +lightblucotton,35,3 +lblucloth,35,3 +lbluwool,35,3 +lblucotton,35,3 +yellowcloth,35,4 +yellowwool,35,4 +yellowcotton,35,4 +ycloth,35,4 +ywool,35,4 +ycotton,35,4 +lightgreencloth,35,5 +lightgreenwool,35,5 +lightgreencotton,35,5 +lgreencloth,35,5 +lgreenwool,35,5 +lgreencotton,35,5 +lightgrecloth,35,5 +lightgrewool,35,5 +lightgrecotton,35,5 +lgrecloth,35,5 +lgrewool,35,5 +lgrecotton,35,5 +limecloth,35,5 +limewool,35,5 +limecotton,35,5 +lcloth,35,5 +lwool,35,5 +lcotton,35,5 +pinkcloth,35,6 +pinkwool,35,6 +pinkcotton,35,6 +picloth,35,6 +piwool,35,6 +picotton,35,6 +darkgraycloth,35,7 +darkgraywool,35,7 +darkgraycotton,35,7 +dgraycloth,35,7 +dgraywool,35,7 +dgraycotton,35,7 +darkgracloth,35,7 +darkgrawool,35,7 +darkgracotton,35,7 +dgracloth,35,7 +dgrawool,35,7 +dgracotton,35,7 +graycloth,35,7 +graywool,35,7 +graycotton,35,7 +gracloth,35,7 +grawool,35,7 +gracotton,35,7 +lightgraycloth,35,8 +lightgraywool,35,8 +lightgraycotton,35,8 +lgraycloth,35,8 +lgraywool,35,8 +lgraycotton,35,8 +lightgracloth,35,8 +lightgrawool,35,8 +lightgracotton,35,8 +lgracloth,35,8 +lgrawool,35,8 +lgracotton,35,8 +silvercloth,35,8 +silverwool,35,8 +silvercotton,35,8 +sicloth,35,8 +siawool,35,8 +siacotton,35,8 +cyancloth,35,9 +cyanwool,35,9 +cyancotton,35,9 +ccloth,35,9 +cwool,35,9 +ccotton,35,9 +purplecloth,35,10 +purplewool,35,10 +purplecotton,35,10 +pucloth,35,10 +puwool,35,10 +pucotton,35,10 +bluecloth,35,11 +bluewool,35,11 +bluecotton,35,11 +blucloth,35,11 +bluwool,35,11 +blucotton,35,11 +browncloth,35,12 +brownwool,35,12 +browncotton,35,12 +brocloth,35,12 +browool,35,12 +brocotton,35,12 +darkgreencloth,35,13 +darkgreenwool,35,13 +darkgreencotton,35,13 +dgreencloth,35,13 +dgreenwool,35,13 +dgreencotton,35,13 +greencloth,35,13 +greenwool,35,13 +greencotton,35,13 +darkgrecloth,35,13 +darkgrewool,35,13 +darkgrecotton,35,13 +dgrecloth,35,13 +dgrewool,35,13 +dgrecotton,35,13 +grecloth,35,13 +grewool,35,13 +grecotton,35,13 +redcloth,35,14 +redwool,35,14 +redcotton,35,14 +rcloth,35,14 +rwool,35,14 +rcotton,35,14 +blackcloth,35,15 +blackwool,35,15 +blackcotton,35,15 +blacloth,35,15 +blawool,35,15 +blacotton,35,15 +pistonmovingpiece,36,0 +pistonmp,36,0 +pistontop,36,0 +yellowflower,37,0 +yflower,37,0 +flower,37,0 +redrose,38,0 +rrose,38,0 +rose,38,0 +redflower,38,0 +rflower,38,0 +brownmushroom,39,0 +brownshroom,39,0 +brownmush,39,0 +bmushroom,39,0 +bshroom,39,0 +bmush,39,0 +redmushroom,40,0 +redshroom,40,0 +redmush,40,0 +rmushroom,40,0 +rshroom,40,0 +rmush,40,0 +goldblock,41,0 +blockgold,41,0 +gblock,41,0 +blockg,41,0 +ironblock,42,0 +steelblock,42,0 +blockiron,42,0 +blocksteel,42,0 +iblock,42,0 +stblock,42,0 +blocki,42,0 +blockst,42,0 +stonedoublestep,43,0 +stonedstep,43,0 +sdoublestep,43,0 +sdstep,43,0 +doublestonestep,43,0 +dstonestep,43,0 +doublesstep,43,0 +doublestep,43,0 +dstep,43,0 +stonedoubleslab,43,0 +stonedslab,43,0 +sdoubleslab,43,0 +sdslab,43,0 +doublestoneslab,43,0 +dstoneslab,43,0 +doublesslab,43,0 +doubleslab,43,0 +dslab,43,0 +stonedoublehalfblock,43,0 +stonedhalfblock,43,0 +sdoublehalfblock,43,0 +sdhalfblock,43,0 +doublestonehalfblock,43,0 +dstonehalfblock,43,0 +doubleshalfblock,43,0 +doublehalfblock,43,0 +dhalfblock,43,0 +sandstonedoublestep,43,1 +sandstonedstep,43,1 +sstonedoublestep,43,1 +sstonedstep,43,1 +ssdoublestep,43,1 +ssdstep,43,1 +doublesandstonestep,43,1 +dsandstonestep,43,1 +doublesstonestep,43,1 +dsstonestep,43,1 +doublessstep,43,1 +dsstep,43,1 +sandstonedoubleslab,43,1 +sandstonedslab,43,1 +sstonedoubleslab,43,1 +sstonedslab,43,1 +ssdoubleslab,43,1 +ssdslab,43,1 +doublesandstoneslab,43,1 +dsandstoneslab,43,1 +doublesstoneslab,43,1 +dsstoneslab,43,1 +doublessslab,43,1 +dsslab,43,1 +sandstonedoublehalfblock,43,1 +sandstonedhalfblock,43,1 +sstonedoublehalfblock,43,1 +sstonedhalfblock,43,1 +ssdoublehalfblock,43,1 +ssdhalfblock,43,1 +doublesandstonehalfblock,43,1 +dsandstonehalfblock,43,1 +doublesstonehalfblock,43,1 +dsstonehalfblock,43,1 +doublesshalfblock,43,1 +dsshalfblock,43,1 +woodenplankstonedoublehalfblock,43,2 +woodenplankstonedhalfblock,43,2 +woodplankstonedoublehalfblock,43,2 +woodplankstonedhalfblock,43,2 +wplankstonedoublehalfblock,43,2 +wplankstonedhalfblock,43,2 +plankstonedoublehalfblock,43,2 +planstonekdhalfblock,43,2 +woodenstonedoublehalfblock,43,2 +woodenstonedhalfblock,43,2 +woodstonedoublehalfblock,43,2 +woodstonedhalfblock,43,2 +wstonedoublehalfblock,43,2 +wstonedhalfblock,43,2 +doublewoodenplankstonehalfblock,43,2 +dwoodenplankstonehalfblock,43,2 +doublewoodplankstonehalfblock,43,2 +dwoodplankstonehalfblock,43,2 +doublewplankstonehalfblock,43,2 +dwplankstonehalfblock,43,2 +doubleplankstonehalfblock,43,2 +dplankstonehalfblock,43,2 +doublewoodenstonehalfblock,43,2 +dwoodenstonehalfblock,43,2 +doublewoodstonehalfblock,43,2 +dwoodstonehalfblock,43,2 +doublewstonehalfblock,43,2 +dwstonehalfblock,43,2 +woodenplankstonedoublestep,43,2 +woodenplankstonedstep,43,2 +woodplankstonedoublestep,43,2 +woodplankstonedstep,43,2 +wplankstonedoublestep,43,2 +wplankstonedstep,43,2 +plankstonedoublestep,43,2 +plankstonedstep,43,2 +woodenstonedoublestep,43,2 +woodenstonedstep,43,2 +woodstonedoublestep,43,2 +woodstonedstep,43,2 +wstonedoublestep,43,2 +wstonedstep,43,2 +doublewoodenplankstonestep,43,2 +dwoodenplankstonestep,43,2 +doublewoodplankstonestep,43,2 +dwoodplankstonestep,43,2 +doublewplankstonestep,43,2 +dwplankstonestep,43,2 +doubleplankstonestep,43,2 +dplankstonestep,43,2 +doublewoodenstonestep,43,2 +dwoodenstonestep,43,2 +doublewoodstonestep,43,2 +dwoodstonestep,43,2 +doublewstonestep,43,2 +dwstonestep,43,2 +woodenplankstonedoubleslab,43,2 +woodenplankstonedslab,43,2 +woodplankstonedoubleslab,43,2 +woodplankstonedslab,43,2 +wplankstonedoubleslab,43,2 +wplankstonedslab,43,2 +plankstonedoubleslab,43,2 +plankstonedslab,43,2 +woodenstonedoubleslab,43,2 +woodenstonedslab,43,2 +woodstonedoubleslab,43,2 +woodstonedslab,43,2 +wstonedoubleslab,43,2 +wstonedslab,43,2 +doublewoodenplankstoneslab,43,2 +dwoodenplankstoneslab,43,2 +doublewoodplankstoneslab,43,2 +dwoodplankstoneslab,43,2 +doublewplankstoneslab,43,2 +dwplankstoneslab,43,2 +doubleplankstoneslab,43,2 +dplankstoneslab,43,2 +doublewoodenstoneslab,43,2 +dwoodenstoneslab,43,2 +doublewoodstoneslab,43,2 +dwoodstoneslab,43,2 +doublewstoneslab,43,2 +dwstoneslab,43,2 +cobblestonedoublestep,43,3 +cobblestonedstep,43,3 +cobbledoublestep,43,3 +cobbledstep,43,3 +cstonedoublestep,43,3 +cstonedstep,43,3 +csdoublestep,43,3 +csdstep,43,3 +doublecobblestonestep,43,3 +dcobblestonestep,43,3 +doublecobblestep,43,3 +dcobblestep,43,3 +doublecstonestep,43,3 +dcstonestep,43,3 +doublecsstep,43,3 +dcsstep,43,3 +cobblestonedoubleslab,43,3 +cobblestonedslab,43,3 +cobbledoubleslab,43,3 +cobbledslab,43,3 +cstonedoubleslab,43,3 +cstonedslab,43,3 +csdoubleslab,43,3 +csdslab,43,3 +doublecobblestoneslab,43,3 +dcobblestoneslab,43,3 +doublecobbleslab,43,3 +dcobbleslab,43,3 +doublecstoneslab,43,3 +dcstoneslab,43,3 +doublecsslab,43,3 +dcsslab,43,3 +cobblestonedoublehalfblock,43,3 +cobblestonedhalfblock,43,3 +cobbledoublehalfblock,43,3 +cobbledhalfblock,43,3 +cstonedoublehalfblock,43,3 +cstonedhalfblock,43,3 +csdoublehalfblock,43,3 +csdhalfblock,43,3 +doublecobblestonehalfblock,43,3 +dcobblestonehalfblock,43,3 +doublecobblehalfblock,43,3 +dcobblehalfblock,43,3 +doublecstonehalfblock,43,3 +dcstonehalfblock,43,3 +doublecshalfblock,43,3 +dcshalfblock,43,3 +brickblockdoublestep,43,4 +brickblockdstep,43,4 +brickbdoublestep,43,4 +brickbdstep,43,4 +brickdoublestep,43,4 +brickdstep,43,4 +bdoublestep,43,4 +bdstep,43,4 +brickblockdoubleslab,43,4 +brickblockdslab,43,4 +brickbdoubleslab,43,4 +brickbdslab,43,4 +brickdoubleslab,43,4 +brickdslab,43,4 +bdoubleslab,43,4 +bdslab,43,4 +doublebrickblockstep,43,4 +dbrickblockstep,43,4 +doublebrickbstep,43,4 +dbrickbstep,43,4 +doublebrickstep,43,4 +dbrickstep,43,4 +doublebstep,43,4 +dbstep,43,4 +doublebrickblockslab,43,4 +dbrickblockslab,43,4 +doublebrickbslab,43,4 +dbrickbslab,43,4 +doublebrickslab,43,4 +dbrickslab,43,4 +doublebslab,43,4 +dbslab,43,4 +brickblockdoublehalfblock,43,4 +brickblockdhalfblock,43,4 +brickbdoublehalfblock,43,4 +brickbdhalfblock,43,4 +brickdoublehalfblock,43,4 +brickdhalfblock,43,4 +bdoublehalfblock,43,4 +bdhalfblock,43,4 +doublebrickblockhalfblock,43,4 +dbrickblockhalfblock,43,4 +doublebrickbhalfblock,43,4 +dbrickbhalfblock,43,4 +doublebrickhalfblock,43,4 +dbrickhalfblock,43,4 +doublebhalfblock,43,4 +dbhalfblock,43,4 +stonebrickdoublestep,43,5 +stonebrickdstep,43,5 +stonebdoublestep,43,5 +stonebdstep,43,5 +sbrickdoublestep,43,5 +sbrickdstep,43,5 +sbdoublestep,43,5 +sbdstep,43,5 +stonebrickdoubleslab,43,5 +stonebrickdslab,43,5 +stonebdoubleslab,43,5 +stonebdslab,43,5 +sbrickdoubleslab,43,5 +sbrickdslab,43,5 +sbdoubleslab,43,5 +sbdslab,43,5 +doublestonebrickstep,43,5 +dstonebrickstep,43,5 +doublestonebstep,43,5 +dstonebstep,43,5 +doublesbrickstep,43,5 +dsbrickstep,43,5 +doublesbstep,43,5 +dsbstep,43,5 +doublestonebrickslab,43,5 +dstonebrickslab,43,5 +doublestonebslab,43,5 +dstonebslab,43,5 +doublesbrickslab,43,5 +dsbrickdslab,43,5 +doublesbslab,43,5 +dsbslab,43,5 +stonebrickdoublehalfblock,43,5 +stonebrickdhalfblock,43,5 +stonebdoublehalfblock,43,5 +stonebdhalfblock,43,5 +sbrickdoublehalfblock,43,5 +sbrickdhalfblock,43,5 +sbdoublehalfblock,43,5 +sbdhalfblock,43,5 +doublestonebrickhalfblock,43,5 +dstonebrickhalfblock,43,5 +doublestonebhalfblock,43,5 +dstonebhalfblock,43,5 +doublesbrickhalfblock,43,5 +dsbrickhalfblock,43,5 +doublesbhalfblock,43,5 +dsbhalfblock,43,5 +netherbrickdoubleslab,43,6 +hellbrickdoubleslab,43,6 +nbrickdoubleslab,43,6 +hbrickdoubleslab,43,6 +netherdoubleslab,43,6 +helldoubleslab,43,6 +nbdoubleslab,43,6 +hbdoubleslab,43,6 +hdoubleslab,43,6 +ndoubleslab,43,6 +netherbrickdoublestep,43,6 +hellbrickdoublestep,43,6 +nbrickdoublestep,43,6 +hbrickdoublestep,43,6 +netherdoublestep,43,6 +helldoublestep,43,6 +nbdoublestep,43,6 +hbdoublestep,43,6 +ndoublestep,43,6 +hdoublestep,43,6 +netherbrickdoublehalfblock,43,6 +hellbrickdoublehalfblock,43,6 +nbrickdoublehalfblock,43,6 +hbrickdoublehalfblock,43,6 +netherdoublehalfblock,43,6 +helldoublehalfblock,43,6 +nbdoublehalfblock,43,6 +hbdoublehalfblock,43,6 +ndoublehalfblock,43,6 +hdoublehalfblock,43,6 +netherbrickdslab,43,6 +hellbrickdslab,43,6 +nbrickdslab,43,6 +hbrickdslab,43,6 +netherdslab,43,6 +helldslab,43,6 +nbdslab,43,6 +hbdslab,43,6 +hdslab,43,6 +ndslab,43,6 +netherbrickdstep,43,6 +hellbrickdstep,43,6 +nbrickdstep,43,6 +hbrickdstep,43,6 +netherdstep,43,6 +helldstep,43,6 +nbdstep,43,6 +hbdstep,43,6 +ndstep,43,6 +hdstep,43,6 +netherbrickdhalfblock,43,6 +hellbrickdhalfblock,43,6 +nbrickdhalfblock,43,6 +hbrickdhalfblock,43,6 +netherdhalfblock,43,6 +helldhalfblock,43,6 +nbdhalfblock,43,6 +hbdhalfblock,43,6 +ndhalfblock,43,6 +hdhalfblock,43,6 +doublenetherbrickslab,43,6 +doublehellbrickslab,43,6 +doublenbrickslab,43,6 +doublehbrickslab,43,6 +doublenetherslab,43,6 +doublehellslab,43,6 +doublenbslab,43,6 +doublehbslab,43,6 +doublehslab,43,6 +doublenslab,43,6 +doublenetherbrickstep,43,6 +doublehellbrickstep,43,6 +doublenbrickstep,43,6 +doublehbrickstep,43,6 +doublenetherstep,43,6 +doublehellstep,43,6 +doublenbstep,43,6 +doublehbstep,43,6 +doublenstep,43,6 +doublehstep,43,6 +doublenetherbrickhalfblock,43,6 +doublehellbrickhalfblock,43,6 +doublenbrickhalfblock,43,6 +doublehbrickhalfblock,43,6 +doublenetherhalfblock,43,6 +doublehellhalfblock,43,6 +doublenbhalfblock,43,6 +doublehbhalfblock,43,6 +doublenhalfblock,43,6 +doublehhalfblock,43,6 +dnetherbrickslab,43,6 +dhellbrickslab,43,6 +dnbrickslab,43,6 +dhbrickslab,43,6 +dnetherslab,43,6 +dhellslab,43,6 +dnbslab,43,6 +dhbslab,43,6 +dhslab,43,6 +dnslab,43,6 +dnetherbrickstep,43,6 +dhellbrickstep,43,6 +dnbrickstep,43,6 +dhbrickstep,43,6 +dnetherstep,43,6 +dhellstep,43,6 +dnbstep,43,6 +dhbstep,43,6 +dnstep,43,6 +dhstep,43,6 +dnetherbrickhalfblock,43,6 +dhellbrickhalfblock,43,6 +dnbrickhalfblock,43,6 +dhbrickhalfblock,43,6 +dnetherhalfblock,43,6 +dhellhalfblock,43,6 +dnbhalfblock,43,6 +dhbhalfblock,43,6 +dnhalfblock,43,6 +dhhalfblock,43,6 +netherquartzdoubleslab,43,7 +hellquartzdoubleslab,43,7 +deathquartzdoubleslab,43,7 +nquartzdoubleslab,43,7 +hquartzdoubleslab,43,7 +dquartzdoubleslab,43,7 +quartzdoubleslab,43,7 +nqdoubleslab,43,7 +hqdoubleslab,43,7 +dqdoubleslab,43,7 +qdoubleslab,43,7 +netherquartzdoublestep,43,7 +hellquartzdoublestep,43,7 +deathquartzdoublestep,43,7 +nquartzdoublestep,43,7 +hquartzdoublestep,43,7 +dquartzdoublestep,43,7 +quartzdoublestep,43,7 +nqdoublestep,43,7 +hqdoublestep,43,7 +dqdoublestep,43,7 +qdoublestep,43,7 +netherquartzdoublehalfblock,43,7 +hellquartzdoublehalfblock,43,7 +deathquartzdoublehalfblock,43,7 +nquartzdoublehalfblock,43,7 +hquartzdoublehalfblock,43,7 +dquartzdoublehalfblock,43,7 +quartzdoublehalfblock,43,7 +nqdoublehalfblock,43,7 +hqdoublehalfblock,43,7 +dqdoublehalfblock,43,7 +qdoublehalfblock,43,7 +netherquartzdslab,43,7 +hellquartzdslab,43,7 +deathquartzdslab,43,7 +nquartzdslab,43,7 +hquartzdslab,43,7 +dquartzdslab,43,7 +quartzdslab,43,7 +nqdslab,43,7 +hqdslab,43,7 +dqdslab,43,7 +qdslab,43,7 +netherquartzdstep,43,7 +hellquartzdstep,43,7 +deathquartzdstep,43,7 +nquartzdstep,43,7 +hquartzdstep,43,7 +dquartzdstep,43,7 +quartzdstep,43,7 +nqdstep,43,7 +hqdstep,43,7 +dqdstep,43,7 +qdstep,43,7 +netherquartzdhalfblock,43,7 +hellquartzdhalfblock,43,7 +deathquartzdhalfblock,43,7 +nquartzdhalfblock,43,7 +hquartzdhalfblock,43,7 +dquartzdhalfblock,43,7 +quartzdhalfblock,43,7 +nqdhalfblock,43,7 +hqdhalfblock,43,7 +dqdhalfblock,43,7 +qdhalfblock,43,7 +doublenetherquartzslab,43,7 +doublehellquartzslab,43,7 +doubledeathquartzslab,43,7 +doublenquartzslab,43,7 +doublehquartzslab,43,7 +doubledquartzslab,43,7 +doublequartzslab,43,7 +doublenqslab,43,7 +doublehqslab,43,7 +doubledqslab,43,7 +doubleqslab,43,7 +doublenetherquartzstep,43,7 +doublehellquartzstep,43,7 +doubledeathquartzstep,43,7 +doublenquartzstep,43,7 +doublehquartzstep,43,7 +doubledquartzstep,43,7 +doublequartzstep,43,7 +doublenqstep,43,7 +doublehqstep,43,7 +doubledqstep,43,7 +doubleqstep,43,7 +doublenetherquartzhalfblock,43,7 +doublehellquartzhalfblock,43,7 +doubledeathquartzhalfblock,43,7 +doublenquartzhalfblock,43,7 +doublehquartzhalfblock,43,7 +doubledquartzhalfblock,43,7 +doublequartzhalfblock,43,7 +doublenqhalfblock,43,7 +doublehqhalfblock,43,7 +doubledqhalfblock,43,7 +doubleqhalfblock,43,7 +dnetherquartzslab,43,7 +dhellquartzslab,43,7 +ddeathquartzslab,43,7 +dnquartzslab,43,7 +dhquartzslab,43,7 +ddquartzslab,43,7 +dnqslab,43,7 +dhqslab,43,7 +ddqslab,43,7 +dnetherquartzstep,43,7 +dhellquartzstep,43,7 +ddeathquartzstep,43,7 +dnquartzstep,43,7 +dhquartzstep,43,7 +ddquartzstep,43,7 +dnqstep,43,7 +dhqstep,43,7 +ddqstep,43,7 +dnetherquartzhalfblock,43,7 +dhellquartzhalfblock,43,7 +ddeathquartzhalfblock,43,7 +dnquartzhalfblock,43,7 +dhquartzhalfblock,43,7 +ddquartzhalfblock,43,7 +dnqhalfblock,43,7 +dhqhalfblock,43,7 +ddqhalfblock,43,7 +smoothstonedoubleslab,43,8 +smoothstonedoublestep,43,8 +smoothstonedoublehalfblock,43,8 +smoothstonedslab,43,8 +smoothstonedstep,43,8 +smoothstonedhalfblock,43,8 +doublesmoothstoneslab,43,8 +doublesmoothstonestep,43,8 +doublesmoothstonehalfblock,43,8 +dsmoothstoneslab,43,8 +dsmoothstonestep,43,8 +dsmoothstonehalfblock,43,8 +smoothsandstonedoubleslab,43,9 +ssandstonedoubleslab,43,9 +ssstonedoubleslab,43,9 +sssdoubleslab,43,9 +smoothsandstonedoublestep,43,9 +ssandstonedoublestep,43,9 +ssstonedoublestep,43,9 +sssdoublestep,43,9 +smoothsandstonedoublehalfblock,43,9 +ssandstonedoublehalfblock,43,9 +ssstonedoublehalfblock,43,9 +sssdoublehalfblock,43,9 +smoothsandstonedslab,43,9 +ssandstonedslab,43,9 +ssstonedslab,43,9 +sssdslab,43,9 +smoothsandstonedstep,43,9 +ssandstonedstep,43,9 +ssstonedstep,43,9 +sssdstep,43,9 +smoothsandstonedhalfblock,43,9 +ssandstonedhalfblock,43,9 +ssstonedhalfblock,43,9 +sssdhalfblock,43,9 +doublesmoothsandstoneslab,43,9 +doublessandstoneslab,43,9 +doublessstoneslab,43,9 +doublesssslab,43,9 +doublesmoothsandstonestep,43,9 +doublessandstonestep,43,9 +doublessstonestep,43,9 +doublesssstep,43,9 +doublesmoothsandstonehalfblock,43,9 +doublessandstonehalfblock,43,9 +doublessstonehalfblock,43,9 +doublessshalfblock,43,9 +dsmoothsandstoneslab,43,9 +dssandstoneslab,43,9 +dssstoneslab,43,9 +dsssslab,43,9 +dsmoothsandstonestep,43,9 +dssandstonestep,43,9 +dssstonestep,43,9 +dsssstep,43,9 +dsmoothsandstonehalfblock,43,9 +dssandstonehalfblock,43,9 +dssstonehalfblock,43,9 +dssshalfblock,43,9 +smoothstonestep,44,0 +stonestep,44,0 +sstep,44,0 +step,44,0 +smoothstoneslab,44,0 +stoneslab,44,0 +sslab,44,0 +slab,44,0 +smoothstonehalfblock,44,0 +stonehalfblock,44,0 +shalfblock,44,0 +halfblock,44,0 +sandstonestep,44,1 +sstonestep,44,1 +ssstep,44,1 +sandstoneslab,44,1 +sstoneslab,44,1 +ssslab,44,1 +sandstonehalfblock,44,1 +sstonehalfblock,44,1 +sshalfblock,44,1 +woodenplankstonestep,44,2 +woodplankstonestep,44,2 +wplankstonestep,44,2 +plankstonestep,44,2 +woodenstonestep,44,2 +woodstonestep,44,2 +wstonestep,44,2 +woodenplankstoneslab,44,2 +woodplankstoneslab,44,2 +wplankstoneslab,44,2 +plankstoneslab,44,2 +woodenstoneslab,44,2 +woodstoneslab,44,2 +wstoneslab,44,2 +woodenplankstonehalfblock,44,2 +woodplankstonehalfblock,44,2 +wplankstonehalfblock,44,2 +plankstonehalfblock,44,2 +woodenstonehalfblock,44,2 +woodstonehalfblock,44,2 +wstonehalfblock,44,2 +cobblestonestep,44,3 +cobblestep,44,3 +cstonestep,44,3 +csstep,44,3 +cobblestoneslab,44,3 +cobbleslab,44,3 +cstoneslab,44,3 +csslab,44,3 +cobblestonehalfblock,44,3 +cobblehalfblock,44,3 +cstonehalfblock,44,3 +cshalfblock,44,3 +brickblockstep,44,4 +brickbstep,44,4 +brickstep,44,4 +bstep,44,4 +brickblockslab,44,4 +brickbslab,44,4 +brickslab,44,4 +bslab,44,4 +brickblockhalfblock,44,4 +brickbhalfblock,44,4 +brickhalfblock,44,4 +bhalfblock,44,4 +stonebrickstep,44,5 +stonebstep,44,5 +sbrickstep,44,5 +sbstep,44,5 +stonebrickslab,44,5 +stonebslab,44,5 +sbrickslab,44,5 +sbslab,44,5 +stonebrickhalfblock,44,5 +stonebhalfblock,44,5 +sbrickhalfblock,44,5 +sbhalfblock,44,5 +hellbrickslab,44,6 +nbrickslab,44,6 +hbrickslab,44,6 +netherslab,44,6 +hellslab,44,6 +nbslab,44,6 +hbslab,44,6 +hslab,44,6 +nslab,44,6 +netherbrickstep,44,6 +hellbrickstep,44,6 +nbrickstep,44,6 +hbrickstep,44,6 +netherstep,44,6 +hellstep,44,6 +nbstep,44,6 +hbstep,44,6 +nstep,44,6 +hstep,44,6 +netherbrickhalfblock,44,6 +hellbrickhalfblock,44,6 +nbrickhalfblock,44,6 +hbrickhalfblock,44,6 +netherhalfblock,44,6 +hellhalfblock,44,6 +nbhalfblock,44,6 +hbhalfblock,44,6 +nhalfblock,44,6 +hhalfblock,44,6 +netherquartzslab,44,7 +hellquartzslab,44,7 +deathquartzslab,44,7 +nquartzslab,44,7 +hquartzslab,44,7 +dquartzslab,44,7 +quartzslab,44,7 +nqslab,44,7 +hqslab,44,7 +dqslab,44,7 +qslab,44,7 +netherquartzstep,44,7 +hellquartzstep,44,7 +deathquartzstep,44,7 +nquartzstep,44,7 +hquartzstep,44,7 +dquartzstep,44,7 +quartzstep,44,7 +nqstep,44,7 +hqstep,44,7 +dqstep,44,7 +qstep,44,7 +netherquartzhalfblock,44,7 +hellquartzhalfblock,44,7 +deathquartzhalfblock,44,7 +nquartzhalfblock,44,7 +hquartzhalfblock,44,7 +dquartzhalfblock,44,7 +quartzhalfblock,44,7 +nqhalfblock,44,7 +hqhalfblock,44,7 +dqhalfblock,44,7 +qhalfblock,44,7 +brickblock,45,0 +blockbrick,45,0 +bblock,45,0 +blockb,45,0 +tntblock,46,0 +blocktnt,46,0 +bombblock,46,0 +blockbomb,46,0 +dynamiteblock,46,0 +blockdynamite,46,0 +tnt,46,0 +bomb,46,0 +dynamite,46,0 +bookcase,47,0 +casebook,47,0 +bookshelf,47,0 +shelfbook,47,0 +bookblock,47,0 +blockbook,47,0 +mossycobblestone,48,0 +mosscobblestone,48,0 +mcobblestone,48,0 +mossycobble,48,0 +mosscobble,48,0 +mcobble,48,0 +mossstone,48,0 +mossystone,48,0 +mstone,48,0 +obsidian,49,0 +obsi,49,0 +obby,49,0 +torch,50,0 +burningstick,50,0 +burnstick,50,0 +fire,51,0 +flame,51,0 +flames,51,0 +mobspawner,52,0 +mobcage,52,0 +monsterspawner,52,0 +monstercage,52,0 +mspawner,52,0 +mcage,52,0 +spawner,52,0 +cage,52,0 +woodenstairs,53,0 +woodstairs,53,0 +wstairs,53,0 +woodenstair,53,0 +woodstair,53,0 +wstair,53,0 +chest,54,0 +container,54,0 +redstonewireblock,55,0 +rstonewireblock,55,0 +redswireblock,55,0 +redwireblock,55,0 +rswireblock,55,0 +rwireblock,55,0 +redstonewire,55,0 +rstonewire,55,0 +redswire,55,0 +redwire,55,0 +rswire,55,0 +rwire,55,0 +wire,55,0 +diamondore,56,0 +crystalore,56,0 +diamondo,56,0 +crystalo,56,0 +orediamond,56,0 +odiamond,56,0 +orecrystal,56,0 +ocrystal,56,0 +dore,56,0 +ored,56,0 +diamondblock,57,0 +blockdiamond,57,0 +crystalblock,57,0 +blockcrystal,57,0 +dblock,57,0 +blockd,57,0 +workbench,58,0 +craftingbench,58,0 +crafterbench,58,0 +craftbench,58,0 +worktable,58,0 +craftingtable,58,0 +craftertable,58,0 +crafttable,58,0 +wbench,58,0 +cbench,58,0 +crops,59,0 +crop,59,0 +soil,60,0 +furnace,61,0 +burningfurnace,62,0 +bfurnace,62,0 +signpost,63,0 +spost,63,0 +woodendoorhalf,64,0 +wooddoorhalf,64,0 +wdoorhalf,64,0 +woodendoorbottom,64,0 +wooddoorbottom,64,0 +wdoorbottom,64,0 +woodendoorblock,64,0 +wooddoorblock,64,0 +wdoorblock,64,0 +ladder,65,0 +minecarttrack,66,0 +minecartrails,66,0 +minecartrail,66,0 +mcarttrack,66,0 +mcartrails,66,0 +mcartrail,66,0 +mctrack,66,0 +mcrails,66,0 +mcrail,66,0 +track,66,0 +rails,66,0 +rail,66,0 +cobblestonestairs,67,0 +cstonestairs,67,0 +stonestairs,67,0 +cobblestairs,67,0 +csstairs,67,0 +sstairs,67,0 +cstairs,67,0 +cobblestonestair,67,0 +cstonestair,67,0 +stonestair,67,0 +cobblestair,67,0 +csstair,67,0 +sstair,67,0 +cstair,67,0 +wallsign,68,0 +wsign,68,0 +lever,69,0 +smoothstonepressureplate,70,0 +smoothstonepressplate,70,0 +smoothstonepplate,70,0 +smoothstoneplate,70,0 +sstonepressureplate,70,0 +sstonepressplate,70,0 +sstonepplate,70,0 +sstoneplate,70,0 +stonepressureplate,70,0 +stonepressplate,70,0 +stonepplate,70,0 +stoneplate,70,0 +spressureplate,70,0 +spressplate,70,0 +spplate,70,0 +splate,70,0 +irondoorhalf,71,0 +idoorhalf,71,0 +irondoorbottom,71,0 +idoorbottom,71,0 +irondoorblock,71,0 +idoorblock,71,0 +steeldoorhalf,71,0 +sdoorhalf,71,0 +steeldoorbottom,71,0 +sdoorbottom,71,0 +steeldoorblock,71,0 +sdoorblock,71,0 +woodenpressureplate,72,0 +woodenpressplate,72,0 +woodenpplate,72,0 +woodenplate,72,0 +woodpressureplate,72,0 +woodpressplate,72,0 +woodpplate,72,0 +woodplate,72,0 +wpressureplate,72,0 +wpressplate,72,0 +wpplate,72,0 +wplate,72,0 +redstoneore,73,0 +redsore,73,0 +redore,73,0 +rstoneore,73,0 +rsore,73,0 +rore,73,0 +oreredstone,73,0 +orereds,73,0 +orered,73,0 +orerstone,73,0 +orers,73,0 +orer,73,0 +glowingredstoneore,74,0 +glowredstoneore,74,0 +gredstoneore,74,0 +glowingrstoneore,74,0 +glowrstoneore,74,0 +grstoneore,74,0 +glowingredsore,74,0 +glowredsore,74,0 +gredsore,74,0 +glowingredore,74,0 +glowredore,74,0 +gredore,74,0 +glowingrsore,74,0 +glowrsore,74,0 +grsore,74,0 +grore,74,0 +oreglowingredstone,74,0 +oreglowredstone,74,0 +oregredstone,74,0 +oreglowingrstone,74,0 +oreglowrstone,74,0 +oregrstone,74,0 +oreglowingreds,74,0 +oreglowreds,74,0 +oregreds,74,0 +oreglowingred,74,0 +oreglowred,74,0 +oregred,74,0 +oreglowingrs,74,0 +oreglowrs,74,0 +oregrs,74,0 +oregr,74,0 +redstonetorchoff,75,0 +rstonetorchoff,75,0 +redstorchoff,75,0 +redtorchoff,75,0 +rstorchoff,75,0 +redstonetorchon,76,0 +redstonetorch,76,0 +rstonetorchon,76,0 +rstonetorch,76,0 +redstorchon,76,0 +redstorch,76,0 +redtorchon,76,0 +redtorch,76,0 +rstorchon,76,0 +rstorch,76,0 +smoothstonebutton,77,0 +sstonebutton,77,0 +stonebutton,77,0 +sbutton,77,0 +button,77,0 +snowcovering,78,0 +snowcover,78,0 +scover,78,0 +ice,79,0 +frozenwater,79,0 +waterfrozen,79,0 +freezewater,79,0 +waterfreeze,79,0 +snowblock,80,0 +blocksnow,80,0 +sblock,80,0 +blocks,80,0 +cactus,81,0 +cactuses,81,0 +cacti,81,0 +clayblock,82,0 +blockclay,82,0 +cblock,82,0 +blockc,82,0 +reedblock,83,0 +reedsblock,83,0 +sugarcaneblock,83,0 +scaneblock,83,0 +bambooblock,83,0 +blockreed,83,0 +blockreeds,83,0 +blocksugarcane,83,0 +blockscane,83,0 +blockbamboo,83,0 +jukebox,84,0 +jbox,84,0 +fence,85,0 +woodenfence,85,0 +woodfence,85,0 +wfence,85,0 +fencewooden,85,0 +fencewood,85,0 +fencew,85,0 +pumpkin,86,0 +netherrack,87,0 +netherrock,87,0 +netherstone,87,0 +hellstone,87,0 +nstone,87,0 +hstone,87,0 +soulsand,88,0 +slowsand,88,0 +slowmud,88,0 +ssand,88,0 +smud,88,0 +mud,88,0 +glowingstoneblock,89,0 +lightstoneblock,89,0 +glowstoneblock,89,0 +blockglowingstone,89,0 +blocklightstone,89,0 +blockglowstone,89,0 +glowingstoneb,89,0 +lightstoneb,89,0 +glowstoneb,89,0 +bglowingstone,89,0 +blightstone,89,0 +bglowstone,89,0 +glowingstone,89,0 +lightstone,89,0 +glowstone,89,0 +glowingblock,89,0 +lightblock,89,0 +glowblock,89,0 +lstone,89,0 +gstone,89,0 +portal,90,0 +jackolantern,91,0 +pumpkinlantern,91,0 +glowingpumpkin,91,0 +lightpumpkin,91,0 +jpumpkin,91,0 +plantren,91,0 +glowpumpkin,91,0 +gpumpkin,91,0 +lpumpkin,91,0 +cakeblock,92,0 +repeateroff,93,0 +repeatoff,93,0 +delayeroff,93,0 +delayoff,93,0 +dioderoff,93,0 +diodeoff,93,0 +repeaterblockoff,93,0 +repeatblockoff,93,0 +delayerblockoff,93,0 +delayblockoff,93,0 +dioderblockoff,93,0 +diodeblockoff,93,0 +repeateron,94,0 +repeaton,94,0 +delayeron,94,0 +delayon,94,0 +dioderon,94,0 +diodeon,94,0 +repeaterblockon,94,0 +repeatblockon,94,0 +delayerblockon,94,0 +delayblockon,94,0 +dioderblockon,94,0 +diodeblockon,94,0 +lockedchest,95,0 +lockchest,95,0 +jokechest,95,0 +aprilfoolschest,95,0 +aprilchest,95,0 +lootchest,95,0 +trapdoor,96,0 +doortrap,96,0 +hatch,96,0 +tdoor,96,0 +doort,96,0 +trapd,96,0 +dtrap,96,0 +silverfish,97,0 +monsteregg,97,0 +monstereggsmoothstone,97,0 +monstereggsstone,97,0 +meggsmoothstone,97,0 +meggsstone,97,0 +mesmoothstone,97,0 +messtone,97,0 +silverfishsmoothstone,97,0 +silverfishsstone,97,0 +silverfishstone,97,0 +sfishsmoothstone,97,0 +sfishsstone,97,0 +sfishstone,97,0 +fishsmoothstone,97,0 +fishsstone,97,0 +fishstone,97,0 +sfsmoothstone,97,0 +sfsstone,97,0 +sfstone,97,0 +trapsmoothstone,97,0 +trapsstone,97,0 +trapstone,97,0 +monstereggcobblestone,97,1 +monstereggcstone,97,1 +meggcobblestone,97,1 +meggcstone,97,1 +mecobblestone,97,1 +mecstone,97,1 +silverfishcobblestone,97,1 +silverfishcstone,97,1 +sfishcobblestone,97,1 +sfishcstone,97,1 +fishcobblestone,97,1 +fishcstone,97,1 +sfcobblestone,97,1 +sfcstone,97,1 +trapcobblestone,97,1 +trapcstone,97,1 +monstereggstonebrick,97,2 +monstereggsbrick,97,2 +meggstonebrick,97,2 +meggsbrick,97,2 +mestonebrick,97,2 +mesbrick,97,2 +silverfishstonebrick,97,2 +silverfishsbrick,97,2 +sfishstonebrick,97,2 +sfishsbrick,97,2 +fishstonebrick,97,2 +fishsbrick,97,2 +sfstonebrick,97,2 +sfsbrick,97,2 +trapstonebrick,97,2 +trapsbrick,97,2 +stonebrick,98,0 +stonebricks,98,0 +stonebrickblock,98,0 +stonebb,98,0 +sbrick,98,0 +mossystonebrick,98,1 +mossystonebricks,98,1 +mossystonebrickblock,98,1 +mossystonebb,98,1 +mossstonebrick,98,1 +mossstonebricks,98,1 +mossstonebrickblock,98,1 +mossstonebb,98,1 +mstonebrick,98,1 +mstonebricks,98,1 +mstonebrickblock,98,1 +mstonebb,98,1 +mosssbrick,98,1 +mosssbricks,98,1 +mosssbrickblock,98,1 +mosssbb,98,1 +msbrick,98,1 +msbricks,98,1 +msbrickblock,98,1 +crackedstone,98,2 +crackedstonebrick,98,2 +crackedstonebricks,98,2 +crackedstonebrickblock,98,2 +crackedstonebb,98,2 +crackstonebrick,98,2 +crackstonebricks,98,2 +crackstonebrickblock,98,2 +crackstonebb,98,2 +crstonebrick,98,2 +crstonebricks,98,2 +crstonebrickblock,98,2 +crstonebb,98,2 +cracksbrick,98,2 +cracksbricks,98,2 +cracksbrickblock,98,2 +cracksbb,98,2 +crsbrick,98,2 +crsbricks,98,2 +crsbrickblock,98,2 +circlestone,98,3 +circlestonebrick,98,3 +circlestonebricks,98,3 +circlestonebrickblock,98,3 +circlestonebb,98,3 +cistonebrick,98,3 +cistonebricks,98,3 +cistonebrickblock,98,3 +cistonebb,98,3 +circlesbrick,98,3 +circlesbricks,98,3 +circlesbrickblock,98,3 +circlesbb,98,3 +cisbrick,98,3 +cisbricks,98,3 +cisbrickblock,98,3 +giantredmushroom,99,0 +hugeredmushroom,99,0 +bigredmushroom,99,0 +gredmushroom,99,0 +hredmushroom,99,0 +bredmushroom,99,0 +giantrmushroom,99,0 +hugermushroom,99,0 +bigrmushroom,99,0 +grmushroom,99,0 +hrmushroom,99,0 +brmushroom,99,0 +giantbrownmushroom,100,0 +hugebrownmushroom,100,0 +bigbrownmushroom,100,0 +gbrownmushroom,100,0 +hbrownmushroom,100,0 +bbrownmushroom,100,0 +giantbmushroom,100,0 +hugebmushroom,100,0 +bigbmushroom,100,0 +gbmushroom,100,0 +hbmushroom,100,0 +bbmushroom,100,0 +ironbars,101,0 +ironbarsb,101,0 +ironbarsblock,101,0 +ironfence,101,0 +metalbars,101,0 +metalbarsb,101,0 +metalbarsblock,101,0 +metalfence,101,0 +jailbars,101,0 +jailbarsb,101,0 +jailbarsblock,101,0 +jailfence,101,0 +mbars,101,0 +mbarsb,101,0 +mbarsblock,101,0 +mfence,101,0 +jbars,101,0 +jbarsb,101,0 +jbarsblock,101,0 +jfence,101,0 +ibars,101,0 +ibarsb,101,0 +ibarsblock,101,0 +ifence,101,0 +glasspane,102,0 +glassp,102,0 +paneglass,102,0 +pglass,102,0 +flatglass,102,0 +fglass,102,0 +skinnyglass,102,0 +sglass,102,0 +glassflat,102,0 +glassf,102,0 +glassskinny,102,0 +glasss,102,0 +melon,360,0 +watermelon,360,0 +greenmelon,360,0 +melongreen,360,0 +melonblock,103,0 +watermelonblock,103,0 +greenmelonblock,103,0 +pumpkinstem,104,0 +stempumpkin,104,0 +pumpstem,104,0 +stempump,104,0 +melonstem,105,0 +watermelonstem,105,0 +greenmelonstem,105,0 +stemmelon,105,0 +stemwatermelon,105,0 +stemgreenmelon,105,0 +vines,106,0 +vine,106,0 +greenvines,106,0 +greenvine,106,0 +gardenvines,106,0 +gardenvine,106,0 +vinesgreen,106,0 +vinegreen,106,0 +vinesgarden,106,0 +vinegarden,106,0 +vinesg,106,0 +vineg,106,0 +gvines,106,0 +gvine,106,0 +wfencegate,107,0 +woodfencegate,107,0 +woodenfencegate,107,0 +woodengate,107,0 +woodgate,107,0 +wgate,107,0 +gate,107,0 +gardengate,107,0 +ggate,107,0 +fencegate,107,0 +fencegatewooden,107,0 +fencegatewood,107,0 +brickstairs,108,0 +redbrickstairs,108,0 +redbstairs,108,0 +rbrickstairs,108,0 +bstairs,108,0 +redstairs,108,0 +brickstair,108,0 +redbrickstair,108,0 +redbstair,108,0 +rbrickstair,108,0 +bstair,108,0 +redstair,108,0 +stonebrickstairs,109,0 +stonebstairs,109,0 +sbstairs,109,0 +cementbrickstairs,109,0 +cementstairs,109,0 +cementbstairs,109,0 +cbstairs,109,0 +greybrickstairs,109,0 +greybstairs,109,0 +greystairs,109,0 +purplegrass,110,0 +pinkgrass,110,0 +mycel,110,0 +mycelium,110,0 +swampgrass,110,0 +sgrass,110,0 +mushroomgrass,110,0 +mushgrass,110,0 +waterlily,111,0 +lilypad,111,0 +lily,111,0 +swamppad,111,0 +lpad,111,0 +wlily,111,0 +netherbrickblock,112,0 +nbrickblock,112,0 +hellbrickblock,112,0 +deathbrickblock,112,0 +dbrickblock,112,0 +hbrickblock,112,0 +blocknetherbrick,112,0 +blocknbrick,112,0 +blockhellbrick,112,0 +blockdeathbrick,112,0 +blockdbrick,112,0 +blockhbrick,112,0 +netherbrickfence,113,0 +hellbrickfence,113,0 +nbrickfence,113,0 +hbrickfence,113,0 +netherbfence,113,0 +hellbfence,113,0 +netherfence,113,0 +hellfence,113,0 +nbfence,113,0 +hbfence,113,0 +nfence,113,0 +hfence,113,0 +netherbrickstairs,114,0 +hellbrickstairs,114,0 +nbrickstairs,114,0 +hbrickstairs,114,0 +netherbstairs,114,0 +hellbstairs,114,0 +netherstairs,114,0 +hellstairs,114,0 +nbstairs,114,0 +hbstairs,114,0 +nstairs,114,0 +hstairs,114,0 +netherbrickstair,114,0 +hellbrickstair,114,0 +nbrickstair,114,0 +hbrickstair,114,0 +netherbstair,114,0 +hellbstair,114,0 +netherstair,114,0 +hellstair,114,0 +nbstair,114,0 +hbstair,114,0 +nstair,114,0 +hstair,114,0 +netherwarts,115,0 +netherwart,115,0 +netherplant,115,0 +nethercrop,115,0 +hellwarts,115,0 +hellwart,115,0 +hellplant,115,0 +hellcrop,115,0 +deathwarts,115,0 +deathwart,115,0 +deathplant,115,0 +deathcrop,115,0 +nwarts,115,0 +nwart,115,0 +ncrop,115,0 +nplant,115,0 +hwarts,115,0 +hwart,115,0 +hplant,115,0 +hcrop,115,0 +dwarts,115,0 +dwart,115,0 +dplant,115,0 +dcrop,115,0 +enchantmenttable,116,0 +enchantingtable,116,0 +enchanttable,116,0 +etable,116,0 +magicaltable,116,0 +magictable,116,0 +mtable,116,0 +enchantmentdesk,116,0 +enchantingdesk,116,0 +enchantdesk,116,0 +edesk,116,0 +magicaldesk,116,0 +magicdesk,116,0 +mdesk,116,0 +booktable,116,0 +bookdesk,116,0 +btable,116,0 +bdesk,116,0 +brewingstandblock,117,0 +brewerblock,117,0 +potionstandblock,117,0 +potionbrewerblock,117,0 +pstandblock,117,0 +bstandblock,117,0 +pbrewerblock,117,0 +cauldron,118,0 +steelcauldron,118,0 +ironcauldron,118,0 +icauldron,118,0 +scauldron,118,0 +potioncauldron,118,0 +pcauldron,118,0 +enderportal,119,0 +endergoo,119,0 +endgoo,119,0 +endportal,119,0 +egoo,119,0 +eportal,119,0 +enderportalframe,120,0 +endportalframe,120,0 +endgooframe,120,0 +endergooframe,120,0 +egooframe,120,0 +eportalframe,120,0 +enderframe,120,0 +endframe,120,0 +enderstone,121,0 +endstone,121,0 +endrock,121,0 +enderrock,121,0 +erock,121,0 +estone,121,0 +enderdragonegg,122,0 +endegg,122,0 +dragonegg,122,0 +degg,122,0 +bossegg,122,0 +begg,122,0 +redstonelamp,123,0 +redstonelampoff,123,0 +redlamp,123,0 +redlampoff,123,0 +rslamp,123,0 +rslampoff,123,0 +redstonelampon,124,0 +redlampon,124,0 +rslampon,124,0 +woodenplankdoublehalfblock,125,0 +woodenplankdhalfblock,125,0 +woodplankdoublehalfblock,125,0 +woodplankdhalfblock,125,0 +wplankdoublehalfblock,125,0 +wplankdhalfblock,125,0 +plankdoublehalfblock,125,0 +plankdhalfblock,125,0 +woodendoublehalfblock,125,0 +woodendhalfblock,125,0 +wooddoublehalfblock,125,0 +wooddhalfblock,125,0 +wdoublehalfblock,125,0 +wdhalfblock,125,0 +doublewoodenplankhalfblock,125,0 +dwoodenplankhalfblock,125,0 +doublewoodplankhalfblock,125,0 +dwoodplankhalfblock,125,0 +doublewplankhalfblock,125,0 +dwplankhalfblock,125,0 +doubleplankhalfblock,125,0 +dplankhalfblock,125,0 +doublewoodenhalfblock,125,0 +dwoodenhalfblock,125,0 +doublewoodhalfblock,125,0 +dwoodhalfblock,125,0 +doublewhalfblock,125,0 +dwhalfblock,125,0 +woodenplankdoublestep,125,0 +woodenplankdstep,125,0 +woodplankdoublestep,125,0 +woodplankdstep,125,0 +wplankdoublestep,125,0 +wplankdstep,125,0 +plankdoublestep,125,0 +plankdstep,125,0 +woodendoublestep,125,0 +woodendstep,125,0 +wooddoublestep,125,0 +wooddstep,125,0 +wdoublestep,125,0 +wdstep,125,0 +doublewoodenplankstep,125,0 +dwoodenplankstep,125,0 +doublewoodplankstep,125,0 +dwoodplankstep,125,0 +doublewplankstep,125,0 +dwplankstep,125,0 +doubleplankstep,125,0 +dplankstep,125,0 +doublewoodenstep,125,0 +dwoodenstep,125,0 +doublewoodstep,125,0 +dwoodstep,125,0 +doublewstep,125,0 +dwstep,125,0 +woodenplankdoubleslab,125,0 +woodenplankdslab,125,0 +woodplankdoubleslab,125,0 +woodplankdslab,125,0 +wplankdoubleslab,125,0 +wplankdslab,125,0 +plankdoubleslab,125,0 +plankdslab,125,0 +woodendoubleslab,125,0 +woodendslab,125,0 +wooddoubleslab,125,0 +wooddslab,125,0 +wdoubleslab,125,0 +wdslab,125,0 +doublewoodenplankslab,125,0 +dwoodenplankslab,125,0 +doublewoodplankslab,125,0 +dwoodplankslab,125,0 +doublewplankslab,125,0 +dwplankslab,125,0 +doubleplankslab,125,0 +dplankslab,125,0 +doublewoodenslab,125,0 +dwoodenslab,125,0 +doublewoodslab,125,0 +dwoodslab,125,0 +doublewslab,125,0 +dwslab,125,0 +oakwoodenplankdoublehalfblock,125,0 +oakwoodenplankdhalfblock,125,0 +oakwoodplankdoublehalfblock,125,0 +oakwoodplankdhalfblock,125,0 +oakwplankdoublehalfblock,125,0 +oakwplankdhalfblock,125,0 +oakplankdoublehalfblock,125,0 +oakplankdhalfblock,125,0 +oakwoodendoublehalfblock,125,0 +oakwoodendhalfblock,125,0 +oakwooddoublehalfblock,125,0 +oakwooddhalfblock,125,0 +oakwdoublehalfblock,125,0 +oakwdhalfblock,125,0 +oakdoublewoodenplankhalfblock,125,0 +oakdwoodenplankhalfblock,125,0 +oakdoublewoodplankhalfblock,125,0 +oakdwoodplankhalfblock,125,0 +oakdoublewplankhalfblock,125,0 +oakdwplankhalfblock,125,0 +oakdoubleplankhalfblock,125,0 +oakdplankhalfblock,125,0 +oakdoublewoodenhalfblock,125,0 +oakdwoodenhalfblock,125,0 +oakdoublewoodhalfblock,125,0 +oakdwoodhalfblock,125,0 +oakdoublewhalfblock,125,0 +oakdwhalfblock,125,0 +oakdoublehalfblock,125,0 +oakdhalfblock,125,0 +oakwoodenplankdoublestep,125,0 +oakwoodenplankdstep,125,0 +oakwoodplankdoublestep,125,0 +oakwoodplankdstep,125,0 +oakwplankdoublestep,125,0 +oakwplankdstep,125,0 +oakplankdoublestep,125,0 +oakplankdstep,125,0 +oakwoodendoublestep,125,0 +oakwoodendstep,125,0 +oakwooddoublestep,125,0 +oakwooddstep,125,0 +oakwdoublestep,125,0 +oakwdstep,125,0 +oakdoublewoodenplankstep,125,0 +oakdwoodenplankstep,125,0 +oakdoublewoodplankstep,125,0 +oakdwoodplankstep,125,0 +oakdoublewplankstep,125,0 +oakdwplankstep,125,0 +oakdoubleplankstep,125,0 +oakdplankstep,125,0 +oakdoublewoodenstep,125,0 +oakdwoodenstep,125,0 +oakdoublewoodstep,125,0 +oakdwoodstep,125,0 +oakdoublewstep,125,0 +oakdwstep,125,0 +oakdoublestep,125,0 +oakdstep,125,0 +oakwoodenplankdoubleslab,125,0 +oakwoodenplankdslab,125,0 +oakwoodplankdoubleslab,125,0 +oakwoodplankdslab,125,0 +oakwplankdoubleslab,125,0 +oakwplankdslab,125,0 +oakplankdoubleslab,125,0 +oakplankdslab,125,0 +oakwoodendoubleslab,125,0 +oakwoodendslab,125,0 +oakwooddoubleslab,125,0 +oakwooddslab,125,0 +oakwdoubleslab,125,0 +oakwdslab,125,0 +oakdoublewoodenplankslab,125,0 +oakdwoodenplankslab,125,0 +oakdoublewoodplankslab,125,0 +oakdwoodplankslab,125,0 +oakdoublewplankslab,125,0 +oakdwplankslab,125,0 +oakdoubleplankslab,125,0 +oakdplankslab,125,0 +oakdoublewoodenslab,125,0 +oakdwoodenslab,125,0 +oakdoublewoodslab,125,0 +oakdwoodslab,125,0 +oakdoublewslab,125,0 +oakdwslab,125,0 +oakdoubleslab,125,0 +oakdslab,125,0 +sprucewoodenplankdoublehalfblock,125,1 +sprucewoodenplankdhalfblock,125,1 +sprucewoodplankdoublehalfblock,125,1 +sprucewoodplankdhalfblock,125,1 +sprucewplankdoublehalfblock,125,1 +sprucewplankdhalfblock,125,1 +spruceplankdoublehalfblock,125,1 +spruceplankdhalfblock,125,1 +sprucewoodendoublehalfblock,125,1 +sprucewoodendhalfblock,125,1 +sprucewooddoublehalfblock,125,1 +sprucewooddhalfblock,125,1 +sprucewdoublehalfblock,125,1 +sprucewdhalfblock,125,1 +sprucedoublewoodenplankhalfblock,125,1 +sprucedwoodenplankhalfblock,125,1 +sprucedoublewoodplankhalfblock,125,1 +sprucedwoodplankhalfblock,125,1 +sprucedoublewplankhalfblock,125,1 +sprucedwplankhalfblock,125,1 +sprucedoubleplankhalfblock,125,1 +sprucedplankhalfblock,125,1 +sprucedoublewoodenhalfblock,125,1 +sprucedwoodenhalfblock,125,1 +sprucedoublewoodhalfblock,125,1 +sprucedwoodhalfblock,125,1 +sprucedoublewhalfblock,125,1 +sprucedwhalfblock,125,1 +sprucedoublehalfblock,125,1 +sprucedhalfblock,125,1 +sprucewoodenplankdoublestep,125,1 +sprucewoodenplankdstep,125,1 +sprucewoodplankdoublestep,125,1 +sprucewoodplankdstep,125,1 +sprucewplankdoublestep,125,1 +sprucewplankdstep,125,1 +spruceplankdoublestep,125,1 +spruceplankdstep,125,1 +sprucewoodendoublestep,125,1 +sprucewoodendstep,125,1 +sprucewooddoublestep,125,1 +sprucewooddstep,125,1 +sprucewdoublestep,125,1 +sprucewdstep,125,1 +sprucedoublewoodenplankstep,125,1 +sprucedwoodenplankstep,125,1 +sprucedoublewoodplankstep,125,1 +sprucedwoodplankstep,125,1 +sprucedoublewplankstep,125,1 +sprucedwplankstep,125,1 +sprucedoubleplankstep,125,1 +sprucedplankstep,125,1 +sprucedoublewoodenstep,125,1 +sprucedwoodenstep,125,1 +sprucedoublewoodstep,125,1 +sprucedwoodstep,125,1 +sprucedoublewstep,125,1 +sprucedwstep,125,1 +sprucedoublestep,125,1 +sprucedstep,125,1 +sprucewoodenplankdoubleslab,125,1 +sprucewoodenplankdslab,125,1 +sprucewoodplankdoubleslab,125,1 +sprucewoodplankdslab,125,1 +sprucewplankdoubleslab,125,1 +sprucewplankdslab,125,1 +spruceplankdoubleslab,125,1 +spruceplankdslab,125,1 +sprucewoodendoubleslab,125,1 +sprucewoodendslab,125,1 +sprucewooddoubleslab,125,1 +sprucewooddslab,125,1 +sprucewdoubleslab,125,1 +sprucewdslab,125,1 +sprucedoublewoodenplankslab,125,1 +sprucedwoodenplankslab,125,1 +sprucedoublewoodplankslab,125,1 +sprucedwoodplankslab,125,1 +sprucedoublewplankslab,125,1 +sprucedwplankslab,125,1 +sprucedoubleplankslab,125,1 +sprucedplankslab,125,1 +sprucedoublewoodenslab,125,1 +sprucedwoodenslab,125,1 +sprucedoublewoodslab,125,1 +sprucedwoodslab,125,1 +sprucedoublewslab,125,1 +sprucedwslab,125,1 +sprucedoubleslab,125,1 +sprucedslab,125,1 +darkwoodenplankdoublehalfblock,125,1 +darkwoodenplankdhalfblock,125,1 +darkwoodplankdoublehalfblock,125,1 +darkwoodplankdhalfblock,125,1 +darkwplankdoublehalfblock,125,1 +darkwplankdhalfblock,125,1 +darkplankdoublehalfblock,125,1 +darkplankdhalfblock,125,1 +darkwoodendoublehalfblock,125,1 +darkwoodendhalfblock,125,1 +darkwooddoublehalfblock,125,1 +darkwooddhalfblock,125,1 +darkwdoublehalfblock,125,1 +darkwdhalfblock,125,1 +darkdoublewoodenplankhalfblock,125,1 +darkdwoodenplankhalfblock,125,1 +darkdoublewoodplankhalfblock,125,1 +darkdwoodplankhalfblock,125,1 +darkdoublewplankhalfblock,125,1 +darkdwplankhalfblock,125,1 +darkdoubleplankhalfblock,125,1 +darkdplankhalfblock,125,1 +darkdoublewoodenhalfblock,125,1 +darkdwoodenhalfblock,125,1 +darkdoublewoodhalfblock,125,1 +darkdwoodhalfblock,125,1 +darkdoublewhalfblock,125,1 +darkdwhalfblock,125,1 +darkdoublehalfblock,125,1 +darkdhalfblock,125,1 +darkwoodenplankdoublestep,125,1 +darkwoodenplankdstep,125,1 +darkwoodplankdoublestep,125,1 +darkwoodplankdstep,125,1 +darkwplankdoublestep,125,1 +darkwplankdstep,125,1 +darkplankdoublestep,125,1 +darkplankdstep,125,1 +darkwoodendoublestep,125,1 +darkwoodendstep,125,1 +darkwooddoublestep,125,1 +darkwooddstep,125,1 +darkwdoublestep,125,1 +darkwdstep,125,1 +darkdoublewoodenplankstep,125,1 +darkdwoodenplankstep,125,1 +darkdoublewoodplankstep,125,1 +darkdwoodplankstep,125,1 +darkdoublewplankstep,125,1 +darkdwplankstep,125,1 +darkdoubleplankstep,125,1 +darkdplankstep,125,1 +darkdoublewoodenstep,125,1 +darkdwoodenstep,125,1 +darkdoublewoodstep,125,1 +darkdwoodstep,125,1 +darkdoublewstep,125,1 +darkdwstep,125,1 +darkdoublestep,125,1 +darkdstep,125,1 +darkwoodenplankdoubleslab,125,1 +darkwoodenplankdslab,125,1 +darkwoodplankdoubleslab,125,1 +darkwoodplankdslab,125,1 +darkwplankdoubleslab,125,1 +darkwplankdslab,125,1 +darkplankdoubleslab,125,1 +darkplankdslab,125,1 +darkwoodendoubleslab,125,1 +darkwoodendslab,125,1 +darkwooddoubleslab,125,1 +darkwooddslab,125,1 +darkwdoubleslab,125,1 +darkwdslab,125,1 +darkdoublewoodenplankslab,125,1 +darkdwoodenplankslab,125,1 +darkdoublewoodplankslab,125,1 +darkdwoodplankslab,125,1 +darkdoublewplankslab,125,1 +darkdwplankslab,125,1 +darkdoubleplankslab,125,1 +darkdplankslab,125,1 +darkdoublewoodenslab,125,1 +darkdwoodenslab,125,1 +darkdoublewoodslab,125,1 +darkdwoodslab,125,1 +darkdoublewslab,125,1 +darkdwslab,125,1 +darkdoubleslab,125,1 +darkdslab,125,1 +birchwoodenplankdoublehalfblock,125,2 +birchwoodenplankdhalfblock,125,2 +birchwoodplankdoublehalfblock,125,2 +birchwoodplankdhalfblock,125,2 +birchwplankdoublehalfblock,125,2 +birchwplankdhalfblock,125,2 +birchplankdoublehalfblock,125,2 +birchplankdhalfblock,125,2 +birchwoodendoublehalfblock,125,2 +birchwoodendhalfblock,125,2 +birchwooddoublehalfblock,125,2 +birchwooddhalfblock,125,2 +birchwdoublehalfblock,125,2 +birchwdhalfblock,125,2 +birchdoublewoodenplankhalfblock,125,2 +birchdwoodenplankhalfblock,125,2 +birchdoublewoodplankhalfblock,125,2 +birchdwoodplankhalfblock,125,2 +birchdoublewplankhalfblock,125,2 +birchdwplankhalfblock,125,2 +birchdoubleplankhalfblock,125,2 +birchdplankhalfblock,125,2 +birchdoublewoodenhalfblock,125,2 +birchdwoodenhalfblock,125,2 +birchdoublewoodhalfblock,125,2 +birchdwoodhalfblock,125,2 +birchdoublewhalfblock,125,2 +birchdwhalfblock,125,2 +birchdoublehalfblock,125,2 +birchdhalfblock,125,2 +birchwoodenplankdoublestep,125,2 +birchwoodenplankdstep,125,2 +birchwoodplankdoublestep,125,2 +birchwoodplankdstep,125,2 +birchwplankdoublestep,125,2 +birchwplankdstep,125,2 +birchplankdoublestep,125,2 +birchplankdstep,125,2 +birchwoodendoublestep,125,2 +birchwoodendstep,125,2 +birchwooddoublestep,125,2 +birchwooddstep,125,2 +birchwdoublestep,125,2 +birchwdstep,125,2 +birchdoublewoodenplankstep,125,2 +birchdwoodenplankstep,125,2 +birchdoublewoodplankstep,125,2 +birchdwoodplankstep,125,2 +birchdoublewplankstep,125,2 +birchdwplankstep,125,2 +birchdoubleplankstep,125,2 +birchdplankstep,125,2 +birchdoublewoodenstep,125,2 +birchdwoodenstep,125,2 +birchdoublewoodstep,125,2 +birchdwoodstep,125,2 +birchdoublewstep,125,2 +birchdwstep,125,2 +birchdoublestep,125,2 +birchdstep,125,2 +birchwoodenplankdoubleslab,125,2 +birchwoodenplankdslab,125,2 +birchwoodplankdoubleslab,125,2 +birchwoodplankdslab,125,2 +birchwplankdoubleslab,125,2 +birchwplankdslab,125,2 +birchplankdoubleslab,125,2 +birchplankdslab,125,2 +birchwoodendoubleslab,125,2 +birchwoodendslab,125,2 +birchwooddoubleslab,125,2 +birchwooddslab,125,2 +birchwdoubleslab,125,2 +birchwdslab,125,2 +birchdoublewoodenplankslab,125,2 +birchdwoodenplankslab,125,2 +birchdoublewoodplankslab,125,2 +birchdwoodplankslab,125,2 +birchdoublewplankslab,125,2 +birchdwplankslab,125,2 +birchdoubleplankslab,125,2 +birchdplankslab,125,2 +birchdoublewoodenslab,125,2 +birchdwoodenslab,125,2 +birchdoublewoodslab,125,2 +birchdwoodslab,125,2 +birchdoublewslab,125,2 +birchdwslab,125,2 +birchdoubleslab,125,2 +birchdslab,125,2 +lightwoodenplankdoublehalfblock,125,2 +lightwoodenplankdhalfblock,125,2 +lightwoodplankdoublehalfblock,125,2 +lightwoodplankdhalfblock,125,2 +lightwplankdoublehalfblock,125,2 +lightwplankdhalfblock,125,2 +lightplankdoublehalfblock,125,2 +lightplankdhalfblock,125,2 +lightwoodendoublehalfblock,125,2 +lightwoodendhalfblock,125,2 +lightwooddoublehalfblock,125,2 +lightwooddhalfblock,125,2 +lightwdoublehalfblock,125,2 +lightwdhalfblock,125,2 +lightdoublewoodenplankhalfblock,125,2 +lightdwoodenplankhalfblock,125,2 +lightdoublewoodplankhalfblock,125,2 +lightdwoodplankhalfblock,125,2 +lightdoublewplankhalfblock,125,2 +lightdwplankhalfblock,125,2 +lightdoubleplankhalfblock,125,2 +lightdplankhalfblock,125,2 +lightdoublewoodenhalfblock,125,2 +lightdwoodenhalfblock,125,2 +lightdoublewoodhalfblock,125,2 +lightdwoodhalfblock,125,2 +lightdoublewhalfblock,125,2 +lightdwhalfblock,125,2 +lightdoublehalfblock,125,2 +lightdhalfblock,125,2 +lightwoodenplankdoublestep,125,2 +lightwoodenplankdstep,125,2 +lightwoodplankdoublestep,125,2 +lightwoodplankdstep,125,2 +lightwplankdoublestep,125,2 +lightwplankdstep,125,2 +lightplankdoublestep,125,2 +lightplankdstep,125,2 +lightwoodendoublestep,125,2 +lightwoodendstep,125,2 +lightwooddoublestep,125,2 +lightwooddstep,125,2 +lightwdoublestep,125,2 +lightwdstep,125,2 +lightdoublewoodenplankstep,125,2 +lightdwoodenplankstep,125,2 +lightdoublewoodplankstep,125,2 +lightdwoodplankstep,125,2 +lightdoublewplankstep,125,2 +lightdwplankstep,125,2 +lightdoubleplankstep,125,2 +lightdplankstep,125,2 +lightdoublewoodenstep,125,2 +lightdwoodenstep,125,2 +lightdoublewoodstep,125,2 +lightdwoodstep,125,2 +lightdoublewstep,125,2 +lightdwstep,125,2 +lightdoublestep,125,2 +lightdstep,125,2 +lightwoodenplankdoubleslab,125,2 +lightwoodenplankdslab,125,2 +lightwoodplankdoubleslab,125,2 +lightwoodplankdslab,125,2 +lightwplankdoubleslab,125,2 +lightwplankdslab,125,2 +lightplankdoubleslab,125,2 +lightplankdslab,125,2 +lightwoodendoubleslab,125,2 +lightwoodendslab,125,2 +lightwooddoubleslab,125,2 +lightwooddslab,125,2 +lightwdoubleslab,125,2 +lightwdslab,125,2 +lightdoublewoodenplankslab,125,2 +lightdwoodenplankslab,125,2 +lightdoublewoodplankslab,125,2 +lightdwoodplankslab,125,2 +lightdoublewplankslab,125,2 +lightdwplankslab,125,2 +lightdoubleplankslab,125,2 +lightdplankslab,125,2 +lightdoublewoodenslab,125,2 +lightdwoodenslab,125,2 +lightdoublewoodslab,125,2 +lightdwoodslab,125,2 +lightdoublewslab,125,2 +lightdwslab,125,2 +lightdoubleslab,125,2 +lightdslab,125,2 +junglewoodenplankdoublehalfblock,125,3 +junglewoodenplankdhalfblock,125,3 +junglewoodplankdoublehalfblock,125,3 +junglewoodplankdhalfblock,125,3 +junglewplankdoublehalfblock,125,3 +junglewplankdhalfblock,125,3 +jungleplankdoublehalfblock,125,3 +jungleplankdhalfblock,125,3 +junglewoodendoublehalfblock,125,3 +junglewoodendhalfblock,125,3 +junglewooddoublehalfblock,125,3 +junglewooddhalfblock,125,3 +junglewdoublehalfblock,125,3 +junglewdhalfblock,125,3 +jungledoublewoodenplankhalfblock,125,3 +jungledwoodenplankhalfblock,125,3 +jungledoublewoodplankhalfblock,125,3 +jungledwoodplankhalfblock,125,3 +jungledoublewplankhalfblock,125,3 +jungledwplankhalfblock,125,3 +jungledoubleplankhalfblock,125,3 +jungledplankhalfblock,125,3 +jungledoublewoodenhalfblock,125,3 +jungledwoodenhalfblock,125,3 +jungledoublewoodhalfblock,125,3 +jungledwoodhalfblock,125,3 +jungledoublewhalfblock,125,3 +jungledwhalfblock,125,3 +jungledoublehalfblock,125,3 +jungledhalfblock,125,3 +junglewoodenplankdoublestep,125,3 +junglewoodenplankdstep,125,3 +junglewoodplankdoublestep,125,3 +junglewoodplankdstep,125,3 +junglewplankdoublestep,125,3 +junglewplankdstep,125,3 +jungleplankdoublestep,125,3 +jungleplankdstep,125,3 +junglewoodendoublestep,125,3 +junglewoodendstep,125,3 +junglewooddoublestep,125,3 +junglewooddstep,125,3 +junglewdoublestep,125,3 +junglewdstep,125,3 +jungledoublewoodenplankstep,125,3 +jungledwoodenplankstep,125,3 +jungledoublewoodplankstep,125,3 +jungledwoodplankstep,125,3 +jungledoublewplankstep,125,3 +jungledwplankstep,125,3 +jungledoubleplankstep,125,3 +jungledplankstep,125,3 +jungledoublewoodenstep,125,3 +jungledwoodenstep,125,3 +jungledoublewoodstep,125,3 +jungledwoodstep,125,3 +jungledoublewstep,125,3 +jungledwstep,125,3 +jungledoublestep,125,3 +jungledstep,125,3 +junglewoodenplankdoubleslab,125,3 +junglewoodenplankdslab,125,3 +junglewoodplankdoubleslab,125,3 +junglewoodplankdslab,125,3 +junglewplankdoubleslab,125,3 +junglewplankdslab,125,3 +jungleplankdoubleslab,125,3 +jungleplankdslab,125,3 +junglewoodendoubleslab,125,3 +junglewoodendslab,125,3 +junglewooddoubleslab,125,3 +junglewooddslab,125,3 +junglewdoubleslab,125,3 +junglewdslab,125,3 +jungledoublewoodenplankslab,125,3 +jungledwoodenplankslab,125,3 +jungledoublewoodplankslab,125,3 +jungledwoodplankslab,125,3 +jungledoublewplankslab,125,3 +jungledwplankslab,125,3 +jungledoubleplankslab,125,3 +jungledplankslab,125,3 +jungledoublewoodenslab,125,3 +jungledwoodenslab,125,3 +jungledoublewoodslab,125,3 +jungledwoodslab,125,3 +jungledoublewslab,125,3 +jungledwslab,125,3 +jungledoubleslab,125,3 +jungledslab,125,3 +forestwoodenplankdoublehalfblock,125,3 +forestwoodenplankdhalfblock,125,3 +forestwoodplankdoublehalfblock,125,3 +forestwoodplankdhalfblock,125,3 +forestwplankdoublehalfblock,125,3 +forestwplankdhalfblock,125,3 +forestplankdoublehalfblock,125,3 +forestplankdhalfblock,125,3 +forestwoodendoublehalfblock,125,3 +forestwoodendhalfblock,125,3 +forestwooddoublehalfblock,125,3 +forestwooddhalfblock,125,3 +forestwdoublehalfblock,125,3 +forestwdhalfblock,125,3 +forestdoublewoodenplankhalfblock,125,3 +forestdwoodenplankhalfblock,125,3 +forestdoublewoodplankhalfblock,125,3 +forestdwoodplankhalfblock,125,3 +forestdoublewplankhalfblock,125,3 +forestdwplankhalfblock,125,3 +forestdoubleplankhalfblock,125,3 +forestdplankhalfblock,125,3 +forestdoublewoodenhalfblock,125,3 +forestdwoodenhalfblock,125,3 +forestdoublewoodhalfblock,125,3 +forestdwoodhalfblock,125,3 +forestdoublewhalfblock,125,3 +forestdwhalfblock,125,3 +forestdoublehalfblock,125,3 +forestdhalfblock,125,3 +forestwoodenplankdoublestep,125,3 +forestwoodenplankdstep,125,3 +forestwoodplankdoublestep,125,3 +forestwoodplankdstep,125,3 +forestwplankdoublestep,125,3 +forestwplankdstep,125,3 +forestplankdoublestep,125,3 +forestplankdstep,125,3 +forestwoodendoublestep,125,3 +forestwoodendstep,125,3 +forestwooddoublestep,125,3 +forestwooddstep,125,3 +forestwdoublestep,125,3 +forestwdstep,125,3 +forestdoublewoodenplankstep,125,3 +forestdwoodenplankstep,125,3 +forestdoublewoodplankstep,125,3 +forestdwoodplankstep,125,3 +forestdoublewplankstep,125,3 +forestdwplankstep,125,3 +forestdoubleplankstep,125,3 +forestdplankstep,125,3 +forestdoublewoodenstep,125,3 +forestdwoodenstep,125,3 +forestdoublewoodstep,125,3 +forestdwoodstep,125,3 +forestdoublewstep,125,3 +forestdwstep,125,3 +forestdoublestep,125,3 +forestdstep,125,3 +forestwoodenplankdoubleslab,125,3 +forestwoodenplankdslab,125,3 +forestwoodplankdoubleslab,125,3 +forestwoodplankdslab,125,3 +forestwplankdoubleslab,125,3 +forestwplankdslab,125,3 +forestplankdoubleslab,125,3 +forestplankdslab,125,3 +forestwoodendoubleslab,125,3 +forestwoodendslab,125,3 +forestwooddoubleslab,125,3 +forestwooddslab,125,3 +forestwdoubleslab,125,3 +forestwdslab,125,3 +forestdoublewoodenplankslab,125,3 +forestdwoodenplankslab,125,3 +forestdoublewoodplankslab,125,3 +forestdwoodplankslab,125,3 +forestdoublewplankslab,125,3 +forestdwplankslab,125,3 +forestdoubleplankslab,125,3 +forestdplankslab,125,3 +forestdoublewoodenslab,125,3 +forestdwoodenslab,125,3 +forestdoublewoodslab,125,3 +forestdwoodslab,125,3 +forestdoublewslab,125,3 +forestdwslab,125,3 +forestdoubleslab,125,3 +forestdslab,125,3 +woodenplankstep,126,0 +woodplankstep,126,0 +wplankstep,126,0 +plankstep,126,0 +woodenstep,126,0 +woodstep,126,0 +wstep,126,0 +woodenplankslab,126,0 +woodplankslab,126,0 +wplankslab,126,0 +plankslab,126,0 +woodenslab,126,0 +woodslab,126,0 +wslab,126,0 +woodenplankhalfblock,126,0 +woodplankhalfblock,126,0 +wplankhalfblock,126,0 +plankhalfblock,126,0 +woodenhalfblock,126,0 +woodhalfblock,126,0 +whalfblock,126,0 +oakwoodenplankstep,126,0 +oakwoodplankstep,126,0 +oakwplankstep,126,0 +oakplankstep,126,0 +oakwoodenstep,126,0 +oakwoodstep,126,0 +oakwstep,126,0 +oakstep,126,0 +oakwoodenplankslab,126,0 +oakwoodplankslab,126,0 +oakwplankslab,126,0 +oakplankslab,126,0 +oakwoodenslab,126,0 +oakwoodslab,126,0 +oakwslab,126,0 +oakslab,126,0 +oakwoodenplankhalfblock,126,0 +oakwoodplankhalfblock,126,0 +oakwplankhalfblock,126,0 +oakplankhalfblock,126,0 +oakwoodenhalfblock,126,0 +oakwoodhalfblock,126,0 +oakwhalfblock,126,0 +oakhalfblock,126,0 +sprucewoodenplankstep,126,1 +sprucewoodplankstep,126,1 +sprucewplankstep,126,1 +spruceplankstep,126,1 +sprucewoodenstep,126,1 +sprucewoodstep,126,1 +sprucewstep,126,1 +sprucestep,126,1 +sprucewoodenplankslab,126,1 +sprucewoodplankslab,126,1 +sprucewplankslab,126,1 +spruceplankslab,126,1 +sprucewoodenslab,126,1 +sprucewoodslab,126,1 +sprucewslab,126,1 +spruceslab,126,1 +sprucewoodenplankhalfblock,126,1 +sprucewoodplankhalfblock,126,1 +sprucewplankhalfblock,126,1 +spruceplankhalfblock,126,1 +sprucewoodenhalfblock,126,1 +sprucewoodhalfblock,126,1 +sprucewhalfblock,126,1 +sprucehalfblock,126,1 +darkwoodenplankstep,126,1 +darkwoodplankstep,126,1 +darkwplankstep,126,1 +darkplankstep,126,1 +darkwoodenstep,126,1 +darkwoodstep,126,1 +darkwstep,126,1 +darkstep,126,1 +darkwoodenplankslab,126,1 +darkwoodplankslab,126,1 +darkwplankslab,126,1 +darkplankslab,126,1 +darkwoodenslab,126,1 +darkwoodslab,126,1 +darkwslab,126,1 +darkslab,126,1 +darkwoodenplankhalfblock,126,1 +darkwoodplankhalfblock,126,1 +darkwplankhalfblock,126,1 +darkplankhalfblock,126,1 +darkwoodenhalfblock,126,1 +darkwoodhalfblock,126,1 +darkwhalfblock,126,1 +darkhalfblock,126,1 +birchwoodenplankstep,126,2 +birchwoodplankstep,126,2 +birchwplankstep,126,2 +birchplankstep,126,2 +birchwoodenstep,126,2 +birchwoodstep,126,2 +birchwstep,126,2 +birchstep,126,2 +birchwoodenplankslab,126,2 +birchwoodplankslab,126,2 +birchwplankslab,126,2 +birchplankslab,126,2 +birchwoodenslab,126,2 +birchwoodslab,126,2 +birchwslab,126,2 +birchslab,126,2 +birchwoodenplankhalfblock,126,2 +birchwoodplankhalfblock,126,2 +birchwplankhalfblock,126,2 +birchplankhalfblock,126,2 +birchwoodenhalfblock,126,2 +birchwoodhalfblock,126,2 +birchwhalfblock,126,2 +birchhalfblock,126,2 +lightwoodenplankstep,126,2 +lightwoodplankstep,126,2 +lightwplankstep,126,2 +lightplankstep,126,2 +lightwoodenstep,126,2 +lightwoodstep,126,2 +lightwstep,126,2 +lightstep,126,2 +lightwoodenplankslab,126,2 +lightwoodplankslab,126,2 +lightwplankslab,126,2 +lightplankslab,126,2 +lightwoodenslab,126,2 +lightwoodslab,126,2 +lightwslab,126,2 +lightslab,126,2 +lightwoodenplankhalfblock,126,2 +lightwoodplankhalfblock,126,2 +lightwplankhalfblock,126,2 +lightplankhalfblock,126,2 +lightwoodenhalfblock,126,2 +lightwoodhalfblock,126,2 +lightwhalfblock,126,2 +lighthalfblock,126,2 +junglewoodenplankstep,126,3 +junglewoodplankstep,126,3 +junglewplankstep,126,3 +jungleplankstep,126,3 +junglewoodenstep,126,3 +junglewoodstep,126,3 +junglewstep,126,3 +junglestep,126,3 +junglewoodenplankslab,126,3 +junglewoodplankslab,126,3 +junglewplankslab,126,3 +jungleplankslab,126,3 +junglewoodenslab,126,3 +junglewoodslab,126,3 +junglewslab,126,3 +jungleslab,126,3 +junglewoodenplankhalfblock,126,3 +junglewoodplankhalfblock,126,3 +junglewplankhalfblock,126,3 +jungleplankhalfblock,126,3 +junglewoodenhalfblock,126,3 +junglewoodhalfblock,126,3 +junglewhalfblock,126,3 +junglehalfblock,126,3 +forestwoodenplankstep,126,3 +forestwoodplankstep,126,3 +forestwplankstep,126,3 +forestplankstep,126,3 +forestwoodenstep,126,3 +forestwoodstep,126,3 +forestwstep,126,3 +foreststep,126,3 +forestwoodenplankslab,126,3 +forestwoodplankslab,126,3 +forestwplankslab,126,3 +forestplankslab,126,3 +forestwoodenslab,126,3 +forestwoodslab,126,3 +forestwslab,126,3 +forestslab,126,3 +forestwoodenplankhalfblock,126,3 +forestwoodplankhalfblock,126,3 +forestwplankhalfblock,126,3 +forestplankhalfblock,126,3 +forestwoodenhalfblock,126,3 +forestwoodhalfblock,126,3 +forestwhalfblock,126,3 +foresthalfblock,126,3 +cocoaplant,127,0 +cocoplant,127,0 +cplant,127,0 +cocoafruit,127,0 +cocofruit,127,0 +cfruit,127,0 +cocoapod,127,0 +cocopod,127,0 +cpod,127,0 +sandstairs,128,0 +sandstonestairs,128,0 +sandsstairs,128,0 +sstonestairs,128,0 +ssstairs,128,0 +sandstair,128,0 +sandstonestair,128,0 +sandsstair,128,0 +sstonestair,128,0 +ssstair,128,0 +emeraldore,129,0 +eore,129,0 +oreemerald,129,0 +oree,129,0 +enderchest,130,0 +endchest,130,0 +echest,130,0 +chestender,130,0 +chestend,130,0 +cheste,130,0 +endercontainer,130,0 +endcontainer,130,0 +econtainer,130,0 +tripwirehook,131,0 +tripwire,131,0 +trip,131,0 +tripwirelever,131,0 +triphook,131,0 +tripwireblock,132,0 +tripblock,132,0 +blocktrip,132,0 +blocktripwire,132,0 +emeraldblock,133,0 +blockemerald,133,0 +eblock,133,0 +blocke,133,0 +sprucewoodenplankstairs,134,0 +sprucewoodplankstairs,134,0 +sprucewplankstairs,134,0 +spruceplankstairs,134,0 +sprucewoodenstairs,134,0 +sprucewoodstairs,134,0 +sprucewstairs,134,0 +sprucestairs,134,0 +darkwoodenplankstairs,134,0 +darkwoodplankstairs,134,0 +darkwplankstairs,134,0 +darkplankstairs,134,0 +darkwoodenstairs,134,0 +darkwoodstairs,134,0 +darkwstairs,134,0 +darkstairs,134,0 +sprucewoodenplankstair,134,0 +sprucewoodplankstair,134,0 +sprucewplankstair,134,0 +spruceplankstair,134,0 +sprucewoodenstair,134,0 +sprucewoodstair,134,0 +sprucewstair,134,0 +sprucestair,134,0 +darkwoodenplankstair,134,0 +darkwoodplankstair,134,0 +darkwplankstair,134,0 +darkplankstair,134,0 +darkwoodenstair,134,0 +darkwoodstair,134,0 +darkwstair,134,0 +darkstair,134,0 +birchwoodenplankstairs,135,0 +birchwoodplankstairs,135,0 +birchwplankstairs,135,0 +birchplankstairs,135,0 +birchwoodenstairs,135,0 +birchwoodstairs,135,0 +birchwstairs,135,0 +birchstairs,135,0 +lightwoodenplankstairs,135,0 +lightwoodplankstairs,135,0 +lightwplankstairs,135,0 +lightplankstairs,135,0 +lightwoodenstairs,135,0 +lightwoodstairs,135,0 +lightwstairs,135,0 +lightstairs,135,0 +birchwoodenplankstair,135,0 +birchwoodplankstair,135,0 +birchwplankstair,135,0 +birchplankstair,135,0 +birchwoodenstair,135,0 +birchwoodstair,135,0 +birchwstair,135,0 +birchstair,135,0 +lightwoodenplankstair,135,0 +lightwoodplankstair,135,0 +lightwplankstair,135,0 +lightplankstair,135,0 +lightwoodenstair,135,0 +lightwoodstair,135,0 +lightwstair,135,0 +lightstair,135,0 +junglewoodenplankstairs,136,0 +junglewoodplankstairs,136,0 +junglewplankstairs,136,0 +jungleplankstairs,136,0 +junglewoodenstairs,136,0 +junglewoodstairs,136,0 +junglewstairs,136,0 +junglestairs,136,0 +forestwoodenplankstairs,136,0 +forestwoodplankstairs,136,0 +forestwplankstairs,136,0 +forestplankstairs,136,0 +forestwoodenstairs,136,0 +forestwoodstairs,136,0 +forestwstairs,136,0 +foreststairs,136,0 +junglewoodenplankstair,136,0 +junglewoodplankstair,136,0 +junglewplankstair,136,0 +jungleplankstair,136,0 +junglewoodenstair,136,0 +junglewoodstair,136,0 +junglewstair,136,0 +junglestair,136,0 +forestwoodenplankstair,136,0 +forestwoodplankstair,136,0 +forestwplankstair,136,0 +forestplankstair,136,0 +forestwoodenstair,136,0 +forestwoodstair,136,0 +forestwstair,136,0 +foreststair,136,0 +commandblock,137,0 +blockcommand,137,0 +cmdblock,137,0 +blockcmd,137,0 +macroblock,137,0 +blockmacro,137,0 +beacon,138,0 +beaconblock,138,0 +cobblestonewall,139,0 +cstonewall,139,0 +cobblewall,139,0 +cobblestonefence,139,0 +cstonefence,139,0 +cobblefence,139,0 +mosscobblestonewall,139,1 +mosscstonewall,139,1 +mosscobblewall,139,1 +mcobblestonewall,139,1 +mcstonewall,139,1 +mcobblewall,139,1 +mosscobblestonefence,139,1 +mosscstonefence,139,1 +mosscobblefence,139,1 +mcobblestonefence,139,1 +mcstonefence,139,1 +mcobblefence,139,1 +emptyflowerpot,140,0 +emptypot,140,0 +flowerpotblock,140,0 +potblock,140,0 +roseflowerpot,140,1 +rosepot,140,1 +dandelionflowerpot,140,2 +dandelionpot,140,2 +oaksaplingflowerpot,140,3 +saplingflowerpot,140,3 +oakflowerpot,140,3 +oaksaplingpot,140,3 +saplingpot,140,3 +oakpot,140,3 +sprucesaplingflowerpot,140,4 +spruceflowerpot,140,4 +darksaplingflowerpot,140,4 +darkflowerpot,140,4 +pinesaplingflowerpot,140,4 +pineflowerpot,140,4 +sprucesaplingpot,140,4 +sprucepot,140,4 +darksaplingpot,140,4 +darkpot,140,4 +pinesaplingpot,140,4 +pinepot,140,4 +birchsaplingflowerpot,140,5 +birchflowerpot,140,5 +lightsaplingflowerpot,140,5 +lightflowerpot,140,5 +whitesaplingflowerpot,140,5 +whiteflowerpot,140,5 +birchsaplingpot,140,5 +birchpot,140,5 +lightsaplingpot,140,5 +lightpot,140,5 +whitesaplingpot,140,5 +whitepot,140,5 +forestsaplingflowerpot,140,6 +forestflowerpot,140,6 +junglesaplingflowerpot,140,6 +jungleflowerpot,140,6 +forestsaplingpot,140,6 +forestpot,140,6 +junglesaplingpot,140,6 +junglepot,140,6 +redmushroomflowerpot,140,7 +redshroomflowerpot,140,7 +redmushflowerpot,140,7 +rmushroomflowerpot,140,7 +rshroomflowerpot,140,7 +rmushflowerpot,140,7 +redmushroompot,140,7 +redshroompot,140,7 +redmushpot,140,7 +rmushroompot,140,7 +rshroompot,140,7 +rmushpot,140,7 +brownmushroomflowerpot,140,8 +brownshroomflowerpot,140,8 +brownmushflowerpot,140,8 +bmushroomflowerpot,140,8 +bshroomflowerpot,140,7 +bmushflowerpot,140,8 +brownmushroompot,140,8 +brownshroompot,140,8 +brownmushpot,140,8 +bmushroompot,140,8 +bshroompot,140,8 +bmushpot,140,8 +cactusflowerpot,140,9 +cactuspot,140,9 +deadbushflowerpot,140,10 +dbushflowerpot,140,10 +deadsaplingflowerpot,140,10 +dsaplingflowerpot,140,10 +deadshrubflowerpot,140,10 +dshrubflowerpot,140,10 +deadbushpot,140,10 +dbushpot,140,10 +deadsaplingpot,140,10 +dsaplingpot,140,10 +deadshrubpot,140,10 +dshrubpot,140,10 +fernflowerpot,140,11 +bushflowerpot,140,11 +fernpot,140,11 +bushpot,140,11 +carrots,141,0 +plantedcarrot,141,0 +plantcarrot,141,0 +growingcarrot,141,0 +potatoes,142,0 +plantedpotato,142,0 +plantpotato,142,0 +growingpotato,142,0 +woodenplankbutton,143,0 +woodplankbutton,143,0 +wplankbutton,143,0 +plankbutton,143,0 +woodenbutton,143,0 +woodbutton,143,0 +wbutton,143,0 +headblock,144,0 +anvil,145,0 +slightlydamagedanvil,145,1 +slightdamageanvil,145,1 +damagedanvil,145,1 +verydamagedanvil,145,2 +trappedchest,146,0 +trapchest,146,0 +chesttrapped,146,0 +chesttrap,146,0 +weightedgoldpressureplate,147,0 +weightgoldpressureplate,147,0 +wgoldpressureplate,147,0 +goldpressureplate,147,0 +weightedgoldpressplate,147,0 +weightgoldpressplate,147,0 +wgoldpressplate,147,0 +goldpressplate,147,0 +weightedgoldpplate,147,0 +weightgoldpplate,147,0 +wgoldpplate,147,0 +goldpplate,147,0 +weightedgoldplate,147,0 +weightgoldplate,147,0 +wgoldplate,147,0 +goldplate,147,0 +weightedgpressureplate,147,0 +weightgpressureplate,147,0 +wgpressureplate,147,0 +gpressureplate,147,0 +weightedgpressplate,147,0 +weightgpressplate,147,0 +wgpressplate,147,0 +gpressplate,147,0 +weightedgpplate,147,0 +weightgpplate,147,0 +wgpplate,147,0 +gpplate,147,0 +weightedgplate,147,0 +weightgplate,147,0 +wgplate,147,0 +gplate,147,0 +weightedironpressureplate,148,0 +weightironpressureplate,148,0 +wironpressureplate,148,0 +ironpressureplate,148,0 +weightedironpressplate,148,0 +weightironpressplate,148,0 +wironpressplate,148,0 +ironpressplate,148,0 +weightedironpplate,148,0 +weightironpplate,148,0 +wironpplate,148,0 +ironpplate,148,0 +weightedironplate,148,0 +weightironplate,148,0 +wironplate,148,0 +ironplate,148,0 +weightedipressureplate,148,0 +weightipressureplate,148,0 +wipressureplate,148,0 +ipressureplate,148,0 +weightedipressplate,148,0 +weightipressplate,148,0 +wipressplate,148,0 +ipressplate,148,0 +weightedipplate,148,0 +weightipplate,148,0 +wipplate,148,0 +ipplate,148,0 +weightediplate,148,0 +weightiplate,148,0 +wiplate,148,0 +iplate,148,0 +redstonecomparatorblockoff,149,0 +redstonecompererblockoff,149,0 +redstonecompareblockoff,149,0 +rstonecomparatorblockoff,149,0 +rstonecompererblockoff,149,0 +rstonecompareblockoff,149,0 +redscomparatorblockoff,149,0 +redscompererblockoff,149,0 +redscompareblockoff,149,0 +rscomparatorblockoff,149,0 +rscompererblockoff,149,0 +rscompareblockoff,149,0 +comparatorblockoff,149,0 +compererblockoff,149,0 +compareblockoff,149,0 +redstonecomparatoroff,149,0 +redstonecompereroff,149,0 +redstonecompareoff,149,0 +rstonecomparatoroff,149,0 +rstonecompereroff,149,0 +rstonecompareoff,149,0 +redscomparatoroff,149,0 +redscompereroff,149,0 +redscompareoff,149,0 +rscomparatoroff,149,0 +rscompereroff,149,0 +rscompareoff,149,0 +comparatoroff,149,0 +compereroff,149,0 +compareoff,149,0 +redstonecomparatorblockon,150,0 +redstonecompererblockon,150,0 +redstonecompareblockon,150,0 +rstonecomparatorblockon,150,0 +rstonecompererblockon,150,0 +rstonecompareblockon,150,0 +redscomparatorblockon,150,0 +redscompererblockon,150,0 +redscompareblockon,150,0 +rscomparatorblockon,150,0 +rscompererblockon,150,0 +rscompareblockon,150,0 +comparatorblockon,150,0 +compererblockon,150,0 +compareblockon,150,0 +redstonecomparatoron,150,0 +redstonecompereron,150,0 +redstonecompareon,150,0 +rstonecomparatoron,150,0 +rstonecompereron,150,0 +rstonecompareon,150,0 +redscomparatoron,150,0 +redscompereron,150,0 +redscompareon,150,0 +rscomparatoron,150,0 +rscompereron,150,0 +rscompareon,150,0 +comparatoron,150,0 +compereron,150,0 +compareon,150,0 +daylightsensor,151,0 +daylightsense,151,0 +lightsensor,151,0 +lightsense,151,0 +daysensor,151,0 +daysense,151,0 +timesensor,151,0 +timesense,151,0 +redstoneblock,152,0 +rstoneblock,152,0 +redsblock,152,0 +rsblock,152,0 +blockredstone,152,0 +blockrstone,152,0 +blockreds,152,0 +blockrs,152,0 +netherquartzore,153,0 +hellquartzore,153,0 +deathquartzore,153,0 +nquartzore,153,0 +hquartzore,153,0 +dquartzore,153,0 +quartzore,153,0 +netherqore,153,0 +hellqore,153,0 +deathqore,153,0 +nqore,153,0 +hqore,153,0 +dqore,153,0 +qore,153,0 +hopper,154,0 +chestpuller,154,0 +chestpull,154,0 +cheststorer,154,0 +cheststore,154,0 +itempuller,154,0 +itempull,154,0 +itemstorer,154,0 +itemstore,154,0 +normalquartzblock,155,0 +nquartzblock,155,0 +quartzblock,155,0 +normalqblock,155,0 +nqblock,155,0 +qblock,155,0 +blocknormalquartz,155,0 +blocknquartz,155,0 +blockquartz,155,0 +blocknormalq,155,0 +blocknq,155,0 +blockq,155,0 +chiseledquartzblock,155,1 +chiselquartzblock,155,1 +cquartzblock,155,1 +chiseledqblock,155,1 +chiselqblock,155,1 +cqblock,155,1 +blockchiseledquartz,155,1 +blockchiselquartz,155,1 +blockcquartz,155,1 +blockchiseledq,155,1 +blockchiselq,155,1 +blockcq,155,1 +pillarquartzblock,155,2 +pquartzblock,155,2 +pillarqblock,155,2 +pqblock,155,2 +blockpillarquartz,155,2 +blockpquartz,155,2 +blockpillarq,155,2 +blockpq,155,2 +quartzstairs,156,0 +qstairs,156,0 +quartzstair,156,0 +qstair,156,0 +activatorrails,157,0 +activaterails,157,0 +triggerrails,157,0 +arails,157,0 +trails,157,0 +activatorrail,157,0 +activaterail,157,0 +triggerrail,157,0 +arail,157,0 +trail,157,0 +activatortrack,157,0 +activatetrack,157,0 +triggertrack,157,0 +atrack,157,0 +ttrack,157,0 +dropper,158,0 +drop,158,0 +chestdispenser,158,0 +chestdispense,158,0 +chestdropper,158,0 +chestdrop,158,0 +whiteclay,159,0 +whitesclay,159,0 +whitestainedclay,159,0 +wclay,159,0 +wsclay,159,0 +wstainedclay,159,0 +sclay,159,0 +stainedclay,159,0 +orangeclay,159,1 +orangesclay,159,1 +orangestainedclay,159,1 +oclay,159,1 +osclay,159,1 +ostainedclay,159,1 +magentaclay,159,2 +magentasclay,159,2 +magentastainedclay,159,2 +mclay,159,2 +msclay,159,2 +mstainedclay,159,2 +lightblueclay,159,3 +lightbluesclay,159,3 +lightbluestainedclay,159,3 +lblueclay,159,3 +lbluesclay,159,3 +lbluestainedclay,159,3 +lightbluclay,159,3 +lightblusclay,159,3 +lightblustainedclay,159,3 +lbluclay,159,3 +lblusclay,159,3 +lblustainedclay,159,3 +yellowclay,159,4 +yellowsclay,159,4 +yellowstainedclay,159,4 +yclay,159,4 +ysclay,159,4 +ystainedclay,159,4 +lightgreenclay,159,5 +lightgreensclay,159,5 +lightgreenstainedclay,159,5 +lgreenclay,159,5 +lgreensclay,159,5 +lgreenstainedclay,159,5 +lightgreclay,159,5 +lightgresclay,159,5 +lightgrestainedclay,159,5 +lgreclay,159,5 +lgresclay,159,5 +lgrestainedclay,159,5 +limeclay,159,5 +limesclay,159,5 +limestainedclay,159,5 +lclay,159,5 +lsclay,159,5 +lstainedclay,159,5 +pinkclay,159,6 +pinksclay,159,6 +pinkstainedclay,159,6 +piclay,159,6 +pisclay,159,6 +pistainedclay,159,6 +darkgrayclay,159,7 +darkgraysclay,159,7 +darkgraystainedclay,159,7 +dgrayclay,159,7 +dgraysclay,159,7 +dgraystainedclay,159,7 +darkgraclay,159,7 +darkgrasclay,159,7 +darkgrastainedclay,159,7 +dgraclay,159,7 +dgrasclay,159,7 +dgrastainedclay,159,7 +grayclay,159,7 +graysclay,159,7 +graystainedclay,159,7 +graclay,159,7 +grasclay,159,7 +grastainedclay,159,7 +lightgrayclay,159,8 +lightgraysclay,159,8 +lightgraystainedclay,159,8 +lgrayclay,159,8 +lgraysclay,159,8 +lgraystainedclay,159,8 +lightgraclay,159,8 +lightgrasclay,159,8 +lightgrastainedclay,159,8 +lgraclay,159,8 +lgrasclay,159,8 +lgrastainedclay,159,8 +silverclay,159,8 +silversclay,159,8 +silverstainedclay,159,8 +siclay,159,8 +siasclay,159,8 +siastainedclay,159,8 +cyanclay,159,9 +cyansclay,159,9 +cyanstainedclay,159,9 +cclay,159,9 +csclay,159,9 +cstainedclay,159,9 +purpleclay,159,10 +purplesclay,159,10 +purplestainedclay,159,10 +puclay,159,10 +pusclay,159,10 +pustainedclay,159,10 +blueclay,159,11 +bluesclay,159,11 +bluestainedclay,159,11 +bluclay,159,11 +blusclay,159,11 +blustainedclay,159,11 +brownclay,159,12 +brownsclay,159,12 +brownstainedclay,159,12 +broclay,159,12 +brosclay,159,12 +brostainedclay,159,12 +darkgreenclay,159,13 +darkgreensclay,159,13 +darkgreenstainedclay,159,13 +dgreenclay,159,13 +dgreensclay,159,13 +dgreenstainedclay,159,13 +greenclay,159,13 +greensclay,159,13 +greenstainedclay,159,13 +darkgreclay,159,13 +darkgresclay,159,13 +darkgrestainedclay,159,13 +dgreclay,159,13 +dgresclay,159,13 +dgrestainedclay,159,13 +greclay,159,13 +gresclay,159,13 +grestainedclay,159,13 +redclay,159,14 +redsclay,159,14 +redstainedclay,159,14 +rclay,159,14 +rsclay,159,14 +rstainedclay,159,14 +blackclay,159,15 +blacksclay,159,15 +blackstainedclay,159,15 +blaclay,159,15 +blasclay,159,15 +blastainedclay,159,15 +hayblock,170,0 +haybale,170,0 +hay,170,0 +whitecarpet,171,0 +whitefloor,171,0 +wcarpet,171,0 +wfloor,171,0 +carpet,171,0 +floor,171,0 +orangecarpet,171,1 +orangefloor,171,1 +ocarpet,171,1 +ofloor,171,1 +magentacarpet,171,2 +magentafloor,171,2 +mcarpet,171,2 +mfloor,171,2 +lightbluecarpet,171,3 +lightbluefloor,171,3 +lbluecarpet,171,3 +lbluefloor,171,3 +lightblucarpet,171,3 +lightblufloor,171,3 +lblucarpet,171,3 +lblufloor,171,3 +yellowcarpet,171,4 +yellowfloor,171,4 +ycarpet,171,4 +yfloor,171,4 +lightgreencarpet,171,5 +lightgreenfloor,171,5 +lgreencarpet,171,5 +lgreenfloor,171,5 +lightgrecarpet,171,5 +lightgrefloor,171,5 +lgrecarpet,171,5 +lgrefloor,171,5 +limecarpet,171,5 +limefloor,171,5 +lcarpet,171,5 +lfloor,171,5 +pinkcarpet,171,6 +pinkfloor,171,6 +picarpet,171,6 +pifloor,171,6 +darkgraycarpet,171,7 +darkgrayfloor,171,7 +dgraycarpet,171,7 +dgrayfloor,171,7 +darkgracarpet,171,7 +darkgrafloor,171,7 +dgracarpet,171,7 +dgrafloor,171,7 +graycarpet,171,7 +grayfloor,171,7 +gracarpet,171,7 +grafloor,171,7 +lightgraycarpet,171,8 +lightgrayfloor,171,8 +lgraycarpet,171,8 +lgrayfloor,171,8 +lightgracarpet,171,8 +lightgrafloor,171,8 +lgracarpet,171,8 +lgrafloor,171,8 +silvercarpet,171,8 +silverfloor,171,8 +sicarpet,171,8 +siafloor,171,8 +cyancarpet,171,9 +cyanfloor,171,9 +ccarpet,171,9 +cfloor,171,9 +purplecarpet,171,10 +purplefloor,171,10 +pucarpet,171,10 +pufloor,171,10 +bluecarpet,171,11 +bluefloor,171,11 +blucarpet,171,11 +blufloor,171,11 +browncarpet,171,12 +brownfloor,171,12 +brocarpet,171,12 +brofloor,171,12 +darkgreencarpet,171,13 +darkgreenfloor,171,13 +dgreencarpet,171,13 +dgreenfloor,171,13 +greencarpet,171,13 +greenfloor,171,13 +darkgrecarpet,171,13 +darkgrefloor,171,13 +dgrecarpet,171,13 +dgrefloor,171,13 +grecarpet,171,13 +grefloor,171,13 +redcarpet,171,14 +redfloor,171,14 +rcarpet,171,14 +rfloor,171,14 +blackcarpet,171,15 +blackfloor,171,15 +blacarpet,171,15 +blafloor,171,15 +hardenedclay,172,0 +hardclay,172,0 +hclay,172,0 +coalblock,173,0 +blockcoal,173,0 +ironshovel,256,0 +ironspade,256,0 +ishovel,256,0 +ispade,256,0 +steelshovel,256,0 +steelspade,256,0 +ironpickaxe,257,0 +ironpick,257,0 +steelpickaxe,257,0 +steelpick,257,0 +ipickaxe,257,0 +ipick,257,0 +ironaxe,258,0 +iaxe,258,0 +steelaxe,258,0 +flintandsteel,259,0 +flintandiron,259,0 +flintandtinder,259,0 +flintnsteel,259,0 +flintniron,259,0 +flintntinder,259,0 +flintsteel,259,0 +flintiron,259,0 +flinttinder,259,0 +lighter,259,0 +apple,260,0 +normalapple,260,0 +redapple,260,0 +bow,261,0 +arrow,262,0 +coal,263,0 +charcoal,263,1 +ccoal,263,1 +diamond,264,0 +crystal,264,0 +iron,265,0 +ironingot,265,0 +ironbar,265,0 +ironi,265,0 +steelingot,265,0 +steelbar,265,0 +steeli,265,0 +iingot,265,0 +ibar,265,0 +ingotiron,265,0 +bariron,265,0 +iiron,265,0 +ingotsteel,265,0 +barsteel,265,0 +isteel,265,0 +ingoti,265,0 +bari,265,0 +gold,266,0 +goldingot,266,0 +goldbar,266,0 +goldi,266,0 +gingot,266,0 +gbar,266,0 +ingotgold,266,0 +bargold,266,0 +igold,266,0 +ingotg,266,0 +barg,266,0 +ironsword,267,0 +steelsword,267,0 +isword,267,0 +woodensword,268,0 +woodsword,268,0 +wsword,268,0 +woodenshovel,269,0 +woodenspade,269,0 +woodshovel,269,0 +woodspade,269,0 +wshovel,269,0 +wspade,269,0 +woodenpickaxe,270,0 +woodenpick,270,0 +woodpickaxe,270,0 +woodpick,270,0 +wpickaxe,270,0 +wpick,270,0 +woodenaxe,271,0 +woodaxe,271,0 +waxe,271,0 +smoothstonesword,272,0 +cobblestonesword,272,0 +sstonesword,272,0 +cstonesword,272,0 +stonesword,272,0 +sssword,272,0 +cssword,272,0 +ssword,272,0 +smoothstoneshovel,273,0 +smoothstonespade,273,0 +cobblestoneshovel,273,0 +cobblestonespade,273,0 +sstoneshovel,273,0 +sstonespade,273,0 +cstoneshovel,273,0 +cstonespade,273,0 +stoneshovel,273,0 +stonespade,273,0 +ssshovel,273,0 +csshovel,273,0 +ssspade,273,0 +csspade,273,0 +sshovel,273,0 +sspade,273,0 +smoothstonepickaxe,274,0 +cobblestonepickaxe,274,0 +smoothstonepick,274,0 +cobblestonepick,274,0 +sstonepickaxe,274,0 +sstonepick,274,0 +cstonepickaxe,274,0 +cstonepick,274,0 +stonepickaxe,274,0 +stonepick,274,0 +sspickaxe,274,0 +sspick,274,0 +cspickaxe,274,0 +cspick,274,0 +spickaxe,274,0 +spick,274,0 +smoothstoneaxe,275,0 +cobblestoneaxe,275,0 +sstoneaxe,275,0 +cstoneaxe,275,0 +stoneaxe,275,0 +ssaxe,275,0 +csaxe,275,0 +saxe,275,0 +diamondsword,276,0 +crystalsword,276,0 +dsword,276,0 +diamondshovel,277,0 +diamondspade,277,0 +crystalshovel,277,0 +crystalspade,277,0 +dshovel,277,0 +dspade,277,0 +diamondpickaxe,278,0 +diamondpick,278,0 +crystalpickaxe,278,0 +crystalpick,278,0 +dpickaxe,278,0 +dpick,278,0 +diamondaxe,279,0 +crystalaxe,279,0 +daxe,279,0 +stick,280,0 +twig,280,0 +branch,280,0 +bowl,281,0 +mushroomsoup,282,0 +mrsoup,282,0 +soup,282,0 +goldsword,283,0 +gsword,283,0 +goldshovel,284,0 +goldspade,284,0 +gshovel,284,0 +gspade,284,0 +goldpickaxe,285,0 +goldpick,285,0 +gpickaxe,285,0 +gpick,285,0 +goldaxe,286,0 +gaxe,286,0 +string,287,0 +feather,288,0 +gunpowder,289,0 +sulfur,289,0 +woodenhoe,290,0 +woodhoe,290,0 +whoe,290,0 +smoothstonehoe,291,0 +cobblestonehoe,291,0 +sstonehoe,291,0 +cstonehoe,291,0 +stonehoe,291,0 +sshoe,291,0 +cshoe,291,0 +shoe,291,0 +ironhoe,292,0 +steelhoe,292,0 +ihoe,292,0 +diamondhoe,293,0 +crystalhoe,293,0 +dhoe,293,0 +goldhoe,294,0 +ghoe,294,0 +seeds,295,0 +seed,295,0 +wheat,296,0 +bread,297,0 +leatherhelmet,298,0 +leatherhelm,298,0 +leatherhat,298,0 +leathercoif,298,0 +lhelmet,298,0 +lhelm,298,0 +lhat,298,0 +lcoif,298,0 +leatherchestplate,299,0 +leatherplatebody,299,0 +leatherplate,299,0 +leathershirt,299,0 +leathertunic,299,0 +lchestplate,299,0 +lplatebody,299,0 +lplate,299,0 +lshirt,299,0 +ltunic,299,0 +leatherleggings,300,0 +leatherlegs,300,0 +leatherpants,300,0 +lleggings,300,0 +llegs,300,0 +lpants,300,0 +leatherboots,301,0 +leathershoes,301,0 +lboots,301,0 +lshoes,301,0 +chainmailhelmet,302,0 +chainmailhelm,302,0 +chainmailhat,302,0 +chainmailcoif,302,0 +chainmhelmet,302,0 +chainmhelm,302,0 +chainmhat,302,0 +chainmcoif,302,0 +cmailhelmet,302,0 +cmailhelm,302,0 +cmailhat,302,0 +cmailcoif,302,0 +chainhelmet,302,0 +chainhelm,302,0 +chainhat,302,0 +chaincoif,302,0 +cmhelmet,302,0 +cmhelm,302,0 +cmhat,302,0 +cmcoif,302,0 +chainmailchestplate,303,0 +chainmailplatebody,303,0 +chainmailplate,303,0 +chainmailshirt,303,0 +chainmailtunic,303,0 +chainmchestplate,303,0 +chainmplatebody,303,0 +chainmplate,303,0 +chainmshirt,303,0 +chainmtunic,303,0 +cmailchestplate,303,0 +cmailplatebody,303,0 +cmailplate,303,0 +cmailshirt,303,0 +cmailtunic,303,0 +chainchestplate,303,0 +chainplatebody,303,0 +chainplate,303,0 +chainshirt,303,0 +chaintunic,303,0 +cmchestplate,303,0 +cmplatebody,303,0 +cmplate,303,0 +cmshirt,303,0 +cmtunic,303,0 +chainmailleggings,304,0 +chainmaillegs,304,0 +chainmailpants,304,0 +chainmleggings,304,0 +chainmlegs,304,0 +chainmpants,304,0 +cmailleggings,304,0 +cmaillegs,304,0 +cmailpants,304,0 +chainleggings,304,0 +chainlegs,304,0 +chainpants,304,0 +cmleggings,304,0 +cmlegs,304,0 +cmpants,304,0 +chainmailboots,305,0 +chainmailshoes,305,0 +chainmboots,305,0 +chainmshoes,305,0 +cmailboots,305,0 +cmailshoes,305,0 +chainboots,305,0 +chainshoes,305,0 +cmboots,305,0 +cmshoes,305,0 +ironhelmet,306,0 +ironhelm,306,0 +ironhat,306,0 +ironcoif,306,0 +ihelmet,306,0 +ihelm,306,0 +ihat,306,0 +icoif,306,0 +steelhelmet,306,0 +steelhelm,306,0 +steelhat,306,0 +steelcoif,306,0 +shelmet,306,0 +shelm,306,0 +shat,306,0 +scoif,306,0 +ironchestplate,307,0 +ironplatebody,307,0 +ironshirt,307,0 +irontunic,307,0 +ichestplate,307,0 +iplatebody,307,0 +ishirt,307,0 +itunic,307,0 +steelchestplate,307,0 +steelplatebody,307,0 +steelplate,307,0 +steelshirt,307,0 +steeltunic,307,0 +schestplate,307,0 +splatebody,307,0 +sshirt,307,0 +stunic,307,0 +ironleggings,308,0 +ironlegs,308,0 +ironpants,308,0 +ileggings,308,0 +ilegs,308,0 +ipants,308,0 +steelleggings,308,0 +steellegs,308,0 +steelpants,308,0 +sleggings,308,0 +slegs,308,0 +spants,308,0 +ironboots,309,0 +ironshoes,309,0 +iboots,309,0 +ishoes,309,0 +steelboots,309,0 +steelshoes,309,0 +sboots,309,0 +sshoes,309,0 +diamondhelmet,310,0 +diamondhelm,310,0 +diamondhat,310,0 +diamondcoif,310,0 +dhelmet,310,0 +dhelm,310,0 +dhat,310,0 +dcoif,310,0 +crystalhelmet,310,0 +crystalhelm,310,0 +crystalhat,310,0 +crystalcoif,310,0 +chelmet,310,0 +chelm,310,0 +chat,310,0 +ccoif,310,0 +diamondchestplate,311,0 +diamondplatebody,311,0 +diamondplate,311,0 +diamondshirt,311,0 +diamondtunic,311,0 +dchestplate,311,0 +dplatebody,311,0 +dplate,311,0 +dshirt,311,0 +dtunic,311,0 +crystalchestplate,311,0 +crystalplatebody,311,0 +crystalplate,311,0 +crystalshirt,311,0 +crystaltunic,311,0 +cchestplate,311,0 +cplatebody,311,0 +cplate,311,0 +cshirt,311,0 +ctunic,311,0 +diamondleggings,312,0 +diamondlegs,312,0 +diamondpants,312,0 +dleggings,312,0 +dlegs,312,0 +dpants,312,0 +crystalleggings,312,0 +crystallegs,312,0 +crystalpants,312,0 +cleggings,312,0 +clegs,312,0 +cpants,312,0 +diamondboots,313,0 +diamondshoes,313,0 +dboots,313,0 +dshoes,313,0 +crystalboots,313,0 +crystalshoes,313,0 +cboots,313,0 +cshoes,313,0 +goldhelmet,314,0 +goldhelm,314,0 +goldhat,314,0 +goldcoif,314,0 +ghelmet,314,0 +ghelm,314,0 +ghat,314,0 +gcoif,314,0 +goldchestplate,315,0 +goldplatebody,315,0 +goldshirt,315,0 +goldtunic,315,0 +gchestplate,315,0 +gplatebody,315,0 +gplateplate,315,0 +gshirt,315,0 +gtunic,315,0 +goldleggings,316,0 +goldlegs,316,0 +goldpants,316,0 +gleggings,316,0 +glegs,316,0 +gpants,316,0 +goldboots,317,0 +goldshoes,317,0 +gboots,317,0 +gshoes,317,0 +flint,318,0 +pork,319,0 +porkchop,319,0 +rawpork,319,0 +rpork,319,0 +rawporkchop,319,0 +rporkchop,319,0 +grilledpork,320,0 +grillpork,320,0 +gpork,320,0 +cookedpork,320,0 +cookpork,320,0 +cpork,320,0 +grilledporkchop,320,0 +grillporkchop,320,0 +gporkchop,320,0 +cookedporkchop,320,0 +cookporkchop,320,0 +cporkchop,320,0 +bacon,320,0 +painting,321,0 +picture,321,0 +goldenapple,322,0 +goldapple,322,0 +gapple,322,0 +enchantedgoldenapple,322,1 +enchantedgoldapple,322,1 +enchantedgapple,322,1 +supergoldenapple,322,1 +supergoldapple,322,1 +supergapple,322,1 +magicalgoldenapple,322,1 +magicalgoldapple,322,1 +magicalgapple,322,1 +magicgoldenapple,322,1 +magicgoldapple,322,1 +magicgapple,322,1 +egoldenapple,322,1 +egoldapple,322,1 +egapple,322,1 +sgoldenapple,322,1 +sgoldapple,322,1 +sgapple,322,1 +mgoldenapple,322,1 +mgoldapple,322,1 +mgapple,322,1 +sign,323,0 +woodendoor,324,0 +wooddoor,324,0 +wdoor,324,0 +door,324,0 +bucket,325,0 +bukkit,325,0 +waterbucket,326,0 +waterbukkit,326,0 +wbucket,326,0 +wbukkit,326,0 +magmabucket,327,0 +magmabukkit,327,0 +lavabucket,327,0 +lavabukkit,327,0 +lbucket,327,0 +lbukkit,327,0 +minecart,328,0 +mcart,328,0 +cart,328,0 +saddle,329,0 +irondoor,330,0 +idoor,330,0 +steeldoor,330,0 +sdoor,330,0 +dooriron,330,0 +doori,330,0 +doorsteel,330,0 +doors,330,0 +redstonedust,331,0 +redstone,331,0 +rstonedust,331,0 +rstone,331,0 +redsdust,331,0 +reddust,331,0 +rsdust,331,0 +rdust,331,0 +snow,332,0 +snowball,332,0 +snball,332,0 +boat,333,0 +leather,334,0 +milkbucket,335,0 +milkbukkit,335,0 +mbucket,335,0 +mbukkit,335,0 +claybrick,336,0 +brick,336,0 +clayball,337,0 +cball,337,0 +clay,337,0 +reeds,338,0 +reed,338,0 +sugarcane,338,0 +scane,338,0 +bamboo,338,0 +paper,339,0 +papyrus,339,0 +book,340,0 +slimeball,341,0 +slball,341,0 +storageminecart,342,0 +chestminecart,342,0 +storagemcart,342,0 +chestmcart,342,0 +storagecart,342,0 +chestcart,342,0 +sminecart,342,0 +cminecart,342,0 +smcart,342,0 +cmcart,342,0 +scart,342,0 +ccart,342,0 +engineminecart,343,0 +poweredminecart,343,0 +powerminecart,343,0 +furnaceminecart,343,0 +enginemcart,343,0 +poweredmcart,343,0 +powermcart,343,0 +furnacemcart,343,0 +enginecart,343,0 +poweredcart,343,0 +powercart,343,0 +furnacecart,343,0 +eminecart,343,0 +pminecart,343,0 +fminecart,343,0 +emcart,343,0 +pmcart,343,0 +fmcart,343,0 +ecart,343,0 +pcart,343,0 +fcart,343,0 +egg,344,0 +compass,345,0 +fishingrod,346,0 +fishrod,346,0 +frod,346,0 +rod,346,0 +goldwatch,347,0 +goldclock,347,0 +gwatch,347,0 +gclock,347,0 +watch,347,0 +clock,347,0 +glowingstoneblockdust,348,0 +lightstoneblockdust,348,0 +glowstoneblockdust,348,0 +blockglowingstonedust,348,0 +blocklightstonedust,348,0 +blockglowstonedust,348,0 +glowingstonebdust,348,0 +lightstonebdust,348,0 +glowstonebdust,348,0 +bglowingstonedust,348,0 +blightstonedust,348,0 +bglowstonedust,348,0 +glowingstonedust,348,0 +lightstonedust,348,0 +glowstonedust,348,0 +glowingblockdust,348,0 +lightblockdust,348,0 +glowblockdust,348,0 +lbdust,348,0 +gbdust,348,0 +lsdust,348,0 +gsdust,348,0 +rawfish,349,0 +rafish,349,0 +fish,349,0 +cookedfish,350,0 +cookfish,350,0 +cfish,350,0 +grilledfish,350,0 +grillfish,350,0 +gfish,350,0 +roastedfish,350,0 +roastfish,350,0 +rofish,350,0 +dye,351,0 +inksack,351,0 +inksac,351,0 +isack,351,0 +isac,351,0 +sack,351,0 +sac,351,0 +blackinksack,351,0 +blackinksac,351,0 +blackisack,351,0 +blackisac,351,0 +blacksack,351,0 +blacksac,351,0 +inksackblack,351,0 +inksacblack,351,0 +isackblack,351,0 +isacclack,351,0 +sackblack,351,0 +sacblack,351,0 +blackinksackcolour,351,0 +blackinksaccolour,351,0 +blackisackcolour,351,0 +blackisaccolour,351,0 +blacksackcolour,351,0 +blacksaccolour,351,0 +inksackblackcolour,351,0 +inksacblackcolour,351,0 +isackblackcolour,351,0 +isacclackcolour,351,0 +sackblackcolour,351,0 +sacblackcolour,351,0 +blackinksackcolor,351,0 +blackinksaccolor,351,0 +blackisackcolor,351,0 +blackisaccolor,351,0 +blacksackcolor,351,0 +blacksaccolor,351,0 +inksackblackcolor,351,0 +inksacblackcolor,351,0 +isackblackcolor,351,0 +isacblackcolor,351,0 +sackblackcolor,351,0 +sacblackcolor,351,0 +blackinksackdye,351,0 +blackinksacdye,351,0 +blackisackdye,351,0 +blackisacdye,351,0 +blacksackdye,351,0 +blacksacdye,351,0 +inksackblackdye,351,0 +inksacblackdye,351,0 +isackblackdye,351,0 +isacclackdye,351,0 +sackblackdye,351,0 +sacblackdye,351,0 +blackcolor,351,0 +blackdye,351,0 +rosered,351,1 +roseredcolor,351,1 +roseredcolour,351,1 +rosereddye,351,1 +redrosecolor,351,1 +redrosecolour,351,1 +redrosedye,351,1 +redr,351,1 +redrcolor,351,1 +redrcolour,351,1 +redrdye,351,1 +redcolor,351,1 +redcolour,351,1 +reddye,351,1 +cactusgreen,351,2 +greencactus,351,2 +cactusgreencolour,351,2 +greencactuscolour,351,2 +cactusgreencolor,351,2 +greencactuscolor,351,2 +cactusgreendye,351,2 +greencactusdye,351,2 +greencolour,351,2 +greencolor,351,2 +greendye,351,2 +cocoabeans,351,3 +cocoabean,351,3 +cocobeans,351,3 +cocobean,351,3 +cbeans,351,3 +cbean,351,3 +beans,351,3 +bean,351,3 +browncocoabeans,351,3 +browncocoabean,351,3 +browncocobeans,351,3 +browncocobean,351,3 +browncbeans,351,3 +browncbean,351,3 +brownbeans,351,3 +brownbean,351,3 +brownb,351,3 +cocoabeanscolour,351,3 +cocoabeancolour,351,3 +cocobeanscolour,351,3 +cocobeancolour,351,3 +cbeanscolour,351,3 +cbeancolour,351,3 +beanscolour,351,3 +beancolour,351,3 +browncocoabeanscolour,351,3 +browncocoabeancolour,351,3 +browncocobeanscolour,351,3 +browncocobeancolour,351,3 +browncbeanscolour,351,3 +browncbeancolour,351,3 +brownbeanscolour,351,3 +brownbeancolour,351,3 +brownbcolour,351,3 +cocoabeanscolor,351,3 +cocoabeancolor,351,3 +cocobeanscolor,351,3 +cocobeancolor,351,3 +cbeanscolor,351,3 +cbeancolor,351,3 +beanscolor,351,3 +beancolor,351,3 +browncocoabeanscolor,351,3 +browncocoabeancolor,351,3 +browncocobeanscolor,351,3 +browncocobeancolor,351,3 +browncbeanscolor,351,3 +browncbeancolor,351,3 +brownbeanscolor,351,3 +brownbeancolor,351,3 +brownbcolor,351,3 +cocoabeansdye,351,3 +cocoabeandye,351,3 +cocobeansdye,351,3 +cocobeandye,351,3 +cbeansdye,351,3 +cbeandye,351,3 +beansdye,351,3 +beandye,351,3 +browncocoabeansdye,351,3 +browncocoabeandye,351,3 +browncocobeansdye,351,3 +browncocobeandye,351,3 +browncbeansdye,351,3 +browncbeandye,351,3 +brownbeansdye,351,3 +brownbeandye,351,3 +brownbdye,351,3 +browncolour,351,3 +browncolor,351,3 +browndye,351,3 +bluelapislzuli,351,4 +bluelapisl,351,4 +bluelapis,351,4 +bluel,351,4 +lapislazuliblue,351,4 +lapislblue,351,4 +lapisblue,351,4 +lapislazuli,351,4 +lapisl,351,4 +lapis,351,4 +bluelapislazulicolour,351,4 +bluelapislcolour,351,4 +bluelapiscolour,351,4 +lapislazulibluecolour,351,4 +lapislbluecolour,351,4 +lapisbluecolour,351,4 +lapislazulicolour,351,4 +lapislcolour,351,4 +lapiscolour,351,4 +bluelapislazulicolor,351,4 +bluelapislcolor,351,4 +bluelapiscolor,351,4 +lapislazulibluecolor,351,4 +lapislbluecolor,351,4 +lapisbluecolor,351,4 +lapislazulicolor,351,4 +lapislcolor,351,4 +lapiscolor,351,4 +bluelapislazulidye,351,4 +bluelapisldye,351,4 +bluelapisdye,351,4 +lapislazulibluedye,351,4 +lapislbluedye,351,4 +lapisbluedye,351,4 +lapislazulidye,351,4 +lapisldye,351,4 +lapisdye,351,4 +bluecolour,351,4 +bluecolor,351,4 +bluedye,351,4 +purplecolour,351,5 +purplecolor,351,5 +purpledye,351,5 +cyancolour,351,6 +cyancolor,351,6 +cyandye,351,6 +lightgraycolour,351,7 +lightgraycolor,351,7 +lightgraydye,351,7 +lgraycolour,351,7 +lgraycolor,351,7 +lgraydye,351,7 +silvercolour,351,7 +silvercolor,351,7 +silverdye,351,7 +darkgraycolour,351,8 +darkgraycolor,351,8 +darkgraydye,351,8 +dgraycolour,351,8 +dgraycolor,351,8 +dgraydye,351,8 +graycolour,351,8 +graycolor,351,8 +graydye,351,8 +pinkcolour,351,9 +pinkcolor,351,9 +pinkdye,351,9 +limecolour,351,10 +limecolor,351,10 +limedye,351,10 +dandelionyellow,351,11 +dandelionyellowcolour,351,11 +dandelionyellowcolor,351,11 +dandelionyellowdye,351,11 +yellowdandelion,351,11 +yellowdandelioncolour,351,11 +yellowdandelioncolor,351,11 +yellowdandeliondye,351,11 +yellowd,351,11 +yellowdcolour,351,11 +yellowdcolor,351,11 +yellowddye,351,11 +dyellow,351,11 +dyellowcolour,351,11 +dyellowcolor,351,11 +dyellowdye,351,11 +yellowcolour,351,11 +yellowcolor,351,11 +yellowdye,351,11 +lightbluecolour,351,12 +lightbluecolor,351,12 +lightbluedye,351,12 +lbluecolour,351,12 +lbluecolor,351,12 +lbluedye,351,12 +magentacolour,351,13 +magentacolor,351,13 +magentadye,351,13 +orangecolour,351,14 +orangecolor,351,14 +orangedye,351,14 +whitebonemeal,351,15 +whitebonemealcolour,351,15 +whitebonemealcolor,351,15 +whitebonemealdye,351,15 +bonemealwhite,351,15 +bonemealwhitecolour,351,15 +bonemealwhitecolor,351,15 +bonemealwhitedye,351,15 +whitebonem,351,15 +whitebonemcolour,351,15 +whitebonemcolor,351,15 +whitebonemdye,351,15 +bonemwhite,351,15 +bonemwhitecolour,351,15 +bonemwhitecolor,351,15 +bonemwhitedye,351,15 +bonemeal,351,15 +bonemealcolour,351,15 +bonemealcolor,351,15 +bonemealdye,351,15 +bonem,351,15 +bonemcolour,351,15 +bonemcolor,351,15 +bonemdye,351,15 +whitecolour,351,15 +whitecolor,351,15 +whitedye,351,15 +bone,352,0 +sugar,353,0 +whitedust,353,0 +cake,354,0 +bed,355,0 +redstonerepeater,356,0 +redstonerepeat,356,0 +redstonedelayer,356,0 +redstonedelay,356,0 +redstonedioder,356,0 +redstonediode,356,0 +rstonerepeater,356,0 +rstonerepeat,356,0 +rstonedelayer,356,0 +rstonedelay,356,0 +rstonedioder,356,0 +rstonediode,356,0 +redsrepeater,356,0 +redsrepeat,356,0 +redsdelayer,356,0 +redsdelay,356,0 +redsdioder,356,0 +redsdiode,356,0 +rsrepeater,356,0 +rsrepeat,356,0 +rsdelayer,356,0 +rsdelay,356,0 +rsdioder,356,0 +rsdiode,356,0 +repeater,356,0 +repeat,356,0 +delayer,356,0 +delay,356,0 +dioder,356,0 +diode,356,0 +cookie,357,0 +chart,358,0 +map0,358,0 +map1,358,1 +map2,358,2 +map3,358,3 +map4,358,4 +map5,358,5 +map6,358,6 +map7,358,7 +map8,358,8 +map9,358,9 +map10,358,10 +map11,358,11 +map12,358,12 +map13,358,13 +map14,358,14 +map15,358,15 +shears,359,0 +shear,359,0 +sheers,359,0 +sheer,359,0 +woolcutters,359,0 +woolcutter,359,0 +cutterswool,359,0 +cutterwool,359,0 +melonslice,360,0 +mslice,360,0 +slicemelon,360,0 +watermelonslice,360,0 +niggerfruit,360,0 +greenmelonslice,360,0 +melongreenslice,360,0 +pumpkinseeds,361,0 +pseeds,361,0 +seedsp,361,0 +seedspumpkin,361,0 +pumpseeds,361,0 +seedspump,361,0 +melonseeds,362,0 +mseeds,362,0 +watermelonseeds,362,0 +greenmelonseeds,362,0 +gmelonseeds,362,0 +seedsmelon,362,0 +seedswatermelon,362,0 +rawbeef,363,0 +rawsteak,363,0 +uncookedbeef,363,0 +uncookedsteak,363,0 +cowmeat,363,0 +plainbeef,363,0 +beef,364,0 +steak,364,0 +cookedbeef,364,0 +grilledbeef,364,0 +cookedsteak,364,0 +grilledsteak,364,0 +cookedcowmeat,364,0 +rawchicken,365,0 +uncookedchicken,365,0 +plainchicken,365,0 +chickenplain,365,0 +chickenuncooked,365,0 +chickenraw,365,0 +cookedchicken,366,0 +grilledchicken,366,0 +toastedchicken,366,0 +gchicken,366,0 +bbqchicken,366,0 +friedchicken,366,0 +cchicken,366,0 +rottenflesh,367,0 +zombieflesh,367,0 +rottenmeat,367,0 +zombiemeat,367,0 +badflesh,367,0 +poisonflesh,367,0 +zombieremains,367,0 +enderpearl,368,0 +endpearl,368,0 +pearl,368,0 +epearl,368,0 +bluepearl,368,0 +endergem,368,0 +blazerod,369,0 +goldenrod,369,0 +goldrod,369,0 +blazestick,369,0 +goldstick,369,0 +brod,369,0 +grod,369,0 +bstick,369,0 +gstick,369,0 +ghasttear,370,0 +ghastdrop,370,0 +ghosttear,370,0 +ghostdrop,370,0 +gtear,370,0 +gdrop,370,0 +tear,370,0 +goldnugget,371,0 +gnugget,371,0 +goldball,371,0 +goldpebble,371,0 +gball,371,0 +gpebble,371,0 +pigzombienugget,371,0 +pigznugget,371,0 +pzombienugget,371,0 +pznugget,371,0 +pigzombieball,371,0 +pigzball,371,0 +pzombieball,371,0 +pzball,371,0 +pigzombiepebble,371,0 +pigzpebble,371,0 +pzombiepebble,371,0 +pzpebble,371,0 +netherstalk,372,0 +deathstalk,372,0 +hellstalk,372,0 +nstalk,372,0 +dstalk,372,0 +hstalk,372,0 +potion,373,0 +mixture,373,0 +potions,373,0 +waterbottle,373,0 +fullbottle,373,0 +watervase,373,0 +fullvase,373,0 +clearpotion,373,6 +clearpot,373,6 +clearextendedpotion,373,7 +clearexpotion,373,7 +clear2potion,373,7 +clearextendedpot,373,7 +clearexpot,373,7 +clear2pot,373,7 +diffusepotion,373,11 +diffusepot,373,11 +artlesspotion,373,13 +artlesspot,373,13 +thinpotion,373,14 +thinpot,373,14 +thinextendedpotion,373,15 +thinexpotion,373,15 +thin2potion,373,15 +thinextendedpot,373,15 +thinexpot,373,15 +thin2pot,373,15 +awkwardpotion,373,16 +awkwardpot,373,16 +bunglingpotion,373,22 +bunglingpot,373,22 +bunglingextendedpotion,373,23 +bunglingexpotion,373,23 +bungling2potion,373,23 +bunglingextendedpot,373,23 +bunglingexpot,373,23 +bungling2pot,373,23 +smoothpotion,373,27 +smoothpot,373,27 +suavepotion,373,29 +suavepot,373,29 +debonairpotion,373,30 +debonairpot,373,30 +debonairextendedpotion,373,31 +debonairexpotion,373,31 +debonair2potion,373,31 +debonairextendedpot,373,31 +debonairexpot,373,31 +debonair2pot,373,31 +thickpotion,373,32 +thickpot,373,32 +charmingpotion,373,38 +charmingpot,373,38 +charmingextendedpotion,373,39 +charmingexpotion,373,39 +charming2potion,373,39 +charmingextendedpot,373,39 +charmingexpot,373,39 +charming2pot,373,39 +refinedpotion,373,43 +refinedpot,373,43 +cordialpotion,373,45 +cordialpot,373,45 +sparklingpotion,373,46 +sparklingpot,373,46 +sparklingextendedpotion,373,47 +sparklingexpotion,373,47 +sparkling2potion,373,47 +sparklingextendedpot,373,47 +sparklingexpot,373,47 +sparkling2pot,373,47 +potentpotion,373,48 +potentpot,373,48 +rankpotion,373,54 +rankpot,373,54 +rankextendedpotion,373,55 +rankexpotion,373,55 +rank2potion,373,55 +rankextendedpot,373,55 +rankexpot,373,55 +rank2pot,373,55 +acridpotion,373,59 +acridpot,373,59 +grosspotion,373,61 +grosspot,373,61 +stinkypotion,373,62 +stinkypot,373,62 +stinkyextendedpotion,373,63 +stinkyexpotion,373,63 +stinky2potion,373,63 +stinkyextendedpot,373,63 +stinkyexpot,373,63 +stinky2pot,373,63 +mundaneextendedpotion,373,64 +mundaneexpotion,373,64 +mundane2potion,373,64 +mundaneextendedpot,373,64 +mundaneexpot,373,64 +mundane2pot,373,64 +mundanepotion,373,8192 +mundanepot,373,8192 +regenerationpotion,373,8193 +regeneratepotion,373,8193 +regenpotion,373,8193 +regenerationpot,373,8193 +regeneratepot,373,8193 +regenpot,373,8193 +rpot,373,8193 +swiftnesspotion,373,8194 +swiftpotion,373,8194 +speedpotion,373,8194 +swiftnesspot,373,8194 +swiftpot,373,8194 +speedpot,373,8194 +swpot,373,8194 +fireresistancepotion,373,8195 +fireresistpotion,373,8195 +firerespotion,373,8195 +fireresistancepot,373,8195 +fireresistpot,373,8195 +firerespot,373,8195 +fpot,373,8195 +poisonpotion,373,8196 +acidpotion,373,8196 +poisonpot,373,8196 +acidpot,373,8196 +ppot,373,8196 +healingpotion,373,8197 +healpotion,373,8197 +lifepotion,373,8197 +healingpot,373,8197 +healpot,373,8197 +lifepot,373,8197 +hpot,373,8197 +nightvisionpotion,373,8198 +nvisionpotion,373,8198 +nightvpotion,373,8198 +darkvisionpotion,373,8198 +dvisionpotion,373,8198 +darkvpotion,373,8198 +nightvisionpot,373,8198 +nvisionpot,373,8198 +nightvpot,373,8198 +darkvisionpot,373,8198 +dvisionpot,373,8198 +darkvpot,373,8198 +npot,373,8198 +weaknesspotion,373,8200 +weakpotion,373,8200 +weaknesspot,373,8200 +weakpot,373,8200 +wpot,373,8200 +strengthpotion,373,8201 +strongpotion,373,8201 +strpotion,373,8201 +strengthpot,373,8201 +strongpot,373,8201 +strpot,373,8201 +stpot,373,8201 +slownesspotion,373,8202 +slowpotion,373,8202 +slownesspot,373,8202 +slowpot,373,8202 +slpot,373,8202 +harmingpotion,373,8204 +damagepotion,373,8204 +dmgpotion,373,8204 +harmingpot,373,8204 +damagepot,373,8204 +dmgpot,373,8204 +dpot,373,8204 +invisibilitypotion,373,8206 +invisiblepotion,373,8206 +invpotion,373,8206 +invisibilitypot,373,8206 +invisiblepot,373,8206 +invpot,373,8206 +ipot,373,8206 +regenerationleveliipotion,373,8225 +regenerateleveliipotion,373,8225 +regenleveliipotion,373,8225 +regenerationlevel2potion,373,8225 +regeneratelevel2potion,373,8225 +regenlevel2potion,373,8225 +regenerationiipotion,373,8225 +regenerateiipotion,373,8225 +regeniipotion,373,8225 +regenerationleveliipot,373,8225 +regenerateleveliipot,373,8225 +regenleveliipot,373,8225 +regenerationlevel2pot,373,8225 +regeneratelevel2pot,373,8225 +regenlevel2pot,373,8225 +regenerationiipot,373,8225 +regenerateiipot,373,8225 +regeniipot,373,8225 +r2pot,373,8225 +swiftnessleveliipotion,373,8226 +swiftleveliipotion,373,8226 +speedleveliipotion,373,8226 +swiftnesslevel2potion,373,8226 +swiftlevel2potion,373,8226 +speedlevel2potion,373,8226 +swiftnessiipotion,373,8226 +swiftiipotion,373,8226 +speediipotion,373,8226 +swiftnessleveliipot,373,8226 +swiftleveliipot,373,8226 +speedleveliipot,373,8226 +swiftnesslevel2pot,373,8226 +swiftlevel2pot,373,8226 +speedlevel2pot,373,8226 +swiftnessiipot,373,8226 +swiftiipot,373,8226 +speediipot,373,8226 +sw2pot,373,8226 +poisonleveliipotion,373,8228 +acidleveliipotion,373,8228 +poisonlevel2potion,373,8228 +acidlevel2potion,373,8228 +poisoniipotion,373,8228 +acidiipotion,373,8228 +poisonleveliipot,373,8228 +acidleveliipot,373,8228 +poisonlevel2pot,373,8228 +acidlevel2pot,373,8228 +poisoniipot,373,8228 +acidiipot,373,8228 +p2pot,373,8228 +healingleveliipotion,373,8229 +healleveliipotion,373,8229 +healinglevel2potion,373,8229 +heallevel2potion,373,8229 +healingiipotion,373,8229 +healiipotion,373,8229 +healingleveliipot,373,8229 +healleveliipot,373,8229 +healinglevel2pot,373,8229 +heallevel2pot,373,8229 +healingiipot,373,8229 +healiipot,373,8229 +h2pot,373,8229 +strengthleveliipotion,373,8233 +strongleveliipotion,373,8233 +strleveliipotion,373,8233 +strengthlevel2potion,373,8233 +stronglevel2potion,373,8233 +strlevel2potion,373,8233 +strengthiipotion,373,8233 +strongiipotion,373,8233 +striipotion,373,8233 +strengthleveliipot,373,8233 +strongleveliipot,373,8233 +strleveliipot,373,8233 +strengthlevel2pot,373,8233 +stronglevel2pot,373,8233 +strlevel2pot,373,8233 +strengthiipot,373,8233 +strongiipot,373,8233 +striipot,373,8233 +st2pot,373,8233 +harmingleveliipotion,373,8236 +damageleveliipotion,373,8236 +dmgleveliipotion,373,8236 +harminglevel2potion,373,8236 +damagelevel2potion,373,8236 +dmglevel2potion,373,8236 +harmingiipotion,373,8236 +damageiipotion,373,8236 +dmgiipotion,373,8236 +harmingleveliipot,373,8236 +damageleveliipot,373,8236 +dmgleveliipot,373,8236 +harminglevel2pot,373,8236 +damagelevel2pot,373,8236 +dmglevel2pot,373,8236 +harmingiipot,373,8236 +damageiipot,373,8236 +dmgiipot,373,8236 +d2pot,373,8236 +regenerationextendedpotion,373,8257 +regenerateextendedpotion,373,8257 +regenextendepotion,373,8257 +regenerationexpotion,373,8257 +regenerateexpotion,373,8257 +regenexpotion,373,8257 +regenerationextendedpot,373,8257 +regenerateextendedpot,373,8257 +regenextendepot,373,8257 +regenerationexpot,373,8257 +regenerateexpot,373,8257 +regenexpot,373,8257 +repot,373,8257 +swiftnessextendedpotion,373,8258 +swiftextendedpotion,373,8258 +speedextendedpotion,373,8258 +swiftnessexpotion,373,8258 +swiftexpotion,373,8258 +speedexpotion,373,8258 +swiftnessextendedpot,373,8258 +swiftextendedpot,373,8258 +speedextendedpot,373,8258 +swiftnessexpot,373,8258 +swiftexpot,373,8258 +speedexpot,373,8258 +swepot,373,8258 +fireresistanceextendedpotion,373,8259 +fireresistextendedpotion,373,8259 +fireresextendedpotion,373,8259 +fireresistanceexpotion,373,8259 +fireresistexpotion,373,8259 +fireresexpotion,373,8259 +fireresistanceextendedpot,373,8259 +fireresistextendedpot,373,8259 +fireresextendedpot,373,8259 +fireresistanceexpot,373,8259 +fireresistexpot,373,8259 +fireresexpot,373,8259 +fepot,373,8259 +poisonextendedpotion,373,8260 +acidextendedpotion,373,8260 +poisonexpotion,373,8260 +acidexpotion,373,8260 +poisonextendedpot,373,8260 +acidextendedpot,373,8260 +poisonexpot,373,8260 +acidexpot,373,8260 +pepot,373,8260 +nightvisionextendedpotion,373,8262 +nvisionextendedpotion,373,8262 +nightvextendedpotion,373,8262 +darkvisionextendedpotion,373,8262 +dvisionextendedpotion,373,8262 +darkvextendedpotion,373,8262 +nightvisionexpotion,373,8262 +nvisionexpotion,373,8262 +nightvexpotion,373,8262 +darkvisionexpotion,373,8262 +dvisionexpotion,373,8262 +darkvexpotion,373,8262 +nightvisionextendedpot,373,8262 +nvisionextendedpot,373,8262 +nightvextendedpot,373,8262 +darkvisionextendedpot,373,8262 +dvisionextendedpot,373,8262 +darkvextendedpot,373,8262 +nightvisionexpot,373,8262 +nvisionexpot,373,8262 +nightvexpot,373,8262 +darkvisionexpot,373,8262 +dvisionexpot,373,8262 +darkvexpot,373,8262 +nepot,373,8262 +weaknessextendedpotion,373,8264 +weakextendedpotion,373,8264 +weaknessexpotion,373,8264 +weakexpotion,373,8264 +weaknessextendedpot,373,8264 +weakextendedpot,373,8264 +weaknessexpot,373,8264 +weakexpot,373,8264 +wepot,373,8264 +strengthextendedpotion,373,8265 +strongextendedpotion,373,8265 +strextendedpotion,373,8265 +strengthexpotion,373,8265 +strongexpotion,373,8265 +strexpotion,373,8265 +strengthextendedpot,373,8265 +strongextendedpot,373,8265 +strextendedpot,373,8265 +strengthexpot,373,8265 +strongexpot,373,8265 +strexpot,373,8265 +stepot,373,8265 +slownessextendedpotion,373,8266 +slowextenedpotion,373,8266 +slownessexpotion,373,8266 +slowexpotion,373,8266 +slownessextendedpot,373,8266 +slowextenedpot,373,8266 +slownessexpot,373,8266 +slowexpot,373,8266 +slepot,373,8266 +invisibilityextendedpotion,373,8270 +invisibleextendedpotion,373,8270 +invextendedpotion,373,8270 +invisibilityexpotion,373,8270 +invisibleexpotion,373,8270 +invexpotion,373,8270 +invisibilityextendedpot,373,8270 +invisibleextendedpot,373,8270 +invextendedpot,373,8270 +invisibilityexpot,373,8270 +invisibleexpot,373,8270 +invexpot,373,8270 +iepot,373,8270 +regenerationdualbitpotion,373,8289 +regeneratedualbitpotion,373,8289 +regendualbitpotion,373,8289 +regenerationdbpotion,373,8289 +regeneratedbpotion,373,8289 +regendbpotion,373,8289 +regenerationdualbitpot,373,8289 +regeneratedualbitpot,373,8289 +regendualbitpot,373,8289 +regenerationdbpot,373,8289 +regeneratedbpot,373,8289 +regendbpot,373,8289 +rdbpot,373,8289 +swiftnessdualbitpotion,373,8290 +swiftdualbitpotion,373,8290 +speeddualbitpotion,373,8290 +swiftnessdualbitpot,373,8290 +swiftdualbitpot,373,8290 +speeddualbitpot,373,8290 +swiftnessdbpotion,373,8290 +swiftdbpotion,373,8290 +speeddbpotion,373,8290 +swiftnessdbpot,373,8290 +swiftdbpot,373,8290 +speeddbpot,373,8290 +swdbpot,373,8290 +poisondualbitpotion,373,8292 +aciddualbitpotion,373,8292 +poisondualbitpot,373,8292 +aciddualbitpot,373,8292 +poisondbpotion,373,8292 +aciddbpotion,373,8292 +poisondbpot,373,8292 +aciddbpot,373,8292 +pdbpot,373,8292 +strengthdualbitpotion,373,8297 +strongdualbitpotion,373,8297 +strdualbitpotion,373,8297 +strengthdualbitpot,373,8297 +strongdualbitpot,373,8297 +strdualbitpot,373,8297 +strengthdbpotion,373,8297 +strongdbpotion,373,8297 +strdbpotion,373,8297 +strengthdbpot,373,8297 +strongdbpot,373,8297 +strdbpot,373,8297 +stdbpot,373,8297 +splashmundanepotion,373,16384 +splmundanepotion,373,16384 +splashregenerationpotion,373,16385 +splashregeneratepotion,373,16385 +splashregenpotion,373,16385 +splashregenerationpot,373,16385 +splashregeneratepot,373,16385 +splashregenpot,373,16385 +regenerationsplashpotion,373,16385 +regeneratesplashpotion,373,16385 +regensplashpotion,373,16385 +splregenerationpotion,373,16385 +splregeneratepotion,373,16385 +splregenpotion,373,16385 +splregenerationpot,373,16385 +splregeneratepot,373,16385 +splregenpot,373,16385 +sprpot,373,16385 +splashswiftnesspotion,373,16386 +splashswiftpotion,373,16386 +splashspeedpotion,373,16386 +splashswiftnesspot,373,16386 +splashswiftpot,373,16386 +splashspeedpot,373,16386 +splswiftnesspotion,373,16386 +splswiftpotion,373,16386 +splspeedpotion,373,16386 +splswiftnesspot,373,16386 +splswiftpot,373,16386 +splspeedpot,373,16386 +spswpot,373,16386 +splashfireresistancepotion,373,16387 +splashfireresistpotion,373,16387 +splashfirerespotion,373,16387 +splashfireresistancepot,373,16387 +splashfireresistpot,373,16387 +splashfirerespot,373,16387 +splfireresistancepotion,373,16387 +splfireresistpotion,373,16387 +splfirerespotion,373,16387 +splfireresistancepot,373,16387 +splfireresistpot,373,16387 +splfirerespot,373,16387 +spfpot,373,16387 +splashpoisonpotion,373,16388 +splashacidpotion,373,16388 +splashpoisonpot,373,16388 +splashacidpot,373,16388 +splpoisonpotion,373,16388 +splacidpotion,373,16388 +splpoisonpot,373,16388 +splacidpot,373,16388 +spppot,373,16388 +splashhealingpotion,373,16389 +splashhealpotion,373,16389 +splashlifepotion,373,16389 +splashhealingpot,373,16389 +splashhealpot,373,16389 +splashlifepot,373,16389 +splhealingpotion,373,16389 +splhealpotion,373,16389 +spllifepotion,373,16389 +splhealingpot,373,16389 +splhealpot,373,16389 +spllifepot,373,16389 +sphpot,373,16389 +splashclearpotion,373,16390 +splashclearpot,373,16390 +splclearpotion,373,16390 +splclearpot,373,16390 +splashnightvisionpotion,373,16390 +splashnvisionpotion,373,16390 +splashnightvpotion,373,16390 +splashdarkvisionpotion,373,16390 +splashdvisionpotion,373,16390 +splashdarkvpotion,373,16390 +splashnightvisionpot,373,16390 +splashnvisionpot,373,16390 +splashnightvpot,373,16390 +splashdarkvisionpot,373,16390 +splashdvisionpot,373,16390 +splashdarkvpot,373,16390 +splnightvisionpotion,373,16390 +splnvisionpotion,373,16390 +splnightvpotion,373,16390 +spldarkvisionpotion,373,16390 +spldvisionpotion,373,16390 +spldarkvpotion,373,16390 +splnightvisionpot,373,16390 +splnvisionpot,373,16390 +splnightvpot,373,16390 +spldarkvisionpot,373,16390 +spldvisionpot,373,16390 +spldarkvpot,373,16390 +spnpot,373,16390 +splashclearextendedpotion,373,16391 +splashclearexpotion,373,16391 +splashclear2potion,373,16391 +splashclearextendedpot,373,16391 +splashclearexpot,373,16391 +splashclear2pot,373,16391 +splclearextendedpotion,373,16391 +splclearexpotion,373,16391 +splclear2potion,373,16391 +splclearextendedpot,373,16391 +splclearexpot,373,16391 +splclear2pot,373,16391 +splashweaknesspotion,373,16392 +splashweakpotion,373,16392 +splashweaknesspot,373,16392 +splashweakpot,373,16392 +splweaknesspotion,373,16392 +splweakpotion,373,16392 +splweaknesspot,373,16392 +splweakpot,373,16392 +spwpot,373,16392 +splashstrengthpotion,373,16393 +splashstrongpotion,373,16393 +splashstrpotion,373,16393 +splashstrengthpot,373,16393 +splashstrongpot,373,16393 +splashstrpot,373,16393 +splstrengthpotion,373,16393 +splstrongpotion,373,16393 +splstrpotion,373,16393 +splstrengthpot,373,16393 +splstrongpot,373,16393 +splstrpot,373,16393 +spstpot,373,16393 +splashslownesspotion,373,16394 +splashslowpotion,373,16394 +splashslownesspot,373,16394 +splashslowpot,373,16394 +splslownesspotion,373,16394 +splslowpotion,373,16394 +splslownesspot,373,16394 +splslowpot,373,16394 +spslpot,373,16394 +splashdiffusepotion,373,16395 +splashdiffusepot,373,16395 +spldiffusepotion,373,16395 +spldiffusepot,373,16395 +splashharmingpotion,373,16396 +splashdamagepotion,373,16396 +splashdmgpotion,373,16396 +splashharmingpot,373,16396 +splashdamagepot,373,16396 +splashdmgpot,373,16396 +splharmingpotion,373,16396 +spldamagepotion,373,16396 +spldmgpotion,373,16396 +splharmingpot,373,16396 +spldamagepot,373,16396 +spldmgpot,373,16396 +spdpot,373,16396 +splashartlesspotion,373,16397 +splashartlesspot,373,16397 +splartlesspotion,373,16397 +splartlesspot,373,16397 +splashthinpotion,373,16398 +splashthinpot,373,16398 +splthinpotion,373,16398 +splthinpot,373,16398 +splashinvisibilitypotion,373,16398 +splashinvisiblepotion,373,16398 +splashinvpotion,373,16398 +splashinvisibilitypot,373,16398 +splashinvisiblepot,373,16398 +splashinvpot,373,16398 +splinvisibilitypotion,373,16398 +splinvisiblepotion,373,16398 +splinvpotion,373,16398 +splinvisibilitypot,373,16398 +splinvisiblepot,373,16398 +splinvpot,373,16398 +spipot,373,16398 +splashthinextendedpotion,373,16399 +splashthinexpotion,373,16399 +splashthin2potion,373,16399 +splashthinextendedpot,373,16399 +splashthinexpot,373,16399 +splashthin2pot,373,16399 +splthinextendedpotion,373,16399 +splthinexpotion,373,16399 +splthin2potion,373,16399 +splthinextendedpot,373,16399 +splthinexpot,373,16399 +splthin2pot,373,16399 +splashawkwardpotion,373,16400 +splashawkwardpot,373,16400 +splawkwardpotion,373,16400 +splawkwardpot,373,16400 +splashbunglingpotion,373,16406 +splashbunglingpot,373,16406 +splbunglingpotion,373,16406 +splbunglingpot,373,16406 +splashbunglingextendedpotion,373,16407 +splashbunglingexpotion,373,16407 +splashbungling2potion,373,16407 +splashbunglingextendedpot,373,16407 +splashbunglingexpot,373,16407 +splashbungling2pot,373,16407 +splbunglingextendedpotion,373,16407 +splbunglingexpotion,373,16407 +splbungling2potion,373,16407 +splbunglingextendedpot,373,16407 +splbunglingexpot,373,16407 +splbungling2pot,373,16407 +splashsmoothpotion,373,16411 +splashsmoothpot,373,16411 +splsmoothpotion,373,16411 +splsmoothpot,373,16411 +splashsuavepotion,373,16413 +splashsuavepot,373,16413 +splsuavepotion,373,16413 +splsuavepot,373,16413 +splashdebonairpotion,373,16414 +splashdebonairpot,373,16414 +spldebonairpotion,373,16414 +spldebonairpot,373,16414 +splashdebonairextendedpotion,373,16415 +splashdebonairexpotion,373,16415 +splashdebonair2potion,373,16415 +splashdebonairextendedpot,373,16415 +splashdebonairexpot,373,16415 +splashdebonair2pot,373,16415 +spldebonairextendedpotion,373,16415 +spldebonairexpotion,373,16415 +spldebonair2potion,373,16415 +spldebonairextendedpot,373,16415 +spldebonairexpot,373,16415 +spldebonair2pot,373,16415 +splashthickpotion,373,16416 +splashthickpot,373,16416 +splthickpotion,373,16416 +splthickpot,373,16416 +splashregenerationleveliipotion,373,16417 +splashregenerateleveliipotion,373,16417 +splashregenleveliipotion,373,16417 +splashregenerationlevel2potion,373,16417 +splashregeneratelevel2potion,373,16417 +splashregenlevel2potion,373,16417 +splashregenerationiipotion,373,16417 +splashregenerateiipotion,373,16417 +splashregeniipotion,373,16417 +splashregenerationleveliipot,373,16417 +splashregenerateleveliipot,373,16417 +splashregenleveliipot,373,16417 +splashregenerationlevel2pot,373,16417 +splashregeneratelevel2pot,373,16417 +splashregenlevel2pot,373,16417 +splashregenerationiipot,373,16417 +splashregenerateiipot,373,16417 +splashregeniipot,373,16417 +splregenerationleveliipotion,373,16417 +splregenerateleveliipotion,373,16417 +splregenleveliipotion,373,16417 +splregenerationlevel2potion,373,16417 +splregeneratelevel2potion,373,16417 +splregenlevel2potion,373,16417 +splregenerationiipotion,373,16417 +splregenerateiipotion,373,16417 +splregeniipotion,373,16417 +splregenerationleveliipot,373,16417 +splregenerateleveliipot,373,16417 +splregenleveliipot,373,16417 +splregenerationlevel2pot,373,16417 +splregeneratelevel2pot,373,16417 +splregenlevel2pot,373,16417 +splregenerationiipot,373,16417 +splregenerateiipot,373,16417 +splregeniipot,373,16417 +spr2pot,373,16417 +splashswiftnessleveliipotion,373,16418 +splashswiftleveliipotion,373,16418 +splashspeedleveliipotion,373,16418 +splashswiftnesslevel2potion,373,16418 +splashswiftlevel2potion,373,16418 +splashspeedlevel2potion,373,16418 +splashswiftnessiipotion,373,16418 +splashswiftiipotion,373,16418 +splashspeediipotion,373,16418 +splashswiftnessleveliipot,373,16418 +splashswiftleveliipot,373,16418 +splashspeedleveliipot,373,16418 +splashswiftnesslevel2pot,373,16418 +splashswiftlevel2pot,373,16418 +splashspeedlevel2pot,373,16418 +splashswiftnessiipot,373,16418 +splashswiftiipot,373,16418 +splashspeediipot,373,16418 +splswiftnessleveliipotion,373,16418 +splswiftleveliipotion,373,16418 +splspeedleveliipotion,373,16418 +splswiftnesslevel2potion,373,16418 +splswiftlevel2potion,373,16418 +splspeedlevel2potion,373,16418 +splswiftnessiipotion,373,16418 +splswiftiipotion,373,16418 +splspeediipotion,373,16418 +splswiftnessleveliipot,373,16418 +splswiftleveliipot,373,16418 +splspeedleveliipot,373,16418 +splswiftnesslevel2pot,373,16418 +splswiftlevel2pot,373,16418 +splspeedlevel2pot,373,16418 +splswiftnessiipot,373,16418 +splswiftiipot,373,16418 +splspeediipot,373,16418 +spsw2pot,373,16418 +splashpoisonleveliipotion,373,16420 +splashacidleveliipotion,373,16420 +splashpoisonlevel2potion,373,16420 +splashacidlevel2potion,373,16420 +splashpoisoniipotion,373,16420 +splashacidiipotion,373,16420 +splashpoisonleveliipot,373,16420 +splashacidleveliipot,373,16420 +splashpoisonlevel2pot,373,16420 +splashacidlevel2pot,373,16420 +splashpoisoniipot,373,16420 +splashacidiipot,373,16420 +splpoisonleveliipotion,373,16420 +splacidleveliipotion,373,16420 +splpoisonlevel2potion,373,16420 +splcidlevel2potion,373,16420 +splpoisoniipotion,373,16420 +splacidiipotion,373,16420 +splpoisonleveliipot,373,16420 +splacidleveliipot,373,16420 +splpoisonlevel2pot,373,16420 +splacidlevel2pot,373,16420 +splpoisoniipot,373,16420 +splacidiipot,373,16420 +spp2pot,373,16420 +splashhealingleveliipotion,373,16421 +splashhealleveliipotion,373,16421 +splashhealinglevel2potion,373,16421 +splashheallevel2potion,373,16421 +splashhealingiipotion,373,16421 +splashhealiipotion,373,16421 +splashhealingleveliipot,373,16421 +splashhealleveliipot,373,16421 +splashhealinglevel2pot,373,16421 +splashheallevel2pot,373,16421 +splashhealingiipot,373,16421 +splashhealiipot,373,16421 +splhealingleveliipotion,373,16421 +splhealleveliipotion,373,16421 +splhealinglevel2potion,373,16421 +splheallevel2potion,373,16421 +splhealingiipotion,373,16421 +splhealiipotion,373,16421 +splhealingleveliipot,373,16421 +splhealleveliipot,373,16421 +splhealinglevel2pot,373,16421 +splheallevel2pot,373,16421 +splhealingiipot,373,16421 +splhealiipot,373,16421 +sph2pot,373,16421 +splashcharmingpotion,373,16422 +splashcharmingpot,373,16422 +splcharmingpotion,373,16422 +splcharmingpot,373,16422 +splashcharmingextendedpotion,373,16423 +splashcharmingexpotion,373,16423 +splashcharming2potion,373,16423 +splashcharmingextendedpot,373,16423 +splashcharmingexpot,373,16423 +splashcharming2pot,373,16423 +splcharmingextendedpotion,373,16423 +splcharmingexpotion,373,16423 +splcharming2potion,373,16423 +splcharmingextendedpot,373,16423 +splcharmingexpot,373,16423 +splcharming2pot,373,16423 +splashstrengthleveliipotion,373,16425 +splashstrongleveliipotion,373,16425 +splashstrleveliipotion,373,16425 +splashstrengthlevel2potion,373,16425 +splashstronglevel2potion,373,16425 +splashstrlevel2potion,373,16425 +splashstrengthiipotion,373,16425 +splashstrongiipotion,373,16425 +splashstriipotion,373,16425 +splashstrengthleveliipot,373,16425 +splashstrongleveliipot,373,16425 +splashstrleveliipot,373,16425 +splashstrengthlevel2pot,373,16425 +splashstronglevel2pot,373,16425 +splashstrlevel2pot,373,16425 +splashstrengthiipot,373,16425 +splashstrongiipot,373,16425 +splashstriipot,373,16425 +splstrengthleveliipotion,373,16425 +splstrongleveliipotion,373,16425 +splstrleveliipotion,373,16425 +splstrengthlevel2potion,373,16425 +splstronglevel2potion,373,16425 +splstrlevel2potion,373,16425 +splstrengthiipotion,373,16425 +splstrongiipotion,373,16425 +splstriipotion,373,16425 +splstrengthleveliipot,373,16425 +splstrongleveliipot,373,16425 +splstrleveliipot,373,16425 +splstrengthlevel2pot,373,16425 +splstronglevel2pot,373,16425 +splstrlevel2pot,373,16425 +splstrengthiipot,373,16425 +splstrongiipot,373,16425 +splstriipot,373,16425 +spst2pot,373,16425 +splashrefinedpotion,373,16427 +splashrefinedpot,373,16427 +splrefinedpotion,373,16427 +splrefinedpot,373,16427 +splashharmingleveliipotion,373,16428 +splashdamageleveliipotion,373,16428 +splashdmgleveliipotion,373,16428 +splashharminglevel2potion,373,16428 +splashdamagelevel2potion,373,16428 +splashdmglevel2potion,373,16428 +splashharmingiipotion,373,16428 +splashdamageiipotion,373,16428 +splashdmgiipotion,373,16428 +splashharmingleveliipot,373,16428 +splashdamageleveliipot,373,16428 +splashdmgleveliipot,373,16428 +splashharminglevel2pot,373,16428 +splashdamagelevel2pot,373,16428 +splashdmglevel2pot,373,16428 +splashharmingiipot,373,16428 +splashdamageiipot,373,16428 +splashdmgiipot,373,16428 +splharmingleveliipotion,373,16428 +spldamageleveliipotion,373,16428 +spldmgleveliipotion,373,16428 +splharminglevel2potion,373,16428 +spldamagelevel2potion,373,16428 +spldmglevel2potion,373,16428 +splharmingiipotion,373,16428 +spldamageiipotion,373,16428 +spldmgiipotion,373,16428 +splharmingleveliipot,373,16428 +spldamageleveliipot,373,16428 +spldmgleveliipot,373,16428 +splharminglevel2pot,373,16428 +spldamagelevel2pot,373,16428 +spldmglevel2pot,373,16428 +splharmingiipot,373,16428 +spldamageiipot,373,16428 +spldmgiipot,373,16428 +spd2pot,373,16428 +splashcordialpotion,373,16429 +splashcordialpot,373,16429 +splcordialpotion,373,16429 +splcordialpot,373,16429 +splashsparklingpotion,373,16430 +splashsparklingpot,373,16430 +splsparklingpotion,373,16430 +splsparklingpot,373,16430 +splashsparklingextendedpotion,373,16431 +splashsparklingexpotion,373,16431 +splashsparkling2potion,373,16431 +splashsparklingextendedpot,373,16431 +splashsparklingexpot,373,16431 +splashsparkling2pot,373,16431 +splsparklingextendedpotion,373,16431 +splsparklingexpotion,373,16431 +splsparkling2potion,373,16431 +splsparklingextendedpot,373,16431 +splsparklingexpot,373,16431 +splsparkling2pot,373,16431 +splashpotentpotion,373,16432 +splashpotentpot,373,16432 +splpotentpotion,373,16432 +splpotentpot,373,16432 +splashrankpotion,373,16438 +splashrankpot,373,16438 +splrankpotion,373,16438 +splrankpot,373,16438 +splashrankextendedpotion,373,16439 +splashrankexpotion,373,16439 +splashrank2potion,373,16439 +splashrankextendedpot,373,16439 +splashrankexpot,373,16439 +splashrank2pot,373,16439 +splrankextendedpotion,373,16439 +splrankexpotion,373,16439 +splrank2potion,373,16439 +splrankextendedpot,373,16439 +splrankexpot,373,16439 +splrank2pot,373,16439 +splashacridpotion,373,16443 +splashacridpot,373,16443 +splacridpotion,373,16443 +splacridpot,373,16443 +splashgrosspotion,373,16445 +splashgrosspot,373,16445 +splgrosspotion,373,16445 +splgrosspot,373,16445 +splashstinkypotion,373,16446 +splashstinkypot,373,16446 +splstinkypotion,373,16446 +splstinkypot,373,16446 +splashstinkyextendedpotion,373,16447 +splashstinkyexpotion,373,16447 +splashstinky2potion,373,16447 +splashstinkyextendedpot,373,16447 +splashstinkyexpot,373,16447 +splashstinky2pot,373,16447 +splstinkyextendedpotion,373,16447 +splstinkyexpotion,373,16447 +splstinky2potion,373,16447 +splstinkyextendedpot,373,16447 +splstinkyexpot,373,16447 +splstinky2pot,373,16447 +splashmundaneextendedpotion,373,16448 +splashmundaneexpotion,373,16448 +splashmundane2potion,373,16448 +splashmundaneextendedpot,373,16448 +splashmundaneexpot,373,16448 +splashmundane2pot,373,16448 +splmundaneextendedpotion,373,16448 +splmundaneexpotion,373,16448 +splmundane2potion,373,16448 +splmundaneextendedpot,373,16448 +splmundaneexpot,373,16448 +splmundane2pot,373,16448 +splashregenerationextendedpotion,373,16449 +splashregenerateextendedpotion,373,16449 +splashregenextendepotion,373,16449 +splashregenerationexpotion,373,16449 +splashregenerateexpotion,373,16449 +splashregenexpotion,373,16449 +splashregenerationextendedpot,373,16449 +splashregenerateextendedpot,373,16449 +splashregenextendepot,373,16449 +splashregenerationexpot,373,16449 +splashregenerateexpot,373,16449 +splashregenexpot,373,16449 +splregenerationextendedpotion,373,16449 +splregenerateextendedpotion,373,16449 +splregenextendepotion,373,16449 +splregenerationexpotion,373,16449 +splregenerateexpotion,373,16449 +splregenexpotion,373,16449 +splregenerationextendedpot,373,16449 +splregenerateextendedpot,373,16449 +splregenextendepot,373,16449 +splregenerationexpot,373,16449 +splregenerateexpot,373,16449 +splregenexpot,373,16449 +sprepot,373,16449 +splashswiftnessextendedpotion,373,16450 +splashswiftextendedpotion,373,16450 +splashspeedextendedpotion,373,16450 +splashswiftnessexpotion,373,16450 +splashswiftexpotion,373,16450 +splashspeedexpotion,373,16450 +splashswiftnessextendedpot,373,16450 +splashswiftextendedpot,373,16450 +splashspeedextendedpot,373,16450 +splashswiftnessexpot,373,16450 +splashswiftexpot,373,16450 +splashspeedexpot,373,16450 +splswiftnessextendedpotion,373,16450 +splswiftextendedpotion,373,16450 +splspeedextendedpotion,373,16450 +splswiftnessexpotion,373,16450 +splswiftexpotion,373,16450 +splspeedexpotion,373,16450 +splswiftnessextendedpot,373,16450 +splswiftextendedpot,373,16450 +splspeedextendedpot,373,16450 +splswiftnessexpot,373,16450 +splswiftexpot,373,16450 +splspeedexpot,373,16450 +spswepot,373,16450 +splashfireresistanceextendedpotion,373,16451 +splashfireresistextendedpotion,373,16451 +splashfireresextendedpotion,373,16451 +splashfireresistanceexpotion,373,16451 +splashfireresistexpotion,373,16451 +splashfireresexpotion,373,16451 +splashfireresistanceextendedpot,373,16451 +splashfireresistextendedpot,373,16451 +splashfireresextendedpot,373,16451 +splashfireresistanceexpot,373,16451 +splashfireresistexpot,373,16451 +splashfireresexpot,373,16451 +splfireresistanceextendedpotion,373,16451 +splfireresistextendedpotion,373,16451 +splfireresextendedpotion,373,16451 +splfireresistanceexpotion,373,16451 +splfireresistexpotion,373,16451 +splfireresexpotion,373,16451 +splfireresistanceextendedpot,373,16451 +splfireresistextendedpot,373,16451 +splfireresextendedpot,373,16451 +splfireresistanceexpot,373,16451 +splfireresistexpot,373,16451 +splfireresexpot,373,16451 +spfepot,373,16451 +splashpoisonextendedpotion,373,16452 +splashacidextendedpotion,373,16452 +splashpoisonexpotion,373,16452 +splashacidexpotion,373,16452 +splashpoisonextendedpot,373,16452 +splashacidextendedpot,373,16452 +splashpoisonexpot,373,16452 +splashacidexpot,373,16452 +splpoisonextendedpotion,373,16452 +splacidextendedpotion,373,16452 +splpoisonexpotion,373,16452 +splacidexpotion,373,16452 +splpoisonextendedpot,373,16452 +splacidextendedpot,373,16452 +splpoisonexpot,373,16452 +splacidexpot,373,16452 +sppepot,373,16452 +splashnightvisionextendedpotion,373,16454 +splashnvisionextendedpotion,373,16454 +splashnightvextendedpotion,373,16454 +splashdarkvisionextendedpotion,373,16454 +splashdvisionextendedpotion,373,16454 +splashdarkvextendedpotion,373,16454 +splashnightvisionextendedpot,373,16454 +splashnvisionextendedpot,373,16454 +splashnightvextendedpot,373,16454 +splashdarkvisionextendedpot,373,16454 +splashdvisionextendedpot,373,16454 +splashdarkvextendedpot,373,16454 +splashnightvisionexpotion,373,16454 +splashnvisionexpotion,373,16454 +splashnightvexpotion,373,16454 +splashdarkvisionexpotion,373,16454 +splashdvisionexpotion,373,16454 +splashdarkvexpotion,373,16454 +splashnightvisionexpot,373,16454 +splashnvisionexpot,373,16454 +splashnightvexpot,373,16454 +splashdarkvisionexpot,373,16454 +splashdvisionexpot,373,16454 +splashdarkvexpot,373,16454 +splnightvisionextendedpotion,373,16454 +splnvisionextendedpotion,373,16454 +splnightvextendedpotion,373,16454 +spldarkvisionextendedpotion,373,16454 +spldvisionextendedpotion,373,16454 +spldarkvextendedpotion,373,16454 +splnightvisionextendedpot,373,16454 +splnvisionextendedpot,373,16454 +splnightvextendedpot,373,16454 +spldarkvisionextendedpot,373,16454 +spldvisionextendedpot,373,16454 +spldarkvextendedpot,373,16454 +splnightvisionexpotion,373,16454 +splnvisionexpotion,373,16454 +splnightvexpotion,373,16454 +spldarkvisionexpotion,373,16454 +spldvisionexpotion,373,16454 +spldarkvexpotion,373,16454 +splnightvisionexpot,373,16454 +splnvisionexpot,373,16454 +splnightvexpot,373,16454 +spldarkvisionexpot,373,16454 +spldvisionexpot,373,16454 +spldarkvexpot,373,16454 +spnepot,373,16454 +splashweaknessextendedpotion,373,16456 +splashweakextendedpotion,373,16456 +splashweaknessexpotion,373,16456 +splashweakexpotion,373,16456 +splashweaknessextendedpot,373,16456 +splashweakextendedpot,373,16456 +splashweaknessexpot,373,16456 +splashweakexpot,373,16456 +splweaknessextendedpotion,373,16456 +sphweakextendedpotion,373,16456 +splweaknessexpotion,373,16456 +splweakexpotion,373,16456 +splweaknessextendedpot,373,16456 +splweakextendedpot,373,16456 +splweaknessexpot,373,16456 +splweakexpot,373,16456 +spwepot,373,16456 +splashstrengthextendedpotion,373,16457 +splashstrongextendedpotion,373,16457 +splashstrextendedpotion,373,16457 +splashstrengthexpotion,373,16457 +splashstrongexpotion,373,16457 +splashstrexpotion,373,16457 +splashstrengthextendedpot,373,16457 +splashstrongextendedpot,373,16457 +splashstrextendedpot,373,16457 +splashstrengthexpot,373,16457 +splashstrongexpot,373,16457 +splashstrexpot,373,16457 +splstrengthextendedpotion,373,16457 +splstrongextendedpotion,373,16457 +splstrextendedpotion,373,16457 +splstrengthexpotion,373,16457 +splstrongexpotion,373,16457 +splstrexpotion,373,16457 +splstrengthextendedpot,373,16457 +splstrongextendedpot,373,16457 +splstrextendedpot,373,16457 +splstrengthexpot,373,16457 +splstrongexpot,373,16457 +splstrexpot,373,16457 +spstepot,373,16457 +splashslownessextendedpotion,373,16458 +splashslowextenedpotion,373,16458 +splashslownessexpotion,373,16458 +splashslowexpotion,373,16458 +splashslownessextendedpot,373,16458 +splashslowextenedpot,373,16458 +splashslownessexpot,373,16458 +splashslowexpot,373,16458 +splslownessextendedpotion,373,16458 +splslowextenedpotion,373,16458 +splslownessexpotion,373,16458 +splslowexpotion,373,16458 +splslownessextendedpot,373,16458 +splslowextenedpot,373,16458 +splslownessexpot,373,16458 +splslowexpot,373,16458 +spslepot,373,16458 +splashinvisibilityextendedpotion,373,16462 +splashinvisibleextendedpotion,373,16462 +splashinvextendedpotion,373,16462 +splashinvisibilityextendedpot,373,16462 +splashinvisibleextendedpot,373,16462 +splashinvextendedpot,373,16462 +splashinvisibilityexpotion,373,16462 +splashinvisibleexpotion,373,16462 +splashinvexpotion,373,16462 +splashinvisibilityexpot,373,16462 +splashinvisibleexpot,373,16462 +splashinvexpot,373,16462 +splinvisibilityextendedpotion,373,16462 +splinvisibleextendedpotion,373,16462 +splinvextendedpotion,373,16462 +splinvisibilityextendedpot,373,16462 +splinvisibleextendedpot,373,16462 +splinvextendedpot,373,16462 +splinvisibilityexpotion,373,16462 +splinvisibleexpotion,373,16462 +splinvexpotion,373,16462 +splinvisibilityexpot,373,16462 +splinvisibleexpot,373,16462 +splinvexpot,373,16462 +spiepot,373,16462 +splashregenerationdualbitpotion,373,16481 +splashregeneratedualbitpotion,373,16481 +splashregendualbitpotion,373,16481 +splashregenerationdualbitpot,373,16481 +splashregeneratedualbitpot,373,16481 +splashregendualbitpot,373,16481 +splregenerationdualbitpotion,373,16481 +splregeneratedualbitpotion,373,16481 +splregendualbitpotion,373,16481 +splregenerationdualbitpot,373,16481 +splregeneratedualbitpot,373,16481 +splregendualbitpot,373,16481 +splashregenerationdbpotion,373,16481 +splashregeneratedbpotion,373,16481 +splashregendbpotion,373,16481 +splashregenerationdbpot,373,16481 +splashregeneratedbpot,373,16481 +splashregendbpot,373,16481 +splregenerationdbpotion,373,16481 +splregeneratedbpotion,373,16481 +splregendbpotion,373,16481 +splregenerationdbpot,373,16481 +splregeneratedbpot,373,16481 +splregendbpot,373,16481 +sprdbpot,373,16481 +splashswiftnessdualbitpotion,373,16482 +splashswiftdualbitpotion,373,16482 +splashspeeddualbitpotion,373,16482 +splashswiftnessdualbitpot,373,16482 +splashswiftdualbitpot,373,16482 +splashspeeddualbitpot,373,16482 +splswiftnessdualbitpotion,373,16482 +splswiftdualbitpotion,373,16482 +splspeeddualbitpotion,373,16482 +splswiftnessdualbitpot,373,16482 +splswiftdualbitpot,373,16482 +splspeeddualbitpot,373,16482 +splashswiftnessdbpotion,373,16482 +splashswiftdbpotion,373,16482 +splashspeeddbpotion,373,16482 +splashswiftnessdbpot,373,16482 +splashswiftdbpot,373,16482 +splashspeeddbpot,373,16482 +splswiftnessdbpotion,373,16482 +splswiftdbpotion,373,16482 +splspeeddbpotion,373,16482 +splswiftnessdbpot,373,16482 +splswiftdbpot,373,16482 +splspeeddbpot,373,16482 +spswdbpot,373,16482 +splashpoisondualbitpotion,373,16484 +splashaciddualbitpotion,373,16484 +splashpoisondualbitpot,373,16484 +splashaciddualbitpot,373,16484 +splpoisondualbitpotion,373,16484 +splaciddualbitpotion,373,16484 +splpoisondualbitpot,373,16484 +splaciddualbitpot,373,16484 +splashpoisondbpotion,373,16484 +splashaciddbpotion,373,16484 +splashpoisondbpot,373,16484 +splashaciddbpot,373,16484 +splpoisondbpotion,373,16484 +splaciddbpotion,373,16484 +splpoisondbpot,373,16484 +splaciddbpot,373,16484 +sppdbpot,373,16484 +splashstrengthdualbitpotion,373,16489 +splashstrongdualbitpotion,373,16489 +splashstrdualbitpotion,373,16489 +splashstrengthdualbitpot,373,16489 +splashstrongdualbitpot,373,16489 +splashstrdualbitpot,373,16489 +splstrengthdualbitpotion,373,16489 +splstrongdualbitpotion,373,16489 +splstrdualbitpotion,373,16489 +splstrengthdualbitpot,373,16489 +splstrongdualbitpot,373,16489 +splstrdualbitpot,373,16489 +splashstrengthdbpotion,373,16489 +splashstrongdbpotion,373,16489 +splashstrdbpotion,373,16489 +splashstrengthdbpot,373,16489 +splashstrongdbpot,373,16489 +splashstrdbpot,373,16489 +splstrengthdbpotion,373,16489 +splstrongdbpotion,373,16489 +splstrdbpotion,373,16489 +splstrengthdbpot,373,16489 +splstrongdbpot,373,16489 +splstrdbpot,373,16489 +spstdbpot,373,16489 +hp1,373,8197 +hp2,373,8229 +rp1,373,8193 +rp1e,373,8257 +rp2,373,8225 +dp1,373,8204 +dp2,373,8236 +swp1,373,8194 +swp1e,373,0 +swp2,373,8226 +slp1,373,8202 +slp1e,373,8266 +strp1,373,8201 +strp1e,373,8265 +strp2,373,8233 +wp1,373,8200 +wp1e,373,8264 +pp1,373,8196 +pp1e,373,8260 +pp2,373,8228 +frp1,373,8195 +frp1e,373,8259 +invp,373,8270 +nvp,373,8262 +dp1s,373,16460 +dp2s,373,16428 +swp1s,373,16386 +swp1es,373,0 +swp2s,373,16418 +slp1s,373,16394 +slp1es,373,16458 +strp1s,373,16393 +strp1es,373,16457 +strp2s,373,16425 +wp1s,373,16456 +wp1es,373,16424 +pp1s,373,16388 +pp1es,373,16452 +pp2s,373,16420 +frp1s,373,16387 +frp1es,373,16451 +invps,373,16430 +nvps,373,16422 +glassbottle,374,0 +bottle,374,0 +gbottle,374,0 +gvase,374,0 +vase,374,0 +glassvase,374,0 +emptyglassbottle,374,0 +emptybottle,374,0 +emptygbottle,374,0 +emptygvase,374,0 +emptyvase,374,0 +emptyglassvase,374,0 +eglassbottle,374,0 +ebottle,374,0 +egbottle,374,0 +egvase,374,0 +evase,374,0 +eglassvase,374,0 +spidereye,375,0 +eyeofspider,375,0 +spiderseye,375,0 +spiderball,375,0 +spidernugget,375,0 +spidersball,375,0 +spidersnugget,375,0 +seye,375,0 +sball,375,0 +snugget,375,0 +fermentedspidereye,376,0 +craftedspidereye,376,0 +fermentedeyeofspider,376,0 +craftedeyeofspider,376,0 +fspidereye,376,0 +feyeofspider,376,0 +ceyeofspider,376,0 +cspidereye,376,0 +blazepowder,377,0 +blazedust,377,0 +goldpowder,377,0 +golddust,377,0 +gdust,377,0 +gpowder,377,0 +bpowder,377,0 +bdust,377,0 +magmacream,378,0 +goldcream,378,0 +blazecream,378,0 +mcream,378,0 +gcream,378,0 +bcream,378,0 +combinedcream,378,0 +ccream,378,0 +bstand,379,0 +pstand,379,0 +brewingstand,379,0 +potionstand,379,0 +cauldronitem,380,0 +ironcauldronitem,380,0 +steelcauldronitem,380,0 +icauldronitem,380,0 +scauldronitem,380,0 +eyeofender,381,0 +endereye,381,0 +endeye,381,0 +evilendereye,381,0 +evileyeofender,381,0 +evilenderpearl,381,0 +eeye,381,0 +eofender,381,0 +speckledmelon,382,0 +goldmelon,382,0 +sparklymelon,382,0 +glisteningmelon,382,0 +glisteringmelon,382,0 +shiningmelon,382,0 +gmelon,382,0 +smelon,382,0 +creeperegg,383,50 +eggcreeper,383,50 +skeletonegg,383,51 +eggskeleton,383,51 +spideregg,383,52 +eggspider,383,52 +giantegg,383,53 +egggiant,383,53 +zombieegg,383,54 +eggzombie,383,54 +slimeegg,383,55 +eggslime,383,55 +ghastegg,383,56 +eggghast,383,56 +zombiepigmanegg,383,57 +zpigmanegg,383,57 +pigmanegg,383,57 +zombiepmanegg,383,57 +zpmanegg,383,57 +zombiepigmegg,383,57 +zpigmegg,383,57 +zombiepigegg,383,57 +zpigegg,383,57 +zombiepmegg,383,57 +zombiepegg,383,57 +eggzombiepigman,383,57 +eggzpigman,383,57 +eggpigman,383,57 +eggzombiepman,383,57 +eggzpman,383,57 +eggzombiepigm,383,57 +eggzpigm,383,57 +eggzombiepig,383,57 +eggzpig,383,57 +eggzombiepm,383,57 +eggzombiep,383,57 +endermanegg,383,58 +eggenderman,383,58 +eggcavespider,383,59 +cavespideregg,383,59 +silverfishegg,383,60 +eggsilverfish,383,60 +blazeegg,383,61 +eggblaze,383,61 +lavaslimeegg,383,62 +lavacubeegg,383,62 +magmacubeegg,383,62 +magmaslimeegg,383,62 +egglavaslime,383,62 +egglavacube,383,62 +eggmagmacube,383,62 +eggmagmaslime,383,62 +bategg,383,65 +eggbat,383,65 +witchegg,383,66 +eggwitch,383,66 +pigegg,383,90 +eggpig,383,90 +sheepegg,383,91 +eggsheep,383,91 +cowegg,383,92 +eggcow,383,92 +chickenegg,383,93 +eggchicken,383,93 +squidegg,383,94 +eggsquid,383,94 +wolfegg,383,95 +eggwolf,383,95 +snowgolemegg,383,97 +sgolemegg,383,97 +eggsnowgolem,383,97 +eggsgolem,383,97 +ocelotegg,383,98 +eggocelot,383,98 +irongolemegg,383,99 +igolemegg,383,99 +eggirongolem,383,99 +eggigolem,383,99 +egghorse,383,100 +horseegg,383,100 +villageregg,383,120 +eggvillager,383,120 +bottleofenchanting,384,0 +enchantingbottle,384,0 +expbottle,384,0 +xpbottle,384,0 +bottleexp,384,0 +bottlexp,384,0 +enchantbottle,384,0 +bottleenchanting,384,0 +bottleenchant,384,0 +bottleoenchanting,384,0 +firecharge,385,0 +fireball,385,0 +grenade,385,0 +bookandquill,386,0 +booknquill,386,0 +bookandfeather,386,0 +booknfeather,386,0 +writeablebook,386,0 +writtenbook,387,0 +readablebook,387,0 +sealedbook,387,0 +diary,387,0 +ownedbook,387,0 +emerald,388,0 +itemframe,389,0 +pictureframe,389,0 +iframe,389,0 +pframe,389,0 +flowerpot,390,0 +pot,390,0 +carrot,391,0 +potato,392,0 +bakedpotato,393,0 +roastedpotato,393,0 +cookedpotato,393,0 +bakepotato,393,0 +roastpotato,393,0 +cookpotato,393,0 +poisonouspotato,394,0 +poisonpotato,394,0 +ppotato,394,0 +emptymap,395,0 +map,395,0 +goldencarrot,396,0 +goldcarrot,396,0 +gcarrot,396,0 +head,397,0 +skeletonhead,397,0 +headskeleton,397,0 +skeletonskull,397,0 +skullskeleton,397,0 +witherhead,397,1 +witherskeletonhead,397,1 +wskeletionhead,397,1 +headwither,397,1 +headwitherskeleton,397,1 +headwskeletion,397,1 +witherskull,397,1 +witherskeletonskull,397,1 +wskeletionskull,397,1 +skullwither,397,1 +skullwitherskeleton,397,1 +skullwskeletion,397,1 +zombiehead,397,2 +headzombie,397,2 +zombieskull,397,2 +skullzombie,397,2 +playerhead,397,3 +humanhead,397,3 +stevehead,397,3 +headplayer,397,3 +headhuman,397,3 +headsteve,397,3 +playerskull,397,3 +humanskull,397,3 +steveskull,397,3 +skullplayer,397,3 +skullhuman,397,3 +skullsteve,397,3 +creeperhead,397,4 +headcreeper,397,4 +creeperskull,397,4 +skullcreeper,397,4 +carrotonastick,398,0 +carrotonstick,398,0 +netherstar,399,0 +hellstar,399,0 +nstar,399,0 +hstar,399,0 +star,399,0 +pumpkinpie,400,0 +pumpkincake,400,0 +ppie,400,0 +pcake,400,0 +pie,400,0 +fireworkrocket,401,0 +fireworkmissle,401,0 +firework,401,0 +fworkrocket,401,0 +fworkmissle,401,0 +fwork,401,0 +fwrocket,401,0 +fwmissle,401,0 +fireworkstar,402,0 +fworkstar,402,0 +fwstar,402,0 +fireworkball,402,0 +fworkball,402,0 +fwball,402,0 +fireworkpowder,402,0 +fworkpowder,402,0 +fwpowder,402,0 +fireworkcharge,402,0 +fworkcharge,402,0 +fwcharge,402,0 +enchantmentbook,403,0 +enchantedbook,403,0 +enchantingbook,403,0 +enchantbook,403,0 +magicalbook,403,0 +magicbook,403,0 +ebook,403,0 +mbook,403,0 +redstonecomparator,404,0 +redstonecomperer,404,0 +redstonecompare,404,0 +rstonecomparator,404,0 +rstonecomperer,404,0 +rstonecompare,404,0 +redscomparator,404,0 +redscomperer,404,0 +redscompare,404,0 +rscomparator,404,0 +rscomperer,404,0 +rscompare,404,0 +comparator,404,0 +comperer,404,0 +compare,404,0 +netherbrick,405,0 +nbrick,405,0 +hellbrick,405,0 +deathbrick,405,0 +dbrick,405,0 +hbrick,405,0 +netherquartz,406,0 +deathquartz,406,0 +hellquartz,406,0 +nquartz,406,0 +dquartz,406,0 +hquartz,406,0 +quartz,406,0 +dynamiteminecart,407,0 +dynamitemcart,407,0 +dynamitecart,407,0 +bombminecart,407,0 +bombmcart,407,0 +bombcart,407,0 +tntminecart,407,0 +tntmcart,407,0 +tntcart,407,0 +dminecart,407,0 +dmcart,407,0 +dcart,407,0 +bminecart,407,0 +bmcart,407,0 +bcart,407,0 +tminecart,407,0 +tmcart,407,0 +tcart,407,0 +hopperminecart,408,0 +hoppermcart,408,0 +hoppercart,408,0 +hopminecart,408,0 +hopmcart,408,0 +hopcart,408,0 +hminecart,408,0 +hmcart,408,0 +hcart,408,0 +ironhorsearmor,417,0 +ironharmor,417,0 +ironarmor,417,0 +ihorsearmor,417,0 +iharmor,417,0 +iarmor,417,0 +steelhorsearmor,417,0 +steelharmor,417,0 +steelarmor,417,0 +shorsearmor,417,0 +sharmor,417,0 +sarmor,417,0 +goldenhorsearmor,418,0 +goldenharmor,418,0 +goldenarmor,418,0 +goldhorsearmor,418,0 +goldharmor,418,0 +goldarmor,418,0 +ghorsearmor,418,0 +gharmor,418,0 +garmor,418,0 +diamondhorsearmor,419,0 +diamondharmor,419,0 +diamondarmor,419,0 +dhorsearmor,419,0 +dharmor,419,0 +darmor,419,0 +crystalhorsearmor,419,0 +crystalharmor,419,0 +crystalarmor,419,0 +chorsearmor,419,0 +charmor,419,0 +carmor,419,0 +lead,420,0 +leash,420,0 +rope,420,0 +nametag,421,0 +tag,421,0 +goldmusicrecord,2256,0 +goldmusicdisk,2256,0 +goldmusicdisc,2256,0 +goldmusiccd,2256,0 +13musicrecord,2256,0 +13musicdisk,2256,0 +13musicdisc,2256,0 +13musiccd,2256,0 +gomusicrecord,2256,0 +gomusicdisk,2256,0 +gomusicdisc,2256,0 +gomusiccd,2256,0 +goldmrecord,2256,0 +goldmdisk,2256,0 +goldmdisc,2256,0 +goldmcd,2256,0 +13mrecord,2256,0 +13mdisk,2256,0 +13mdisc,2256,0 +13mcd,2256,0 +gomrecord,2256,0 +gomdisk,2256,0 +gomdisc,2256,0 +gomcd,2256,0 +goldrecord,2256,0 +golddisk,2256,0 +golddisc,2256,0 +goldcd,2256,0 +13record,2256,0 +13disk,2256,0 +13disc,2256,0 +13cd,2256,0 +gorecord,2256,0 +godisk,2256,0 +godisc,2256,0 +gocd,2256,0 +record1,2256,0 +disk1,2256,0 +disc1,2256,0 +cd1,2256,0 +1record,2256,0 +1disk,2256,0 +1disc,2256,0 +1cd,2256,0 +greenmusicrecord,2257,0 +greenmusicdisk,2257,0 +greenmusicdisc,2257,0 +greenmusiccd,2257,0 +catmusicrecord,2257,0 +catmusicdisk,2257,0 +catmusicdisc,2257,0 +catmusiccd,2257,0 +grmusicrecord,2257,0 +grmusicdisk,2257,0 +grmusicdisc,2257,0 +grmusiccd,2257,0 +greenmrecord,2257,0 +greenmdisk,2257,0 +greenmdisc,2257,0 +greenmcd,2257,0 +catmrecord,2257,0 +catmdisk,2257,0 +catmdisc,2257,0 +catmcd,2257,0 +grmrecord,2257,0 +grmdisk,2257,0 +grmdisc,2257,0 +grmcd,2257,0 +greenrecord,2257,0 +greendisk,2257,0 +greendisc,2257,0 +greencd,2257,0 +catrecord,2257,0 +catdisk,2257,0 +catdisc,2257,0 +catcd,2257,0 +grrecord,2257,0 +grdisk,2257,0 +grdisc,2257,0 +grcd,2257,0 +record2,2257,0 +disk2,2257,0 +disc2,2257,0 +cd2,2257,0 +2record,2257,0 +2disk,2257,0 +2disc,2257,0 +2cd,2257,0 +orangemusicrecord,2258,0 +orangemusicdisk,2258,0 +orangemusicdisc,2258,0 +orangemusiccd,2258,0 +blocksmusicrecord,2258,0 +blocksmusicdisk,2258,0 +blocksmusicdisc,2258,0 +blocksmusiccd,2258,0 +ormusicrecord,2258,0 +ormusicdisk,2258,0 +ormusicdisc,2258,0 +ormusiccd,2258,0 +orangemrecord,2258,0 +orangemdisk,2258,0 +orangemdisc,2258,0 +orangemcd,2258,0 +blocksmrecord,2258,0 +blocksmdisk,2258,0 +blocksmdisc,2258,0 +blocksmcd,2258,0 +ormrecord,2258,0 +ormdisk,2258,0 +ormdisc,2258,0 +ormcd,2258,0 +orangerecord,2258,0 +orangedisk,2258,0 +orangedisc,2258,0 +orangecd,2258,0 +blocksrecord,2258,0 +blocksdisk,2258,0 +blocksdisc,2258,0 +blockscd,2258,0 +orrecord,2258,0 +ordisk,2258,0 +ordisc,2258,0 +orcd,2258,0 +record3,2258,0 +disk3,2258,0 +disc3,2258,0 +cd3,2258,0 +3record,2258,0 +3disk,2258,0 +3disc,2258,0 +3cd,2258,0 +redmusicrecord,2259,0 +redmusicdisk,2259,0 +redmusicdisc,2259,0 +redmusiccd,2259,0 +chirpmusicrecord,2259,0 +chirpmusicdisk,2259,0 +chirpmusicdisc,2259,0 +chirpmusiccd,2259,0 +remusicrecord,2259,0 +remusicdisk,2259,0 +remusicdisc,2259,0 +remusiccd,2259,0 +redmrecord,2259,0 +redmdisk,2259,0 +redmdisc,2259,0 +redmcd,2259,0 +chirpmrecord,2259,0 +chirpmdisk,2259,0 +chirpmdisc,2259,0 +chirpmcd,2259,0 +remrecord,2259,0 +remdisk,2259,0 +remdisc,2259,0 +remcd,2259,0 +redrecord,2259,0 +reddisk,2259,0 +reddisc,2259,0 +redcd,2259,0 +chirprecord,2259,0 +chirpdisk,2259,0 +chirpdisc,2259,0 +chirpcd,2259,0 +rerecord,2259,0 +redisk,2259,0 +redisc,2259,0 +recd,2259,0 +record4,2259,0 +disk4,2259,0 +disc4,2259,0 +cd4,2259,0 +4record,2259,0 +4disk,2259,0 +4disc,2259,0 +4cd,2259,0 +lightgreenmusicrecord,2260,0 +lightgreenmusicdisk,2260,0 +lightgreenmusicdisc,2260,0 +lightgreenmusiccd,2260,0 +lgreenmusicrecord,2260,0 +lgreenmusicdisk,2260,0 +lgreenmusicdisc,2260,0 +lgreenmusiccd,2260,0 +lightgrmusicrecord,2260,0 +lightgrmusicdisk,2260,0 +lightgrmusicdisc,2260,0 +lightgrmusiccd,2260,0 +farmusicrecord,2260,0 +farmusicdisk,2260,0 +farmusicdisc,2260,0 +farmusiccd,2260,0 +lgrmusicrecord,2260,0 +lgrmusicdisk,2260,0 +lgrmusicdisc,2260,0 +lgrmusiccd,2260,0 +lightgreenmrecord,2260,0 +lightgreenmdisk,2260,0 +lightgreenmdisc,2260,0 +lightgreenmcd,2260,0 +lgreenmrecord,2260,0 +lgreenmdisk,2260,0 +lgreenmdisc,2260,0 +lgreenmcd,2260,0 +lightgrmrecord,2260,0 +lightgrmdisk,2260,0 +lightgrmdisc,2260,0 +lightgrmcd,2260,0 +farmrecord,2260,0 +farmdisk,2260,0 +farmdisc,2260,0 +farmcd,2260,0 +lgrmrecord,2260,0 +lgrmdisk,2260,0 +lgrmdisc,2260,0 +lgrmcd,2260,0 +lightgreenrecord,2260,0 +lightgreendisk,2260,0 +lightgreendisc,2260,0 +lightgreencd,2260,0 +lgreenrecord,2260,0 +lgreendisk,2260,0 +lgreendisc,2260,0 +lgreencd,2260,0 +lightgrrecord,2260,0 +lightgrdisk,2260,0 +lightgrdisc,2260,0 +lightgrcd,2260,0 +farrecord,2260,0 +fardisk,2260,0 +fardisc,2260,0 +farcd,2260,0 +lgrrecord,2260,0 +lgrdisk,2260,0 +lgrdisc,2260,0 +lgrcd,2260,0 +record5,2260,0 +disk5,2260,0 +disc5,2260,0 +cd5,2260,0 +5record,2260,0 +5disk,2260,0 +5disc,2260,0 +5cd,2260,0 +purplemusicrecord,2261,0 +purplemusicdisk,2261,0 +purplemusicdisc,2261,0 +purplemusiccd,2261,0 +mallmusicrecord,2261,0 +mallmusicdisk,2261,0 +mallmusicdisc,2261,0 +mallmusiccd,2261,0 +pumusicrecord,2261,0 +pumusicdisk,2261,0 +pumusicdisc,2261,0 +pumusiccd,2261,0 +purplemrecord,2261,0 +purplemdisk,2261,0 +purplemdisc,2261,0 +purplemcd,2261,0 +mallmrecord,2261,0 +mallmdisk,2261,0 +mallmdisc,2261,0 +mallmcd,2261,0 +pumrecord,2261,0 +pumdisk,2261,0 +pumdisc,2261,0 +pumcd,2261,0 +purplerecord,2261,0 +purpledisk,2261,0 +purpledisc,2261,0 +purplecd,2261,0 +mallrecord,2261,0 +malldisk,2261,0 +malldisc,2261,0 +mallcd,2261,0 +purecord,2261,0 +pudisk,2261,0 +pudisc,2261,0 +pucd,2261,0 +record6,2261,0 +disk6,2261,0 +disc6,2261,0 +cd6,2261,0 +6record,2261,0 +6disk,2261,0 +6disc,2261,0 +6cd,2261,0 +pinkmusicrecord,2262,0 +pinkmusicdisk,2262,0 +pinkmusicdisc,2262,0 +pinkmusiccd,2262,0 +mellohimusicrecord,2262,0 +mellohimusicdisk,2262,0 +mellohimusicdisc,2262,0 +mellohimusiccd,2262,0 +pimusicrecord,2262,0 +pimusicdisk,2262,0 +pimusicdisc,2262,0 +pimusiccd,2262,0 +pinkmrecord,2262,0 +pinkmdisk,2262,0 +pinkmdisc,2262,0 +pinkmcd,2262,0 +mellohimrecord,2262,0 +mellohimdisk,2262,0 +mellohimdisc,2262,0 +mellohimcd,2262,0 +pimrecord,2262,0 +pimdisk,2262,0 +pimdisc,2262,0 +pimcd,2262,0 +pinkrecord,2262,0 +pinkdisk,2262,0 +pinkdisc,2262,0 +pinkcd,2262,0 +mellohirecord,2262,0 +mellohidisk,2262,0 +mellohidisc,2262,0 +mellohicd,2262,0 +pirecord,2262,0 +pidisk,2262,0 +pidisc,2262,0 +picd,2262,0 +record7,2262,0 +disk7,2262,0 +disc7,2262,0 +cd7,2262,0 +7record,2262,0 +7disk,2262,0 +7disc,2262,0 +7cd,2262,0 +blackmusicrecord,2263,0 +blackmusicdisk,2263,0 +blackmusicdisc,2263,0 +blackmusiccd,2263,0 +stalmusicrecord,2263,0 +stalmusicdisk,2263,0 +stalmusicdisc,2263,0 +stalmusiccd,2263,0 +blmusicrecord,2263,0 +blmusicdisk,2263,0 +blmusicdisc,2263,0 +blmusiccd,2263,0 +blackmrecord,2263,0 +blackmdisk,2263,0 +blackmdisc,2263,0 +blackmcd,2263,0 +stalmrecord,2263,0 +stalmdisk,2263,0 +stalmdisc,2263,0 +stalmcd,2263,0 +blmrecord,2263,0 +blmdisk,2263,0 +blmdisc,2263,0 +blmcd,2263,0 +blackrecord,2263,0 +blackdisk,2263,0 +blackdisc,2263,0 +blackcd,2263,0 +stalrecord,2263,0 +staldisk,2263,0 +staldisc,2263,0 +stalcd,2263,0 +blrecord,2263,0 +bldisk,2263,0 +bldisc,2263,0 +blcd,2263,0 +record8,2263,0 +disk8,2263,0 +disc8,2263,0 +cd8,2263,0 +8record,2263,0 +8disk,2263,0 +8disc,2263,0 +8cd,2263,0 +whitemusicrecord,2264,0 +whitemusicdisk,2264,0 +whitemusicdisc,2264,0 +whitemusiccd,2264,0 +stradmusicrecord,2264,0 +stradmusicdisk,2264,0 +stradmusicdisc,2264,0 +stradmusiccd,2264,0 +whmusicrecord,2264,0 +whmusicdisk,2264,0 +whmusicdisc,2264,0 +whmusiccd,2264,0 +whitemrecord,2264,0 +whitemdisk,2264,0 +whitemdisc,2264,0 +whitemcd,2264,0 +stradmrecord,2264,0 +stradmdisk,2264,0 +stradmdisc,2264,0 +stradmcd,2264,0 +whmrecord,2264,0 +whmdisk,2264,0 +whmdisc,2264,0 +whmcd,2264,0 +whiterecord,2264,0 +whitedisk,2264,0 +whitedisc,2264,0 +whitecd,2264,0 +stradrecord,2264,0 +straddisk,2264,0 +straddisc,2264,0 +stradcd,2264,0 +whrecord,2264,0 +whdisk,2264,0 +whdisc,2264,0 +whcd,2264,0 +record9,2264,0 +disk9,2264,0 +disc9,2264,0 +cd9,2264,0 +9record,2264,0 +9disk,2264,0 +9disc,2264,0 +9cd,2264,0 +darkgreenmusicrecord,2265,0 +darkgreenmusicdisk,2265,0 +darkgreenmusicdisc,2265,0 +darkgreenmusiccd,2265,0 +dgreenmusicrecord,2265,0 +dgreenmusicdisk,2265,0 +dgreenmusicdisc,2265,0 +dgreenmusiccd,2265,0 +darkgrmusicrecord,2265,0 +darkgrmusicdisk,2265,0 +darkgrmusicdisc,2265,0 +darkgrmusiccd,2265,0 +wardmusicrecord,2265,0 +wardmusicdisk,2265,0 +wardmusicdisc,2265,0 +wardmusiccd,2265,0 +dgrmusicrecord,2265,0 +dgrmusicdisk,2265,0 +dgrmusicdisc,2265,0 +dgrmusiccd,2265,0 +darkgreenmrecord,2265,0 +darkgreenmdisk,2265,0 +darkgreenmdisc,2265,0 +darkgreenmcd,2265,0 +dgreenmrecord,2265,0 +dgreenmdisk,2265,0 +dgreenmdisc,2265,0 +dgreenmcd,2265,0 +darkgrmrecord,2265,0 +darkgrmdisk,2265,0 +darkgrmdisc,2265,0 +darkgrmcd,2265,0 +wardmrecord,2265,0 +wardmdisk,2265,0 +wardmdisc,2265,0 +wardmcd,2265,0 +dgrmrecord,2265,0 +dgrmdisk,2265,0 +dgrmdisc,2265,0 +dgrmcd,2265,0 +darkgreenrecord,2265,0 +darkgreendisk,2265,0 +darkgreendisc,2265,0 +darkgreencd,2265,0 +dgreenrecord,2265,0 +dgreendisk,2265,0 +dgreendisc,2265,0 +dgreencd,2265,0 +darkgrrecord,2265,0 +darkgrdisk,2265,0 +darkgrdisc,2265,0 +darkgrcd,2265,0 +wardrecord,2265,0 +warddisk,2265,0 +warddisc,2265,0 +wardcd,2265,0 +dgrrecord,2265,0 +dgrdisk,2265,0 +dgrdisc,2265,0 +dgrcd,2265,0 +record10,2265,0 +disk10,2265,0 +disc10,2265,0 +cd10,2265,0 +10record,2265,0 +10disk,2265,0 +10disc,2265,0 +10cd,2265,0 +crackedmusicrecord,2266,0 +crackedmusicdisk,2266,0 +crackedmusicdisc,2266,0 +crackedmusiccd,2266,0 +crackmusicrecord,2266,0 +crackmusicdisk,2266,0 +crackmusicdisc,2266,0 +crackmusiccd,2266,0 +11musicrecord,2266,0 +11musicdisk,2266,0 +11musicdisc,2266,0 +11musiccd,2266,0 +cmusicrecord,2266,0 +cmusicdisk,2266,0 +cmusicdisc,2266,0 +cmusiccd,2266,0 +crackedmrecord,2266,0 +crackedmdisk,2266,0 +crackedmdisc,2266,0 +crackedmcd,2266,0 +crackmrecord,2266,0 +crackmdisk,2266,0 +crackmdisc,2266,0 +crackmcd,2266,0 +11mrecord,2266,0 +11mdisk,2266,0 +11mdisc,2266,0 +11mcd,2266,0 +cmrecord,2266,0 +cmdisk,2266,0 +cmdisc,2266,0 +cmcd,2266,0 +crackedrecord,2266,0 +crackeddisk,2266,0 +crackeddisc,2266,0 +crackedcd,2266,0 +crackrecord,2266,0 +crackdisk,2266,0 +crackdisc,2266,0 +crackcd,2266,0 +crecord,2266,0 +cdisk,2266,0 +cdisc,2266,0 +ccd,2266,0 +record11,2266,0 +disk11,2266,0 +disc11,2266,0 +cd11,2266,0 +11record,2266,0 +11disk,2266,0 +11disc,2266,0 +11cd,2266,0 +waitmusicrecord,2267,0 +waitmusicdisk,2267,0 +waitmusicdisc,2267,0 +waitmusiccd,2267,0 +bluemusicrecord,2267,0 +bluemusicdisk,2267,0 +bluemusicdisc,2267,0 +bluemusiccd,2267,0 +12musicrecord,2267,0 +12musicdisk,2267,0 +12musicdisc,2267,0 +12musiccd,2267,0 +cyanmusicrecord,2267,0 +cyanmusicdisk,2267,0 +cyanmusicdisc,2267,0 +cyanmusiccd,2267,0 +waitmrecord,2267,0 +waitmdisk,2267,0 +waitmdisc,2267,0 +waitmcd,2267,0 +bluemrecord,2267,0 +bluemdisk,2267,0 +bluemdisc,2267,0 +bluemcd,2267,0 +12mrecord,2267,0 +12mdisk,2267,0 +12mdisc,2267,0 +12mcd,2267,0 +cyanmrecord,2267,0 +cyanmdisk,2267,0 +cyanmdisc,2267,0 +cyanmcd,2267,0 +waitrecord,2267,0 +waitdisk,2267,0 +waitdisc,2267,0 +waitcd,2267,0 +bluerecord,2267,0 +bluedisk,2267,0 +bluedisc,2267,0 +bluecd,2267,0 +cyanrecord,2267,0 +cyandisk,2267,0 +cyandisc,2267,0 +cyancd,2267,0 +record12,2267,0 +disk12,2267,0 +disc12,2267,0 +cd12,2267,0 +12record,2267,0 +12disk,2267,0 +12disc,2267,0 +12cd,2267,0 \ No newline at end of file diff --git a/Network/eLib/src/main/resources/plugin.yml b/Network/eLib/src/main/resources/plugin.yml new file mode 100644 index 0000000..8195075 --- /dev/null +++ b/Network/eLib/src/main/resources/plugin.yml @@ -0,0 +1,5 @@ +name: eLib +version: ${version} +main: com.elevatemc.elib.eLib +depend: + - LunarClient-API \ No newline at end of file diff --git a/Network/eLib/src/main/resources/skins/chars/blue.json b/Network/eLib/src/main/resources/skins/chars/blue.json new file mode 100644 index 0000000..e0ecfe2 --- /dev/null +++ b/Network/eLib/src/main/resources/skins/chars/blue.json @@ -0,0 +1,266 @@ +{ + " ": { + "signature": "wk+acR0aHtMNzg3QADf9sxFzj5XYetWEvy8/WkV/Nc8rL9QebgyAZtHbjADu0yM2W+RqvEi2XEgA7viZgbC+6fh0CAkzHSm0Qn70hQcBbU3TgYxr8vagaaBdIIvsvAu41uzrRSMT+8uxKjIYugPuR8LfI+EAgfNiWIFHnaFJIVEu2Ir8EB2ydT/z+dYJuP9rgQpxcw3tAZKQqfCvw2qQDAzYpLyI3qD4qS3E6e7QSmlz09GqO4JHIPTuefu6/3d/PCXA42NIxawFx0xfJg85OfFoeInveZuTpk2hut+vWzIFYP/8J3wMxabEBA933ksL6A4+QjCDW+veep2nHCMUppyvzRg+6u4JTd+1jOutB0AnGl1MN2NTA6SMC6Vm2xHya+JlhIuwXR3dggJKeGOXFH3A+rTviRBSPyj5rbIBUYLyUovFMeKM4ljQIJcAi5fQdyoZ0ZPkM/Ad39wOYeNKZIIyFxCnyc++aBEMJegq1/O7Rg4RM909XyNuR3yLFEnkuelx499e05Cy6LH6/YKjd5hW8wbianbJdXr8WZcaTIGoPMkSiBpf3/2/nfDEc2bt4yb0G8SJjcGNvk8Hc2xlwENWBZRYPv0SiIKGIumCGgcV7Lujmqqnvl2J8nyoMRJvSQ6Pev/OgRVq8ZW+mzGS6v0KmVT6xIt5Jty4dkN7OhI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExOTA1NzcwNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83NmY5YzdkNTZkOTk0MTRkYWU2OGNjODIyZTVhMDFmNDRiZDEzYmY1NjA4YmJhNTFmZThhYjhjNTMzYTVhNzU3IgogICAgfQogIH0KfQ==" + }, + "!": { + "signature": "D4w+P+gvgo+oAqzWajRFiAVs77yyxBRgHarZhPvoq2+QHthu7BpWHU7SJ0DJs1E4y+qRRy6vV2BHaujb3bMb+09cYOmyiKCImHwbpi+8cJYydbOMffGIQYv/30XfuzgmlhPWDWej+oVvtcW63kItOJ66FAgW8mc1EMbJoPRu9d556f0r3hkwRqmU2nalRAwTZ2Mi1LggTdA53HS45J0oiFIIHaWP+8UYphGr5cnINtSMd0LGc7Ejb4Oo1iDoq2jLwMFoOONbzrL8dVSBqCzpD0X0qxOq1WSIshEyySGQdhtuJf9zvLw1i1VgpMq2FRsTqOq4mACQb82lcyRC8ovIGbMRO10KyK8hvl0X/cIRC9MAjQEuZY/KQfX0lmiQXtcMDB/8AUKpGzuvRi8ZnGv5woE5KkiwXtq/FovozmQaNnC5nPaPNEZ8K4mQu3Iw+JwaOh7ZP3c2/NVoAeR540tqeEOpoupxQs9e8tHfVC0esEJueoRULZMEHV9cUAY7QDr1NYjtPANlJ5vQWEN9piZxJEBUJVQlh6/+fsAizrx07UVx11x5zEk2LGZB45CGNpJTPci5MiNqJzpKfoplDomUmg2gjA7LtNw2TSlIlY0Ro5c/eE8U7StGyXlvHdf7C9yOPULwEdV4HiM6hIDAfJ6l/mAtHhAA14RAjL1QQoz4D7k=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExODM5ODI2NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xOTBhNzJhZGUyZmVlZDFkYTA2ZWE3NmY3NDQ0MWE3ZjRjM2RkYWNiNDMyMmUwNTE5YmY2YjY2YTQwNzQxMGZkIgogICAgfQogIH0KfQ==" + }, + "\"": { + "signature": "KaGYHgoRKMqw/K2oqtZR+T2G6hp8S2NSNO/KASJ3nz5kacafCiMdxRAXaJ+pebMDMeCeEGD0TNnG2i+NaH08R6VsgA6Rk+57zaXXeKHRmerT/4BmNsE6J3aqjlFzGnaKoCa69mWEdA8o8uBhbNVcvX/0K7sZPtHk+Yvezt9hYMffS4HrKAMuLwXD57zJv6uQU68tWsX0IOsMZUixZ+ZXu6eV1A3uKh8ZbnHnA2ko3D6EJefcDTYjNkRRqjq9NyZO4rVISdLN7u0D/pR7OObGW7fCJz+Q0v+RADunsYNDQ/lhNAE2e+mY2yenS0Z6LjV+mkVQykjWp82wCL9KspT2OTuAVe7IPiH5JW0NGEG5YzJ6LnYDsEwnvknUL2QyQodAdZ8GUHepvwyi73LLwzi+Dqjj6EZ9DhTQ9f3r2hhjaLF3WKEeAPqQqFcOJeNQqwbCqyY2dXrI2ByliG1fA2BFq+DPmbxjV9aWZ3QbHT0Dy8QVo+VzrpAax7hZmggZimg5p4ReL7v5h4MbPAcqLl2htu9mEOs8di9fsjYRbOEIPOAPYGQweNLGKV2Y/n2zXLrqcT7DOHm42Thos8G4Lf6548qEN26Wuvj2/BdguidLjB0VskOSXJ1wHe5V7fKZwDFKyqLEutJ1fQxD+3uDzebZNm++rMxLwg1TMa1cWNqa50Q=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExODgwMjE4NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zYTEyZThiYWE0YWZkYWE2NzNlNWE3NmMxOTcyZWE2ZTY0MzM2ZmFlZDcyOGNmZGMwNmNkY2MxMDRlOTJhNTczIgogICAgfQogIH0KfQ==" + }, + "#": { + "signature": "cp5fb62bSf/sDYywSbJxLaFazcitrzkz7tS6PaU8VD5tLW+0uSkGQ+Vs4hSDIAqggIhIKc6unsz9xyt79k+hPxtW6iCdx4Mh6PtduvQv7osmMkuA5qUuJjFWP5GXDTfCBxIFuto8jVWHHLzsI80tEixkNERyv/SMF1KA6PKADBB248ZCUcleieMwZHcjZt6yLB7XVTg1xY/UaRlkLAP7CQEquLPhcvYpiob605iP8+XBzT+a42TwA3iquc423RICv7/32S/Jbu91zdo0AfFubO8TXKZBOUYnxF8+5WFOy2hnIqhr6VeiXbfHNgWF9hcBSa3U878wTaaPlUpm4/GGU0pvSDGS+uOfmZ3QaFOVkoLtqWhEc/yU2iQgUuWegbksifJred4ONwDGJ7OQJZ/zA+3BtWQTMfBYzeH4EdF3+o6wn+hWUz7/BFlePsbY1ZZmHiqPiYUdzOknNbpMF4VotG08Y7KY56yDVZBg2jVdJ6J900WvMggI3JCvQJKbwukbJt1jIopPBbjZplE5wljjSdRotX42Yxy2S8/bZFbcrPux0LGPfgc26/xlWUygzxcdETvrVyjrEaKUsWrSpQR72tXa3qdipdPbuHubNta4Pw5UsxAYrw3Q1MLGjbW/cwYtd27h1eSajyBavUzv9O4Fj8elIOthn+TDrnhYLrBpqrw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExODY5MjUwMCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lNTcyYWE3MzQ3YmQxYmFjNGEwNzEzYzhjYTc4MjQ4NmQxY2NiMmI4Y2I1ZmQzZTczNzRmYjIwMjkwOWIwMTdhIgogICAgfQogIH0KfQ==" + }, + "$": { + "signature": "PRB/zc3d3P9R1I3qpEbwaSSteB6+W1V2uMZYtZO69KR6iSVYpsWziAxS6DY1tsN5K/tNj1cMycd9xtQzQvCWkELrm9SGeY7pH3lKb9KAEJRc0uovcwyUa/yXkmcgWUgqY+gOTY04n4Lg0BRFaa6+y3NLWYnZ4W7UcOWMA3fUUy/2s5IIkKf4wk3PPv6wIJZ6GC3qk/Hvo9Fep4+eF15zhbCgN2j+FF3ChcDsjwe/r2i8rLz8c8PzsBb8jg+3kqQVlH9OE8wk9eIGbgqN2G3CpACNXAs3P3MCVROPqA3iiFdIT78LY7xjZNpzUy6DRqMzrVOE2S3KQdcsBREmBcCN7FMXhMBGO2I/1C+L8sC+w0lENvkSLt2MOUadx15Xu4VDK+K2KIm+JKQpqH6LdQbvcwblrKTv7iOFhIJ6i1O71ERk71qTnHNG2uJevzdSDWcg2NdpnkDmENl+Wq633zmZzM/BeuJdfgCbSpPlUizpzug7JrICu4QN7YrnDfEc+Lr7bdRjP3FOE5WpaE64uLqDtsorHQzlfv6i4fSDd2Cj3A7QwsQOgzCFm+z4zGqlmdp04bD4e227fFLdCGeKsk42MRtfZ2+iUNp5Q012n/+mPq7hzU+lqrtm3BeUAwti2jc9ZBEVMBR3CFQZwMCedOCoP4bQjwPJA/Rvi562NvmHnB0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNzAxNTYyNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zOGQ3MWVhNmEzNTUwNWE0ZDc4M2M5NzQ1OTFhNmQwN2I5NjVhMzhkMTNlYzEwNzgyM2FjNTQ0YjI1ZGMyMjFlIgogICAgfQogIH0KfQ==" + }, + "%": { + "signature": "J0SrG1o7cPsq5Xq0TUUq99vuI/ebkwd7mOq+o23Eoumb5wKMZGPSQvVs5yYiUBez43bOn4qWEMqSx1W4SJTVvF09iIG/cbS9WeI7egP7hCqsNJbfhsnO6RSzq7zLUiI4XWl90btQOGnR0rdcCluJs4chO9u4Ukvg5toBPA393VmYNsIk5FtQ9McsCoQsVShd3QJTF+AtJ7la5RE3p008G7r1c8ziIX35C2l4Hpu5xzjU5SEmDdMzgK0VGuTn2vwSyvZQcsUYvdz5BnCuAITXl+WcueLNGWAvIlNCqLPcW/FxzT05vyV00qyv+7hj67i2VCH9Wu5jT+c/pZpCXfPI4n8HNFPIgbpKeP7ywD7eNgOanmiIgxh0Qn/j9M9xFnGIEqsoQWxIHlzXjZQVpRVoWo6Y0w8Od4+OKhJ40vtVCdHrj1dwo0fvBUINbBjR9hScjP36Ovr2ZPP7K49EWAItN8xcOlvyxYGPJCU1okRvc1hHI9oiK673JdCrwWhw2pfZX+9tKEU/a384iojnrP4N75zapwm6jNlldgtIzfNHURt3KboCBVRmwzuqeS4CQuHKTsn+06Mc7bQdiha4zmT1tl9PIKzd1awmn4xLOCn8tQ2FiQK9Oh5mv9ESj7pkmisFj0AqUCXC+fKVi4jfPw+nlbWombY36Y5KrjZBoMcGnMQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNzA1MjQ3NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mN2FhMTViYTk2ZjIyNGNiNTNhOTRmOTRhNzBjNGM5ZTk2M2VmNzMxNGI2MGE3MDA5MzA1YTBmMzU5YjU2MzQiCiAgICB9CiAgfQp9" + }, + "&": { + "signature": "aLXsjTCyBDJYM6FTzCNAS6DZTH76oXNw+aY6T3qzRQ8rJfFzZu/FWtn7iY+zPlK9H5xnwkrqX+9wl/2D/D4rOxiDq9zL7hp8Aa3s7oUU5Ca+5dxkeOYoGYc7CiOZj6Dy5jrbZ6/PAHsnrUw2oOqozqDNcLUst7BYH0Cwv+NAvFMLfF2/IkGuy+4pbmsZiC51mhFCDpQsX0kYIhJ1dADHJ+DMdiSCvFcA7ja40dfvyF/LzOemIx4UJu3j+q+Uv15McAjGUUPkfKULWVyfi7OwKdAFb3ZObUl7dXfzr/6zmOgi79kvusiji7mizp9B/XXRB5/QwAsKWqaeCfOZ4HqDK6MPn9DJOcc1UybrFyh/HjNYwK/BS9bZmUrURycmgTZh86VFh5To/EyJYUZU0h5gkzKMpwtKLQw+18ZZY0Z9ZyJI6Fv6Ccr6M9nVAs2ll/+pfWsU5KSaLJFiGVV8j/MEQwDoBziH/3iOYSlMI0F1Td7JsltWnSMU2MDwXpnAmu4oRQMMcCcrZ0RjGMxKbUJsNyIqRUGG63iCHmAh9pX1Vl+x9iCekyS9zNtgeXDyMMt0ZapsOzC4SGoVdpo4PJIdmbw9zQ4YwKVn/MoGLXXGpNaLbpQEIYDcEXjzCgjA8PeiYvwHdJ0tU7w4UAwwxrbAJtgJ9tBoSagT4gAM1lxrV68=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExODYxODk0NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82MDAwM2FmNjljODBkMjE1MzZmMWMxZmFhZDM0YmU0ZDU5NjA5OGQzZGZhOGM0MDg4MmYwMjFmMDg2YzljMzIxIgogICAgfQogIH0KfQ==" + }, + "'": { + "signature": "DYj3ZTOy+sN6IP+ltsARGUfFmgTM5U8WC2x+s8nuVl9y7c/tF+6sbVRLEoM/TBpdOMbk7xXnhqUGLBuICYvRKFcbWsPv9aEedV9gNRNh14vTidpZxV27U8o1jFUuLxiId+Qs6fHdL3+V5FhLLJdrKN+bUc1cbLRkRv6IqTRF20q3Nk3x9aGdCuRLfkmiakCxeQmaFjxCIEU1wI9n1rNRqyBqjKMZcGwG4N3tnxJhuX3RUdbp8cGwRcTGnHFd5rZiC2v+e+xcDYl0v5Klg/Yu4j/krxVqC0TrZP+fxwVFIWEe1MT09F2StQ2RC6TtV22JPz5WRx2mGZ3sAq7YFEF2mXOtuPAJd7B2695O8HXpuUZmIMT2DMFEiWk5jYGTgTDWePY3ED0TckbdPZw7klm4MQ68i44URQ138Bnh9r7dWttdAhmYuMxIVZKUt9IJy+FMbIy/bqrIgXsc1maIJx5d5QprCSGY1mCojO4SLUocS/AQoAxN7zlmpiKbCq0yvID93pN1iF6250pL/AdhcHHt9PSAvhd+ywSyHh/Km++O2EtcvXMcGwd1rRZDuh/dzNTw+bUO2ZZHfWGKiv2cZnWHCuQW+0odu1FjAGl4ubbL989nx+XtHuFQP2udM8NxOw3L4lJriso5heDE1jXrkMSwdWo/KxRkwgGVYwOwmNc9WSk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNzA4OTQ5NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iNmNjNDIwMjUxMGM5NDJmYWJkMzc3ZGZhYjY0YjJhMThkNTliZjRhOTFlZmJhYzJkM2ZmOGU1NjI0OWQyZTM3IgogICAgfQogIH0KfQ==" + }, + "(": { + "signature": "QYehvXlnNv9Ea2Gauevcv4VDYp1lflDbSF+03Ljhb1PcsEj4QoEMK82jhKEMoqE7DtaYpCan3PLQolTlwJEMhX015AIEN5zPk7pL2JaJ+qE/hWMOZ6xnhAke+62FsD0bj6pZAK7EKlLtgLvK7dBScWxwxDCl/SRv/DpomDukAA6XwELT7s/WQA8Tr2HPbRo/GVLZKg+yq4AYcDv3y7UrCkNDM12rbu/2LYZz0Duc7AYTLlqgKUFz1CeZ9Z/FXC8lUtCY3H+TADyHVL8XeQ5yZnqRMPebPXlEJsERjvofBFFP8+Wh4zVqGJa4b2oD5mFY/8URvfXF9onXfeuLX6byR4nXksrASrbuujHbtnr7vmFROQJBvKQJoRXFwRJPy+vEq46QHLwyUocC9kuG6c39Mo+QB75kvdUWIluBxlQtfk1Y53zh0R2PZxyUssllmCeQocToHHVDwaUoXbCLg5e5qxBVXSv9IBH4FE91NBbfo+9eBXbnthRBz2/iZWRKotUAiVORE5yCYm2U6AWNx8ySAJgEOfGWbzaWLE3qmC2yIfNhG5I+ZzNv1Zp8aYkw8T9gTJp08wrJZb7PQGU6KQAKUHks0d8IiL5EmdEY1yxsfXqLpVZlZmpnR7QmpVpOKSlrZTmwp4i8GkUEoFXEqjKxpIubHD2kLJyW4RhTswy42Uk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNzY2MTg3MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83ZjJlMWE5YzhhYzBhNDg3ZDcwOTQ1NzRjY2QyMTM5NjlmNzA5Y2ViYzhjMzkxODE4MmQzOTA4OTkyYzliNWE5IgogICAgfQogIH0KfQ==" + }, + ")": { + "signature": "FNkBPxYx2ioxSFW5cikDuSTTgeIdMRGJ3lgxzwkBnRTTQTCQQmlOiv8rAWYX8SAW7eN0hBO3oXpyUmUG1W5EK4Vf5YrNWUgM7rmYUf/d71KcoxObcmZDg1dbpHEkF+vxUYTwFA9ytZS7XLPjomkzGzTnX8JlRQE/TkzGqTjXQIcY2yQVMpvVpI/yRZvORnjA3fgJ2r4Kx+2FUtJNSxGZMuo6q+HS53+Z4dZLzB0sSvIEb53tHKiHv+ctiUyFT1UBI8YIRu3XNc2mrzvakH4u7ppUhdTdJP8lnLaVYqrAhvD6m7sp3lOPEDj83NuRSCaY4JdLu+6b5GInvQDQ/NndXZiWgBcO1+pLU148bNxW2glOHW1jRc5r/ywOC4s0BAlElXMhxBbqLw9B6uhJtl2izTH1dMiZXsVHxp0liZyursCMH+vW4Qb/uIF4U3+SryTX3wTc0oveGLFfQUuV9BoxWwjUCUfXuyuGayNrQHT/bL+3eRhiJdjmZ3JMfrLqkAXc3rIg9LXvAf90X89rOaE+LKrFi+PsUrd2aiLH+MvtnNqOxVH2IDBxLX3i1DYYe12qHvc1mEvTwEnzp1LmaSkb6nqdkM/BrShjwYQb3ou8+kfi1XzKBY/nm3SEzCKfcm3XHXLJ7porfSB+n9Tb6TDuTYgR0es8aPSsvA6CXVT9pSg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNjk0MjQzMiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iYjNhODhjMjcxODc0ODU2MzUzMjU5YjViODVhNGM2ZTM1M2FlM2EyYjFmZDY2NWFhNGJlYzQ1Yzg3OWQyN2M4IgogICAgfQogIH0KfQ==" + }, + "*": { + "signature": "QeOkYpGI6BuveGe2UgoDLKFdS/9EoHs7Bcvk7Z+ctZeEa8n7PGYyGatkU0TO1Sw69Mm259DafMp0clvIK1krnxyRllqKMLZcznLokphazIcZi27CCLP1G7PL1bjV22dCLRQXyDo8y2ATx/A3vstYxwgSUWgIgezmeiOx/4WEFZst03oPxbZqdnu8DNkuJB4ZYJnNqELGv4OuUxrPkUMjbORNHLVPcBT5WECxCiYg4hfMtbktFWtWyGihxJabP30uFkrx+EpV8oUXH65YEjBQ8oytswMPEoBgsktbJXdRP6zEK+ULs3RtqLF3laadtGox3jvf2hOyURu+zB71dOEHUJfpFB3QQqtM65XMC+X08fXkC+rcVWecXbQN8EqbwrNbmWWcGMje6s1R7x8TbnwrCTLg1m+BUn/qoztqBqHC5EWm85q3mjaqEWgeFq5yMOb4q3RAmKTvoALT2dJwzHi34se+BMcxF+bL7ggiOSzy26h1rf0iOP8fFpwOccjFikw/Bg9pRV9bWOGMFASVq+vhe7GlDzIg86dvPW7zYXRVMzeI374hhF1NMrWzETQCaHjwJJ5IJUVHt5qeI0lFlyL6awPGNfMUrwMH/gQOwEH3yUMIcrm9E+VLQpQMLT9qKykVxrYKUWIxfBjon7gBs7NkLgWcwMiCbGytxmZs+phnT+U=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNjQ5OTkxNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80NDRmYThkOGM5MmIyZWFlMDlhMjRjYzgyZmQ1YWU1OWJlOTBkNjg2MmZlYTcyYTZmZDVjN2ZiZDFkZmE5ZmI2IgogICAgfQogIH0KfQ==" + }, + "+": { + "signature": "hDtteOrfQNlY0jcaejLWgGrFZaw0EsjnawetaK6DMJWH0SNTsHfN1QNMdAzQUAv2QO0skk7wg5LagGCzAU+n4kBqSzgLQRGN0vV8rujxl26GYp0gnupl9Uj8ZCxTiT6eCj21nf77HDu2IIoPj0Agj6NxTFwnpdr8ZQDSc7iUJVcNiXKtOijqvk+OdVNBTyX30QqNODqpB8c2n3WiPySUQqqlwiDOgYbRAiECpxiIceF3afJFSt+1W9rUjhakNZcpWQSFY5pYN1sYJ0+Rb0VRfrtuJR5lau6dVLVGHYre9594Dd5kIdnILLmEegYvKS20LsQlAxkblsUMdCLCI0PKtp6wZ+5rHrR0thak81qY/dhgsnGdMCbRDIf6znBrNCjfTyX8R5TdnkyN0nL6l8+jPCj5G0JO+NCuUK1WEQ0/fkq84o/SbvEJhVtSmNbNFRES2LdsAv60qmqWIgprrSnAdAfOpD7un9PbzJ1qFCbb7ODj1vL8IbwfzSNRvkkw6WR9N6/+F1mJqxqnpSmfKwpyS5Dkg5h9YgecMTg0lNIC6TiJzUAE0oYkWUy/bz0spgV1eAWUC+2shoWnue2xVoy3R9pju5TM8XfgJIpM/xrX23bRuL9L6fdvfFW6TGx2DqCcqvzxq0CUxy/pV2LVP9PMjUSTzFShjhD7soSmBfYxKZg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExODI1MTMyNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82ODQ1NWI2NDhkM2M4OTVkOGUzYTU3NzlmYTc2OTYzYTU3YjBjOWNjOGYzYmUzNGRhZjllNmZiZjg2MWZlZmQzIgogICAgfQogIH0KfQ==" + }, + ",": { + "signature": "sktM/bR8VwLFAkehqdRxwzMiL8DOEHkVVCmS9AEn1Yfca0towpKRyAgzxKtDzw/ELpbduf1f/Q5Vjr4HJnYN52Xx+B8cfI7qYgYGufr1gBDd1mtyqP7RC+MIob65NDDTedYA0XgxU2XWwT1HU5mQPDpMkK6Lq9rIiZBwCM4MpdLIbY2hqI6hBN5yA4kfMvzMMTUGDIwh8z95YJoC/CZboBroB0XK3CEZgv2z+airIDcqF9XUbYyXM9rOPwdJA1myPdFJSQaqChPjYbyMW5PxoOldjPexmRNUcYM/JsIY4ilWPynu7b+1QQ1kGYaePu9FS2I5kI/ma5049lRLXaBoMaTnHM9BTAtA9QSP6bc0puO9p/OgEYRqBoRl/7Ik8SmHuCj4cZOwQRnNf7w5A2MuSaAMFFj4lZK0bwzdp3xHfS01qCDdO7d81TAoHAK3JoOGkLp2EtPLYxYd9eDkHkJFOCoCo2gGeY4FAAGUxYpegAaQPMsIPERG4kgWLfxl1grVrc0r76yP5U9rPPSJw9TZRMjTKB1qb4RUyvCHeBocXS+wdavHIjj2w9DseZgSFpUxlGZg9UJPXVd4kbAC9jDmK/y7ZGlPkG/cvwNr7cUxhU18XxHAesySZtfl/ykp/mT5vB4xEcq1tL2QXgDOvywanIxnd9NZmJVh4fWeYhHz73E=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExODEwMzU4NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hZWZlMTUzNTM1YjEzMGVkOTkxNDhjZDMzMjQwZGMzYmJhOTFhOGY3NTFmNDBiMDQ4NWM1MjVkMDEzMjEwYTgzIgogICAgfQogIH0KfQ==" + }, + "-": { + "signature": "a5iSmHebsgygZHtlaxvkRUeIxlG8ndMiI+KAlulIJNBFlvU8Szs9a1ifoFjF6gxJcEGDmauusMY+DGpL5JiswhLct6LbWkZccl7UkZuW40y3+01h5R8NkPQp3/1Gj5SO/SzlyVjdGjecE3f818C+E3F2vSp/nc9P5G4dMKyefSkxYOGyQz7sjgC5QQX5fWWIto4CpaDzNYTpXQs85H+XecpW3rO5tCI9t/wLVGKeZYAVSClkRUDOBNH+lchEz9aGk/wswJPZaUjVJlf4KYKKkQJzQECa0EhDsd3WjiMiFXJzhOWP/cWnbabxXym3LmfamAE2VR/ur7Dc9KdxkPEH9S8g/aP1TostcmZcPZ6lL2L7a4i074T9+epXBmE4IEhySNbRXGZcTLP3HGet5HH2BLUDQfk0i9se2KBxeBvL6dzDuRghEDwRxz4P03XoP1z6vo2AHOQsGSEg45ljgoyDhRiXmqapNgRuYiB92TkhBJZHQ1sPuZJ6BRN5cKizSEQC2IxQYBiuw7qU5LtVKWXJEQ3UgHAzVsWAzkxmsX3wr/kV+epDojYwUy1XlU5Fsam1ZMjfRLGltN3HFfR/fSVzkda8VB7rtwyLNUQ/E4gb1re7DyPxP7n1lezo4Nlf6VWsKWMDGkybc8l9np/ZcSwbQ9lnPKdMoiyQbSELw+KfNq0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNzE2MjM2NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mZmNhYjU5NTIwN2QzZmM5NjE2NzI2ZDgwN2I1NTg3MTA3YWJjYzg3NzhkYzUyNDY3ZDkzYjAwN2VhYzAwOWIxIgogICAgfQogIH0KfQ==" + }, + ".": { + "signature": "GXqLbRXiv7pzJDvNd1RvgOtAIwKlEzN78IAvnJz7tlmUd6D0KM1Ay2qQktBq9fvInc/hXlXAERDY5sYQOZOOwkbJsvypZS6wFAvltwklRvIbjPq8X+vyGD6mJ3LobPCVec98RYuiSZL5pbRPTGI/smO1Hxw7t8W9olxtU4Aaa79nkba+nKy9sgQ25IuxP/YofToIVhG/QTax/UgpBxL30FkmiNyQN0DrAY2ZN7diGx/kB/9cfqXvHTrEG2qLhs9Pswl0UAReuAD/ktnFBapujuPXUiGJ3i50b63GQ0TbXZU/by+5nv9OJJU6bA7M0Vzhi47RzbjdciMKXgogs4UvNxD0tV3Uxp19v4vQ4whZKMpCJnE2omUXzy52hHJNllJS91S7dTU0Q6HYw72Sidn9lPuwdlBkPAZcE2sDMG7+6V5InfR4wquZAkg22LmNJ4oydPrv+UxM96UbdeyPRcOhKGVGr8yPbU/cezD+26r5o151RTjsxjoqP1SS8kiEODr9jpOXUh2iRssMBzoWDXZhxV7xBH6fcgrL/rhQYbOaW2ICnmFk5aonjbEtEcrUgte73ORuZ5euJnjh2BfQLXavjlI5n2yO0fJaH/Z33rt3uSY/Zt2oKZyiJx1jmXc3g7J3Lfi0lYl4dWkSrr+W75wX1T+MojVaouae1eaqilLINUc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNzYyNDMzMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hZWZlMTUzNTM1YjEzMGVkOTkxNDhjZDMzMjQwZGMzYmJhOTFhOGY3NTFmNDBiMDQ4NWM1MjVkMDEzMjEwYTgzIgogICAgfQogIH0KfQ==" + }, + "/": { + "signature": "yCLguuSm54I3pCRhO/KvuimBkW6RsoE/igxvXbw6jj0lFvPgNL3R7EY4vjgy1LTizTxhiYsQ6yN+pipvvPgpqoR84suoUr9cfNN567gKHaXdw4WnR0M5j3l9YtDNrCrUY/OF2VEStNF73l+XAOtXXkrbMuddQnyXOlzqrX5jaoILXoQGMKDxPxIcZpn91lUT9vEQlwo8DsBJQIIg4cKgnaV1y/uydKsViSu401AcsKAmzOdGrMyp0+vXMnJ67kXSbKapvWvUEEnKnz0FUqgw6QHGSbBlUIIvC9BpWlqwOqo8DqQBZc6uy5u/LB/gAayTpyGnz3gTG1GJ+eKP6JS2XJCeGShMEOxEvVLFgaFuCoake+lXoHiT3iEn8TCre3AyZtyuB58fpcBKFbk0rwlge1sOThgAWvqsqc9iVr1yik8BNBilLX/AvrJW2CRrklQnbi2U3sP9u18koJwUv7sfC2m6lYL81xd8Qsx6sz1/FvI/HjvuCtZPkYOQ/FTwIgCSZf8u2lF4LVKI8IWD3R4tJ2N6t0/0Vllhp2nXvHvFq/ZpKa8CLTey/seN1Tdy9BoeKQLtPkyGNu/f5q9hib6x6tO1/tA+Yf/divdZVdyPJR4FycqfZtQYzQ05vwsmokGo22bFR6plodUNeDGI5tV/6IjI/lAa8A5zsSB5obUlaD4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNjc5NTA3NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lNTBkOGY2Yzg4MTJkMzI3OWZiMGJlM2JhOWI5ZGI4NmM3Mjc5NTVmZWY0ZmQzYjE4Mjk3MDcwNjNjZjRmODFiIgogICAgfQogIH0KfQ==" + }, + "0": { + "signature": "KgHliIzFSsgcYf6QVwR485uXQq+t+UCcg2ok1UyV6Eib/j7AAitSr9xsyYwC2EVtHn6v9RzmlStDPsnU29rj+mtcXEpe8pcI+Cjqlfnigkqn/hu2okxozdOy8rz8WHE3Qckm07sbfKbFsSesz9sdNynNs32QzhlOMLeaCxuibxR4ZrZe2+iTzaO4qqcisP/t6zYJGSHAMzHhN89jP068rWsamrbiZbIYKL2byRY3aG5dD22sdyN5wy2It3mTgJUxbIDbWnrDNfXp4AHiZFtbwgz2pENcpD3ll3/9J0O0WJlWrlk9A82q9TLOo45CybMNlOWFWKN1gx9LpO0GNIm+GO9hp/VZJRgAUBQqCjAas6M+N+mx6TtUhQdyNkSWZQft5gbe22Sb7bz9dEF0uZCHMs/4yfR6mSWJlJ+RiaCWrR6jc4QB29URbREA2tc/HSWX7zsKExl8Q7KiadC8dxfjHhGiNW2zmM2i7BMwNaBxl29QX/UVI7CfqMoC8PE7JKlyfOcSE+qOjaCF0g/nhuukVn/mTQzQDbBMZO1FW/SyFb6J6y5f5iIxhxqAN2Dooi6XiJ8LnFSfiRjYvmHCM4ZqQejY2iid/vkdloG+WXJF4lZosMQeSRup7pbOypRZEiMCNktgT1laEOxiIMaGH7ldwBu8PxNVtbdulG6DwFuTlSs=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNjUzNjcxOCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83NjUxZTQzZGMxYzk5MWYwYmQ4YWY0NzdiZTYzMGEyNmFkYjNjZTVkMDUxNjc3NDZmZGRjNTNlNjlkY2U5ODciCiAgICB9CiAgfQp9" + }, + "1": { + "signature": "Tx9wwrTnK0kJrt8UbwI4R6Z0AdgC2pxm76aN9emF794k7DO0+P+5FzyoPnNsExX9YdjdUWF6x5i8/pXFFj5rD/g8/JfBYJhVTKqj6jyA5u5mDLPmmtnC/wGLQUmYWak2zXZECmJxtIH8t1/r5IWVqu+Ji5kgV04f5uxaggWIApTgyyvJ/zZ3z2QQosdlRAw9b7ZrQR1MuLbh2zkdp7+2Y01gkRWGZ8rp5X21mFAubhT7mV8Qw6DOTV0EwKO1NnayuS53SzPyIzavd2g2InX2ltGiq8KX6W849rpVplvO1zurDVaiepF1lrngK9bMabBb4OO/ckqF9qtogf3qYYfjKix2/0H52D/99kZ7qaAp+6wHcVazYxil3MlqrmwnMdjlJBUVFaPFwBBkVjmLELunJDfjtTeGB/oNmhrxlRTOaie/NO0NVzafDYaLoUTqTarRPLXLdk7+WHRZJ/d5iHv9TAC701lDrKjfMDM3wFSVAidyUNlpucPHfQ2K5xDcQDUm8+m7W3l2MLixq2YGmEtv1jrHWH/KJ2/SPQPtWl2f7Tyh2Z04+7OQd/BDrWVQZ6paePyRgLKgy0nMmPhVm4asD5g6mh9xPhFZwz2EiDmWvfTFuAcKnd7P2PNds3cXtPqhEStzAQAOixdTIKbwpgZcUrGB4NOpVXkQFv4qg1Slh30=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExOTA5NDUyMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kZjBhZjI0M2U4NjIyYWUxNjE1MzBkZjBkNTE2MGEyZDFlNDI0NWQ1Mzg0MmRiZTdkZTdkZDU5ZTk3MTQ2YjEwIgogICAgfQogIH0KfQ==" + }, + "2": { + "signature": "dSe/OaJlHxPkeHXZjfkfpk+cJCGLn4uYmjv10JjO1u4s5VZtF6okTWPfTDmmkbsQDm85A/6quavyVTs/sDiO0f6EUI30musHCQY+MkImK2ncJQhWi+eQyEiMYp3JLPqz8KhrX87HVP6xyF+QJNfidBr2Eg0NRtbKLF77CnLVwMsfTTZMXmxMV3fLdUpbffkj5N9VsoKGthhb0KgRVObmx4CkofeUsIU1qp8X+pMSZ+rOvY5pveHaaYNNzx83wsxcgwgl0o6ArYt5DXR48DIj/IRU290czShmy6ttOXY5CQHW/oyIPlQ0yWIFrHVJloDp5GSpGcKcR4mu0uQHt7iXogrdXa5EdqP58fohY18ye8UCKy13eW7hNU7iRvmvTM+PxldWjFvp/yBX0po9j5ReZdg/Esq31NZIRVW+gvsghYVyNNLEd5QAAoMD/Ob6lub31BuW5l3+ugOtdwnQD8N/VGeedC7a8h6heX7jwVZIf+3e1RAVsG++cSoYfG5RLj2/aYBqsBBRwHUT59N0KIJC5bqNy5PRxjc5B6HQYJR4uKk7UKyrD2o5DxFDd7zrGxHPse4PJs+6OU/ylwBAkKRd4qZx6WDBFQWaithN6gve6j8MqTRbzCyKLIqW1pYSV8u0FRcia2PwnITNo9/h0HX7GHeLqFz/PWvGWvxVSfbYopQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExODMyNTA3MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kNzJjMDE1OGY4MDc4NDljMzZhZjE1NWZjYjNlOGJhZGQ3YWVlMDM4ZGE0NWJiYjE4MjBmOWRhN2MzMDIxNzgxIgogICAgfQogIH0KfQ==" + }, + "3": { + "signature": "R9ILZuCHs3BzS+uCwtf+z/2uBWs7kfQQU/wR80MaaTN+glizXiz2wg/NS9Pwt7k0PqVqYKtwIYkMjYtce5r7frzoq1XMcCLBq2t1CBhs7MVeTe5DJVHIkYW1Hmtn3yJZm4MVxztLr8yZ/Teq6z8qqAnpY3/sWWXG1cg2YUl7vRZF7ktTo4pPTsyG9tP0AWzJtaSqflzBYANLj2v9QUR4Q1923bK6R6r65KzE6KYAiwf7tSDfkbTysdtIJ6X2W2i4SOkWS4aKobWFYoBwC3RFG2uBjIO72TjyQlbGMNRfxPRmLhbihrz/VsWjJeQ9FvwLLrKj9ds+HtyBUiPAklzYkAwz+jzKihalFQus6Xpysb/djz/4MCIB4KUzOco6bIl/J5EytOOQqFB3cdXg/zcmnDwHdh+Mb2TUUmx8w9ZGTRzmwPp/qYcw3oOfUBoUaOQqAR24lW6VH4VkPZNEdy8S4XcHGqjUlPBrxKVRkzTmAWLFOv7SrQCokJLoeWQgSyKnevRajDLtxhfjRdFrEV7WgfkZFH3RSvRtB55sXwO+5+tryE9mn+gjhAvtHL9A72lfc2cVX6cD/cTPThIafcFHQOho9hl9tUsSFPVpPGEEWdO2nV8qkZfW2mzJA3ZO3RIDGTykSyvb+WcRipatKoHmMwTOOYlqf0B7lLfkw49Fo9k=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNzU1MDUxNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hOGFhMjhjYmU3Yjg4NWUxZjJkZTUwNWEwZWQ5NWYwMDUwZDc5MDM3YjQ3YWQ0N2U2YjQ4YjJkZmViZTZhNjE4IgogICAgfQogIH0KfQ==" + }, + "4": { + "signature": "KbVwbhJoDHXYfImKZcEOw4qGM0kLAj1SOuQkbFZEi9xZ74zVhkiO8emNE/6mLx/jDCQflQt69HhhvYXt2hcqHLeZCHGlyCTzcw5QCpgbaG7wdG26oFN1H2yAZMeh5Tpv2XXFe1PMXRCI10LgG1KNx0uVqmmbf1FET/9xZlFz8R5AEWPPVvzhIzptHQz5SiG37Cqdpi51aa+T6p1gTQzyvJ7PAHAvKL43cjBPL+CVwN1IZofi0qTyTIEkDFrVTeU/fyUfQ5vW1vwTGTJG7xvYbnv77S6AXDl7lqKrlCKmsy6P5DmryFz/iwXXDAqJzisdh63pp+8AX+cUq7kidt3/qjhJrzBH6tOHIVL1s+uO73CQzYvwKz9My5AFoTIb6uKFojWPhIWouEdgr0EZsAwZvffNkvdKTeQ2frYlukrgY7dvOA2uCEusg+USW6WQEUQMRqKHWvwZsL5YSUJqWPBt+i2v3e4Zh+qPnvFJ4p6ZEMLSXCctCpX8dcSCy5HK4mD87NkswliJOS5jAA5za3s/6Ht4BV7cdXOIpj64I+qSz7kvGyX1C+0c7gmDkwWqdv628Ukm4fXUMcARmUS9bgXqRrsMJNwX+AB/UmbwU/6D9bcKFiL4Q3IUBwABCOWJxkw9liWajVNvBVoQF32Pixai8IiqKwekAYTEOCmyXcG/68w=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExODg3NTQwNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82NTQ5NGFjNmQ5YmFmMGYxMTJjN2M2NWUzNjdiY2FmOGEzZDdiMWQ5NjQzMzk1ZDUzNDYxNzI3NmJjMzYzMDc0IgogICAgfQogIH0KfQ==" + }, + "5": { + "signature": "AKxFQrWi9XnNX6b2iBOxiw0M8D4SA2MpNdhWN+IRI2YdD9x5XHxQrtAepYmT9u8mIBmQUIlHTAjemv6uFXb5aHf3+nCNk4uT3hSwZmMOe8JmxBUZadoSd/urYiCnJhZ+Gmno9U9JpBsARdSgjQ+7oAThhfj36M+AP2a71g3/8ZT7gpR6tCnX2hsXCmUNEp28n0skM71VGx5QWA7YMGaZT6mX+c6RtYDVTYdGY0Ns/n3ey3uhYVWqB5eBeNQrujMxQjjx4Fh1Q06IWXyavb/UcV8ufB89hLlG+p1qpuVMyX3/20Sl+VxpBvcZDUGNVyAmQRbDZXyycoTWHuZOGKA6Hec7go8B7jxJoxfXFRgfiwa1xPqnKv5B60Xgko/ecuk5bxdzDe0xVOvTXIlz+E4E2dBjCgyl6/vHneWNKiWDtygsocUg1ikY2nrMYwX7hWb/8bctb1cLelOhBe9GoM7zjoiCAVFp/Ba3C4rR9WhOK+kSYQiDf6N+IhwV7KZX9CP4L2aAqnbwyhwlacOyfnqdhO6qec1SuzpIFoTmKNFIBunWo1MCWqSu4B9B3o49B4J/JmVSJiGb/cXfszvYMlLNQHR2iyqdIhub2WYajxoTTdDeC/JSk/4CHbXVsKQAZvU64V6oEkGTHQA+LxRMqiAwBsMpQvLjKGmL4txD9olDaTQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExODI4ODEyMCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82ZWUwM2ZiZmI1ZWUyYjcxNGQ1Y2NhNjY0NDE0MDkzMGU2MDk1NzE3ZWU1MmJkY2QxOGM1ZWFlZjU0YjUwM2QiCiAgICB9CiAgfQp9" + }, + "6": { + "signature": "ec51XsTnoeS3Qi28hXsXEb0pLDQHHBQ+qW17LeIyQZ7bDD4Uzesap9BnV18YF8VV9bau6Vq9R5sKFYegtr7cAhccbk6Cko6elP78WFah6scuQ/JYknSK2OSCUUBm7ZEQC160bmnLISj7Ls8cLjyUvTgABRyPH/g5Do+IiT9COLjPsrtP09VoCE8H23a3YSWmSV7mplExVvhbV3j5rAp3PE15TAi0QHSUaRmkseTU095t+fdzRWkHqoWNczEnrZtVf8ilStO1YHwBagPjGRDWz5mokbyekK/zGc35QPwtKN4GwvqWmcYlR7dsNHAe3r3lZw1+bjWjRBmrXuXVTAEZzXiFpqM+j5pdhHChyS+0q3sIFh4VK4Lq3+aSr2wSS50Z4iHcv1dfg2BTiX9W8Y2SMWb1961JU+3GpiPUoU9INxP69LJ9UNLzP1V+26jR6yieZnUKF8E8XGm6DE+bD4+iawp+b5feZKWXUnwwtlTGONLczNHrW+aJOIjSWDINNvredWZUU0mPaDqp7Dm5FdM1q1S0h6OiyvCCFlL0nSBaAppS/pA6PYRtVBUVHRvlMzaHqgFo1b259CIS5QM1dqXbuMlW0CoFG9inwlnncJFxM6fQIeCjcAg89xD+gpOou623kptb061WHhGIiVcQ+vgu58HYKMp6j2OFcuF3qT36GMo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExODkxMTg3NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85N2U2ZmE2ZDMwN2NmZmEyYmNiN2QwMDI3ZDQ0ZWM2ZTgwNTc0MDE2ZjI1YTUxNzZlYmU0NmUxMjQzN2Q3NTFhIgogICAgfQogIH0KfQ==" + }, + "7": { + "signature": "b9tJ7kHaxd9WDIHtFEXYtId0rFVGQmDmpZ0ejQJ+huTlfCs7kNjM51FqpRDJsSpRR+CzmXs4F+JFZJRFynFhGjyeA8rCrXK+S79Dqa9w3OVHw44tdaq9I8cD1gdKB8UdmRHC6ki3uONu6uqasNVie5QNSXDPM/Yl1KqWWHj8WLpSrCMW7ovWgC19/oIE6R+rUcxWO3iNUI4QE4knyTRmEZGzo3LheyuaSJfoDOxsEs8WoirNhereSBfFWTZRo4KSxUwfN05CEJNGLTGC58u1jKie4bKZn3T1Jv0q70ggU7ernFAtjNi1hQHH2x1LXn/MnSksewfm7lfsfWUtB/HC73qMzrksGPz0GhiFK4pDsUH3NBmqRozJm02cVMzBRb4CB8X+MlL8sWSJvJInXb1e3FuXTaT+lvss7+8Gn2ib/8xu8BuEr8+1+tpCDKSSiZ3ooZQqkq5EQ6I6soW7qDg9+/1TBr/q8VKgzUNn5ccuwpZGclpcfs67jQfQbfm6QV+6c6Vd4wK5MGv7ZgGBvlvKvsoR6jvdNIsspZSQY6iot5XNwjYTmWRsg7aWDVM4TAYwGE5PLT7Keebq/H4uW6KBtKSeE+siiU8mEIVG9aCceDHtBOz4mRRBtzgCmmkPBbQCnzU/rkPxt8xpVUJds0cVhxOeyQhVviINkBbzNj+W5jM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNzMxMDY3NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82MTgwYTRmYzA3YzRmNWNmYzM5MGMwNzJmYWUxMTliZjk3ZWE0OGE1YWQ4NmExMWY3Y2NmM2ViN2M0NjU1NDBkIgogICAgfQogIH0KfQ==" + }, + "8": { + "signature": "nWhg6AtUnw5n70vqjsfij2iEs8N+g/doOM2m2Eo6HLX6/NWRmMIYENLVreVWIWO3gthSrPD9K6nFaVtOhCIWyRunlNLsZr/A17vZBCqEqfkhTYOa9qdqd4cfwN7lUDE12ujRUJQaORS3e2iE244N1mdHZBVBjpxVNlvt+hboSDkrWoPwA2reroO8d4azR2P1jlcc9XHd0nrNskJ07/GWCeBo/z83xWQsKbUUsWtPsSaWBd6ZIirx9Pp7p9V9hKMtPuHCVIr4PWnU6uzHfmU0nuWGCDwkgYrC92bbVaps5F/QBGUrep+OUyTsHrNC1xm+8r7+iuXfz5WRhhcoPVjtsTKcqQcgLnNh3Jhi6hslrs79LG04LgLQqd6RAcLs4lBRVsALFGkSqu8Nm7jgTnIHBKbZWTj3RZ1Z5/rBS/5yNtacybLUGZTETj7vbG38yrv1YaOXSsUJptfUIuk3C4i03SFphoq+LRNBqKR5AqP5XPjNYjDIZ9WK8RLf7whhhBoavytVdU63EaLh49pJJV+USqSu1USIM6U2/pCwsNAoSH1EPlTOklrm0+CdImqbkADXzy/jdFOxQXKWuryHkFHZ8keqpStx84+yFC/0Dk9H0I1dCK7XArhpg/tj1xWVrOXT7W5PXnPlSZIbrLgFkagfiuFSq/b3xIIfxaMLZlgBPh0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNzgwODgwMCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81MmJmMzhlYjQ3YzhkZjZmOGU3YWFjYzBhNGZkZGQzNjcyNDk3Yzg0MGExOTc0OGYzMDY0MTY3OTgxNTE4OWNhIgogICAgfQogIH0KfQ==" + }, + "9": { + "signature": "aEn4PWAiTb8jZ5uMZuEreWp+u7HP6hT9pmKOqerJf70hZ/S0s+1w+MtHu1Dg8lyXbH4n36PQF1mECRlL4p9OTnJStC/+24NYalhsU+B4Si2//57yR4X9ZaA5druuTh6GLNasulN7v9dvakqjwxBaqnHRxYDl+X7e0XVmt0gBYHQyd1h00LJx/uuvKkbFMYbXnU93QIYfLQg6/xxXKFKap8TkaXOL4ifxdJ3XPxFxGtg4vrthlG3So/VXQ+zHQNCQOmv4jSQdp7lDuk1fVIVlpydZZMI5NYiSPkr41AQrPB4h9izo0Jy6hV7oQxzdhyFtcODhLPoWL0aYimsrsvbJzJ1zoFJBqr40L0Xsupq3LM0Xf028/4d1DnJRpdXOXWjb0vJgzHgbDP2fCcuJo0RNxG4gdQa3plx6ywseDgCcK3Hh3ElM91bztspSd5XJ/1fucxQ4Ba8Xu9wZGF57tA8eTgruIUyImrBslyAtxt5lMGeySVO4P5T/FDwDhmvXITwkB+ROmZw5mA8L4KrHPQLv7T2oSmDHqDmbvsDFFmIIQUVH0bRsQHeRhocOr6X43FF5IH/Ci+46cuNF2PqHuxO02LRw4OpPaOR7nOqmUwOhRPlw6IwsMxjFDwoEFGppAkzYhL6rttPzs4itNQf14prvDfjOUnwb19l54HIsA0wvKvA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExODU0NTI2NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zOWRiMjE5MGFiYmNjMWIzZGJlNzYyMWFhMDM2YjA4YmM5ZDFmMDZhN2MzZGMwNDIxMDYzZjkwY2VjN2ZkNGY2IgogICAgfQogIH0KfQ==" + }, + ";": { + "signature": "XMZol5eB18HGQtRWqk1uyT29G7uA34Aj8EtkMfzqGdtLzrMFSfjLxUe05nUvBTFCnL2qO79o6+33UbCOcMnwqX0xm1cWIfq192IX5JdZtkDjIENnvtJ6JoliejqVHvNeCcqkezw02rN4LRHt5HvLC8m+zDggOjB5erLVnyZKZjGdWpnzSWDJJfKFiT666vPVwsEBiW320Jy3/eniusC2MnOCBR77tWa5jT6qnRrww1xnXVFEWB0/X9bjWk7G38673bWbJ11OjbQatdqKtjAqVgptRM2CGkPmnQD4iCUwwX56ECYvzuwNP9poXIBVgL49P1ZXkpHt7bn0nFcui+3dBbcOcWHCUIvoGnk2KPWx+687sRTRY4v0STVBRazU/B+lWQ26TkDA4vI/sCH6olYr0E3FqSwN9/Hj8Wo793wGAhZmNFAVdJKDITyuoNbjP/KmqQQvnjip24xQpfg5GnI6DSk2TGTFoRGgHEAVTZytafBGrP9Xe9bZMHq1+znVVSCct53q8gvnvG4SiLXBqbXJuUf4fE9ldoSca1Tgj2Lha0nj5063ORecxVHeTw5QubbJlQbimwd39fAp85umrTHG2HxBRj1YCH8bIIK1xyA5WM7kxPLOYb1jIh42Q5DMn39McWo7vuEgwfRHL5iX0qTG6SvCL1hSPyNrzVZ9g797z8Y=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExOTAyMTMzNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83ZGE0NzMyNWZlMGYxYWQyZGQ1ZjI5ZGUxNDQ2YWFlNDAwMTI3ZmQ1MWJjNGJiMjhlMTY1MDRiOWIxY2RkMGY5IgogICAgfQogIH0KfQ==" + }, + "<": { + "signature": "YZ8FZnAm7aVimvEM/GgUM8vkpZOXFR1Q+AlqegyJrkblTsRKJsR7Iw7mDz7EIy4MfB7hJfAjlpeldTFHnK7p9W+uBhD22C1RlVLmZYQ6j/Y1+tT+lUsln0upaJRf/aM/PqqPYHjGVVnytfi5NrLAlkSOsVotZ9+H4QG6zmHxosrHh/v+TifhGbaxBATfxIyF+apriw3R2ovCLPEx6yvc6cVOlt3lTXtsP3crbpD7cUE202hQEof9Cq2S4VVvisexBpq/rarki4ftnlO5P4+tFuwebNXaF6Xjjbko3ULhuteUoIHtR5GRi+hNEA/uJ385awp0AXrQU9lu3SFWYxdTM3W2OinjV+R4pi6H2jCYjEiaou1ws8v7unjQkDiptiJWy44v1qokM5j76/Ob7ptZGG/zrUibvnKBZSWm5ZMA7a1ff7oj1xx7VDLwkyJ/tTgz5LATHEGoffD0TzDwYLKhq3aja8HH6418EfzboNfWMDR0r2Skuw2Jmj1K5lKhEP1/rcHtb5dSU+MiztcJ4Mebm7foRWTyiUlfEv9rbp/6fZqbjjUy4cfxRwN1AadofqkZKr6JXSa3fUlhfx6ycaEEDfu14VnSKPLvkKeKYganQn2kBNcVYECOGGkDBd4l7yWmm6ZxEV3Y5VfL+m0FbL1CSrzFS+Lcub899yY/Hss6y3Y=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNzE5OTQzMiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84Yjg1ZDUyZGVhMTI3MjZiZDM3MTBlMjRkMjUxZDUyNjNiNjkyOGFlMTQyMzllMGViN2MxOGQ4YTFmMjMwZWE1IgogICAgfQogIH0KfQ==" + }, + "=": { + "signature": "iXz/QGNRmLvhsNHMb1e4UMqUFRGPPpoOTffIAJRhbsSzAW5lteAhK1+09cBKjZJJyvQD0dh1pEz+Am2Nxdt1rbBK5SHL1KN08bFUi8K9uScOa8QDLjhJwib+7Sxwf0GPWBVxp8ThWzo/51Y4hEfTzl0n7OAjBv9ap9fBYMBwJFWN/kqTrETFzgv00G2jBa7pdYKmyDbrxIDvF4Zw4+hBIBSjhlNfcG4mMp2QgBSEPp9zVEGSdQLwIfOSBQWaNARNYdox6FEJpSPenFuC157frI+OcdoK6O4FJ7GaIc3XzLx6hzpjar3clBYA43FoOZ4aP6Zwb1pMpRT/BkO+UUhRdXGo69IzxtXEUiDT7HCtkCrZZFiUDPNGiBZBT3FcGhYITrCnEqhcMOY2hNhjpMjw+QEc1idm65as/7FgxtEUM0BzVeAJ3IZf+XiTGiA2LeF7IDSTA7xjHVsYQHPXph2xUQHtoEX16kptH74es2f75YKCEPTCcGY04wIBBtPU2x9UM8bRlTZkdI0cEq9a552nFuRVTCrUwnhyuk3InqsJuIRvSTIe/i3AglY5DcmffJ/463Gesh55rK3ZT3/ijddEr+p5noG5uoEmJKncsoWXwV7L5k5Hsx/2rIq5FUa+GMMBvEXqnkQJu1auRBaZ3ot8VShIN3naqFoGjQzWJC62/CU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNzc3MTc2MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kMzM5YmUwMmM2ZDRmNTdlMDY5YjM1ZDk3ZDNkOTczNmFiMmU0MDA1YTg2Y2Y1ZmIxMzk4MjY3NzdlMmUzYjA3IgogICAgfQogIH0KfQ==" + }, + ">": { + "signature": "Gy1JLBab17T2EdIo/7GNUbl03QRjWaIheiBftAqBvtQIBAqy6ArA6fBvpgXCSgssRhLmEwlraj/XXKSTFUGroPpNiWjGu1KdJqja6fz+h4bv8p518/CVF6naZ2BMvVq3EBa+Hh+JEpj/+7H50IXWp6dGRjs/QzUwJopGBIIlv6oWqjSxdIvpI4E7AVFH/gXQITXXDPOh71oFu7ffdcA2h+yrIf6YAuLT/oKiVwhhP94yt52cCp3ziAWmv35QwlsfU/sIeiY+yGjagJ+A99pknKPNG9Vxmv4GKl6uvoDzzmogCP2Gqca+gwEHUuc+yBM6XO5PpIbLxMFbSj7TICnGJRKr8SiMu/i2S37Ii4aXlTl8ASBqOgWHNFblx5K4DIsZxv0EPuJ4QJgGGfQFLTabLlf2stLgHpW7mjq+ed+HMg7yJGjpQqj6dCGJMbqjFGyxTK/cSq3Yc1hnV7E6mhopluqvJy2m3PEp/UGSXd/dUfP/ko738OBdfPnsCa5301D//9tmWItzed+5vOXK+gQb/zAmI9eSlw+w8wBRBrQXxvlzH8L7SzR/RTaR9/zhJwxZVejCSRfLoUyVQraTWCI6yN7/qyFckIFhLFzKFI45lBdAfl2mFWv1bjpbHZtswCAgMGCmq8yVmDrBRZh9ZhSE8f7id4GZ2n3yY5oeZTM2fdc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExODE0MDAxMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jMDlmNGI2MzM3N2UxNWI1ZTg5YzRiMjE4ZjFmMjJlNmVkODQ1YTA4ZDFiN2NmNGI2YTA3NDk2NTBjM2Q3ODgyIgogICAgfQogIH0KfQ==" + }, + "?": { + "signature": "Eaq+fAoFpwh0oBdY84bTSMoB6XRxflFoKGfCT1xAR9bgNAdwCtYz/gxDlJa2QsHUmpPEqy6PiOmZ+R0h7U6PjmPdEkj8cKURcDaRbxON8IjOWUxKh2DyyBgPsUkvSwClnIq6OtIBo3M/3JzslrOWcw8Y0YHZqicfRqVmmUYf63vd678QqyrHVyvtDsi1RPIpGIcisOH+v7B55odecK15XK6x7R0HF1mMnTTLMkykkSBLfi6O4BEMdK5YOzi9eLp76h8k6HhMhkouW2KNGQ9kwy8OPmW0P3X/aNHhqOIccqraPurt6hZB8s4kYgbdyvCXC55G5tXkd9DhB+RczvqV6NctMHeM1o8ar2HJOo/aaQbWTJh/GluHE7iZjGVuHeE8X8ZRVx1FHFQG75q+OB4aCe8iiS4yl9GPIX6k9LqDqJd5N9JxZIqH20AQptV1OVIDdEaz5aC+bRu5/AK1S5pguGNCux/7NsDV8jzfTWg93LyZfoLAK5Kzb5LwHfcziu/cBUN+sIj6lOnvIjl/pnJxJRl2k7mI3e5ZnfqzQaKfNoOObmKgtam7R+MvE2CkTo+HsUjp1TqMofPQiIQIT/BbdGHB+55kU4OwSJ5RCPC5cjlWiqCEkEV/Tf6yp+FXl7xJPQl0dRRfab8f4YG0ZoaDQVYIrGPQmTKkmKV/0W1WkGE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExODcyODg3NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xYWYwNGUyNGYzZjgzNzNkZmMyYmM3NTEzNWM5M2Y2MDQzMDcwNThlMzg3OTg1OWIxNDMxNjk1NjI0ZjRlNjRjIgogICAgfQogIH0KfQ==" + }, + "@": { + "signature": "UMHm3cK/xfr6dF5+QAQoyfSov2tAE+WuR+dYnaEDHTZ6zQMNB6ecVpccco9cPmNqQGssaFBqgPhpsUIfvXj6vrR1xV0q7tvw09po/wq8THNQakBRs+9rL2x3QIJ4EIxQbvCypIn932MLqGE9plLFBve759nF1CmT4YZXtH8OwZbMFIvoWW7WxpeM1gZIbNFTOs4it65l82DcjM1FGNnG0gvLROK8kwyl2Q6qmPl2om/zisxhX4zDsigpsyGvIq0yLCO10XumliAdsJxalDOpMQeJaQrjOBho8tRdAAH+kk0RzDAB5LHYRbpHA5d3lh2IUGBs8MM6Rm5Tz5QkjknRKVstVEliCHcYdCwh1aDp5J2KCiEOhSVdpW7r55vc9Q7YGBoe3srXvCtNZqEjPxMOIqr+jKfLUrmZOBlZkNFR/JfX94RLq5GMuhp0CnLzacQycDbksBU8ceghuLkM/Z0vxqMfwgaU1LLGNlju6VAjYyWpka4MWfPzlNS1aJ+PkOCRJ1I0e6XybAuoHIX9B/5qSE3cH3FxPqmbuARsDOtavb5OVRXfAII0cvJZUIO7humb35GMruuFvzxfdCNDs/lpU7ELE6gyzEGyQtXWM3k6D7X/eKUrdt2je6eZ+CWSMtuMSLm3XLwRbikOBO7SZD1OB6tiVK6HimsWsAghtsbBZN8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNzUxMzcxMiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82NmRhNDhhNDkyOGUzMDBiMGJlZGIxNDI1MWU3ZjkzYzdjNDcyMDBjNjdjMmY3MTg2NjY3NzU5NjQwNDJmNjZiIgogICAgfQogIH0KfQ==" + }, + "A": { + "signature": "CZ62zCozAA0hGQkO22UuwdBpMZMhBEq43zv2GhRKVpJv8gwa/oIzJO7aniMhVqf8wYTNrcT3egs3qstCLmcBfHzs+GtpjDhA9vKZKh4XCbAS6aYnm8cRudpLH4f6/05M/v80l4nZg1hjRFdts1mhfzG2yldk96j8EwfyqQZumSpFVHjNHOYbyA947qmkgG63Nauw+6ncpih7ev90BcSFCkYcSxboyVdw6TZFz7PvUb5z04HTVtMU2BWOw6YPunVWmL30AuptNIzs/0faTvjWrOul0JHTICI9JwNXZaw8cKfic2VJ0bKUvt+lA0HlzdrcesvVRGkv/moVpTQzSSOzdyqEACbKmyOmmdIlpHG+nC51xbDP/3AlmuGJ24NN7XbdZdtVOcY07Fvq1G8vGxxkQmsHP5YkMaZAzwbC+wNdhz1kejycjTqdpDZ8mLr06PgI5YN3izu+sFL33LV4D2S59k5+ETwdZzwBO9/jIc2EZ+20p7dtLB+9AGbNkZYX7T8M6Mwa4sxrr6/+0pJohguclSUfKvKyz6Fzj85j6qNkPBDv4N7rLD9p61pbI/uS2tBrN0VxIf1jCVb4Q8thqBLNp+YMXVDxoWL4S3OCCaWimN2sYQY6N6AIQlVeph45zgkd0ePsV6jF9YE+rsxHfewya0fAgPdMPLhl0vRnsB8LtB0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNjY0NjczMiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yODQyOTc0ZTU2M2E3MTk0ZGE3OGZhYzhkMThhMjA0ZjIxOWI2YmZjYTA5ZTEzMjA5MjlmZTJhNzg0NWVkZmY3IgogICAgfQogIH0KfQ==" + }, + "B": { + "signature": "n7jKtR30W4ug0qRyA3C6M7SxWKnDITuuC0u6Rn9qA36ZSklYLnppRz7vzNPAgIO+ISq4kWyN0JfUNb7I2waYod4iYDKa2tEJj8Je4Ohns9V+HXAc0HemhzIemA7nr5w1HQ86Szs2NrGhWXGYQpV0ihWUt1FsHb16+PZNW67GoFDbcGrzQeTnFNJkV2HvTE4z+/XKoGKulS95ATt29IwYLuO8J8TaqjTjI7fiQ2KZMLPMZ0oosXnQZsfMY1JZFOAiNTEbk4fJms0yXkS83LDJAt6M9zzVSc1TtAwjc+hiJHD6+pof1AiCyzvTOR8M3OKk6HjDByQkZKJx1/pdNU2O0K5C49T85QSiaTGcnrl+R3j6anHl/aC+1vyQd76n39vS5xQJAQQo4t7YVQHPJ4fZCbnFTtwHRAO90sESJpoVUIGjDMNUbzXXX62wMetU6KYcj58PR0Q6GJ7HnYvfU1D610jtP+ZTLyBvcSYoWXwQB0ZHG6Q1t/4aby+L9XfPWMEzP3qFTogkRoaK/1PriwUmfxra0oFCGf1Q2rjk4fF1ttchmtr6+QSV/msxy5p6GGlholfn5h6QePhfLrUexuOJBEcxthqw3ydVZWmOBxzRKNaUTeq8mPXyahgtC7Mh4KFMVPQCx/gQpPSgU60zkVeDJnYWQ5bkcpXqtL9/KcKDSv4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNjU3MzA5NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83ZmNhZTJiNzhkZjk3MDg0M2JiN2M0OTMxZWMyNzA1MGFmNDJkMGQwMzE3NmQxMDljY2U0NjQ5MTNlMDE0NGUxIgogICAgfQogIH0KfQ==" + }, + "C": { + "signature": "xoJgYPeyYqNBOY/Tr1qpxx7g6rRilbLHnXV33ijRAjhhuMwL0RXrVxvdiWOyos6fSzE+Wkrk9We04+h9ZzXuDWjibMtlJwS5Ic16eaNUq9cMXIoUlx3ZeUFOlL1/2RsnCvu6fTL1ccx5U88AokuiE9j5g+ubz7LMbei/AHgHUHADr4dBmSDo/3MDB+0McAp6zf3n7X1vEppwBhtK67Pdi8bfwWNAFMrzUBTNJ+/sk5agqsFjgmkbg9j+gpPldLRVDEY1hbClVZ+TCwprCB2qDsLoq8onR4uaoocdZIWufj9IxO65jUwqloataH3Lc5C6VGyWCylEtyEBtZoRJUNwb3kD1v3bL2Aju05PTRudnIM+GRFrJAm6HSvwbUCkhFvTgPDODXKKOCtKDn2OM5dtUV2Yx5i2t0SC5tmmBCGwRtn6TbPnUtOMYr7QPz8MgB1U4/XuLIRy+mMLPauAutNR+YRzYkfyPBoVXLcpzrxxleJ7g1aIWg8Ynh2pNnkaSehj0v6lFUcZ8w8ETMynpA7OsAQxXOfoeO9Vz7Vr1bsvm6v+Wsku81I9W6DbYjpaYrr1wvVcV4WpkQymGf7VbTGXclJWHwJZHdK7lcfs4IhH0RrpG9a+60C+YmYWR6NaNZTSv+FESyRymB6zsTqGdqREs1bbntltsMLKoF7jx50sAR4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExODUwODg0MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kNTY5MmQ1MjFhZTQ1NWI1MmRiMTAwMDNiZjFlMDYwNjkxMmMwZTRjMzM3NTUwOTcyNThmMzY0MWVkM2EwODI5IgogICAgfQogIH0KfQ==" + }, + "D": { + "signature": "iTlYhEOF3rf7OEgR8p/BVvJFSi0EF1/uiI57XIP8pRZxY+ODAd4Ihf/RE5RizT1NmE2zU6igGDU5KOSU1DgwSzin9zF9xwxmgcr+BjDYJawy5H9E8+jp4SoCJIH3LC57NYqblzEjfUorQkhie+0MVWihGdMKvhsBi59Dl5CdX9gMr7sb25IkMh8iifUu+Zui9M09Q08oYR496RrBnn+pvV7OHwwc5Y5ltxYRAoOrQaooSJ3+VGOk8q3Emg3p17JCzZ7WNxfjEPfJQqyXa74jWY/NOAbUAWXFIi9B6ZntHQ0gfd2xRzNRQfQSnSgnYDytMUl2p3hNzY1U1hf3ORrMNoIatkNsS2nInrkeXGftbfYrHMIxxp8zaLy4z+BZo5yvWUWAtgIGCrJDDGyQ7qxn0zo+kH8y12l69nQ/rLj7JQZ/Pz72BzKU8BhAMMwzMsfFPjIp1rgKJsNL+8T5FEap4APpSGLEqAKhYTZp//pUBFR5jT0qsCzAqkGlWY70CcFRFjisUoqgzDMbU3Dc1PuQyLj6K866jW1y0NPedV3D7H5cfBZfZg0gnfjclpmt2VNqJSwgC7QTJ54KVRuGO7LN9MtYqyYl5pL4RpigdKRaqsk/bULr2XMVy7CmK//aEZfCK5rMpNdx5W3m9KAmebB29ZwfknyOFwsLCGJk3OYvMtk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExODA2NjI1NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84MDZmNmVhMTY4NzgxYTNhNzkyZjA2M2M2ZmZjZjc5MDQzOWY1MWJmM2NhMWQ0NDRmMGNhYzZiNWQxMGExM2FlIgogICAgfQogIH0KfQ==" + }, + "E": { + "signature": "snOESdgZJ17VwXvmSIg7i4hMjl+b8KXeE4m8+plX4vd0NwC6hyUlZl95vD20drbMz4oWSfiuHT5GicchOorY4DOQXgunW1OloMPYIBQ/xeBUNvSnsz+Y2gonw1sBdkMQDZCota9s3I6C4KlK6fdHfgeoVB45OF99QTILXyxngvfRPA40RcPUpcoHALYFiS3RywiZkQd5gCH8JhzVy6XTEypWm1fxIs5MVQ3mXmV0F8uyeRbGAlyUfkn4ErZOuiDhsQU2yZylIGILreyznCNtRJ0rhmtnuflu7/1/Y7lYUSA2DLZY0s0R0ADn+kvQE7z3FkwX+Lxe3nmCdMAwZ8BWUzaSqIlzIlns7DAwajyK2aq7anFBymNfiZopfw8wiG7vMwTgfr1i6jYjgBm8IUfDJvmBKQG3vjS3OwXnjlz7dlA7lVBQS1C29kgUAC2zicdknW1wexqsNDK8Gw7p3CWU17kKcgd4AU8mitcGWi6kx3K1vUgfNKv42XOnD/mWoGD704h6T0WfspfBrrQ2711+U4W0QTKstZIb0fbz1OHU8YVNnmbPzmO6qL/V03VfNu3myURXujx6HzGA/pUtGeWpi/A5js1d6YXATb2zo2MOkpTyoDyPuM+7MDlFFBBS3REXZ1EkgZt9fPKbGxEnEd/I/aEPI0S0FQNCwWQyXBR+AXk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExODk4NDYyNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mMzQ3ODk5MmY0MmNmNDQ0NGViZDBiNjI1MGEwYzQxZDA5YTJhZThlZGQyNzQyYzhmMjYwYmIxODU0YTIxMjdhIgogICAgfQogIH0KfQ==" + }, + "F": { + "signature": "hxXqGke3PQChdRRVEgghDXmxVU1xuWmMMrBb+jHmb8V0DgVSoM5tePFTrgb6h9AtJjEXmmEXFsq7jM8jML11qVybIcorBkTQsGvW9GIGmrHj6KEMs/uasFM1uUzqVkP8KtkVIXAZfZiOOIpp1SkBn1/q9cnJQ07QOLkaWAd5iYYpseC/hanLOuqGb79KAPGljmYdHlEQIKoUPrXW1WxcjIVIchJsF4NasohQMoApy8Ji8ghzg6cR+tQzHVwUZyHxuTyoWSjesaMxGQpaZmHjweJbF6IAKqo8gYiEvHCEz0AHxAIwdj6LFdWcye4om3/ra9+xt8vQ6KGdQ1s9PCsO+B2BuAs7Va00/rBuizJS7FG+d0fnvzEJ+GI67098WODTxItKM/wt5wK0ruoHia7IXPGz1oHMoUHL1jcPff9OHoqkQqCz2LnnWjiBOZiAMW+/LiDVlMgjSAzmzcDvbmrVhC/pxodAuUtdjI7I3Uuzu78bjSXTojKSbv7eWr+av3Zmf0ngl4jYpO5qicvXeSJVeC3ghNtpdKHlUPNjeCbIEY1Qn/KdOTfOfsY5A6Z772dvf+bv4aWShp4GLvcEebV1t4nLenxGjovzkE68O21jCAvCoHOXAcZ2VIDZoknUhJTcx64fb/gtmQMl4TkCER3PRO2L6mSd++hOJ8ZKQjTkfdg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNjkwNTU0OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hNmJjODc3ZDFhMmRlMzIwNzhiZWE3NDhhOGMyYTMxYjhjYjQ1MTFlMGRkM2I2YzU0ODE4ZGVhNWJlNWQxYWFiIgogICAgfQogIH0KfQ==" + }, + "G": { + "signature": "VZYbyg9TP2fo6LGKpiKcUwGEOUW72W45rqa+eNCuQxwlZhmH8xTAmwvbT/SZmi/eVCsohbc0T5p+EkuHvrotHXbimR6SyLUnDYwYkXjJEyBbUQbly/CAAEwxwjP4LsyziyHnTAI2LB6TUGFDbccDrYjs+7srpZuMLK7I13k+7K4KZRk5bbKEtpM3tGy85zpr74HGrq5WxgxLGtXEAFy08A9xlc6qDiCJ4ps8mA8gzIwtIvMBzhh/nUsYfMx27IRIkxutrYGNmnRjAqGCjV5ZkrGF3cOoZvdxO2DWbTkTVzzziKvUkaIeGmRtcoV273+wwDjZkCBrpLIUf4H+zaRmSCVxTea1tWivgnzgv051KMHqbApAlOQ0MhQo++Ycx1xl2tlU1b7Y1DbWrW1jZr7zRmJkLDOK2aAIbhJ+jmtABSnmgW7ktJfIAkZ5BWIX6AkHd2GxUUg+Kby5/8SSut1VQw5V+lQTZbWZ2zGEEeelt/uZXG2TnImPCaiQS6uuFRLeWvVGUEPfOtGHcFZTtlXzXrm9R9i3cOlmWkbP3ZZt9MhrApbCszNOR/6oSxl+wtaXJK8ulF9ejWXoBeMVmGGyupXVLS+tH0cyozjr7t2D4fZIbEEEI0Fx6MRZifyiQYJ6b8HtYokxakg9M3bk4ox+L54bAtGqeW99HVs/wbcgIgs=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNzEyNTkyNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84OTI3NTEwMzVjMmJiYTBiMjc3NmM0ZWFiMDcyOGNlNjI0MjBkMTQ1M2Y3YjQ1NzZjMWRiMTg4NDgwMThjOTk4IgogICAgfQogIH0KfQ==" + }, + "H": { + "signature": "ojOzHZ58i/FlGBtNYA8yvwpdQKZWfQgAk2U8/DnYmAm6BpSAHT4aRArTf4iFjfVvj5NTk7C7YvJPRINwYBe6M2Ukj7+fK4BV21GOkumWcYetqTOJXiPZkBGnf8goWCgUuBzL1YhRSTwi7On7wuWHVrXBEM477E3N5jM2VZvkfSAjsE9yXnzG/D2Z+lKjCS3pNCA33R+Z5dpDsi27Gdv75rofSZvZxdzJakVNRPb+Wfbt2uNE4ipwq7XJcEMUnawP3sZ1UgnjSULE7Fg09oO8OPNhhyvfnweRv7KEmIcQEpgiDKUdqhjLEiCkOXzwZ8SfuTxY3ZxH002DSipF6hSvf9r67u3IgiqR2JJaheN7g8wTBXtksjvH0M15Gpc35N2vrTe36iP2lSEJtEOqVyEOmiWNqp1Xi/YeJpDhn3zvwpIUnYyBMWTaLCYG5dpMX3niyIBQmi8AwxCqIdTRl4kvZjmTXBSC/83+BUcx9VVeVpkDCBxv834MLle9Dirfyhx67nPuNc4sslV6EXAyOtPBAhqjBxY03hmLvAA3u7KYYxsIto+TKrk72nCHw/7c9vBK+mINcpaT+p7DzXZATFvU7U3to+wyiIhnd8yInBKtOO4bmAu1CCHxTdFHpr1vRykfFBA4QfNmFkOk284SsSKIqKoMFonwcIkSjSGfBmOfYO4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExODIxMzg0NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hMzZkYzY5MTcyNzQyMjQ2ZTUwMzE5NWU0NmVjMTQzZWMyNzU1ZGMwZmE5YWYyMmNiMGY3YzA1YjE0ZWE3ZGJkIgogICAgfQogIH0KfQ==" + }, + "I": { + "signature": "lD0VBWlKTKdQNrcLXmJmUOT5OPzfL+Fss7T0VHJqHTx6gNQ3NFnHyZiORto/zCR6eooTxAPoesRhi5fbmB7RkD890xqc4J5kzo+8zChl5OiT6dpe90iKHqyiHwtay6ftzKTopoq94Jz4D5ygkZgH978f+CLRRlcc7Xy79YuG0UMOwuNLRe4jmUnC1joUOjYWplQfndD7FcfBcBge+U0bEX/nQZL+vCrX3qxHsUz0u8HDWJ9sr9aeQPFjAhZHR+DdjsRcLYl1DTVsr122HgJCOeSppj5J3kq1BkdjHAQ/9NDLJ0wXdcQ8qTDrYV7Qh4Nh8E8PF0Wt4hK++KcVgc65t1tPnC7xgw7g4qXhnTNO3E590IDuJeIjTLu6RosSNj1Ph+WEhhYnxYggtweHj8uTgURLrpJi9cgRLmrtfzc57aocGwQstuLnMMb2Ou0cyDakjUiAaIlHW+0iRr6ENElbqnxXzy/iXcn6t4mTjocttvnWwiNOJkSJAXUXXqMX4qMzzXdyMEhHTBVCpsueXbN2RbAdeC8pDhAIwAyLy87Sz3o0icDsaefcavOkofTzP7aHs1pQqzgVBU/gBqTh9vO2Qo/aNPISzwIjSG7AszJJeD1MUc1PfOsXjZrXbmvTElpbDJKJnQ+chAFkL5ZaPqrEp5WdjS8ITnziN3Hth3+IUg8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNzg4MjIyNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zYTZlYTFkMTlkNGNmZTc3NThiZmFlNzQxYzMwNGI5YzY0ZTlhMjY3NmRmODQ2ZmRiZGE3MTJiNWIwN2M1YTZlIgogICAgfQogIH0KfQ==" + }, + "J": { + "signature": "v5PGTNgTxHvFAOHe1yRlXVzuw/Ol9iqgimGy9R4PYBkNtVj4fkRe+P9fRMuQAqqZn61755C+6ViFwyapz3381crW6oCYkqdnEIMSygpcCH+EA+xTV42J9wTuA9TCSweEvpyWYo/fBMy1N43LU8gfaGIL8peOxRBpfsnx2+QdmTLTtY+FvDmM5lNVvrO19juYxcYtoiR2tFLym43ReG8/PQcnnjU8C7vMLIm/TgdBKpUKbmA+YFKq1Y4h+/CycB69YoIGz4KSeEoz4zCYihb4oZQRrLsokX0wfIdguvfRplPpxN4tqf0gi7RxDZ3vG/rsJRU2bG0waUFxq6g9X/eW1zW5Ey0CBMX1T1SAn8sGkOofAQ3ocRR4asK/72hqIewicnE6jaz8uXpmEApl9uDvXQqBRXbJOQ2yLsC6PxrKDz4WV2BBWn2r/mdUFf6TSIcWHslIQEGjEp9gbsJNuksRVWWjNsEku1HnyamkLWSWOVCDg0F3LnLOUdQzWpjIa1Za/SPsUl/Fqud13C56RxxS6uHL4lVNQQDLMP8uHG2UxtvrQLpFpHYNhqvNj026ZPtPXku/OUMtzz3N9FuM4cN8ozCW4CteNj3zUOKPickwqwTTREp4f2EW9tlp/7XcHk/YdRytQWeEaMLs5Z0DPp9mXuFO/9o7Pe87sK4zz9XLXJo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExODY1NTkxMCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83OGJlZmYzZmYwYjQ0OTUyYzEzNzdkYTAyNWUxYWJlYzU4NDMyNGZhMzAxNDM2Mjg1N2IzNjg3ZmFkYTAwYWI3IgogICAgfQogIH0KfQ==" + }, + "K": { + "signature": "sQxOHpvLO35K7xGRO4ev82waATC/mw2W64rzlmMW+g9srVJ3le4lR92sKFJy1XiQadtY8b5xazGw1HFKuy2Pu/007Xk4nPIqDzojCMx6d8A8KJ4yRhAp6ti62uAKyspeBQ/N8jO+qQJLdE+HQsZCWKOgWO1hT6+223EJrxL4RwFiS3fZaNiueHaP1wNlPupLC9/U7HAWWdEjRs6gvSieY+n0HZVmdGvUQAjaDb6bbe/oGXk49ne0ARIliFDOeDwsCHPSZ8LGKjrE5ZL1QCvJ4dOlONpDH3+GPl5bHqxZEMSLvtuOe1Z2beNH3H8mYucALrAFnPaQaiSULsNvGNAL7LktBizpyML6RIZQwOzCHmSHBDQuay3s7CTqlw25TIeP/yMRCh+2EqzO+UieFNUsFxldU4025WUZR0aO1S+xpK1J9GwrOEB0xTrdSfvJwtpSo/Q7PuHOHuUSyP1xn9ftTXD5I9CiEMDFV8wtLaZsIHieYpvvpUYhVmuE5MetoKDusmgk3c2xNq5rHOwySkgikI4xPAks/+OWMtlXUEn1xTcz1EoT28wMrDmecrSCCREJWb6JFIpVyCLmxO7LqDauzu4blYItIUPh5wlJU6jFQDL73qpaLOO/0fvgyzi2uVXIDk5MqrWbo3hlWTGS5ZLLqHZ/1BqjrOKZbZLSc7hTS6U=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNzY5ODI4OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xZGEwNzI5ZWM5NWJiMDY1ODI3OWZjMmM4ZjA2NTNhZjlkNzIxMjUzZjg1Y2FjYTZjNmQyZTgyOTkxOGI0ZjM5IgogICAgfQogIH0KfQ==" + }, + "L": { + "signature": "Wc3XRS/WwDifBb8RYotDA7iI8rzDHGA8t/4myXHH3k9kB/AH0HMtzXyoQTD163jmto/1uy+HMIbqgjiFLm9OO6RoQh6uUtH+mJ8HMuiqzryvBBJTKL13vcJ90X4mRi+Oz1tM5paCaq9imWgfFG0R2UX1taQEe160QoDe67Z/Q4gHdE7Zp2dZqPlhn42Xo2XQDtMyL/nR1XNL1zQwIPHOMQzsmMTXEM9bicDkcSXoTOHLQ0A6OD4qS/bRBWgVKY7GY37G29zS+r/IgUSYT08MJ/rDHlUCgDX6RSNshdHuxTMy22qEL+sTX3jtGX4AxjxSqOqIiaQpHamdKuPiIrY0OsutcgOc1YrsRSXANWHD0nmMnxaJeniFEXh8AIRfBequzvZv2bJm2cuWe24Uf/fb/n9V3ZxuGJBy6ayhY0K6yOCPK/EE0P6DVayX8+xPMejFSDIyrJ+EYXDfgqNr/kRkinS/RHnKNEuYJVwTp2eBNvcXZ6ay/cY5uiYlbXdgTGNqMMEDA9ofIkLCLX5gnNzElWyRyS9vbY3SKOHe+AQ42F0a57wKrYDptIFJXnZyGrLjbDIpBO7BX8uHlgIKtPCQMcqnoWJ19s4qdv+/+dzO815JjLLLYCoNR2wi04Eh2ogJf8In4+d++8GQOTRLQUbUSDmaKrgfIPQGEVViI4b8Fzg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExODAyOTg0NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mNGNjMzViY2ZjMWY3Nzk1OWY0YzQ4YmY3YWIzMjBhYmUyZjc4NWUyYmY4ZDkzOTc5YTAzOWI4NWM4NWFlNzRlIgogICAgfQogIH0KfQ==" + }, + "M": { + "signature": "v1UpT4G3HYPJu7oVXN6ht6efJiymlVke+KIGSm1W++xlY0CvKtKYQpDcsV4ORUfBDWuS3GbUaHvJ/Id+SNNCiVzXA2v66Bwh2NyHYtl2Y4fcvPXOFxl61pVrBCLSTedkCyU3JTF8Duy43fJqTr/ZXIlvVCjVqCQyeV6hplcIljZsLqlzrVTYGcJ97FmyzWz3nFIo75M4cXUda4/AAR0l+2U/KW9X0Xi3RvvMHEup2UyN+Y3cuHYmIwCQaZcWN6bQUnYKBKltjuTERB1nVP/55Ov/yiJ7ZO/qw9riF0w3JQNVTamntB0Tr/BcPkjvDKxTvbApHAVrD8E37AYd9cNv+uRp8NPzgiz8ls2qtBcngFWr7ytYhW/BrNYVWF2tovgFA9kMJZ8OJ82rLg1GBU8MhnxGn4zLxCV27UB1eaaW0qz4QcVknT42gpYIUqmN8VaUJH8B3TK4t+GRkJrUK54jJNJTkWOjGGg1Y0yCSFw4dB7sToM7WniV/HWtYy09S2V9aAnPfeKn/VhusILB9GlSPM/XpVOJ09xPyxkypTFk8kwXW4kMsAwYosJvB6p625JXqgKuOW5+80koirsE610Jz5N50BVXWSLNb3olExQeL3eDWSp2z08XbQwPThSfJZTzMaibbJclAtqqNKG6QbjUMyk5sij4L65K+mzo/BQJVnM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExODgzODYxNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yMjY0NDAxZWUzMjk5MmQ3ZDczOTkyYmYyNzU2Y2YyNWVlNTU4ZDU3YzY1ZDNiZWVkOTk5NGY4ZGU3OTgxMTUzIgogICAgfQogIH0KfQ==" + }, + "N": { + "signature": "fQQVvfovaY6wj+t6Qmog52000YeT9nsdBJsKPnLJPueTwzKo6RxYlyrAR0tgeyoyFdBQ2eB36HMeC3S8Y/pzpQFCIeXTW44FeEfYtsMoucD+7L2COgE3vjty0JBIbJrKequ48Fsipzl4hrj4TSkoVq1AM101HBnvxsHB+pZ3EyG6U5DhygVoh4ozJP0Ic5t8a2hcGWqEPk31NwXRaDAhBFfpEbpQQt1Z3yNxzv1rLqDxIcrUB8v5kVrz2i3tMyVrDI6AzqnA4ptLYnuu2sOrcAwhtmO/oHv/CpBW2w0oZNT3XI2wIoDDoaglRWIVgvhOuP9/jsFe2UuxQjRadl5Gkp4To1SvAPEeJZMgTn2mmWjoGIgtGrCbr/gUg6ey0ftIpDCDPl0LNqZOnVw2gqWnLqMWkQhYIl5ni1Z2v3SeTGm9Y5GQDRHUtu8JiweO+Ea0xepz10ajTzFtV3VKRTahmiCIKMRZB5z2DvMw0SuCSIKn5BhQBeAvPOJmIltQAwttpXTWf96/OoM64xfuFJH/TY2kvSSYvyctuig2VZz+Ln56ZzGEBvJ+jbKQrPoIXRipjZwrgCoTyUJiIgSyL5iitFY8rFOOZeuLPj0hEPBtDZ+kwOhcfLec/g/zVdAE+OJ3h8BQ4mhq5voDj8HgANBTS2pFdA12L46CHvvDYB2wIRQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExODM2MTg1NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hZTYxYjYzOTc0ZWEzMjgzOWU5YjMzMGIyZDFjZGU5ZTg2NjBjZjU3ZjU1MmU5NTE3MGZlOWYwNjQ3MWRjYzQ2IgogICAgfQogIH0KfQ==" + }, + "O": { + "signature": "rs9+5Sddxg6eiV/05Hz91lEq0TghpllPEspoA/HmWfxaMSjNtoei6t50D7DNiVLSiPm2RNPYcEASnH3zH7vjtlzNVyk3py/n72fa+Qrdz/3HcXnj1iJT2f3GevSfhzcagckcXJ4hNX2F58+kq4ic87HD1yp8DpS9h+deu66sG6zjVy/HgDowndw5VbmXMyPUbq/4CwXM0WkoOl9DljVCVr/5AjQqoOQbdcu1yzbQuzumX8XKSsgyamAYHSsT3naGOriwao0JZ2M7300QjdQfifXKBI2+hWY5DE9oN7t1hzX+52mgicZFanhVGLo+5uQxSS9mxc8ys8vVKhSqW/XFm9RH0RfdDo2pb2Ts4ThPb5wlw7fJ79UGVyojgJhplhskp/Wpzz4tRrjN0zzIizePpp1H06NibHO+L96CYqLOr57VrnkkcEPSqx72bxvW+UzTsnAazwyIboAXyw/H3pELf0H6yYIShRJ4ENGSvOj8A6RgRP6/KDjDkIoGe9nEwNd6h9Z5d6u3JBWxik0PrH98vsF03LBSxviREGbwGQ0KKP1aOpMKMatqzHtH8jjJKSSFXCSGe32Q+vWWbfHZyA2Fo71YA60RaYmyE2o8GUYbH5Krx6PNwUg7wBrmx8CdyFLtSppZGbmVOFzqEMdWtWlP/1pV4T4jujZXhEoLY5pVkxw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNzIzNjI1OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85Zjk1YjY1OWExNjU4NjBlYWNjOGVlNTQ2NzdkMGM3NWIyNTkwNjVjNzU4OTc1MzA1YWE0MjdmN2JlODczZTYiCiAgICB9CiAgfQp9" + }, + "P": { + "signature": "vj5/Os8BtBGAVyQSZV8dg4ZETy5YVyN3xNktDLb8nVOuD0zT1aOjB+ZDnwMHDihv6R74us6YSTQVIehBFE3gM/O3o2XLtclmA5x4xPQcd07reWgKgNQ9Hue3mkiGU5TQycJWAxRK0CRIDfv2gq4IkyW8h4z3L11TcFSgEQ4SrYNlfZ9SJNCAV6gM/TXu6CCiihmAdlO/AH3ueHR+ccDAE3N1xTMCau2lLzpYrlEWW2zPeRQ23sturUhnBaZyybkwdIDjkkE8TDCyX4z1uDotNMomtV7Tp37+2I9p6Zif2QuL7GLPPkfJN0D6dhosnRBLn2rVDs/in0Qw7hoZ3iIsm1hYWd2n3NqJw2CVnXpYr4rtOWIw1IJ/GIk3zEXxZOgaeN8ciJg1JduQBU0TP+l20JaesFhiim41605ooHcKYx918nxzTiyXs+v1OSOSXXvoyGz9odIK1cu6sNeOdVdGhJZHzoN7OGyfgjOvtKv09avTwDACa0xItIYeulQFg/9ewD68kailUsAmvsRMUiyFH6G8bqXmTLfg0WALRYr8YFV+yqrZ7tYHOZwGlSLfIU5RyCCxaI3WqjyC2ZkqdFOqvlTZVdu+cLbsORnub3TUD76+GWrqfkAh7Vb7sfWohGToS5BDvxHoTVIlR0I4/+4S865ow8Ovalz3xDtTnoHA3+o=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExODU4MjA3MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kZjQ1YjE1M2Q5OTI2Y2Q4ZGE2MTI3YTg2MjI0YzkxMmE3OGE4ODYwNTk4NjYyZGVlMDY2MmFjYjM0NWYyYzkiCiAgICB9CiAgfQp9" + }, + "Q": { + "signature": "cPzLuFakYv0GsmDBzdB32EdDwySU9wOCdLuIZ+imolNeYzMaYPbCI105FSxpLTK//dHLqeSRZIW1Qq0HmgXwD2tCYN3jhiJIlpxApzP9zsdBYI2iHI2LVKW+HADtbLFkPjyQFJyPd+t+T8NHJxG132sPS6i8HtueC6g2ggyJiHo79/hXSoTpXHA0J/bFFSqUgykIB310uMlgcY688NwdXyBYow+wlddyMYHNEPnYeLGF6cUVGAwn93MscSiuPRWrK5WIR08prTpDoRZX9pBNzhM4VNe3QUzcI/7UGfjhviXZVXTK8lSDagRzaQ0XGYzLotsax3VAWNPtmjG0PO++J8gqfTka+WbuCZ223cIj1btAhO3rSJsFJntb5OZCJZGDsv+3MoNQjjIXo7R+Ju98fy5G9iYxExeotaXBw6+WE1bgvQuLJPzGLx8VuyfAnb8n3MGN3xsn4u3APyistEQwmH2BirkA4WLMfXhagmqksSgyDkBkN4Mav6ILu5yFZniI8Rko9Q9x5aXtTdQEIqK7bDTNFctsvlCQ/ZS79DnBlFzZMNfLBqCwKmn2GNYoCIQ3yHlRSmdBn1FIPKp5M3z4Pt5rnxgF83uAk/mkksA/pU+7BF04nsQCWK1oJ0+LrggKhZ5dAiNDf3dmyKZsouUmsxsCRHyCA24is0X6iyXfLMk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExODQzNTI1NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hNzVmNDgxOTRlYzVhZDg0ODFlN2E4ZTYzYTFiYzhmNzE5NWI0YmQ0MjY3MjZlZjM2ZmJmN2JhZjllNmFhZDNjIgogICAgfQogIH0KfQ==" + }, + "R": { + "signature": "p0cpCIgz3rTXTNaVUWCfyagqWoB4hC2qSkotTd7eJkQ1s7pHaWblM2hY5MHIZm3PP5EdH1BEfB8al5wdlSC0mFXOO1KYCP259oUjjyuQVNPdkKA15lx7eb50HGVU9G2oSKz4LAn7bisMy1XgPQDZ61F9A4wkPTnhTT1vvMVzPBkR7RiPfsY5up2Ah0ANYgMZcPdll6024L/q/kETOAyhkdoqiJwdmJr2JuYaa98C6wQwxfdXAwJBjfdGXN/7oTie1SizvCkID1tchgNcAtJ+Ds08T+Um9FpKTZOxq8i+zeyodKPD+P9Xvi0IhFXCScs3d/snk7MRmJ9zmibAn0O9Nv/yPqYvkxTFL6Sf7o8BoxbGsD7X/gQWSlQ2tFnRyHrYUiOur6cEwS2InTn0USZBBhR1Ftta8cRG4e6dxVp/m4ezU7+vLbQ2RxzaYXX9EbazFpwozCTiMbo+i6RV1PtSGlZ/wAsb3njttap8pMkvoMTAW6DiKGJbpCn6vKOeVda5dDDXLNztKRrASaI9437jJR0KAp7TdTuIKLbti1oJ8Br9EcHoVXtMFRwpCpZFyIjabmKjHH+CNkQs7geNoxmRHu41pr3kWGGOtTItYWICYxeMjfgY/k+8KId1x0b0WkHub9dhy/aTu9BZSb9NYjyDBtJxtBJmLBSngnD7vBcJ5do=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNjg2ODU0NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mMjk4ZTIwZGFmNzBmYTIyNDkzODE5ZjhhY2FmNDhiMzNmZmFjYzA3ZDJkNTkwZDg3ODEwNGRiODRhMDEzMjE4IgogICAgfQogIH0KfQ==" + }, + "S": { + "signature": "BZMKYBLRP0d5zY3rxU9tQkjUHYIVCA33cDPOe+24n6jBWzxJSKBmWM0UmSsFwUU8Jsdh8PFZgsTUW/q0yxhjgirUyhlPzfO5z3tKGgl0tW6nbFiMRJCM/nt88sUmrYjH/PI1uhGHqv7Mw5PQch66d47XBTKEIdQNWoHFpEa3BMxjsv0Ny9GAJQhflfHxaAoOgRi4j0VkKzHBInsZLHdSjg3o9QojC5gwUJp8euHjEEuJCIGn4GQMM2AEkLRTEVuvjwk9m+/YVQ5a59Y7pxJOH1R98zEo3lx3jmb60OyXyo2d9D2QlDqKBAoDk+X/QX7cm+RM4NfA/Z1Lzq1z8kgEmojtWHuczaCQAH9MhXEzCJkPXcGTX2eMwYh4iggj+gMUtacFz7zot143UN7QSpND7cSy8G2m1TFmS/ndJ85SM927VuRLAgWHNCpm06ztTUFqQVkacclruyzFHk/ytOHkfbnuqLGKdAZ3BDZvBnO35ou8uNf0uyIIpsSmPUXy8uu4gc+aYnFZAQ3Q9tRWJl/CV9EY327SoSlae4muXhHKnSof12DlZBzNagrCgbF1eFedFeN9tpFs8CUDR+u37b4BXoLNs7aMLk/+FcIdCM30L1Gvp6HMLIHtiSuMMUt1Zp6gpjH6A8hCRuzMuChbmvAlV6k/TM7AR5SQjjikY0gH/IE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNzI3Mzg3NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mNDU1NmVlNTU2MDliNmI0OGFiNWQ5MDQ2YWE5YTEyYmJiN2EwNTllYzgwYTU2YzUyN2VhZWE2ODFiZWZhN2RkIgogICAgfQogIH0KfQ==" + }, + "T": { + "signature": "HYCtOyqUsA1ghNR/fFQBzv8tEajwwTBzD75fh2ClwrcmkWYXozlxjapDhc2a4VtKWw5lEJBrkUgsiDOlpLuXZMgKtxWU8f+OQacbyKtLfwP+ZiBIi0n23jvMlYgFAtVnySBGrJhjuNLr3e2LH58GKwLMY5TIl6h2p4hh4gqEDtlz/66EjPvh1AxooCc8zqW3cmhnn2guHEDTUxCmhNU32AtbCcKqbIQxeAOKEPoX7z/jlAVyP2yq3mF1iQzj+sppG64xxX3fFeMiyM/t/VytnFseTGP2TRvvCXowfN/R5FTfANr0Iadc50Jm4pdvSniCQqk3sKeFIAjYarShBudHJSolfLMy0NVHwHWPPqcb5SLrqc9prjm0m7iTbI8SC8eZJ/U5il3IIJGC3pJ7EHiIlC1Ppfo2LpkVUhnj580oe2uVfebaExgIc0PATXztcVPKVg4xB6jaeSIZNYhrvRseBJC+zIJPlFWvaN2gsrFwPSS8C42p3RhPQAK4W+SiFk0EVo7L1y23IXuyv/HUW06/jJKg3m5w7emOSGBHrHM7y6bgH8R8yQ5r4Adi4XMZIsT9jogdFKXoJBemk6WjBGO0iGGQ+HQnsGdJyCajOrhH5wzjaogNwEy4r02k3TIEimgf/vN7OGwE2/ePJPUsRUlLb00tJt5Tbedhfqela/y9KSM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExODc2NTc4MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xNTNkOTM4N2EyNjY1ZmJmZmNmZjBhZGVlODAwYmRhYzMxMzNlZTNhOTI4MjhmNTIzODIxYjBiY2E3YzJhMWI4IgogICAgfQogIH0KfQ==" + }, + "U": { + "signature": "Lr8jqJRmGGg2T3cJRVGmK7oO2+mjKWuwoAlA5/qXmf//nNDWqUfoHiKI6ODdE19X2TIVa9lD/gTaEIpMG/yvji3Tzr1vFGv7HgZWBzpYTlwRVygLZwJlkLKCaH1/h3MEQXmfIRRMuCTONG4PvmZM9cfK40maRfl3P1kJL8NA9mm3/kWyCK90Zo2XSPixqBHqFPJ4PIsydJSZdK0aXPqw42HXYMUiTXMxCBDUWp7IrF9n1BqTNEsGBkzv1I05QIS8QOHsemL+FNqqhFHcN/pmfdhGBWFoAWt2bpgTCN9MXREUlyGFVcWkCLvLZnO/IFJMQVJ2YSf9pWtuAWO1ASJWG34ThrnBBWvXRnfaI95BF4OQW9bm0Sse2XPaWJqnensaZyoIRHqIuXnfmeNNeFlk75G3lC+bmhntyscDd/oqMYYCT2tTM1D49mBgfOadbo1rq+obb6E1LBDm5V2HlarP/g8gxG3NqIvbssY+DpbCQTFdpqGcTJ225w4BevIv8LHT5x64IPIvuIGv0CMIeJ7SjGE/rrGpf1h0efXzuMEVQVnsLNGas0woxshV4LZnRm0zSwO+5MR5TWD/WztmNisv1qMZzlVff45SmT2QtCqaazCGi9YbsjLf2tZbUWle9lAwoc1RMYzNeNibh4dGWwngUbvY18KsAtY3D1G3IgZcDKc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExODQ3MjA1NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80MWZmN2JkYmQxYjQ4NDc1YTI3ODk4MWUxNDljZmY1Zjg3YTIyMDI4ZDIzOTdhOTg5YjA0Y2M5ZGQ3MDRmODhjIgogICAgfQogIH0KfQ==" + }, + "V": { + "signature": "X82twJuR9tVY9tVXYmw0zOiEl0fRTm9MGa8E+yzYIHeFQKp90+TR1Iiydi42B5exboX99yE6DLHdba5j/XWrDenP6+4wAi0Ji4W16oXmZZ4W94Zb1+zmkhSEKNictCWZeeWyQBULO1U4i3S2lzIETk1ziNDyZ/XSa0f2Qg8MCnIdvXapZjHIhcK7IKdNpjw8a3RBvYa5ZiTtZtdJE7qCFNtSG32Ot34DC4Ri95eZdYFABupAlSXw6CxxqXNcCn22fAJoPTaqRZ0hH7Hdf4Ga9yo5cqG7593YtdphkIuh8mOt3K5DLYyFIkopFixnwnRaxK7zkOR3AK6MVlWBeDY4SE7w9Zuej3EzDIDFVQBTVXmV3jpHzKXoVW1whogMNii+7a6PFDhlxNkEYvNE863rV78UL4c719/xm+Sp1CzSZwMotiBh2JIDbfkSrzzQmURE1Of99cNnhYii49o+csrENYqUhgNN5PoP+7RGxWnrGXsUoXk0mOYhPHfEmaWdgU5ujV09u2QLCfFuEuzG0HCuDDDsaRuKOR4LVeGUn0ddg3lpM1Ssi6zrCgEDZy1e4uSWn9z4GJ2JFARv7uvamlofosKsPbYbaSrlOmNDzdFJh2Wbx6oTaw2UIuAhhQn3kZ+3KZSQVSpbw7VbyXeVS/GTBek6/p+Ebfy5OZiSn5k5ZCQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNzk1NTQyMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82NjFlNjMxYTRhMjAwZTE1YWE4MjRjMjRlYmVkZWFiYzNjZGM3NDM2YTM1MWEwMDljODUwMDQ0NjE1MDk2ZmY3IgogICAgfQogIH0KfQ==" + }, + "W": { + "signature": "xEW+77rbRV0zYD+RvCa5Q6+m+7I0hNwb+N+wJhrCntubbIABhZRK1rg1tOoMkddIk2z6oco/KsKLdRL2Lb6zKRsuObTlJdjUgjo6QKog/qgpR15KacwW6Eo8OF9lje4W+pXE71EZ1zofFg2RwLILdGX1hi3dDTabzLlxO4QG7assbdk88oGacYxWXprpqv+jwn1Sjq3wNF1MrHOUqRGs1HSRM/sLRrNnh3jr+cMDuY4xnmxvLiCx0eRY4JCo0/9gxOG5GFzfXDat6R4oF4w+PbvPJvahNc91scGeIcPDDNblK8ZCv1tsUZmNrxh2M7zExxdWof/TTuUlc3wKn0uSjjg+tWGV5R5eJjHkb1KlmaKPy9jk/tS7AVM7h3xmE1D2Sy7AdWKKotpNNP6qAylOAz8/JNF99aQfFRib6mn45YwIUl+Z3VyAqGkRsQYcrUdIjD0Wav9zF//dyXjilTVXWplXgCl2Sf/BHkUIGXujBiXh4kjV8Pr++lPifrEsJNHCOQMlDm2InaTJUI/qJixf6KRMHi6pguwa+8sjc1SdDi7AbadPXel8XZbvba/ZkHl8Op/ipDFpXMNRXiVQlr6hoqnZgh2GHXNFg4myQ/RTgOiSlrm62olKiyD4cOcz2j1/GWfX63Zx6SG3rpklJhre4rv15LOb+YuYrU6dsKWkO4k=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExODE3NzAyNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84MWYyMjY3MDA4ODkzZjRhODI1YWQ3MTJmMWMwOGJiYWIzZjc4Y2JiMjk4MjlhOGJkZDA5YmQxNzg1ZDJhOTEwIgogICAgfQogIH0KfQ==" + }, + "X": { + "signature": "U1DxBeuxHdr/LiikRajX04kJeTNxWyjIS4ZW3qgFeSvA48UQLf1RAOAVmvlAiUkBKtscqqUgc3CLtPF90by+CpNtI4W/B5qx9uOU7nGsLxNOhSBhMKMQLgyDWzIO/0gDdTxIHDdrwUWu/LcIv/GNTaOxkGjJ1dFC8xie2wnouQaty6F2bOVj+9RL0npRraE+PullyT55GNHJrtuaM6eNlx3jpZsi3KyhRLtn8Hv99UNgAX+Ak+YHTnGPtkF/RWov/MxY2hcwLkCnOotoabRHCxJrcQ6m9SqcG+4gTEsLnvlXxFkWP8Px8T3978hl3PJUwn+DKEHas8mOrE6n4I3PTUXh6n6mxc9D5IUUYp+DsdIu/snGEBOcJQybsgNdSeMcQlCbAotZSwAG/CtFLlDD7NfOW3GovsW+7N2m6Bf3y/3VTX/pX10tNzxFIIrGpRfMt4cMGMcrb/nM596Y1RGErUsAA21WrvZI++f6xj28dtOo7eXMcRC4nDMdAPspAdZfaYolA5t0wDOJcBYaN1tebOJRhv6imID2tIuQj4n7rnappFwTsYmYA9ecK6Riz7UN9U5kxXGn1IyNoIu7kU61GaK0w0gd8kS062sCqL5UkwnM8fEmp/SNmGdvGTSuF3w084IihtxTq5+af1TMso3J0Qx9Ro9HjXnjsmyIF3NDufo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNzQ3Njg3MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82NzQ3MDNlZGU5ZTY5Mzc0MDQ1YzQ2OGI5NjY5OGI1OTk1ZDYzYmE2OTE1Njk5YjliZjAwNTBjODY5NjQwNDQ2IgogICAgfQogIH0KfQ==" + }, + "Y": { + "signature": "WsgMqe5Fod2XOSw5knfDvHxbzQuryrdhVQqphMMNKNpr4SpxDlEW0vtYsPlynQxmu5Uk1ouQ28V3lGew1Eeu3UwOrFIPM24r++cuX9WztFnvTcJE+UHnikGMjSgXKkhdqNQ5PU4G12N1O4xp/ukbyN+D7Q4emHdSsbgTJGoHiu09urgLoemhdW0sDQpjbaSOHdwpQ2GKNJdgHCUzbFFiTMld/qp3XGKtBFxRXWq7/cY3MhnnN4n3Ibmiu58vdlSYjFjF9S9fHZsk4sIPQYj+n1ahb8n7z4i53UQqyjSsW7v7Ak7xT8wCJToMU+kcUznSLXB4R9ej9UZbKsJxov84dA5CaS3VnyTKXWCrc+S9KeSE9ffGDt96Uxu7gWOKbP84j4BzaB2g+JOru5zCaSJmbflhKbxl+NOQ32Q6AJNPGN0EqsItiTq1OmFN669C+Wb5q2yxpjlsE+WgUTTPOBGOmh482V35sKD0NHwQArwoP19XBYv5HQD0IuE4sHByNcMRQlhs0owuZnL5UutBRDHMWFq+wWB5sV2IlTvU14BpK+gc6M4eDn8auPCOgMHzcNyLCQqodTtFSloh9+Tc05jU4m1fUTiLeFWJJAyqhcTlEWLDD8OzXOv8Y46hWkaXDRvx3w287fKGSidrALMjBiO7GVVQ93zoudDnVAQWwpkudMU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNzg0NTIzMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84N2RjYTMzZmZkYTBkOTY1YTE2YWUzNzczMmFhZjY4NTFkYzU0YzY5MTlmZjQzYTNlY2FiYWYzM2ZhNWM2OWZiIgogICAgfQogIH0KfQ==" + }, + "Z": { + "signature": "T240hh07zfFFLKQunmgKFs58XdNcDUh2Sr2hOp4qLk8hsLM5piEhSlvdLPEKLpo2jb5+t7p+1cWwagVswF0FkqQ6hoUZexKlXr4pKMHqEbRIYC8rNOLa2KtSu8NsnPzZ8gQqzKKpHv11DQxUgf3IMUPe0MLIuP1+Eg9YBuAqe9uFzmOFUGMfEiKlnFT6HpNDqZYEFTQHuURfGXXX3oQh0yjIwrRUEft3GssnguOzCfxs5o6JvxCNLBvyK5Rfl8LcnuFQJ9nGW9WP378VJbjbopO8Hv6HNvPXccc1TJ3qH093s70s/mKwNPizOCdlws7zbszDM8Tzs9CJNsLCp+7Vqaczvszlahu0Pdwo3I2CfpplEOVzlLQaPj2tc+zT4nvhBAHtYb3MSyFfX1oj1In8mkdUxgoo6KWOlh50oFiCGjvpgIyLTu2z+trcPCyvRmTJijn1al6AfgeCi8ZaQopRDhGgLH3EjQXtqoK5kcdTOebWPSts1298uAYps9BKdvkq+uh556gJG1ZvHqI4TXugcvJ2+cXh9bUPJE9E4iEYY9kipaV3xMimL7P8BI7pfJRSzm0qBh0WQ6IFfiECi8oc18bDufvf0FILWw+b1RgejcJQixTlNtO7eggjxKlIE7uLn5dPBqemn381gtZF+OEBeYyawzBkYSL9EC2ZZdHe1ls=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNzk5MzAyNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hYWY0YTlkMDBjYjA1Mjc5ZDJiOGNiZGFlNmFiMmVmNjZhMmIzYzc2ZmYxZWQ5YTM1MTJiZDUwNGM5YjMzNjBjIgogICAgfQogIH0KfQ==" + }, + "[": { + "signature": "F8JjKeNv6VeLM/bVe91yGpoZjHpUueEEyhYM6g11HdsHy+TkDBIQBt7ZLMtqKE3o/abs5md2e7VORTw3FpbgFym8PEYVgkNF30u+BoMwqDOpFO5glxqWIFfjyBiSkoLiSZ9jmuZhxAawtN6AX0Lyvs0ixAP26dOhEEb6/Oj+rg2uyThDBwfL2n/93uxxiwh1byh+cl6w/YZRmaJ3aFuP4ck7J7uwFHkVQTAzrFNjtXNjojs8gOBAeTqPVoAzIzWzqIFFahbYCDBSBPX/3q53INe4y2sRZ2/GroawZUl+MdLwrVlVE362kqJ/Xf6l/HtXcOMhKgPKK0c2c0XqtD4BDFH2h8FJxvKE0rxTpGX7+/pVUarJThsat7oA2mmzQ9gUIQ4qg3QvIDcLF0T2Z0D4dfq1ozjvS6SyETJaoCgNmBBDe2aPuNVvkJaD5V0i+WcmrkBpV4lkdaKA4LVJ6xUQJWyJca/RY3PeZ8QLWj0zQdTXaSNjAlrWcUGJ/k7nmc5PO6ZeYeb2mvbtRMbgdeeU8AiXbZwmcqIiH7Qg0U4xjLp5F/cakxqDdPfNr+nmSFZZf5XT8/g9ptHhBOZ/qcmvH1k/OxVsxIircTNBDo5uakL7CBxehD0/OccQv1CJBO4rLfLRYO+OlKavP6tIGmWwACa6X0NcJhtJLkVc8JSNa6g=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNzU4NzUyNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81NDg5MDEzZTU1ZDM5N2Q3MjUyNDYxMmJlNTViN2EwNjkwZmFjOGE5ZGVlZjFhNjRlZTgzYzlmNjVkNmFmNzgwIgogICAgfQogIH0KfQ==" + }, + "]": { + "signature": "sueE7CTAn3nTUbqeAWrLIDDom6HaJvDkxKoT2eZMqKXNRTZuc97gKlfQQ+EcloW8ve+XYHvM3kfRiMk+dIYX++kpn1eQEWrezA37gjGE1euZx0AHheTJtVmiJo/VfZ5sny1z18uO4dtjwKBtfAVwuR53utoKr5C8TP5fvQ0zD+491xqzxF+CH0zbtmZUdKPJXCIEd/Ui6gQv1KIepj8bASpCsnWgLaueZ8V58IaoihD2NOV5Li3iKJALu2bSKFQQC+4MRvAbu+E3oYXoUs//a+8UtCPZTLSCCz261Xke2yWl43aDsWoaAEFTom54cmcrCPJ60QH1OYrwa9uM4a+gy8PgfoTpxHAraiP2mt7c28JEzTmqv0vIcOjvG2ekIyqr5rPeY+OzhjMSuRrAx+S3yx4h9RxLHb1lxOJ7jiIkNpuITM0einH4DUw4HCcs2KYrfJNLNHjPwaO2SHue43At+16HtjnBYZj4nsAQN2VfkZJlvGUvGG/0Fmqyt1QgOZyDt6rW7NRPZ1ViZmYl1m8D6iVRN99+pIM/FTNhTOTnluJm6cs7vtCPTKoMXNvmIRC785nJi+Wke65VJVyppZzVxa36q8x80pLwHvoleHHGlxkTk/E6k3qCxJYTa/o1Y7VinbuQKdCli+2eaMfwnVsx5YeZSWdIJjcrdhgijnqxP18=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNjgzMTYxMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yNjYzNjk1ZDE2YWY2NmM0NzQ2NGIyNjEwNTcxYmYyMzI3MTdlMjhjNTY3ZWI3NjAyNDYyMmY0ZWM5NTRlMDk5IgogICAgfQogIH0KfQ==" + }, + "^": { + "signature": "sj6BUdHBMyGCyqmV3IGsglmFNsoAP7XE6l6GGZ4+wtyDnXer/ENoo2R8M5Lb4cC8H52fZrCj4zOQpUZ/+3AVz8IvEj9K9Ed6c59rACNKSLTQ5iqVnFF2Grdr50Wso9iYVlYWLKrvItpO9WPg2tszHJlqT0ymw7iisP0hnKvUV1Ckj5aydNRAhWcTbHNKIdgVLMeBfdUptO8ckJ10goa2wRXpWw0Psb9VKNYUe+u1neiRL5aW9T4MfxJzTkGImIyNrlHlKQwZurTmmvTUGRaKNhF9iSt1kcwMGS7ZMlJNrUJnVn+ptzn07o2gy1VpDkWa90RZKZMZ6zK0KnPT57Y3b52kZzEn8uTaTkPWX5zdH6JLVnvalsPV7ZJKM0z2bKwl0j8CuzhC6UleLjvubWiMSaaI1TEdnLbJwGedRw29Mkb7+FM55RM0HoOylL0kiv/m/DpBqmqkZpf+ndYLNcs29q+lfmPjkZxbfvzCrjvLLv+dAiM9lY7/AyooTVQZVblUNWkx3LZOUzDL3qeK//dL/YvCEVWJLH3g6r4+zE2SBMuVAWyRfCNS0sjONFTV1e5Lv/HVPJXZkZIb8UxYOnpHNa+9T2v1k4Qj5pIXm3zWY4q8+hGwtef6bVBG94xI1Eg02pnC0mgsNVqO5myNrjJdUX3aVUzN7es628yeZNXePyQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNzczNDk2NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84MjZkNTVmMjU3ZjVkYTdhMGI1YjkzYmUzMDk0ZWUzMDk3ZmY4YzE3YmIyMmYwZmI3N2RhODQ0ZDhmYzgwY2Q2IgogICAgfQogIH0KfQ==" + }, + "_": { + "signature": "BNxmpBqdvOMH1FZBxUh3uUxHaFkbyTC29cGR3JeaBu34hmXP1qDHRG3UsCGK93K68jveH+a6umnpsgv97fQTqR1azMb99N3vl9IjOO2Zlgdu5iCxczhZyhO/0mKRaJ+FuBSZT35oHO4Jeq3P+XQNfs8TwVr8FDbPc1y0SCTb69hQzd3FIGtVR9/EKF6v3mONffv3mIvsGjmzIORYF4JDSvqXcHh6RqEwBY+ASRPQyZMomF0SP0wK+OY6EKtjF/9ZHdeI9xmog99up18wXJBQsNPLSeWIfI24CQv6Vsqx6oWK2k52ZhuvCs1ONCx+lJAn2XvwxV5yaoOI1wuZqmy6dzekZMYTbTLEiGnHwRyfOtXEoqlHEIfbAa0nDtCZvftSOENHmf9GSFtmm/unwdajbZwDr4Z7FAsbDKgLD17+T7g5KfPZThXKYsj1Hjn4HI2livs1VTUQXRVwZMKs+7gdtOWXhy37wiR4uaUDviK+gXkkDiMjg5Z22J784yiUDPhqtgjPGERIq1epmTgEeywIA+A6oPLsruF5NtKu2TuoT2lSYjqOEMmxlJBw4vPPxXb0jQsD+hnEV/Dkqhqz4zD79ijvvJJ+pVpWW8dM6Iuki9lFIuHmCYTxjmd7lDn76+9//RbPvwSpOLY562cMfBH71D3vxGKgNo+tPAVklSlM9FM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExODk0ODE5NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zNTU3MzgyZDU4MWE5NzZkYTc2NmI0NGNiNDEwNWMzNjQzMWEzNjIwYzE3MjUwMTczYjI2YzY2MmNlNGY4M2NmIgogICAgfQogIH0KfQ==" + }, + "`": { + "signature": "gsj2cT7StXKvXg71LaFnq9g8jxHJzzBfFAXKaU+7uV1bmj6JNsFUrQLrBI9ERYdEuuGEvquprtKlSFhR68SHc1nAqCUM/l0UgT+UZgMMlaEeGjsCnYDPwDJQOQ3HShxPgPgUesS4fcowRPUhoXPbMyaEGILSU5YdRPkF1QY2sF+AHU0CoTcu5BHmduixzv+FmkKihv/ep362SEc7wa3k6VFVUZfvQpHy+1I45LxPwwrWJeju4hO9xrW7KZQ3Syj+vycPkyxx5NUm2UcTY4Dz1uY1gMA0nYVtj9azGi817+aEOAI27j+hQA3IzTl2hyXcMyEnv0H5B3rTJ2dU69ZeGabNqhcPLf5noQDGDbQBp8vdedfQTBq7VK4JjwtBGW3Z3picV/IlJtmvoifj5tdvjL9xjJHavrvYL5zkIP640gIi2gv02cK7uoRKIgIUqmroki/yfTGv9ZN0FkJ2W4lc+tOiMrVmU6kk/odoy7oe6C99iQBl/q9jfDIt4ZV9yyWQ5reOUKzMa/CX8G3YEz5V1Wqj8YhiebS86tnyAS1QBAdCZj4OU5wacArIMG0o9PJuBBPyKWM3bdUIMeyQsxC7CgZ1I6ljTnBQ5ULpXMRxbP8Gff8OxmW0X4uA6Ejl38G6zalcRG5FIu0nKCOta5H5OWni69UuwYgfOsGYj7P0QAQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNjYwOTk2OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83NTBjNzNkOGNmNzA2OGYzNTU4MmY3YjVhNzc5N2I2ODJiYmIxZTIyZWQyYzgyM2I2MGEyZDI0ZGRmZWNmZGQxIgogICAgfQogIH0KfQ==" + }, + "{": { + "signature": "Ho3UlmNxUUtiruf7t90sw8g5aXHVLgvPUM9qx628GsbDu7beft2EwROCVvFLLlA+n763lDL4XTRmBj2r4zgy8HCHR6lJ2UI5yf2pTlUTKkXtNFLedwVVg6eUHjbnjBVwygtVC3scAOMIshWm0SonZG6qVAvGGWjFyMP8pMJRtbY7r3AAT5n13b+JSugNEYp3aOZM4ktzLarZYIss8XEMBIpRMXVKkJ++HUo8Q48WD/fdFATFrwGtAZpciOywhw2n3iqObvHexs3e7+vJfGdgWK0J8IA+wIPh/m/CGKa0K9U9CY6LfBABxOdGTSDRfWpKUc2Ra+taUWdjag2iZrbcf825SkjdobuWaUmq3Xsu8OnywupZ3I89YygVVy8alMGYXs6TC/eEY2O5IBoX6h8/w3cJF8iuEITSpohWcW9qUxmpy2iEDH48G8Z1LaHOZMiN8TMkxY/cdFYmhezfjuJdiNzIBlykGDnevGLSVMKOMxVQ+X56W2Ixym9MV76cUtEzSP32nOCRKL+hf7qTfQSMiCZAlXgqvxmWNVuXXgQQmjc7DZ4gLHqTBRqV4WNCHweSA37G/VIq7XWr07+YZnTcxhZPZlgUE7Iwd3HP4OE6GcZc6qBfDNEdsUVXTTRLb0jRQUndObyWewLXobDzZrJIqgNlU0dzUrnbdHU4L6mjLNQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNzkxODYyNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zMGY5NDk2YjM2ZjVkNDJiMDFkZjcwMTgzYmI0NDU1YTc0ZGU3YjhjZDZjNjUxYjExOWM0NTZhYWU3YzY4ODE0IgogICAgfQogIH0KfQ==" + }, + "}": { + "signature": "PkTsbkziaHUQCnA0UxhslSiKYXoPD38Axeh41/nfrljF/4D5+hJA7IL0SWmxowjkADIfJ9WFT/xQoVliPAIkjRxHRyu83aM6XzM2j7kUc1oTAc3tUZmd5EPsGttBmigHiwrIwx0GAMnWAitL+yildIRudmNzYRT5vhLiZLYqSa+glvEEjKJBLNTrIoTaS9DEwoT6pt5bVPJZ5k6aFSEOlqQsg/az420fyRAcFZp4f6R8C0mWtymEim9Nf+Hb5e76SuKRcMEJK40KFzzVP4Eq9H0XLk36AOYF1xMh05IRLbCv1LOw72NJLLgLKA1xF72e+HqK7yd30kITbsmDt8yrhLLCkSgrMdsyKfqD6Tj13+flf++fbYKx1Xv6Qqb63SzhSBBEuJTc8Qr74SkP+uzQnn4uz+0QMntCAWm21NckLIM8moS7y9dCMwlmncqHZOKQ1uMSjGFHe1xy6//73CbKevePBWDEIVj1TcdzHxLadlPy94e+qRf/oNE8H/9HSE7nAHSNSUs/R/FVtpLqCMmetW4/NuchtJmzexwuahLs3K46/tRYGnwZ1/DqrTyOOPVKwsdkHd9JHFpq8MuBSUUeAwrKOmCdlZObBTIeqUJLRXUs0W6PxLlOrVgv1Rbel32vL3CAYwCtFpgCHcECuZruCw0ySw+5Y7dnScUbY4wyX4Q=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNjc1ODExMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83YWRlNTkzYjRlMWRiMDNlMDE2ZTdlMzllOTRmMmIzMmRiZmVjOGI0MDVhOTEyY2Y5YWEwYzdjYzgzYmVmNmI5IgogICAgfQogIH0KfQ==" + }, + "~": { + "signature": "nME/VREIhL432PdWSz6pag1EU0QMYxO09yq17YJhvlSTXQd34qXwI9N4yRA83nqv+zE7km3smeGstICRBbsuZioBPccN2cj+0eWgRnKG3WGhH5LO/6mKjQ6h+leY4RaH4F4yhc+/qIoeaRL/uIXIUNHqwNfFXo43z8L4tWslAYvg77mKgapjgpmq7E4KodYZDiLj245AiNbQ6aA66+f2E2YgcgTGTqb6y84TgP9AubDBvhGy8Ox4F3nekVY/KbhAMDNZpicpXDMHbZVKbdSCy/JVPKnE8yCEvOInX+aDq66q7SjaEkxnRRbwb5S1RCcTxKdCBXbd8waxLDR02kr7oN/vI515AoocqZowyAGtCyhntrI8MyFd4ffdOwktCnnE8dCyYxg5s1MEcvoe/AmVoz4Bbq4SDkv8jkAFhfUHg6h3o3rkYbpxSn51qBOtQwrfOPeZkdBHsov4EdELPfjSE9aFBU2RVEMT0FCF8gZRTDKWvJevvxw5U6FUsNIdQy/WOoZCuEhcnsFZtIpZWWbaJOt/ZAER6tlfbjXqpTm1rc0eevsuuoMSxFXX/C86fbG/bxzctsil+n5enkLpZ+bUqIOoV5VkBlDXaWo8GS0lepGwm4EuRba7RcuFjrVbb6EP5QM5jbKWez35cDP61vABRT56aruI/W5BqLvdIItqCcI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNjcyMTMxNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hY2U1ZDIwZGI0YmYyMzU2MDcyMDgyYmNkMGQwZDgxNGM5NGFhZjRmZjFmYWFjOTEzNzIzYzk5YTE3OTE0Y2U1IgogICAgfQogIH0KfQ==" + } +} \ No newline at end of file diff --git a/Network/eLib/src/main/resources/skins/chars/cyan.json b/Network/eLib/src/main/resources/skins/chars/cyan.json new file mode 100644 index 0000000..4f98d34 --- /dev/null +++ b/Network/eLib/src/main/resources/skins/chars/cyan.json @@ -0,0 +1,266 @@ +{ + " ": { + "signature": "FSwmcvOpE1W4BzvjePS8UHYP//EQ3xEVXTfJuzG99yjEyuF1M67/VC5I0sf13lv+jog860kdlyizcnPf4wnDCLdY3gRfi+w7BcFUG6HCIu1A9xbKJP2Q5e9nqxlVrWLhh0YdqYtNJtyG4t02k9L5euiJdyOZEFlZVBmdXEuyHYqyJ54+IqYVMiXUa95+Xy2LrM7/rbmJASJL3FT3uwjCznWRfn5DFDo61yhrL3EYDUIBF0g1SDxUEM+l3+tUVjPFgF0pNplJm2MokcDlCUvsDuDZNmFObnQp73i4WQZLCUQ1gsBTDZE74BFQalEI0FBEjz8jSoHw3avjRvZaB6dg+wxbEynMl8hTCu6W1vbmlIT8zi8ir0B7rBHpzJGmUStz9xmAZUaGF6BX/YKC0bA3CisBWDKzzjmeTCBidy2wJKVN2KPE36WaJMtux279HjzXg1btN8MkGvDnO8qNgxwEE8O/5yKgAdDmTvi32hCyKA4EgLSL1Pn4P+t7q1TzYgLE9+vAy1dMLbJ033gYcrGhj4mS+024jDJf4iTwf3X6ujnDTVD7Yia4bLimIPlvmzO4C2rzsDiyIRaU1D1iGysoRZqODKWMGlCMKftGsy212MVxtvue80xZ1jmfpb038X1Ze3fDgMbOqwFBg0cGoGax7uy9QJLsoH33Ve5gIj8gXOA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMTU2MDg1OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xNjQzNGI5ZDllZmI4Y2RlMTNkODU5YzY5NzRjNWY1OGVkZjJmZTFlYzJhYTExZWQyMjMyMzUyNjVlZGU4NjM0IgogICAgfQogIH0KfQ==" + }, + "!": { + "signature": "inS0/eFR14whEFZHtFrOxR/gX2x/v7s3a0QU6YDgZjzt9bJ8G83xj7Zslzt9imAyAWQkfACxyS0uk+rzZ7m3H83FbLKDlkuVGDBsLhMdL+fWGOhe8/zLw8QyquwY+LALLTdIgMAB7qct6HeFmQtnKDUz68nu2jG+riXG4J9LZmv3ockPv+H5m6X8pBAIUX/K2knUyGzOwvn0+ybAuT5jAx7/iPSHkclGh2CiT8ASEZpyof/3OZwv+7XNp1RQdqJJY6EKv3bNI5J+GgOMtx3vvsV/ocESVC4bt3LEpUlI4yYcDVrPS4y9TaRjTZtHGQcDjytWlrPNMSGc/3xQ32JswvEnbpJdIs7Q/nM5tbB4CPt2H4DJVU4NTG954tKevhoZUKMTnQCY8AWCLE2/I+nMlA1/WtCftvA5F+8zVPm1mgpuBW7/l3HOTSD2q3u6Mhe+g9L/E1L5ncvR79XJ32Augg3I8uP/8UDPX9JHNFaSXSZp9PyrkmaAeXP88DInhUX1tZTN9cHJv0/vg9eCbfKMXI1hoxdjnLFcqmj+hRBbmDMhv/QZy5Pw/Zo4VWjP3iiOSX6RICx24zjfF1jZ4zJ7ATW9LXmxL60Z5ZXgF1WBC9TSy/rYE2m6bTZXPsoomIlosQ+XzU9t+OMUTU2WRGDXEXegoR4bRJu7fKmuEprESrk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMDg5ODc1OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81NDFmY2U0OTc2ZTcwMzVlZjIxOTQ1OTQxNzAxNWZhYTY3YTA0YWRkZjViNzc4Mzg5YzMzMTA5YmIxZTAwYTI2IgogICAgfQogIH0KfQ==" + }, + "\"": { + "signature": "cMSHlOyaven1h0c+Gh8YN9l4z3UoKK4EvdvZQXp2B57oO69s/Jf9uPSfLm2fKOO4hl6awh0W5P2KeK/+IXoQLANU5V3fPn959QIpinmPLwUQmp26TgTNw71H+OG0Crfwg2V+kPo18PIzAevgJUGjvsHSv0t22s+q/7wKuuuBNurBilFtgUgBqVbifpWxI5hBsApHU1ee35+uqJt3CziRu+WCEWwCPJKFh/9TwEd/4JwdqAd1HOnDePp2uSD3KYb5x5vSGIDekkC1OZEYiZ/HOohVN7S9GllflrP/xPVq9DXsjUFU0InSW6bGqPs2yiZblVjxUWbGr2DmxBhGQKm0s998iEs5XFnUn0twGU0uNEgkP6pXsap1udW7WV8AUapMpcypRIj/bDZg78hzlvneuidKN3mbwV8v3sY33oLKSOOq81cqimLs892eEjqsbuD3vzkt8Ha21t71Jjo6m0Qp9yFbPWJ4rLSvgdVWXICGiFMVQfARHbs3rfUNu3hCNewX1o2ge/3r+IUuZ98ipzNjm60qGpqYRGV9Enc4cW+C8WT6O9LNheXd0Vjz++6fJKQ62YmtFjQBSvOJXILgWo3Y4/qP3y8RtxCrxcOiBXFFsGe/XdsqlKY0UavjMsmfcCjYmhlmXOpzVLq4azU9fQC2dEzxV0TtCYfddDKhuk7CBCk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMTMwMzMzMiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83YWQwZWFmYWEyYWYwMzQ0Y2NkNTRmYmY4NGNlYTQyYjYyMWQ4YjBkZmI3YzdhZTM5MTEzNTViMTNjNDU2YmU5IgogICAgfQogIH0KfQ==" + }, + "#": { + "signature": "ukijbFU2Fj2fuE0neG/boR3sLlB0naOVKM/+qI/7oFuAcVkSRSyeAJE5/GvpR7sfVeLWjTPXfj/p8GHY6taOydV1ep9pe0mtJlXaxL3dkx8baF03bBmw5IX/QqOM+DM7YaA33WwDRVeNfzbpYwqyqdrzv+b/zki6TSLJHyzZwdma5VjHh/loBGt4jsSF4mR5V2LQm4Tp5hnUn1YN4jRZpfLMqm45MmBGXfEGsS6IRRSQ26tCa6po24ZBKMlag1/nlmSXDOr/+i/riJFr35M9DBIS6iZ8kyJU7ChKitICuH6xxdttZRVa68HEVazwb4T+liL5XhMJdYEEsn+ljZu/5QcCpWJLKhw7fpPcuDpl/4PAZBlwnErByOV3U+voNkA5Qo22bIdr1y/N2AKK3wgLx2z45JBX8kyTmuBCmx09aqsl5WTnHvMd9w2wmlKw9EZ7ktZYvsq5sj6UMpnK6XBqDA9/qjGYfAciPDXSsacBEED0SObYapPcvC4kMK5OWk5rcA04gFa8rm7KqCMaPhkJOYHae4pwIv/+w7c7CBjPmVykhlKjQFsoLtqvvH55E6MWOdqEHtJm+fbIqhG1ean8e7lJxgnv2OpV5ESbLt3dk9Sw44BHI7S8F4aOrZQaAAfje/1YoBA7AsrwvERwikxC13gBH1MMP7p6NtLQxtQ694I=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMTE5Mjg0OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81ZWU0ZTg3NDU5NThkZWU0NDM0YTc0YjFkN2UyNmVjYTUzYjQ5NWRmNjgyM2Q5N2E5MDI5YzZkMzc4NjgxNTc2IgogICAgfQogIH0KfQ==" + }, + "$": { + "signature": "tJDLRr1OvOFVqtLMs5gZ54iiX9AGtK01mURJwDkrxLfOCuiv81AG00wp3RQxk8wv/Xlg+oa76zJljT2bC2V51K8KNlyA+WEDpXUvHFtFHRhBhaT/udXEGnX+/zscvxcUhLi6Gr4PLTOZ3F46i8AHQerdEFMYqmqKiNOE4AxJTCsjc/H8UVnBqGZF+Gt8bwNNIeCBDI3G4hB1wboeAmLJOaZ/KxBpiVzi3TrRaoocnzOpR/E2GoZkdh3A795AapV+xSOh6enMUTK6Zwl93TvUP8OjiwEh5J0jaG4PhWAnQysnKsf88BtXtV4t1ash/GjBcvvSg3226xxLROmQ1fxUFJcF9m/xJ52kI1pZQC0y4pP2kIlmRcdPGAZymCLPp/WWr2254wKquF36QihhuCdPEZYyQD/aqrAiXAw7zb7Wov/MNZ8/l7Gl2QUO2fi62oSX9uUmhsyB928AEQp2AGtMNu2mErTDAXr5JfwBJddw9nmV0C54DQerpzhSV1Gb6LBGKO0o+3xTIP1ZoynHiWxxG74LbUqf+Mpx6VYsJPl7FImA2V1UA04RtJTQ5cc1HQGMRRNFvsohfAEeXorRJvQvfuuzmnf4dOtxXymZOYSpCP59KIKMBJEJPFbwNKrDoOJU5oxWu5ysj+dO9x6G4MkN/zIOSIfRdJ8eu1RM9oVctns=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExOTY0ODc4NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zOWY5MzlhMzA1MGMwMmY0NDMwOTgzNWZkYWNiZDI0YmYzZWYxYTAyY2RhYTMyMTA1NDdhNzc5ZDA0ZTkxNzIxIgogICAgfQogIH0KfQ==" + }, + "%": { + "signature": "duYiyzMmBvATj8YHHRViVsvWcZ9I22KaYJrztivuqQwFi+NEVT1EFDCeVu0ZxEQtH2WkiQU5LR2GjETxqkcPTkdKICgw4EPqMXfQqRbFYFXYOAGJWn33bWUcX6hZxd+hC8dcB+V+SCFju1EuDuCWFRA5I8SuUk3XUivckZrfZdtyoyFbErf8DGwzaPb3pcPpqn+FZ9+/U/K8phGqjUb36MXbOdxSPCFT+7bAmqWZT3uOG0Q1OsB6Uek1wFltig58lhkSVq7pTqmRjm7UDBUQorJ6oPKbBz9Mn5iLGi2nYIH9+PBn4PduPfjkg8UhTypeTn2sTWEsu6Uuo4inSs4IILCuoydw6apfjoBxNco5eIwRQgLLncudD/6yHGlVMjHTR1Hf9CayTBmyyeCISmhAFFkzP44NDeiUsbD0OL/Iof95+ZZH2gek+JO3/wXHVemtFwX6P61/uxonLSu0uV96YxxGSIZqYb6lczKun1uo6O7URguikTzqKMhS+0U5cttsG+IEGAIIVQX/OeMoBB1Ag8sY7/XDGjYM4tTMUEDGja44zWOT/C9AjZ9KtnLHPZDmVDfwDvhR7/fP5FcpYVQ7kdS2cVH6wkV6pt3+6/FiobKtT1EoPcBO2G5wLt0utUNlohVA/ox6QaZyzAEifVtU1MNNX0EQ9O+LvMq6wsHmhLM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExOTY4NTMwNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82MzFkYWFjOGI4YjlkMzQ3ZGFiNGUwMmNjZTVjY2IxMzFkNzQ1NzlkYTBhYmVjMjU0MDg3ODgzNGEzN2I1YTdlIgogICAgfQogIH0KfQ==" + }, + "&": { + "signature": "VPwSFqlmfsM9n+LCEx6HXbLPIOrVLoN/q/Wmv8MkS+IGsL8C3pMhgoEqhRrp1GdNM1PNfViKtJsyNYhh5c8XlJ82LCnn8hDPk6jjWXp222VU83kKk4CrzRq6CfH6odq/lVKCevWB89ryTRDiqeVEdompFDyPqwL8cviX2s2R1sDrlcW0MsE3ChdpYtocD0CckZz5h+/jMcRtfsjpobE5kVje8FzFa1RRVnSwKRGfUiiZLu5uQAU5wdjUktLyigKQJwLhbvX9r0tYJQYBaK0gvhOJg09evo/0dVNmUzCAdUh1eRTzLAIlpVTb+VgRakKSvmNYaYujjWAUV/ErqYYJrQP9r7swbo/qPpJuZynIX5x62FS4KmcqbY6xhvPtSYLqJs0AUC7QBq+ypar/ox/UiWBzOOPkFHa0h2qhKcxlaQsDHfGK2MXpP123GiyjFzizA+fIAY2A/saVsMCr7J+AcITey2ZHwSop7TGmr3HLKDq2ZKcJZS77PXL+PGkRhnwb4/7zkx7tWuXUARG/gxCOLeWnAhj8gXDmISmc5a89GCePy9g8WAZZvJagQtzRi9UaglC77JLXL1deBfTts++T8ZZmqV8/GA7zBOuibK7obDQgThC4UyxPtKTdQ8CAQ5J29ou/7uWjrzQeKXhsNxSNSCx52nTagZgNMW2yMpPIslU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMTExOTY2OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85OGRjZGJjNWEzZWU5OGMyYzViYTBhM2ZhZmExYjNhMjczOGRjOTMzNDhhOWMxMTEzNWQwZjU4NTk4YzZjNGY4IgogICAgfQogIH0KfQ==" + }, + "'": { + "signature": "A7SesC1Fm1csgh20sZeekh/6qVSMI4SSjr95V8i3vnnmbu/466kvI0aYJwSfXMWrWkGkm/Im6ArpIBB2N8ZRi4R0UTHGAv0RsOy4WGPCoWMe92ABqrS+MpVEtR+VnZ0KRHY8Iy/bkkQpljjEW9BDdg75ZtS+03RFbXeyrmRPLsheHF6f8f1nq5TrC3C53h4JLhF7ZTMl5zeQSqJqc4c+YYK/uwh4cmnAI5se5YjuKtgmjLuRo9gNOVcSnaYmoENKy50kxp/3X3Gt2HikwHcjPkRVD5eRsQNUlnktNQk2k6sh36KEGXQW8gBMRjO3hUOskFJq6d1sE/5IUrQlgRiK0rfnJ5yFs4OrV+OWLtZ5w3vulyFGvWbE6dEH+LU9SQfch7bQUt58BfX1l3Ioo+06JR36ybVDtbOlmDTJveOjVJ7LpziGqumxLLJ8rXlT/zz5fgwzY544Pm5HlMMUvX/AXDRTzWEzpr4Qme+xR/HKqANU5ESVCM8DfEprCXfwLUm4WEuHPOBj73M88mIXg7xF4AUkWLMb5aF43LP1yDA9/ZjKEnxaD9I5GYlZPd0O15E1ocEInQdpL69FJl38BnTC5A5Rd2FeLBp/SzlicMoB66q+/+qccIZ3aHaTpV94vT9oSoReV5s7iDwtscfnNLyU9/EHvyMqjBbuSBIBhmgXwo0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExOTcyMjE0OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kZjdmNDhmZWJjODMzMjEzY2U3N2I3MDQ4ZmRmOGMxMDRhN2NlMTdjZTBkYTE5ZTRlYWNmYzIxMmQxMWFjMjk2IgogICAgfQogIH0KfQ==" + }, + "(": { + "signature": "Utxp83rKzH8GznYqO2o8v/HKmdbo40NrjhgjF8R67Z0s/EpKWyAV8+hZkxzAOXUtU8lSfQKrUVpkzSWXhzoHG5tW5g/XebTe/+vJYLauli0QaPpjCAun2mTNTK8wWX3ttk+miAtbULdGZ4HYD+XPh+pNzDR1CU2/i87EiUD6S1Z1+oHz9+SH6uqmMe4sPTiKUtx80CnkEDLbSlF9gy1xnByD+v+epIgsJ2g8FGZ1lYqtN8x6thed0+CpvYdHekW39nvkjGKEGifG4hyKVRPn2VrvpIzq9K3F+nuy9R2BSGHS4XSjqPRIm3b0m+waIWFbKxsHRg7rN+dpt8rmup4Cl+EmPzXa6iUfdSFE8M7J1dGt4LbUMR8dnvl6pU/fWsof+J6RxJmFG85Gol/dpiiK9tmip47AKLIXYS4gfWpIHunLeAaDxq/u6I+3FwkhEcV2C12xGJ6sHuY1L26bGc7jBo4EF/RzOa6eh/DYnP65Xf0d9Wkl2Lu9aRNzkRrHz8Ekn40MNcRXlmP88b77Fqimq2mdDpsUryyobPXSHySiPJs3sjrs00tp0NUQvovYUu+FdXNt4YcGqJRWRANjmYi5gtKcXmuCZnPmiFua2xxPEKYkQ0PJXFQYUbuvIwnduLGfs2Phtn/MnWINbcTkVk3CyvTf/wC0ciWxicy8DsYv/XE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMDE2MzcxOCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hMjk3MDhiMTI3MzMwMTY5NjVmYTdmZGNlMTc5OWQ4MGQ1N2I4NTVkMTk4ZDc3NDRhYTQ4NmIxZDZlZjNlY2QzIgogICAgfQogIH0KfQ==" + }, + ")": { + "signature": "wX44lFLDjzItI96yUGT8hwmYJ6K21loPrOljMVIZvbBrEaPIdxFPWySDX3BRm9Qj82cUqGJMNLviD3Tb1I9oucVY+sxurhkhRYvdWWriBiQXVq32l4o2g7L4wnFZnyIVrS5TPRZq3+uBIxRoSCoO2br7YmP9mIMvrLgi+Y6emXk5dCOtWdJ3gf5gbBe5bl4/ENqhjRaoKoPNFeSawOglET8S83EcQo3Sswva66YptfSpL1E6mrm2EqAdooMmONT4pgM0lnJpsGclAUO2phFsiGax6YOY/i4Nhdxq59qPHUNcxWlbWHloKvJyTOMB14nL05J2UrbXT0njFixXv/qMi2GH7EB37//uuJOUZNkPks2Rdvwooo/5XINvT57pyC8GU8VmeMGTRm5dHT6QOR/jM8rrM8pHjTlBaGbXzQci7kSFMhLPJr4N8ql03uS2u6eFjmoJprkv53BOynGsHzTOLDl0n770Y81ypvf+mjzpJ5xO4nEf/eHmKgY5SJuSmm2hRaNf5xBUgACtmrGbAsK9oVBG0E32nd5BTp4EcAcMeY3/Dzddl4fwqY8Va7xM2qkepajBzEEts/lDuZdxuByTasVz60yhI6W2ypKf0IUhWey7O7GMxKIjTOCtJlT2hTNtpSAjouOQJ8fCLJfZZFHvgngDXHbKUCBAoNt/8t5djIE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExOTU3NDk4MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80MjRlZTE5MGU2NjRhMzExNjdlYzMyZjRkYTlkODJmNDYwY2JhNTBjMzI5NTIyZDM4MzFhMDE4ZWM1ZmMwNDkzIgogICAgfQogIH0KfQ==" + }, + "*": { + "signature": "eph13oZbwhmF4e47AtuW+Gz6zGjGpAkGKZcAGU9W9tszxpM3jTqg9nx+BOt/BQGpwI4CA1hu8jEz2djsibfA8EJ74jJXEa3etB+iV8xv3Ae3iS92XSvmGYQPA8qqerpt/SCo315sNEVnpL3gcMKeDoUaeOUduVhfHA7L0zWwTnZeDI7qM381UfX2+OpdV4ESozDs/9t8Uf5HFulvJsojNOEFw8hQ9QRFGMsGnWBnQVF14CrZWnS1JcXqXQjQqmn0RinDxzCOsV4CCLR6Voyc/yH3bmLpqB10O74UioQU/ZwCWzcnX2N/XtRUSP5uriYxSlwoo+uWPrpWr+7kZm/pU/MoOIcwurDYdJoU12as1iilVMVk/NUykDrM3SsbiQelAdAk8U2bJWY98JFfGj+fKnN7BKIYoFyc3vhfeq41IjNf2U1zgk0yC7Y5EVA/YscAzT23FqZKtoblxgFV44NNvytf2vxpzpA6Z3HE8YYioqHbFk7TnZ42Idh5P7ADeBm/toVvdeSlBViNHsRGg5cPtb/yhUKHFKRQzTd5i4uaT6WwwXzzxkkSlQlfa069Hz7/jKlREPiekY4v6Uycs4TQUluZEbputxh6ityHRpGiDaqpp+Wl/lHUMY8xtzEdwCFLw+O89or8g5wIu3XdbgcCKVcDfIgvXW5tzUoHPLQ/FUY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExOTEzMTM1NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xZjk5ZGE0MWNjOGQ0ZjJhNDExOTVlMjI1NzFkNWMwMWRlOTMwNGIzN2Y5YjA1ZDYyMTYzZWUxZTVjYmIyZmY5IgogICAgfQogIH0KfQ==" + }, + "+": { + "signature": "WbBFtJc+TxhkUPHnKkFqjzZcrRjFWLU8TMK9/2csOp/b7m+xV1ZDBwZEpHLLICsoFK/8NLM/0O/9nh89PQCCYStlWPSGzXb5/7hESjlyz0hIpbQfhlGjt6rQSN/1Osh0BlZe0HbMFJbvEr4C3GQYJbeHo5zhZ/J0cEzPZKMnB2mFsWDQB1Cmat3nCBAUWtaotEqNC9xlfiuM65M2fptdC5PMS7baw9ps06/xInvUEUtXs0Efh4epq4tk4KdDDfmEgwPBp/zLvlEFp6aSiHpzZXIXSyYu/vnWNAMtbiI+bACwMz6Ld2iRsM0YKPlVyQMZgW15OQTQp3PdQQhIV2FvRre1s7kA3fP1t8DhwQ+ZzZPSNCJXsPpUclZVyvWkIsOrPDxr36LTFSmtmcM6La1F6BQpNqM9Xa7oppUwyT0JBnolsmZLnoxzbVVKDQmTpPJ5d9yUMwiPyWU8k4HbPliwrqHytBPU1z7eJs+vokauPRrhvb7hMaIWhcQHdyWaPNqiK0nDLj/Fq/db+/54ajdsRU41EZi8VTZc89GtTLm6uJtVY++tFuw+suDANkAwpbLs26Nubzphi+9mCfcP0Dmm20Xf7azWQM0O7jMXQ2ge1YPmpyG55v6Ggzt4SizqwgA73GeG0wnW/sE0JcxAMZuQeNmX1RZ775KXKDsRN98N3fw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMDc1MTE0NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80MzBiOGE4MTM1ZGI5M2YxOGMzMTUyNTI5NTE2YjFkYzMxYWE1ZDg0NzAyNGU1NDdkNjVhMDc1ZTQ4ZGU2MjJkIgogICAgfQogIH0KfQ==" + }, + ",": { + "signature": "cp27+NFbADPIJJuLPCFLGR9493LVVOHBRvcdqIcsANQPwaXz3i6rtl9o66sumziiTXHomCcNmixNVDxKzQOBV1gnZL1YX1lHdtkkEcwUcSit7r8O0PmHCsePJCvqQWnIltdZLmwrUL4BpLZiM2nbWEkWjMoGtiEJRtS1rfgWfHXdW/7XvryOUKrhmh8xU2nV5wRvXPvxONnfHRYuNPNU6ugrYSPCUnoTlSlaNVpW4ZFXKgSrjg/+G90nx751cgvgO/ndIlEeCT/S/zYEPnjSGkIuHDRC6LTE8JuSrgyZtlaQ1sLN2owt+9UuHjRqT5WX8QggDgG+zGcTfgvZiKMC7yqEgli/8XVTn4g4LD/syT/Z0+aTgxVr49+2YQSnQ38CY3drasgNE9SZVcD6KAAOys7Qt2l9imDwS/fert3u/7wivMTMBuirrI+7ellmYTeRmjdxt1ObZek83jzSmvBjlwY9/DQmdfDPYlzrnKkpqjkU/oUgq8H8awwpgWrOzp9OQ8QdEAtqqn0MF+ZNoxPMnWwXxn1Kxt7KxNS8cCVSzaRXGYoCg023BB4YokpM5gWm/YvjvbVCYTuwLdmKco45jxpf0Cnsghq0NPER7QQe7+QvrZ06OG54Iqj/2FDMGVQsS3acqvAfrucz4AUOxYb+y27X2XyvXQrxRqq0M9YkHC8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMDYwNDE1MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xMGU2MTQwZjM5NzY1N2Q0NDA5ZDVhM2ZiZmMxYjc5ZGYwMmM5NDI5ODhiMDBmZmYxYWM5YTA5OGZkMTNlZWRkIgogICAgfQogIH0KfQ==" + }, + "-": { + "signature": "J2y/q5a13LGy6GfDe+g76yzm05/2Wk6FMVo49n4olrQislL82PNopDqkVj1RcAfFR7PhdBZEx9vCTgx1W6sg8nPnSZEFotBTfcSGTRxarYpQv9K4q+VEVmYPBgn4+WJUph4TG9J5sSV6oc81N/Pj5zP9ITh6n6NAxlm/TOgMObQuPPCPg2Bv/J5uF653MBJYNZpL428oKD2tIcuXcFwQMUHCFeHeS9okGNSYRbbZShtyVj01jXzZ4mFiHfsqLkcVKjsIkXUdiNLW/BYQJ6kTMvGKlivGJHti48Jicyvi5ItnlmVFTsNZaNDXTaOXRwcWyUEijYXd8p8BJ6DfC5KuMU9EQm1iLSoJvp+yvhCXi/j03igJXi7OjDVUzRAfB8z8lwbZQc+uruQ9TeNfjUhBLd7G9cciY4b4xk6dJ7p4qf0b+XNSmDO6ZGFUYHlNjBXHu9ETylrbDtKSvwe7yiddwciRNcQAzJlsN2K82inIYmUZ/RYdvJRzMJm6rqcGpI0gy5INpxWj1VqXb/oh1SGGDWpRd3DjBQnrOB7PCAStPWLchrqu3ULJ+XyV3qA6EZdN2/6AjrpX9V/6xZ2aJTB55gCQOO1kUXVJW67vQSs9q1RDbWRSkgmCR/DRLCSZ/J4XBNJwyNvjqK7Ox3uXbzcMBkGJprzBdSs0jHD0YzLCUjo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExOTc5NTUxNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83Yjc2NzE3MjEzZWVmN2UxM2U4MTQ0YzEyZTQ0ZDY4YzA5OTA3NGU2Njc4ZjI4YmZiNDgwZWNlMmZkZTNmNmM0IgogICAgfQogIH0KfQ==" + }, + ".": { + "signature": "cq8XmX5yssJe8InHdU1gJpAzpTMZcN5KVHl2TVE5Pc5pysuU+dCvc1xhKpI0IYBugEd13U1pFPOh72hfx2Z+IAjx+CtfVINPdXBoZfYWAIMJ9GQlpP3ij2jKEQZOpkACMNybQjDe+5ho853dxGjIbKEgUilZG5HoV2cYPCOmr6Ws+t6drNqVTBA8d3/ZVvM4XCX50A08C3pTFuhI//78d1rNZJsvdpNcKmfzgtOp/MCi8jkZGA0akMSuALB2Tpe+l4PqWAzQ2DmU3+pi+votNLxZRF8ly7PpFDV2dgz+DALQMp/tdNrQ83oYY1yq94iZJE0uAY0FcA2f8/KbOQpxkDVA1+dknE8n+VTk4LekXeagHTmd2Ed3iHUGv6NYKta9WbemrWQiM54y3toDH3CMBoW/bD6bk4pDmOQGMWWgaor86clBx1uPk58oxQViTxzvS+8GEUhsFea5RV0u4NzhYeRPlC5lHB4nl6uHShAcYi1q2rJJjo0tHwZQw9Ef2JdLwFCjgRfp/sPG4jzYqvk2TeDyEwzTI/FVNBQxDdUO0Q2Wkm/g5GKWZijw97IfNDYEYpk1lkDNdWRG2aym8srVtHEPnpY+bypu3Sk8Xe2lbxw8u1J3/RmJJdHFgUWTeXvZRqHphixKKc+HBmXgVErv3xQkXyPM0cQSIv7ZpiRdinI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMDEyNjgwMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xMGU2MTQwZjM5NzY1N2Q0NDA5ZDVhM2ZiZmMxYjc5ZGYwMmM5NDI5ODhiMDBmZmYxYWM5YTA5OGZkMTNlZWRkIgogICAgfQogIH0KfQ==" + }, + "/": { + "signature": "rdeM08y8AGscuUiS5zjob8P+EkFSwqT+OjFKsWDDUg5vszoD41cxvHyX3wOa7O9wxdbhgbyaDrerW8tE38lINqAVTFq1UMd3XmFMjbmwqux96b75+bYoUizgxYqqW6bSXeufGiWlwYsIbOigzxzR40zilCMuB9QWxhKkxviqyw+W+uU8oBEv4tQA1RmyF9BNjbHjzug//Y+zvq4L/2MwuS9ARwRj74woHHDkvzQGpkuXTptMeVfQndyZ8C98MJ7KH4d8cb4qMs9DuntxPT6WRREU1fYG/uuPWmSg/PYImtjyviEu04kFHC3MdEes/OZR86kP5brroirBM7OMLz19E0cHj6+R7T92lqkPXerJmt/cPGB6bQg664/UhPZCe/BliPxO5KXbU+3RCd3wUBkENK+vlGygwblC6eAMdrqdfJcQRwFkWDREtIz66mAAYf2CHsRLfUaDjwvQ6tccGF9HXz6gQS3ry0skCzaRDGWgS81efQ1FCEAXJc4mlgmKmODl+nKC8rSuQfKVhbSdi4Q/HtU85aHsdxCNbUEVKI2W4PizHjZIs4lamkfcr3VEoBCTog31nnE5umCD1obNamkkHSG0Ha5EnEK7/kFIdOFxARTI/lUeUaVvkdzhX52lQqDcbJU0VrKUcLL2qIu1d4uKEB8zuiG/o+G8tAFKmPUYsZI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExOTQyNjg0OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82NDU0Mzg1YmQ4ZGMzNTFhODJhYjAwYzRkZmNhMGNlNWZiYWQwNjcyNzczMGMzMWNjNzdhNmViMWQ3MzNmNDRjIgogICAgfQogIH0KfQ==" + }, + "0": { + "signature": "GuKmOc2Hnsb8IaPlmqDNRqD7yh9y1bMr0DIruLsXJRmZNLqYmD5x9M10IJ76GlWfGRC7pbiiymte/3tyIzyW29P7gKNRUqyk8eyg/6GJO3Gs1E6fMS/vJ8gMNrmF5HZ1j9oVaF0mIMovdxO7DXE7JUjNkzvDnBTunpACNpmc+8hlaWbWQuLY2Ao1MB5c4jNQO0RbDrYkgjqCnNTt9JEnRThbYVFMWIKYhoX3ryD1XJ0s8E1x+vdzhL8BcFpEHsdTB25AfUdVVrb/4QpNNjkS9aLHOxJh5WMFj/2vz0E4R9SbYq0RdrmI1fXartgFHSWuwUV9rHYAxMT7/ktoVe5tYsaRuWrAvwdyNmqzULquBHKldQboKq1f8l2xCpdnqJ+SuMedaMNp5arVGKIOvvEw0fyrbMKRkDS7mvwAstOrPxXIeyyPyr3KZq9rqf4HOHQFJN+5RXmJTR/GOL5U5DQwHcMloEHD/xVidmhkiw4zZR4W8+LvB3Izb0Y5omh2lwrq2lA4OdZVODxhUnpdDe7SREmulSkyLjSdayjpK5LsmL/MwkEQVazhwhowKae2U8IccKVCvdieK4l0G7iHAbt0WlVCumXbdIQXkOtx+PaSkICaFLxunlrzqh4JD9AYypvJgCO0NCwGbxKMuuxpb3GkQO3vg6FD8VUCCtUrZYc9Yec=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExOTE2ODE5MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jZDI2NTlkYTJiZDNkMjljMWI5YzMyNTUwMjFmZTMzMTdjOTE5YmJkMjRhZDdlOTBlYzE2ZWU3ZjQ0Y2Q1ZmVhIgogICAgfQogIH0KfQ==" + }, + "1": { + "signature": "XCKstgKStOSSoRb2F6P2Fo4JH+FL2+s029c7TYBSFGgek0QKSmfruCTHndbUPfDczzLbruLj1bbFqTXkoAUoR69Va4NW8V0gL5HV1Y+beBQCW34dLyUEdlgQDXPhNW0NoqymBQJbEkQQLJ72At897/AaozptlcGDppEH//5bhQCy+UVj2KR0oeAa25k20YXm+bHCtpuIgO73gAwaAN48q7HQy/SbL4w9uhgSZOYRsoAyedHvK2r66x8iZtBr2J0MB0e+w8FiFYOArpbdk8nIDgua8oI7AvyJm07a2P94CTY+DIZyzTF2lH7swQPoHKwkZWwainyGhXF0CAUpp+tgR0YcMFoEL8qNq/X8n7Nqu5BGvglSyjmPLa5NFW+Qf1+gYF55UFvBirA97rHxIbVd3rvDvJFEWijxXaJx82qcwIF3RW+JYyDNpJHQB+qcvo6SIKZj0uCrfHgWq+SLpFmquh/RK+vvRfOiB9MqsI1yBsRKwd2NBV2X2qkbOPAhdNgB8ig0ZV2aS3wK3ub69yKw39v0ixqbl+SVy63RLj6FmitxMrDOd4sPYreBmi/FNjasMH7Tm/himBZZM354yUkm2oPxEwOI9oNzQFmAjTRgvfWHpEZYpPc6vrNB+Sp0OQ2P3o1OBq18RpOkD4Q9PBoMac5S+S42j0Ih8yUIbH9K2AM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMTU5NzI4MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xMDc5MjQ3MmUyMDA5MGIyZGJiZGU1NjBhZmViNjcwMmZlN2EzOWUyNDU1ZGZiNWU5NWM1MGE0MWI0Y2IwZDgzIgogICAgfQogIH0KfQ==" + }, + "2": { + "signature": "WBg+wG6A2DZaSZszBK4svtBpqVTYpnwndyjhkkHt8qv72P32Fu8UOKlT5eEyHVeWS8E3ktKoZwAR/O4snSWxSpJ7+eYdZbIYD6lJ2zGgAe8LDDgrAy7+zZx2LT+QRQgbLHeN55dgfU2kjF+8Y5V/UEslL7RS6/wko+xaIahQrTtHYdCDSTGYrlce5JzDY9CrtS7UBEeeTJNmjF2/Q1bjgJ70BMPJIl8mHmCtgr2+4dblsIpzlUF0zsgtQGErmq2r8jBWm9qamgI6MnJ0lXqjWz0+zER5LLl7YLctiWYv95Putk+3Ss4WWYOK8AwZSYq5VkvXKgN1Ko3izSFVWEwqimnkr3G1z6J1s+fkCzKHeaK8rD80tmEYtgQf5iAvw2C8IJZycaPBq6aCa90NSZx8nvhIWsAtuZmJmLrg5IcaUKJDKMq9AAnO8r2s4hmJzbYxUBhOD2Qr2wMjOwQKzMBBTijb8xrxleM/rhRZU0x76g7CurF8g8WlcA4k6k6pETRSTirxaetbpwsTsH8K/b7V+Cwj/ZisDkqJFaEuOWIobkGt+XNARhTscCo3uG3LAkRXE86h9PSER63OYD2Ym4JWjwQEdGJNccc8M3MOM24deaW9gFGwOIY+G3Ti3uRqaez5IYFqjClrbBFnoVNB1Z0FjX1xZlLaU+B2z/f1ldmIMFY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMDgyNDUzOCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83YmQ4ZWM4NDUyZTllNWE3NGUxOTA2MzY1YmFmMWQ0ZGY4YWZiZDljOTg1OTk3NGQ3OWU3NGViZjhjYTEzMThhIgogICAgfQogIH0KfQ==" + }, + "3": { + "signature": "q/aoE8TO0so/DVHOlOnnnkwYJ12xeo3RXvTfjneZ9UBTrg7NFBeFXHXlJCb0MepyjmJ+JjrMEejScjhJNYkIpv10aio1r60zp6gHE7ycOS7YUUTtcNvHNjxgLxJ25Ee5RAJM1YfP2ASrb9OjL/tOrjgzsXdCecHaJXuKQaE1b7vaKzEmgRU0Oc6g78p9ZAFMYFYDDyTABpLl6H6+8ftvJrYCLEEk8HuD1zbHbQeij2A6069Z3Zy/a9Cqg0b3/yVrlwLZBAl9CscL6oMwwxzkZe86XdFtsDfr39ZOVYBCnUel+CcOk40z67RPF5kXsCaGm3E61eD8055NSfA9/vuWdEys7AhDRGdckgUXQsUvcpEvVA3To0hWFAyFEBkY2fNqe2NUprtlbq2VLcwUAWkY1d1EMN2EgdNAjOGzkglzChmAXuwYyRj4CHXMOXXWEdgOI7/GQAgyPDZvlOvudiDEJGZiRVXqTU0ftpsiQXBqqHENKubnZH4QquffoyzqVQSNLlsBClJJJAoGky2Qsk/p+yFjOxkF7X3fXKy+Bj7SSMJmeTMAqQ6L8PrBDq/c52RwfgQGkxYxLJDcesH07r4RMrzNE44OgCiM8gZehYkvCJo5u0DFPgQiqIhea+GNnjoRF2LCy4Uk3B94fZEzC3wimia+DXXG932CE8zLKLj2EA4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMDA1MzU2NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82MTRkMzAyZjE3ZGI1OTdkNTJmNmE2ZGVlNjFiZTU0OGRiYjMwOGQxMDA5MzQ3ZmU3ZjdlODgyZmQwMGRhMTgwIgogICAgfQogIH0KfQ==" + }, + "4": { + "signature": "D8SORoia2hqPlZCvfzGbkCL24CdYwWcddyQRmZuSip+Kvs5mkbRB7B6dOHe6dInD2ZlOZxibUsuHwFYNiLb5Qsj8NboJRRTwokI7xSOHLwAkQ02fpRuxHK992JsrzK78hR/N4knWGTuwd4IzLkGydQ4Il11hUXx06iw24XViAOvSDi0K6RooTzDh1Ncmd1znWt+o8vOFroNcLX4Jv7t8RmxGeXiHfKqRK+5e46lNYCDV3vw06snAlQG9XLGjhwE1NdydQEV1MVad1O6EjqDKBz9vBC43oZO8kEOf2AR1cAARoXQKVfqn1qUORiz1xYP3/QtQdK4I/3s+FIXBRWPyYw8W8xp2P0l3h9Bk/c35nWLDI0Prn/tNuBFCVr+zJV9SuOdFSiguNbAlSOcdQeh99EgudyOD7blr7JaHqRb4jv498wKTEMumH6zIdy1MvyvCWJF3lIJRaY9Z7ZhUo2Pb2V8v4X7/ACRON4OAwMblBL6hECpjmgryg3dxLJ4sGsQf4XuwjljXsMcIzKRnCrWFEJgDoQ3eUIPNXfA9jtujlo2RyM6Ym+mEOXm4L1PrvaERYA7uNewm8GYgwRFOtMqj5S6pJdlxXEaZWflIhHEjh2Whw0Dn8bLWrWE3bCv5kG1TRpacR0vIVmmHSwTqwhLcgPzajg1yLMs5Yr9x9sQCs94=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMTM3NjU1MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iYmVhNjY5OTM2OGQ3OWIwOGYxNGY2MTdmMmRlYmM4MDE5N2Q2OTc0NTczNGY1N2M3YmYwN2JjZGU0NDZkYTM1IgogICAgfQogIH0KfQ==" + }, + "5": { + "signature": "Fan0ByVyV7S1SJVKITo7ec47g0r+5dr5ttn9zExFt3C+QpeVHSiTNUccFuctEHWI4HXgAzvxDKgcJ+x0icHJhBrVBtBfORgeupi4nn5AtOYwzzZDuK03TC9fXzDRYeNP0TOKtnr/eD/05c26oPDkyaEFGMUXyRvdn7XgVTgRAHY6imZt1tFH5kdjVlfp5YB80xCxIL7fpU+HjGaB2orR1jSyKxiVx591dvMpB2frMn2aMXIDza/CD9FK5v1apodku6DUeVL96XaJ/9wEB9Lm2doaF34zjSD8VjEwq0Lq89Oq5QVzxBCuk4tpFk8XvI6q5AWhXvAMQ4P7k5FLX7/9Sgv4daT4MnqiT2VmT8i1t+9J6ZHmen0LDyMFDFo0M9JB4GXdtvI24yRA6tOqh3GB7bQbiLarpXTxdD3P8FXy26BwD1PP3t+PbDxuPiacqjXB/d2V86k4Yf9zH/KBF//gvn9eU7eWs2I6TJ4AbCQaXSsJpfwduovBdlR4cPxecU3NnorSBFo2qjtGO5k53E00Q/Xdnx+B7Q9AEUpeczu8WIgi2QrvRfFWpfS1cPv7qyRO3fgFo+xPGBEX/X2gza0hxG4l44/PZWihBCaidrtbwzONT2kfhgRgNpm6UpztJM/08CudUn6AwZar1CxzKlqMcBWNNbDRKIlbZwSBaeM0SY4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMDc4Nzk2NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mOWJkMzlmZmQ5NmE4ZTQwZTE5ZDc0ZDdkNTM5MDUyNTY0MDJkYTgxZWYxMzJlNDM2OTU3ZDVkNGQ2MDkyMjZhIgogICAgfQogIH0KfQ==" + }, + "6": { + "signature": "nkUGP+0yq4NHt3Yrd4gCkHpamXRa7IOhTPAII13gNA/8cbwFSq7Y4Q3L5R7glj2UX47hdKqfB0prO3CNoXLJEfqvkPcDW1NMSvzN9dsdz+99eWpq0dmtqc+/HcRYUe43Ox5geSg61owcPUFVXxEQY18EfiYQXhgOgtOzCZVUURNa7p498EDPfn07MxkyWpZLZpqWESAQ/SuUhKNtavwg0WCZClu/P7NP/QqRp+K7qNddRzkvC6XGChXegaopHz417LOFxB5qZ7lewTCxYqhk3/gv9xQuqO9ttMHa4BMb+0Xe9qqyfbrKF0uUHF3BPXMmSYpZXXs/vQ64pP1dnqR7B1N8JTqeXh2t2ZXydLSIcxhWKAbJ0SkCGjj7XBhzqE4U+GEnYXr7cFidNVk0qDMdBsmRwzBc813zK9FytiJO2V+VB0dxjainbuByxF4pfkegD1EEpijKNZlr/TSF33AQMFozrn7nxj2b05V7VvmGIuvwrrepz9345a6r3iAaw8GbtVDa/B8Spy3VAWccie860eP17NtzGFD5PHlciT9MOFlSwhC2IoAxB36VSJOga9/MXnySfWeicNwVIp3KVEXnTWJs3Lo7s00IQE3Tl9ASHF+KkdzFVr+QOlDFaWLHbJ+WUKFlkB9Zw7MRk3MjCM1tOOW8iG4k4BoM2swoLqEuRtU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMTQxMzQ4MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zMmI4MmNkYTg4YTIzZDI5NTZmZDg5MGFhNDRkNTM0ZWZhNDI5ZmIwMmFlYjc1MzlkNDBmZmE4OGY1Yjc5MWIyIgogICAgfQogIH0KfQ==" + }, + "7": { + "signature": "dl9v+VrXXQR0YRZG9CTAoEWN0Z4VJtsjUesfP/kOBwQlAN+Zjf21bn2bpAmRiziga6C2M4mkEvlth3iJINCyUg0/uvRZvFHUIlcfyGP4yMNsiX1yax601MtzK80q/J6dzLnuTWhGBjVwRMK/Mu7MvmxmG/q6JNFRtw1Ln8gYMY5pltgNLLgm93m1hUAo0ktd0aqqPMRyr4AgZP/SvQjRQyD/MjTxJTpInf6LQoiBg59u+ypkoS9VOhuKcqM6Cg6OIrG9DK7T1fRkI4QKQA6FdhjmHs4s+vzdgxy6kar2u60lTUoHFrELJoVeyjjRV6DjnSLvMaCcX720Kd29ahyod3Qx5JTRwPr6Y4pkV/vTYBBMghiJ1AiASIeu0N39tbYkj5tax/KLPtnA6bKIU9iFPoGXGqsnXf9tGma1crjC9vIghBabo8QsXeoC9ZuvQVG8pLSKJ/fL3mOvvCW8lmuvH1Vz9kkG/tcfrMfbfhyhIngVtqy2qe8QuaWB/1TBWcCia9bQyQj5Ct3N3CSWBdhWlE7SVU5k6ASYRwy1vvbWDYlXOxE22xdhSEibxnh+tw2wXbAzq16xPjcd87QuJpK3U2636MBSVqDveG5ZR1C8r2DMZP4crLG7ybUFDsxVXxl5jW2XtDXodYbfpiO68p3Jtsud2aOGFLpLToGQ90AQSqU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExOTk0MjkxNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kMjE5MGYyMDFiOWJjMDk3NzgzNzYyN2JlZjk1NjUzZjA1OWY0M2Y5ZjFmYTU5MzhhM2RmZGZlODY5YjIxNTQzIgogICAgfQogIH0KfQ==" + }, + "8": { + "signature": "A7YnmEXDckgh4DmROcmkRYR73cphtcDeCMyVzyQi+wEO95L5Wm1C0Jbe934DxJqr6dhEQ2Vg66hMMHZ3dwlwHzfE8lZIUWIJBiUJOQNzyIynHp/JDw1Qnv0wF/BQ97TUxo3vLyKVcMYBz7nl7YBArnDJHFKEbt2izsrKccgWiCVB2eR18NQoHiuUQq2fbXAQE3WttIuwTidzXyCKi6xIw/+VPFZEi5pnMkUrJyJ5SKUKz+wgd/FgRtHvj27zupIhbJI1/Wi0M+aR2hGsns4qP/UX9mWwR6nsdXU2CF9tJJY9zb3MqVikw5JHmuK6g2V1GOxV3zauKZaQwQcUL8sU+/oJ0pelzN6VXigZ/A9BpjF4frxdMWfq/JFlcLqcl3lu2df3wwNspQkHEEg0PxF86xdtPYi9DMLpIpNIDxyWQZtYxrPsJdru5Zhq/4L+BHRKkNrst42Fgg1+IFKgr/ZKnRJqiuWzElZkuiIOWB4Rm1kdmmgUZ9jaSUwMRmsgLHsiM0YjtIyHreI7o/kqtv0Qtho5iyqf5A57bUXJj6KdsdwvpbirrB4RLa9V48NLglhQ+QqBe9gaHL1IFUCceDR6126BhhGX4EUYlGS0SfieAIvhK4TD4Tgv3Tsl9wQiZLomoLE2y39R9tAR6EFUH92UzE2STi0q3s1okmKHo5pLd4k=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMDMxMDk0OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lYzUwYzM3MzE0MGZhNDA1MjQyNTllNTE5NThkM2FhYTI2MWM1MDI0MGIxY2IxOWJiZTczZWVkODNiMmE2ZGZhIgogICAgfQogIH0KfQ==" + }, + "9": { + "signature": "nX6DoL58It6H/pq09Ple09f+oe2ONM2tYzbtJEqCDz2KZbS3NUAPgYvpOp2qvHBHXGgNzQ5mp4zzqQbnOjaBlS4nFaQgoL7njbBmKwUuzZ55ytAOs4GmqzSNJWSiHSmxP2subqT46jsbk0WuzFSqw2CzAPm5U99iZglqTU3qzky5mIfTunn0I0jzM4IU6El5l+OjG4atEXLUQwtHHdDt/xOylKs+4Z/EEZEPh/4BwA3tWJvaKlGM2StAv2OmM/SSeFpPwvkLPD1qHHaB0CEpHacCVfMQehAEPff2yprgxKHZYaZYprSNEolpULm3jem+CMy4kJZZxrxS7fmwA/npFHE3otSa117pplsH49IDaFXr20j4LiG1OVbW80HJxCg2fVe7Bmy/2CV4KZ4iHNnlBhD6ePkM79HkqbRAuyiHhQ32vHo761EdBBC7IrUQ6X40gPapIxl8DiCr5m8lrBeTT4Ka63sX0vEd3YKyQ+v6iy3WmoTddTIoD7GSFdH+/FSBIu68E4hFc9SKpEJbwNLLJ90Kv5v7EzHhEai+L5c/jXG4VSMUNehKt0M6z17K8EJmPhnqqDrcV63gWnw+dK50KYQbIu7fbciTyUfnYYU1djNPzqAXOeOU7FMCAZFpgVcm1+pOPGT2sGIA9Uqxfv7kT3UxuzNINU2+u5jX9Hsl/ac=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMTA0NjI5OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jNjI4ZjQ3YThjMDIzNWMwZjBmZjI1MTU3OGYxNTE2ZWMwNmNjM2I3MzAwYTE5MmQ1ZGJlNzA0ZTMzOTA3NmI5IgogICAgfQogIH0KfQ==" + }, + ";": { + "signature": "Mug164HkasgCQbJ9rhy52kU/FZ4kwBnfG42L5HaouOYEKst3cDjEskS2re6lIwQ+WrqIt3wt8BV4kFTsna0B+09r5B+hSUxaIwLFAR0mFr1ICt5jeDl1+kihClgI9t1tlKVeAIrfnVYNANZhvAwNgroC90B25f0JH7EvkbGihrQVLITunBLYY2nrcUoIget1yoFepN9LV0X2JVfP4ps8YwhY2IkNPY/u8eM2fgQPVWomHnyb6/UZG1qnCXZX3IPf7gybx/mba6TKtcHWgfGQ8Yyx0fgVoJr5qmBg4w/lvt1oHnWtQsRnzx5TMhU0dr3GInXNPx/tDry215ai5pTW39q/uvogUu6ags1zU/iAy/p/OcjGHuSQjWr8A6ke5b5OJzZ1cdFbxHKDdh30KgQzAqDklavw1j4YUrJl4RsVA9ntLZsw144TE8e05tCa6IhkwS5695nXGGhi1WQQiPorrUPCc8RNEmFFU3c564niwT1yHLdPBt7JPhkP3Ydby4ah7AvUvG1pgImK6RAEsvPpZaUGMV68BOKFKYrznmTwUsag63ijJkiU5a+QmSpJrmhYL4x3Qzo1pBaGxzMfNBvfw5oSGX0gtIxAe2DSJMjSwMJB971GspWIA4NOA9isY3ZX+CewpBTignWi39g+iUJgWTQVMd1tQxCJrRr2iW7ZbOc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMTUyMzM0NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83NTI2NzYwNDBkYjExMjc2ZjVhODhmMjI3ZTcxZjhlZjlmMTFkNDdhZTRhZDIxZWUwNmQyZmE5MWNjNDYzZmFkIgogICAgfQogIH0KfQ==" + }, + "<": { + "signature": "I1pqqBWg7hRVc+adWNXduqRLr7IL1HpfWLWCLsAZGlGTS9Eyu2U2/f2f5rb+wUQrUlGJ22OiISFLn35TyiX1eIqwdDqRUzm/KVNLoERCwmaTUaAWA5hqagKcCV4jJ3ZFddjLaIGwzizlGq+p4S1sMne0393TLiXok9TqmdcGEw/R4Jpov8sfUlrZzFYD0b/7p++6Kk2jb/NiG8M4XWh70b4s0nYQO/j/PyIa8mjvM2dO31slA0qIQrpWVIHj2mmKmIOJrcm1EafM0o/hH8PDpc85jFyoj5puINAnerdt5vV25Qwy0qJbTaC1BYfLKVi5RHr3DP+1Z2vtkldhHlPbXO3MhRcSblbI8DYLQCib1zv6wblel0ZpIBaiWwqByaw6CJxf0jF8SzPYBLegYWpSb5Wtx+xArAtcCKt2e6n4hV3iUxol2kFMpujHF4BAgD/T4Tb2PyKe6Hi0pWKbiU0/TCi2ZJhBXheiMkAWehOyah5mVXA5zt0dYgCIHrw5rUL6EyRKnkCorWEdGwwi0tEBeYQbc+9yVU7le1r7yv+1IHvPlLl68XMkukRt3QrDqO7HtasZKrNizf/6jnJgpQ+aK//b5ehQdKXvLjZpZ/HGpRjcRCC79uSibuQfovfSU7OTZyKn8kl+vpAMJxEFGuRj799p956GawYBa+gAh7b8twU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExOTgzMjMzMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80Njg3ZTVjZDEwZjBjNDllNWVkNGVjNTUxMDE4MTI0YzUwOGFlYWM1NWRmZGMzZGZiN2MyODAyYzU4M2FjMWU4IgogICAgfQogIH0KfQ==" + }, + "=": { + "signature": "jXIRObwtd12w9jrS50JMN06VaXvr8Uck/YYNCy2PfvVZUT/eGzs4RflHZ8fJ1Sc395J2ec7NfKzsvy9TSr4qNmkfRu5zmIJPoPk5D3de2KyCHx8g8OF4FzroylyFRruo1L/SBLtL6jMfxUejziXOFVI34n4uL6sPPdYCWQEhmdvbmcUNNs8nSR0fAQzyzyI9zAsLm8YMiapLlSeDcs++SkXmudcmGjArdSNtT58wnFmYm+ap8Qa6DqpBGNYT+j3BykTLiy/X0V3QkBiWr6+Nb4J4to8URpTOR9gTYzTPocIPAvniiSS+mumb2Xtbi+HWwNgwo0P7cIX6pqjtqWZJQmn1pjB2FPylQOlUVxHQa9reU2oDFnTkInbXR1DrXMLdW5vyRMz5wWlQWowQbPehlSy+Pb21JOalZwSo/0NL1LJ/bBLiba6wwB6+g4GZnh3+8nCqsjmW/O7kum7cej8HuwhqSEtwMMo4WZx7g3FF40p6lIii7vO/5+giviSYhKVX1nJ7aKqmJ+ugWzDLRBpJUvaK7uEWYCL3g365mQWHkMWXGhbwM7HbxoZcNgvSvjp2xU753TvS+w9r4n8AsGsraAc7TLQFyPCFY0ujcn1MsON0KcMaGq22UIf5Z2vGA8lEr+lpQFFaX8KACeNH0koVtw7zNE0RcfoUmbCYIKJ/V94=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMDI3MzkzMCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yOGMzMjU2ZDUwNzg0MGNmODU2MGRlYjMzNDUxYTc5YTFmYzAxMjdmZmUyOGM4MmNmNjgxNWNhNTlmYWYzOTZjIgogICAgfQogIH0KfQ==" + }, + ">": { + "signature": "CORcOKSnp8qphAxUB4OwIzCzvBGgqaVXJ8ar3aj8CWLVPcbPJpAX1sEQsEhewkb3R+2aE/HYA1gOP/RapuOJ3iKJouw70nFmnE0U7+5aZhjNdJDJOxDIphAZ6Jnz/G8H8Ov7rl2t9uXjjyDRjRVTDG1ptLP1CiCGYztGW+K80sH2zD+SdXMreEJvJwdG3G6LPhkd64RfaG/4m5k6blPHahRUAigILWZeih/IRMDthcP675BWomWBsGJDxpmoHesvofZ8n8QmpPBjVafeYuJIDwPQvROFFOa+q8ZIbPv5Yn8mOJl05LBXK63tzlW499XtDrgYCLz+bBSWKdfv5zHYffFi5SxWYO0WmX1CxR9xy894DI5GBLFvbQPFWT7qscnDXH+F8GZo3sh/wo4ApBR2CbpUN+DG2GNA13A4lrOMQ70gK2/eWOIvTdh7oYsJG42VvxPWH9VrQp3/UigSKoFQb9xUS4itqlpFtDXT1zMAwefa1qWEK8uBX6TV+cl8OKhaydbxxFWf9F1ZzbZc3824tjZqI6xJSe0TqrQSR1NYRjY4IOV1h/iyTdrh9uvEct1OJh1cqBwN0eCT0rZ0vuHrUXKh2BnE+P6a9vZKSG+4PlW1eYRHvHOJbjQie+BzxM8VoYO3Wr6PBsbrcPcvqBv3tXMD7ty+TEdyOqRYrh7eHvs=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMDY0MDYwOSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hZjNmNjg3YWM3Zjk2MzVlZjRkMWVkMzViM2NkMThhYmQ0YjY2MjAxMWU4NGJiNWMyZTEwZjdjMDMyYzRmN2FjIgogICAgfQogIH0KfQ==" + }, + "?": { + "signature": "LKNFYDtiY3zGqcYqQI+3I3mXwPHxtmLljVn2tdG3cxKn3kLcr44MEM9JZzcHBbMwovqrroGeIE8eDxexcIYEbBWnv4N4AmWMs4AgW4wobDqIsz+T8BRiUOpcVpylpxLMthc2kmUu877Nz36rnU84ZyWGenh/V2x9gnU1F2IIF3gETXMylxn+KcTBUQdYMYYSWRGvxIRhzvq2Nij8pWj8BSXbaIF/PuWdWuD03Pgz9IIkR9vcBV0eLsVqAtL8QZV7QOo3d91ob5KEGuQE2ZYvCL+PibBy6BAjcbpkF2sIc8FFL9ZaEQlcjSffdAjzxgvmFfVvvUBJewJTr1TkY0xmx2BMe+nOPoNPPrvfL+Vexhe62f3BaleuFj3iUwCDv0c52HaX2q3oH49nqq2KjD09rbwDPfxDzR1lnwBbgaIJa8QOLHyoe+9FHD41U0qvugKJxzAtws+MPM/NpuqFzegaXpf+zlDDMSUOmXHytYc/2q6pxtxHQwDv/bN8hesq7/ydrVceFpdIUJ0eRuvkoQO7nTNSZjpcvPYXMC2xA902tpTXm+I7WWVpS25CsI4DIxPq/mtVcfS5B3N0+N3/K4hlr9ttZHrtQ43vXM8+PWrnBuxK2kI9tkJJPQLbU6P7oECAWUsh0BamjgLLv/Y+11b/UHi4aoUGdyNZgevCYkk4GZA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMTIyOTY0NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85NDUyYTAyMWM2NjQ4ZWEyMzU0YjViMWQ4NDQ0NjM5M2YzZjU3ZTIyMzcwNGI4YjY4YjA0NWFiY2RiNmM0ZWNkIgogICAgfQogIH0KfQ==" + }, + "@": { + "signature": "uNZ/1GrQ+0DGP6svB4lbF1g6Cm0SS5AfYBh66NLjlwlul/AFOXdq9uxUHr10KkQVjsYx/lXgjErYWed6p/ohvh3rfRpCBY+vSLwBObabQV/aNLg4VXh5Xo0nb4IwIsxhSGbpIFJ9Sbby6/TbDbDyGCSs04dRK/0d85wTl0jOGZ74giRHmTnYrtFkVaVldSY9ywnQvKCHYjLF4wdbKfFGJVjRH7TjUrYoddTXyfvzQLVmjRQNmdpFDrDto/uWXaKesHPCxE8UcTuEP7RhlQe6Q0WxZsyzwksSDArvvZtpoRf12VIdL+g4evTIMFK1Absuq3Fy1W1ZX9mC0S2t7BPigTCUEeyAmWTBWwMacEzlmRAM9nEHKPNuqF3o23QFVlMvhWL3RixmD68ryexR43cTrP1QvCzcPfahhs304ex9ypaI+D/2CY3bSxeJRbVeWgOBtsKIViyatGsvEH2hNyBOq0wG3+GJqc8jziQtFxuPefT+9TmRqDftinIhIcoAbUYciGxj2S+2cXv6WAiHC8iUl56npOSIyWpFi0XdUYR8MjX55wXwrgtaf7vbZ5zjlOvPd2dqBFYLTisz4JH0Z7pBfBX8NxctPAMiWBW5F68Ln9oxSGZ2CVsh2qsyo2BHgvbDhCoT/opAz+a//9sI8ThHzzEeOFjW66CCh8XaEt7O570=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMDAxNjc1MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kMjliNWVmZjk2ODRhMGM5OTYwZTc1MWI3NDgyYTk2MmVkMDYxZmM4ZmU3NTcyM2E5M2M0MTUyZmQ0YWExNmY4IgogICAgfQogIH0KfQ==" + }, + "A": { + "signature": "YO/gUeoDNqurtHnngWpwlpceQgvNGd30iWAUKhHsFS5w4n/5bX3sLqHW/n+ybCeMv3C01YBJ9ZuedsuYlxvBt1YNOHDS3sfngbxqJYMA5eXnr+Yp6Q1FyC8hWgb3AuRjdbUfJ1qKSXSg7yfE3tOJqvq+1DIQNMkf6xBBBUDRdtUwBfYOMSQw3qUarfdmzVg+RGsfF3lhDlpQgV1xJ6Hr37dyBcrJ8h4Gp6dOne+XiAGS2gnp2QmuavNeafLlCBc8heaxc8Ka0BaTDS3NUIZkoczYYnHfAwZEnTTBq7v0nrv+IjAKLzUVJLBrW1+U0niSQdrvQvx3GC+TRsxpjuhM4pnsWViCBIaTpNCzqNXA+rUIhQNPjt/wTx9L+SYS6udjl8mI0ePmcLb3S4va/JjnByZ0tX15/IUCjpkWQHgLFiL50bfL0aDg45oc26g2Ai+sDeoOcs5EKOfNFISmNpHWGXx89hQmfJbqNLkm/eDUsVpeEZ61y0S4LcN78zW5bdAvfagFb7YHg5jXJlnqtTmgt4i8leu5YUZ6y/jdq6eqHfYGzNolJ+4rHg6eex0k9w63+60tIK0Ixifw7Ga33FeYBpCnCt1rGSDyvqjKInf0eC0f/ow2/NiAvGl2hqLKq3K90jzdiN+n/H0OY+sU/oqjOIRE5PqgKyP3DwKUiiWA5Hg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExOTI3ODM1NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83NDVhOGFjNDE2YmI0ZDFmM2I0MDczNTcwYmYwOWE4M2QzZjEyOTFhYTlkNjUxMDA2OTExMWFjOWEwMmI0NTNjIgogICAgfQogIH0KfQ==" + }, + "B": { + "signature": "X/e6VnaPAA9d49KR8mwYmmAUzBsnVgri2mbw7epxPzsHh4/p0CHeTaZyf0GEF6YyNXBhd+RDU6YHxKgwXZyCEyvL6SZkZGOChAJoWcHQmdnBAsGk/nBXvGcwuvFUTt5a+yL8V8C3xBzxt2drG8nfcYenlkaLTUEVfpzlO3zbpqeRL1MalfaiSxj96Q3+ioBAF92eoCI+C+kwCAkGwlUFevCv84bBLQgp3MZfU44Q2yeJ4aaeOszFIcLaGI7e0oxaFe8bqFPcjGLjem6edbNA/l8v3GyEkDPOu+YjgSlRUuUB9rJPI7rfXKsm+avbJKyUfloluHc2dn03DM54ppbwC0MnxbjmruXbNZXGONDddRIFXRbAo9idwhTFL0MlIjudeb+df+VPqylIVxjqn3s0/bDhFVA3R1aMUtIm8QtzCXLVLrPj7/NtyY+m3dlHW7po18kCKGk4bmSKfEWi0XLtqhljGcI0/ISVKMO7Uz0Q7zMh4kc+RNeYE7Fa6sAci7lnXY8FM1fosT/lnd7L/t9kEDzrzJ63h4hOMg9YCem9qx8S+viKnEuUhXqh7nbFOyd20T4v1NQ09ShNZ7AaNmw2KkOtuq/nUARvU5+R9+h+iCfq6gZTIIh5bU/0Ysp684nOfgVyPAIOR6qNgJAEd4ZOyAX3zQCmOOrcNfd2jAhReG8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExOTIwNTAwMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hNWFhZTI4Y2I5M2EzZTEyMWU2NDdjYjU0MzAyZmQwYjIyMzFjYTFiZGQ4MzEwMDkwN2QyNGFhNzFiN2I0ZjI1IgogICAgfQogIH0KfQ==" + }, + "C": { + "signature": "T7JGwy1Jn2o56ST6lPgkjnHOMFJH7kzw6z7HETKMP5n7ql1jsDCbVQ9cqk518iqTqCz/a64qHBXEL/h7IhMMM963B6LReGQDcZq1guYQF+MPY1eCnzYh6IdGsZfdUgyQv9x7Vh8LWjguAhgQ1PWSRr9a4TxX6bLVJlSkVRhoM1djTcABUPKBFI4WzetvY6JoH9DbhTXfEhoLx8kEuJ0+FLWk0qEqSkEPEpeyQkSUpoIRVkgUv8wHPNr8oZh5TSC2Pd4zt5sPcJzoLBSxqtANpCmEmQidYAvGOI9U4qrvlYj33cyxr+oRDCb7kvjs+TxcmQ8YfeFxiwoqiTNJ0gwqIJ78TphJGEBUCtfpsvqX6FwlVLNKWM8F743vJGQUuVETW9N7G+1HE9j96CVldy0taliDMpf44ddbhSrYiCfgU2GhnSDLQRfS62vjIN/UMZjFphEsd1b3H7TR2aJInLkVpArh2TSCHT7ZI2IGxFKCUBD88/2xHSmwBm2L/1lzvKF/onZGjN9yO7f5fYz2iVFYRO7r8G8FK30w+uQ8voUySeUcQkflZfRAus13MvETV7Jl2bvA1mKZyB4MQCu1RZBCWbYWRxn+W3z1v8gFv9wyVJGp/YGW5TDqHDjF+3ioPaD+ZjIoNPe06pOZdwvmghZMjfJpV6L0k0IeQD+O1M034Y8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMTAwOTQ5NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xMzQ4ODdmMWFmZTEyYjg1Y2UwNDZhMmFlZDFmM2JhNTVlMGQ3M2MwMzg4NGJjMDA3MDQ5NGVkZmU5ZmUzZDM4IgogICAgfQogIH0KfQ==" + }, + "D": { + "signature": "ubJpd3BX9ptBUBnUqwldskTFcasYPxgDiTq+H6Z+bYCWYHA1/XC6T5OMPNVUhBOOCx4UVbM3EopkQplEsHYOg4SekvtqttCYpvc25C3dfFvBCjQFZ6OX5miqQ8q+rcnD/pHusXSyMd434at/BOOXeUQ6X1uLQj485fNKdoCgY4Lp4WCE2jpGGZeQ1cQOXpgnax3B8BI37eRKJg3XtpKBXOQaGB1kgaP5M0ZypjVT97rf6S39uqBxksPZNBmNRi4LcC56S7YWxQ/T3Z6QS/gct2CKZ3LSIv9PWyyrCXvI5XuK79j3iK3DROYnIK99pkpQmow3C51n6NILn94mxNgGNE1//CCtNHAZY1eJRrFfeiN2zil88HhDcanaZ3fBVlFN3kG80W8GOI0wNwQP6xZhZl0DsG9ynb0oxZ7qFwUEaYW0LI5kSIiku38jz5NDQZM+/72t0Y09CnZ+EIlZxcliuMQjZH5/JKdMkSjdBDxIP+1tHcB11qnAIHdPxZ/wY5c+6PZNfHkkPf/H9BLrhXH0LtE33mBV36NhIte5JDiMCGYoZLuiejO6N+Nhr1wf1/6H5sjwTsQDleNPg3FscEngc3wEZep1DIo5xK6nkttyGq18vHKhTQCgOIWjuElNLzdUTrw7kYAH/RKlqo7MFIOT51Y9xwze+l9v4cccMBEHwZk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMDU2Nzc4NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yNjVlZjk2NDk4MzJiMThiY2IxMGQ3YjE4YTYxOGM1NWFkMzgzNDMzNTA2MjMwYWI3NDY4YWNiMDFlMDAyOTAwIgogICAgfQogIH0KfQ==" + }, + "E": { + "signature": "NmhvdfLSV4DJ8YtK7SRgJ5YCNBXnneRy1MFvRKjjY2mwXUVniSZdYbynB6zSHN9L1X8oE4yKTUD960605bVwGp/oO/1nFKm60+Z4KYSvgsJSrLRwk/BchDjoEfoWv22IanFPfuG6OPAmFEyq7fbDnd8GxKztiaA3zubJv91oxy6o0EWtm+/ptTfNXKKtr7FhpPjPfwgXk+XsY5gvH/K4wHIOZUkMQbQyvMl0ug8/23CadVtnV0PXIezI1aVX1rB5/CHwCPYQ/kCuLyyUK19WLn0LcLatMvf/QM3IDdzTlpMxd05XvWRqV49UmHZitS9VLrP7HJE0HoTb+8Hp94FpxzyqHwxBB8tbFsBpIZxXPrvzlAvvV1fFYrWI16Lg44qSJLljCw8fUAksTxLTlHt9SxKNrDS01lq9qIe+fu8PkQPTeNgyP511aNc32yRFomR0eilp7g2VN3eLPUpBGSV6l2XrhPxGW6vPGOQiUDzS1IyvohNhcweu1a/1q28n0oQjzyy8LqqIO9i6I8qrpg1D6ox7xgSTKQq5Y5rbJr5ZIpcynFp/ZInK36K8duOEN7Vc1GDumtHH8g2gI3kXNHxHqey13rK4rIMVChPRRWElr6RSxkTA/Q/WrHI6W9EZMV4vIAtrStesjdcr7pEQPDa8+ZnBqsuoncwDP5uNn4iuN+Q=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMTQ4NjU5NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85ZmUwZTczZDYwYjEwZGQ3NzIzYTNkZWQzZDE1NjA2ZGQ0OTk4NWNjYzM4YmIwODYyNGU4OTkwNGYzNzJkMDUiCiAgICB9CiAgfQp9" + }, + "F": { + "signature": "AdeXp45Em9AYQQmeZdTkyEbrpYQftLTERiFr04UR9mvBaoKvtWETWr+jQBKtGZf3JR9fvdcG/cMXJ/ApOpcm5Y/ZKAHakrhI/iRucEB6eVLR7Rb7pUoE4tNGkqhBEwtZYNHhBfvg7H9JP+xwPzrExlSFP/GXXpTW/8f/1/JvI0k/023MtaNARb8UfIo6fTNn7xIN4xXgp6Os80T2NTyV8oauPwp/yUgkJ1AwViX+GKmoIhoOfWFD+cCSUBEZApK5u9+2XqSK/dRbgVUQELxA2YfVtoRhfXT7o42mmpaoDKZgQYD4xzTLJMkkFuENPp9LZSHf8kwr3yl9+KRjpvVKtfeWS73xWRwpeZskQf9IPheV/24DR3BflYp7Moi0W0MHw+Njgm8Sh/RZh7j7LM3QOlfSz1zHThWoyD27Gim6sNGYDAHaeIdOIT++YWHgilCSbOXJcxqf9RvJzcWVRwOxyhpdM+k8lT/+v/tayKbPkdrXFEjQcSsk90sEtDCJknAEQMxIr0g0/fiy0aDxq0Lj31ByUshyixlyim78pd6sMnw6i2Ub91eRPoUXPIuQkw06iyI9vTrjVEh4y8hxWKqO3zcSQAvqOcyQXzEPlkTOHP6xDEzFypgAIWEBNyO2/VjJHcNF0eHYQJpI+010xWojDY7uGT1fH0/3TMubMlAKUv8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExOTUzNzU1MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jNmFmOTdmNDZlZmNkNWM1MzcxNWE2YWYzZDU3MGU2OTBmMzNjOGY5MTI5ZGNjNTM1ZjRmYzM5ZTE0YTRhYjQ5IgogICAgfQogIH0KfQ==" + }, + "G": { + "signature": "P1qQXuoY6LdsITlAJWkbhlYaCEctISVIq+9JH+Vsn7WIWPe3zjahonmUm+p18vNLNsqN2vMs1whHA6HNTAEUdqO1XG8P+GNd6dXgoKTBIdK610+q97Lvr+YS31PewaDeacE47dKg2rACrJPGVT1qy9TDLg6jqJ/BpNdcx2MTckUCZgDHvM+jPqzdbLB3x9xGjlKcAIk7LzbbJVUrQoIrqVJ0obTyDhsUzTds0Rbzzt4f985Nn45nZX9tnLwqNgGP9yI7FRgjNPMEYHWjCo6KsHPfdiOQfnFQsixnnqNBfHzosgGrjKq0goEVGuDyvmLLpWBO9DaTCceo+Z3jvJv6jBaXq5ZPOO0aW7PYA6z07y1bTopkr+o8gwhc0ryrq4emTQJpBkxH2RJuLeYWS7WHwZ5D3wo0WOb16SPiMQjzQGT7+lfZzpHuFZV2HoWpmxuPvmdG+pY+/a6MhyjxC2POZcIQMXBTN/a9O2rFaV58TTg5kT+s2L83WK5g3H8zod0jvPQklQXatnVdPTQJ2x5RJURnANBPFCShjfc6D0FSw4hCb9gMf13KXj88T+z5vbIJpBDjxf/v3fXgv8MqFaWqpiBbGtkVJZ+k2+vBvr2sEznLZvBfJm0TOTec9+lZZjGCnnBVRJSUqfTDcxkgl0DbPuzEJlGEZXvZlC7B/vId02E=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExOTc1OTAyMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mNGQ5ZDBmODM1YWQ3MzI2M2NjNTZmOTY0MDUyMTUyODk1MTBiNjI4OTNlYjNiOTViOTA4NjEyZmE2NmQ0MTA4IgogICAgfQogIH0KfQ==" + }, + "H": { + "signature": "xMBrakGPlnsyo67pChtj5beWC6h0CnPQSRfs7UOLx3Xi0g76R30s/l6lP9cm3HJCIoiYeAYGfuOx3dgriZi8iqjlggAo5l/qsDUAeYFqe+vBocc4V4hnytSc4xL4MIwpE20z/o4ON6jWylZwTjhYDQ40o33PmNgbT8i9U93XKSPxDZ6Gf0W1PzQMBNsMXhRz7iU8w+FKo0GvBwL7zImdFs0gWfoWb7OOQFN0V8NX7m1DLuwoPmPzs99X7AemBXHFNp7dnR2aKbX2vXbgVz9nG4bHfS55lsAEn/ViqphvL2+YgDXoUrn6gPGWJUsabTWEi4p5mGKKp1p1LZTOp1oVv4cQvA5W16UbaS45ZlQKlZGaWZgdMgxdO4YihVx3YZbtKYb+Y9ZVkDOgerQ0O+gtRo9e0GWlk2xyYnkoSaYYEaQLRU8lv185Q9PPV4Q1gYIsIyw3+5ORPXQUoQv4xTaIoMRGd2YVERnFT4NwR7I2n0ASHp2iedEitLnnnZjCbzZL2IDcgbZqqA/ZJMmSYhUWfE+RKWKLlCy7L7atfNF8prxknTG+1/5Q1RHyujI2598E5N/U09pDAK2XKgxewOcKjGs7tPuXmxcaojBc6DTl1LhmU8WbPmZpUZ6LH/3iqkd7tclELrI4nRd73F0jT44NFCuqdl8C8m69dFWv/EHun5E=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMDcxNDMxOSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kOWVhZDg0OTkzMTQyNGJmNGY3OTk4N2EwYWM5N2Y0NDI0ZmU4OWE5N2RmYTVlNmE2Mzc1ZWEyNGQ0MmNkMTU0IgogICAgfQogIH0KfQ==" + }, + "I": { + "signature": "PnyfQPn0olmShKt6rr9jPkV++v7jHvMHgFDAwtVTJMro4utJJ3ziO9z94aLcI/yDNCDL+dFwtM0lKUixlfe8psI1KpyQJqlwNc4GeGnYpwwJhc20lbfa4ZygFALGay9toci8Y6upIjs+Njew6PZF1q03EZC6gBlUttBDHZmUsQ1xNR2GW6ypuJuJcfQ4iaJrBKTUbw4T8QMHG2BwYECJyqfiBG6a7tchQA1myOQ2+4yzPQf9liO0HEZYvmO+xJZ6fSOL43nUdkkZyKPuLToZgBXxmjL4Ffap4qtkVUMQZf+M5cOTLL68YTSoqBs1I8fBTlcswhnFMTQH0PMOpTsbMWi1whX87gLuC7RddTrUXvIOzCum+Qv9rnjb/sHfy5xW3U1pqJw9SMXH8yH24RJW4aCGl4VFbH/SVQU7jIA2doc+/cx3s7elqZXdm7teidqDugVvYfV406Kw4GXU4nQKIIbHdVRRPkCuP5rXrC3qAT2IQ0e/oOlUuIr2aN+wWgV3ZAwg6zCHGib2/NesczzA81ISrVYFO/FIVXNe9FjxqfQGfrDcgWLe4uCA/iuNiMRCuFDp20/rt10XnL5jyRXojaFpWGwCPxWSzX2NV7X2nkoDUPWDz5p1XFJIcfB56Bas4fjwyIpzRPqyfY5IvcFGQT8b5W3XCuIP3alUj4Nrm6g=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMDM4NDU5MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yM2RmZWRlMjExYzg3MzE5OWIxZjAyMzQxNmRmZjA4MTk2ZmY0ZGMzMDJhOWJjNzIxYjZmNGNlZTFlYzg5ZDQ3IgogICAgfQogIH0KfQ==" + }, + "J": { + "signature": "c1md8BOaoSoNKrtqzIulCMOJDlkKvsxRvGpTxkaLfZ0+CmYt58+CPtBmkQjd8FmtmsGIbYsId5vkIyo7qmZLnQ+5TQ9I1YgOKRU4j7OwBC4dT7UMrlnfYFg4GCgr6aqwV3nZaqNGxb//JNqEGdBlxJiLMMUt3ISpXSrJjQqmQDbi5DfhX+MavDcrqu9sMv1qe4E0YQPGIUGtu1fIw6U6i7tNKCq0B6DRL4bD3nEuvReC3bEsl9H35M26+PJwaufFtq8UP6vA4uGn65OrsJ/+bi4TTo+JYKf83TQb9lfNo9oJMfD0yA1SFoX6l4iJrs/kAzkHwL9ndQ5UbB03pwkSOAg6p8h91I7LkBjgNkFAW/e1yMum/q1oZHAACHv1G+OrJlQnegwzGlCgDzO0rmRPIUR8pwFicgOjIiKy8tA8/yeqpsi8M4HsP3IRRW2rdj12rDQGS3N3Z1WaknBRgvkSWvLnM1UYieLReor6WoWrOgCMBvAMuoZkJydisHcbYvyBWZ4GVeVFYC6fPRO3ARVj6+F/Lj65nEiHEp8xPGQBXZ4PGiqlDwMJIlX33VG9rsqWRGDjW7afwrCKYA1l2QChaZkhhr9J6/Zrx7PFDlVRRQcSEIFIXIfDlYWM+J81UqFHc9w6ZBAq55LryEBC4a3zJ/80jsrUAtUhmJdrrGX+dOo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMTE1NjAxMiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83ZDAzYWJkYzIzYjBkODFjYmU2MDMyOTZmZjM2MTVjMDMyZTc3ZWY4YTljMDM5YTFjZTBlNmJmNWVmYzI3MTJlIgogICAgfQogIH0KfQ==" + }, + "K": { + "signature": "RG7tq7Z8CJDuFLoBbxKvN8hnJXfaKuj8BY3b6I2HTvpWWYi5Tt+VocX9/bwah24Fld1C4xPDNIkalw59rAV6OD/3TXOJhllUO0JCgqTCDUR4N7ggAjEF5vfUXPI10M6vZqGZwsNZbcAbI249h5DahwfYK2OOkRDLSSOkYQJRLC059jCc5N0uytx8m6PMBO4ffvhLWzDEsNlXUa9E2IP6TvZxMcuHV7qw4p28T/zCfpCTXRmusOAHHdAiYGmaLtIRLtGn6h/aFBKE1FFsJ22UO1nGKa7B4V+hNsPvm44W319lE6qF5Q601k22vQS2LL06eitgUth/ndqzt+zQv8yvk3pEE2qgV3sHd9oNOPWTW/VC4dpLWc2qNHQk1MV/hG7KltQNs4DuLePDh5NbQWvbCtUP+oxhV1JieQCW/Ec2o0Yqo/jrpwEJqo8AurfKvqhFv4fEsFeksrwB8C6h+lYplFFR3wrlhAFyzH8vURN/RDB1EtScM6CjnbHvxmKR7z88U5kSylTf7CbNDmd9gkYE0VCcsagtU2ie/GP09RrDfDX+zTxs+RReEFI69/dpFXHZHTlfB9sLtahxweUR8rV/+dQCroshNlDpU2QX5qXk976B9scn5A69QUv4qYRfbOZidvarMJw4/hd6KEBDU6UNhkooRuSQF4fkNrMUehSX9Ss=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMDIwMDExNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84ZDU1ZTYyNWQzMzFiZGZjZWRmMDJiMmFlODUwM2ExNzNhZTlhYThiZTA4MmJmMjc0ZTRkOTRmNjc1ZTlhMWZhIgogICAgfQogIH0KfQ==" + }, + "L": { + "signature": "W4smCprYf81BxSHtRQHdUH9B/7C9G619Dlu2fG9t+D21eMbJ9mEu0BXgVMi0BDWb64rYTSnvx55pzuomKZ3WaGS5K/SeCJpHxUAIfRxYSY8aRY5lElVW/ROJqwn+1E4OZLwdUa3YN8Poqzs3TElN6g8AXrranoXk+tB3P+9owukxLcU0NX4bONZG75r2+0u0y2i19fkFp1EBc/kB1t/4Ke8dvRMztGRTB5KHNgo2aiA4ow819ZNa7fS7jeBPKxaRAFmZxt2t4Hf9nsYMv7A+Tt9LQD8DGfKBHOpE0QCiVdn3o73vCDKumHJ8sGXDTEirjAfMGEhCAsEtUIKY8EkUZClWDkFRd5tDgwsRYFa+EF96KWaC5oAo7Z360bHK102srwY1BytmCwasm8fMAqvCljC0SgbYRE5flYXjfQFYOA47hwWPa+7NLKD0VyRYDx+QttMgkvLcYY0AFQPVUrwo+3AIqd6SMrwRDDKGssJvBqO2VOnt+dgluqlRtc+HZ4HWpDIWzzEVNlC8SVQRei23l0oPMWwfUNfRGZLjcdBkDwuj3KK4zlXNk4OMBOldZz0xtp+1mCuFa9P9XVE3H00jPBbwf70isJghcrGN9U9mRP5Kj5uj/NdJehQfXhNLosnOgiwFAuVbeAEZKEQZxZBGLhNBlRsb4hAWGVUMi4wnFd4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMDUzMDkzNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hYzkyNmQwMGEwOTBmMjJlZTQyYTcyMDIwMGFjYjk5YjRlM2JiM2MwYzI3NjEzNmVjYjUxZjFmODdkMDZiNmEzIgogICAgfQogIH0KfQ==" + }, + "M": { + "signature": "qd6FTPbLg5H5HhsUdM/3bUUVpwBad9B4Do5Nsg0NxYm/PNZI7cmdJDvPg+K792tucINWCdlb4NXLzFd636uB7jzDwPBvHyZVrhiBM3MVAGV8M1EkEz9oonrDB6CNiUiYi7y3kPzmoWmQsTTlktSOrOqXFUbf+VIBpzxkTl9ezqBMe/Ec1Bkr4ab3F6I2pTvmG4FwTPQdP+ry+zZG0xwbIQstTWgw94TiqwHUzpTMtdj9f/gQygQDJM29tWtw/G9QgqyRtQaqfi81B0NfKUQcLRFfwzvuEWrNeJ43+mKGHqkmjX1BSliSjZCp3jJ59wm++JNLzrAMJENtP8TrM3fEJWPAbBMw/7TAP+BLMm+Zy0PydAHhxxCfuY74ZqKPdI1LtO0yHyc9jkCfp+zeKuOGIlhZq1gp7Q/bCJSa2Y/J3XGph7gZ27ZVfRyHC+TKL9pS+FNVjOF7BQv5eUZ4taY3cipt78X2ukTF4iwRAN3JJW3/KqV0yZzQLPT4+DsaTpka1Fe/Ao1rjZsciTm6Il9gcdX4nb8MvA+GaJugfuIdhdb/a8mtbqAnB2/e6nUoJWxFXx42OMVemlSNjiDluk+z4YdX7TD+iC2PvkfBRtVdxla8Po9CbACtaLgEFORC8RS+2E6NAaAUGWU/tRdClj+WJzYPhxkKFlnmlowuZd2pwkY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMTM0MDE0NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hYTA5YmU0NDQ1YTM1ZTFlZjM5NDRhMzMyYjE4MWRkY2RkYTQ5MGYyNzMxNDg3M2E0MDUxNGY4MDI2MzhiOGFkIgogICAgfQogIH0KfQ==" + }, + "N": { + "signature": "TYEUpb9O9bUw+e1dqTB+U8rw6chUaNFyPsKsiQsphMLFBgSw8QWB6DaOlPScd53Qvrx8VA2zjvvEC6OMpW45YEaKyD8AI43dW7pVPX9c273bYGHhafBaqJKhrm7yqg0hRGb26h370aSCIA6ZJzTRO7kZPGFH2uGTiImoqQ5eu/bfRfgzVM0BKHqnzYvQ63rrU47UeEuhg5GX2fPKxm/0tKTfZqSRobyX32doLftmOM+lfNiYDL51MQD82qyHI4ijdhJ8DEjBYhpkBrSy0ctDpXP94jH7D8fJb7zreA3Pgs0v5ztYAVzXZTj23Ge59mHPm1BoF/q5bGzS+9QjR+87G2nHy9Ojrhsg56piHuFCC3KAODSu5f7V1Sjd9X/oq4rAaaIU7aonwTGdaq5dzNzvstLMFlHKSVKP3U6ygXw6UeFqU/m69j75CiYxPvUydkbK6AEMUSqgitzZiORZGnfM+vGmKfaiBh+pGMJ6sr70t6fUoA5wvthuZSddLX0caSu5cZd7uZdYcRs3wvGS5jkYaZdaDTouIjk+MOi648a9nTujMsOy3TG5xassCfM43jWKmuwzCdCf20Et6xjz/V8HWnL73bKQF/lZ51gRN3Fhkb1HHz6RFspDeP3egEqojKPcJh3qAq/WAZcYT0tUnwMAzJIFlMqtc4LhKMDVs6hucLU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMDg2MTkzNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hZWJkZWE1MTJmNGIyNDk0YzEwYjhiY2NiYzZkZmZhMWZhMDhhN2IxZjJkNTgyZjA1ZTNlYjQ0M2I2Y2FlM2ZmIgogICAgfQogIH0KfQ==" + }, + "O": { + "signature": "t/t+Pu9HbkDMZaKiNcLbPc8PU0wrd9WbVHtu8VFygFI7+OOqBgl9j6Fnr0zn/VqdoIIqe2IlXSshGrTy2XVIYUlx8F/xfNjZ2fkA/SA/kR27IdRyUQGoTG+OBhXGvaU9o4bxXK2PdHoN7alVaP7fTJixAYCJYfFeZOaKKj8dN33KyhlOn5EiTDmOd5i54w4jLvZ9YAG+8pDYGVsjF2o0Dcm/dLTx2SGxBG/mPH4k+pN7ELS0Kvz/Z4sNHq18YNZu2vxFvAdDHX7yOAdZyFIW+1qrnUNAouRTdkUXqRRbuUA0QSVpj/9rkEct2eyrnQWBE+u+ZDnT4XvFMf+xACbi7/lGnY9ubI4igOzlcIPY9c/Ram0lX8pqiJFAo4UhThzj7EFWzmQM8BwT/Ky0iL5eq2jl0FPIORdKa6p7ccAymoZlaYqdY47BMBLZaRMXM+BDZSkmi3XLCan09cODJFF36iGCqKhcmfrftmLreaup9i5HlnFykmoFayaicslduwEfxSTLDdut+mJd/Q+1LzdUQOKJ9kwoO982qWcZAAS1pSirhMGShsvfGe4QnqiSiwzfy2UrJ4N9YdGjc5wMPlvnd4rsvCSduuBs8JL9L2JUdMeApg2nyydOSd/qcml7Y7oZ6TTzDnucVCp8ZoyzUwqlC7McAfwhkTwn8etD0cxS1fQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExOTg2OTM1NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81YWZlNjJjOTY5ZDc0NmNjNjZkNzMzOTM4YzJiOTJmYjE0N2M0ZWFkMDNkYzMwZDFkY2IwMzFjOTRkNDdlYTZhIgogICAgfQogIH0KfQ==" + }, + "P": { + "signature": "d9otijYX6Z44/NzKa6LiS2IBWkYHpZW6d5PQGd6IBqkRezbeTK5xWQ/UDrNimXZXLW98PR6wRhUyuIwYhkeK9RhsAuZISAdUbYjCQQj4RAIMVz5opZ5KRo7iRCfGLtZqPXxdwvYGZMf7J8LS5DW2egiD07YkttXDuDCOiiysrHlbI++UY3yjbEt3Z5PFAXFQSI4ugG3o0eaPYfMdryDMUcD0xmAi1pRWeY0GcdM281ZMEeTkesgg5UMGeRdyPrq/51Gus/pFWlEr69YidgYnlulffuJ1Ow7sAFl9Q6AwENpdVeroGRdtE/WDypO0om5j1OXbZ+n/5MIZaL8IsFuB+5d9eiaEnmAbjNOSTgfHNPzYdRoCEihgb1o6gBag/kk7CrX+3yNTBEbWBadiILE+la50DKqM86tyNRvFKUAmOA9ARypwhD9DFyDKmMJWx0nhW4qOanDbh5wBudeiy7bT4hbmfn8Bbi9Cf2G+T5grT122iW51L7/jzWizer5F3J4Dvnk2I+gGSaVq3rAjiHGXSKXZzyfuEZdL37/wCWO94qPhp7ugU2PlmXm22tl39bCXC7cFSiVde7tIrBwrgJefWt3z0uUd9XhljvnlM99fFalKgrLGzYmUHAV4fNgTrFvl3UM3zwMtUQyf1Sdo8hPCmM7AkQ4jr7Y06e/V5mjUeNs=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMTA4MjY5NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lYTcyYjgxOTM3YTc5ZGI1MjBjOGY3NmI0MzMyMGU3NzE3YjYxN2NlZjYxNjZkNDZjZjk3MGU5MmRlNTEzY2IxIgogICAgfQogIH0KfQ==" + }, + "Q": { + "signature": "Fz1SAzBrhVObvGNKzJVRBtO7vuDlM1lEmxJi9alsrIH8lnAVPLZSg7/cnd3pD9cM3VyT3fXt7VuYVVkQbPhiJXoNwskuQh3HuyNLScXLZflUgkbholJzbPyAg8J+glwtRDumhQZE9doKxASfW9Gg3XKYIVYdO2WmzGD0lrIYeLrQdZQ9T+6MkcajwKAH1Q2+d22WuANI8VAgsGmfHCq97jXWcFljfTOir3jdqLMmDiGsh1k/KsMMgztKeEG17V4AyAe67qbzgiujFJWP6QYQORyc+WAFCGbeWiEYiwbrdmIaxQ6TOJoLoBFwHa++FQBeaLV5bD0XMPlP/9k1YYuAvXdBWh0XTJSRW5iwBADAgnxeOKxm2g3aHIKmsINlcT7sCNuHifIygEe8VTQOfRFnB+LZrprnXAkKYobeBXaKr5HrLhtRy2Res0BqcB5pYNAUYua5FLYCFOyPqgLr1p6hDyr1UCI+iF3bYPg8M/+PSgAmhrm5Qt0dQNUrIbc3JOcFDqRBM0DeI/uGtn6e+COmdXODrBhi6g/5Tt3f2lfxyuxTcEy6n/ubbKNPsQ4KFx7xxJNapkbeuLHjHUD9mjYAHhzgAPO4GSgIoAQnRR11fEcWRBm4AeMY40bowNBATs4xYN+1/UWyBdbDtN5lRSOmMFnq0Hui/8EpCJV0VfOFvv8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMDkzNjE1NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mZmYzOTM1ZDZlYmJjOGJlNmNjZDI1ODhiZjI5ZDRmZGEyM2NiOWYyNWEwZTc3MTFmYWQ1MzcxOTZkYmMwYTJiIgogICAgfQogIH0KfQ==" + }, + "R": { + "signature": "sw65HHAcDBVgjEA8m3GGWnrAC3TMh5JQb23DII/U+U6O8P3GLDV3/x2Y+1adtAWOwQSEZjV3DQ/fbMC7G8U33aDtEuaNrDRZQ5N32zpPWX/lQof/SyuJxpkp+LBoqx00oKjo9VeiyQbY6ia8gv4W/A3EQeV8CQDoKVK/XycANah7DNGi8dgmQEumphFiCPGVKVro5xpJKC/UhlUeLI5teYMPDVG3K7g1DPYTAIdbxbFoJSc/ROsv4/nDFkA6B1679WN3mMZOH2a3M+vlGmrzlraOR8gCNCugbxohnto4YvtudywwlAxjnxChWsFvAA6nzh0/h6h4hXfxJYX1OEiKUb6QPHr8yu+ITlv1p3mEwtiDvTPVC6FQmCtAmJBqY/fwoThzGIxFhqxwbOiMhTH8d6tgt/C01XqXnhvrTXSz9gcUw0dz6R8LdNIV6qnAb1hyHOgtK+cx8gtQu4oK/WQN7g9BIaQ1SBLN96YzlUiOGqLmmFXPBDjJMyKKfmA6SF7Ns3Vlfub2NFfFdE0u/dzBRaACOojchqntbrwSt++3ZuZeV+HtenopwDjSw8vEHEPhKC/vRUT3uvl2f7JF6vLl3uKgx0ONImfYHm6W6VLRjjokUi2dVkj2OtSA1IFdeJLQ4cBXQDRCzfkZuHUpD/875KXrmU+zO5tzx75uX9ORKQo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExOTUwMDczNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zNTJlNDk3NjcwODljYTJkN2U1ZDYyZDk4MjY0NWQ5NmU3ZmY1Y2I1MmUwMzc2ZWVmMWU4OTM3NWQ1ZDQxMWEyIgogICAgfQogIH0KfQ==" + }, + "S": { + "signature": "AUwp2eRlpKn3jg71r2lpICD7EwFMIHmSMNMeonf1W77dfZF7+DoPhusixbKqF9t1lxnPRNzlw0oGOJtxwjB+jCpAHMwZAFZj6WlqE037PExscf+6qpQHS+ta7iuZUi2xEIjbTpK5YB1z6sNG+2vGF5P4ZdFNyrciP4pSyOyeLoV6QyUAQek2L17cMXkycIkCzt/HwalUIGQocsryNCdkpcAgDksDTE8Gy7vDUbEMrd4GqPQpB8yVsprUuDTxNXk6XVVujXRRIfaXrfatuF4WB1cMr1tD9AyXFZSA1s4Sa3Sx38YQr1axxKFgXQSmgqhRqCEwKiPsuY5I3neaDJrRMgwoyFO9aiqLvaoKoEYEfzqLu/YoupvpGqiGPOp8mynF3DNv94+vkMEBHrHjQinTQp/wool1CCIh/IGKyup1trXynCbDsYneoL5r1EYWB6Q5nKmMRKIMADSt4Act2c3bQMt16kAJO7fNSmseDvQkp+nmwFbPbxHjkNHhEM2AiCrHJqSqLgL/bLpuDxEGBhuV8aqmR8cwbyqKpC2Q53RIfBrOtsfLf1vIn9E/44EEFZJJaUJizpT0M5iVhv7PgJzJGHBNEUZuwVS6+m/rpgrGw4CE1fdahW8dB19mzXrOMdKLY2OK2uBe88uXMw8y4Nr6NBAy4OMkrxPiZgDnu8EUsic=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExOTkwNjUyOCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81NmIzODQ4NzU2YjhiMDU5N2ExNTAzYWYyOTZjNzdiZDdmMjAwMmE3M2U0YzMxY2ZkZjZmYzI1MmQxZWFkOWNhIgogICAgfQogIH0KfQ==" + }, + "T": { + "signature": "dTK2rmtv50416ycW0IdGS8r9DFzoJWZCD398d1ZAF6Nvz/AkXt+Qfod7TJJGnyZDhuljTMMb7PUAXeRMUItvqBGdjJ0Th7HwYH/McBePXH+HZ2gmUTGneHkvvFG/DNlZDzSlqvEdfoHeY0HQdYzdMKZiMPnI1tlsl83rBSEDhbSsOtrVtNp3c6DnzEpB8zr3PblcgsKYwGUXXenf7AZo+qi+iSG6l6ff6GpjVsTcvEIKTxF4F5jJgZGIEPzM2sXpREy0n/hY+ofcr39UfTU9+AX89IZeW5eyNKaqQ4i3DmPQY6QqZce88BzhHxq2e3BRufLzFT3It7a9vPudXV1ivGVqTndjbD+x7mdYDwvi1lTgz0M1d4QXECY1Tnroh2tk/jGn07tVQ3psuNPJzaTzfBjL7wtiKXjlO46DDYruM2U9co5ybei5f4H4jgC3fdwxwti9OadkkkiWwa16OFW3c4bWiHgkC+yrcsC2SmsnYSQgXwwpt2wNl+miPOOhB4Bn2bYpRu5c8il8wgDC/YyKoGAHGgCXz6FJnzsmxQ6vF+jx+z8JuOssrxDQrkr04D5JnKbTJMC5Da2ObiVQ8Ye96+X5DUtqskcflpPD9EHf0mkRCzyw7HFYEZXIX27Yy8xldrU3NvvfPKgsmV+kRvilmjtIjIvUt2xPWeIedYyet+c=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMTI2NjQ5OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85M2RiZTQ3YmNiYmJhMmM5Y2I3YWVjMGVjOWZhYjBjMjA0Yjg2NjZkNDU2NmU0MDdiMTBiNTY5ZGMwOTBlNzlhIgogICAgfQogIH0KfQ==" + }, + "U": { + "signature": "vxV4NzDE1SiVRLaDGQrMPKgk8gkGtz9gmf8t/GFC2E/qRfgCepCdFi72CMJHMLzfuE4noSVRSFxh/HEBsNkYBa19Nx0FRU+vu+dO0G+BMugB1VSElck0zYaXHNaleTT4B+N2NKHnM735Pk00L8QTD3yOfh2Fmpe62PPK+SxyuUGA1SBgBcjbogVN8twV8vy8q+2WmQgQqvKkseULrJAZxqM+81Y5GUlSllX8O059CZQLToYMkhpjaT9gyoH6A0m9zWT6tLpsAL7T9vj+fTdGA8vDHiRmdeEyHhWPOFA9PbrqYHmiaCBIetEhUfZYWdH22sqRia4+D1DL66sACexMFL8gSlhQHhXcq2Gwundn5bDZsVhlwjR/qh2OGsHKj5gd0ASMmJdQQrbAH30qZSOuDIB3F5igLG2lfQ6Y5EEdlyHFwxguZJJSHyx0by2dQ2I303V+9r3Pk4/k3KaNjL5njFPWtpanrB8vcjJRcoUSSx9+scF0W7xbEmTPe6qzAQewRuyX7E9x8vJD1NRoxRlIDNxxS3X5kqWW97X5YF8dxfSX5WJa32w1FAq97iZaIEcbHypkJBy7pqTm3NH5eArSTxhC3wsAKzAAkKxAq+0Hz9+CcUigoAJIHghL5i7jbw3/DMSQn6PlkGeFVK9dK0xXVCjYnNKcABNwgtZFIU75cJE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMDk3MzAyMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mYmQ3YzViMzdjODNjZDMzNDUwZTExZWU0NmVmZjRiZDIwMGYxYThlNTk4MTYwMmY2ZjQ3YTU3MDA1ODFlMDZiIgogICAgfQogIH0KfQ==" + }, + "V": { + "signature": "jyyEhyGg1HYctb+wFMzMNzik5LzrOHZGn1bMfdTMMd/0lRmq5YXqAIT7lPUSXCPKej9VkibdM59DXcwgZum8DT4+dFcebUXHrftLUf42EpPLfcxBaSlU5vZljZVjbcEufxmoSCk2Rt9xFc/k6Ntps0fikQDHKrudLRSImgDpikJl7Zcf/4HChNq0AvUpkokagWeY4AK7Zr3SCa4nJKScu9hfOCLXiFF2ovfO+iu2ANCbGH3UMrXSKC4NhVb+e9X0Tr7AEigGPGzs4J+mp5/vqOlUryYhAfL7PeQ0CDxpbY2oicck3sTBq/HaioKgg5+1+PU8whJAa1AfDwJ7hmnp+8yIxl6CO0Elm2v1/wT0x1qxNiTuMaXv17YzQnjhmYfE9suv6hVMpyUVDcXFhw33zfSULNEGfeQ2jogaf79ep5qzxbfa410/cfQQd3zqhiVH8NSrTz39cU+dpPkx1+h97U9RHkQUBjXEhh325okvVrEKlisZjUgkB4DbUg7QhaU1UC6fYa/4a/xV+o5VbTyNkwPZ+NmYxRWUU0WGrHPHEKMzE7CTOyECzVxpSpD2zbJVmMIoR9UYgRGQlNOIZ9v5mV5wvyYuFoH6VDEQ8nhs/iaJVkHcaW0TSYW8w8RCzEu0SZdOIzSYlQbZBV0XA3sqEnQ4QJzGme+EXDJCrSyH538=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMDQ1Nzc1MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80NzQxYmFlM2YzMGMwOWRjN2UzYmEwZWNmYjFmNTYyZWMzNjA5ZGVmN2E1MzEwZGM5Mzk0MmUxZTdjZWJjNmUzIgogICAgfQogIH0KfQ==" + }, + "W": { + "signature": "usOF0FFTML7r1E9PIWtre/eViDCgB70jq3bcSrmuXxoMiAzqoQq/ZIU827Zce6xwvf9XJn6Md/AJ1sD83uW7WGUTvnhYBjMJgG0nDFXqRJP6ba2x/pquY9FMdYRDt5YYqBmoYSEUNxOJ5dkgRC1+CsxQRdUSFLbewYnlyLI5nF93ME010gtZrrE6KGiqCV++bwJMsVEvSZVnDyBILFU+gstWK8ArRAUbyB0z9MEYRvMyvt4hSPIwqVPH5qYeC0gA+fClewkXCEh1N5F6pqDJAgW45UXigzzp7NBb2CHrr5xv1pe6zmcElikpGYV8mkJYzzDCTH6PJVQ4j1kKSQViidvLarZlCSZ2WMIcggqXJjbc818ruHpgfgw/Lmx88NJGc1OguDC2yvWgnRDGuU2a4TXv5WQOEs2HrdUMrzqZy2QqmNHI7VS6lk90xDJbvzm7LIekQxojax4q4ewEXyxM90XdVra5KEG1eUqAl/3ySIQc+m6NWHyABRs84tf3bU1CUlmgfcyM+y93OPyicCzRs+pXoaNTN7RsL/CuwPxovVSLpuhgymKitgri1SQQuKgWCCX7W5UXyIct4FxrMNbUTaEjv8eV6j7kYNnEz3ATd8hGRZCA2Yid+yg5CYHRyAwGLx94IvjW+6XoGA3+/ftf/iVGphduBow14ebVfRo+Nlk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMDY3NzU4NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mNWFhNDA4YWI2Y2NjNjMzNzFiNzNmZmM0NDU4Y2FiNWEwNWZkZDI1YTI0NGU1MWRlYmQ4YzExNTY4MzdlODRjIgogICAgfQogIH0KfQ==" + }, + "X": { + "signature": "vzdsSmE5ADZVO9UYYTSNjxastN5NwcG0XKk513cGy1roJY4+mHRZYioqsVgXLtZwJZMM+T+t2/wi6rUlv0SfGtSMkX7iIxeWe9mO3rWObzAckHfbVBa4uH1bXJDxmMU8lMsQzmCP0v+KEGLIB7ogMXI49I2tU96IzwaXZaj9+zwO7GpAuFQmcxf70te0Cc0Yd43Oo6Np876FuCwWONh2fihmh+EkTvNcc2TX40u+fQccLlh7RkZtFa+Vt7GKqlSsTvtIoOCyWUP0LPjy5sDTNlaEO/+hpwiDxbzBAa2yk1v/t194lhe9IYzzhbfjXLu8euCm7DZerVwt8u26lr/wfbTI4qLyH8ZbdAO4ASu6tiTBOTiW7KKXlNHbi75O1zyvvcS+mwW0t5xKnohd+8oNTiwdewM7n+GzJ93GbH9b+lRvZR81gh22cYuKq9t5wk0L7bfacAS7b078KR/q8QZmmesxLMRRp3JRaYdk9KsyTa45feSUOXblNwoCriSvHE+ruqA7nRhnETKpYntQGkyE/KX/ehDl1VLqhFprrZ71rKv5z4H2JtavXTIQhrAUzmg2iSp5aMMgMz0b3xcmQcllzszDuqGWk2qg7fyOBjQhgy6vJCIynPiFJ6DZuJ/yuwCr9qKI4jNxg2dU7ioHY2zoe1U1h+H0BJVYIYj5PvfbSys=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExOTk3OTczMCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iZWJiNzBlMjk3NjliNTZhYzIzMTVhYmVjZjM2NjRiNzUzOTNmMzY0ZWNlOWI0NTljNGU5NzllMzNiMDY3MTg5IgogICAgfQogIH0KfQ==" + }, + "Y": { + "signature": "OmO6qoXxLRrsIX3BfBkP3AOf9USa6zbDlg3njZ9ofJRc43/0mN4MDCPWvi+wQmPoClbC72b+Cc8vJtp6+S9fGg4ioJLKqOhYCp1Ti8kVvG+FlZAAfF+htZe3Bp7KMz7yR8vGvDy4/8dDAPqHMhTUIfhITpN2x37fK582uDPJtFD/QQnxDl5CVrYq818jjqD3cEvLY09zwgzgiVzc8mU95tOe/yDHoNhlHKmoqZOWxgeFtGSR+6nsLDoFxVBec2Ppdcm5P4thP65iaHnbUcYZqA0HpVo+9V3FkFM8WU4v+4z9fmQiUycHc9Po00x0Bfp7mx3iLFUKpqD6oqpnQOQYVBM/qqT2yUE/yc3i3OSf+06dhb1KzZcuPhEitvtULNPGXS2xCYpST8xTFbG/WvGYXLadtOj7UikM+qd20V/Os0MvAoNOTcUCnBEAIHjSeynCPt3drHE3/3FTkBF1sAJ6DPBykGtuQtOZrOivCLY/lj+OByR3TwPaRJ7aff+npzL3R5xxHMsqwTMeEEP0r7YT7fKp182aTsYQAMgqioeKbmugM5X98qdRuLQKB4VUQhFL6Bz1lLdFoYIxboH9Ni60FGHqGVre4848VUgynGRmU3LOq6paszzKCATcFSQnwXtm2Ci0vCJ0vsg7yfkM06KqRKihBxAaOd40Nmp4v/vacdM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMDM0Nzc3NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84NmM0ZTNjMWY1NjEzMWZjMjA0N2E5MDMwODhkODBkZjgxYWRmNDM3ODQ2NzE1ZDE1Y2E0OTkwM2M2NTY1ZGVjIgogICAgfQogIH0KfQ==" + }, + "Z": { + "signature": "i+DORS0zbGAAbeptZTJkEreYT1ew4mOYQMAxH7GzGGKVAIZobN9WarlIjr+bVmnwHveVFdCTjkppWh2+mtB2wUwt3t/fxTKRnIBhPK1e3lNarWV2NploLQZPUpLLaW4Kvje6y3sUKIEtr4euVbIhCDJ4/W8iKmVHx1c/4UMGNEuDoWqcydgzk7jEAp/XFlenkEs31qvgg3WdYuIhkhVjbLjzQCbbXPre4k+Pt062i0muMbpnMmr07PeAeJb2Ea6F6eM2AWWq+WsbhHGVfkcWyq1LIyi1UWYKmhZc6eQjsXajDX0hDfIBXkpgrZzA0rKTsXky1+SrtwmeypygT8hnfqXxY8ALCaYLNSJ3jg6J0g5hNUz0iHai+pXAv1udYVwHtAK7dt2bS8yrVt79MAJu/U6LnQPVHUzrEJw00FCwsf9MEnMPvoIpA9VPnoTUeJYYdvnfIZF4PKH8hR8JeB6n+xBMLAbvR7Vhmd+FxUIDo7B3KyitN2drv2XC6Rd2IZD+fDq0JfaOgj6NAbvSzd5pzoCzE2fHKh6yGxbjIM+J0faFOP7eyVZa5z/1IWcMWr3rTHziQOujVIliubb23h8XXZ8vrp5nGX0888LnpcxfHjx26wJ77r2j5p7sYO7QefDOvAooSPOoeyuMXjnTknqwFwAGNNDZO7WutvuQ85X93cM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMDQ5NDUyOSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yYWI2ZWRiMWIxYzg0ZjA2M2RhNjMzMmI1YTNjOTUyOGQzYWM5NWJiNzBmMGU2YTMwZmYzNWI3NDg2MjUyNjZlIgogICAgfQogIH0KfQ==" + }, + "[": { + "signature": "CtTA7oj0acShb36K8vqExB7KSaKpASLVHL06OiqTSuq0s+wrbrRmZnk537dXUtluuwDbDOix6zTWSBzWZXTpfcgJxaeKQmYU+hoGdSanW/GTsDLnpRH/hy8IHZZcwG1Ptui7Ro9NSvimmulvilk7H/VXNVa4+7enA65hGpSUzGtDN+UgHthD4r2mRMY4rMqseZunxT8Y6WjGl8AmXILrp7GdD9WKybnq7AE+sy/lX0VNrZw6JydYfQI/9T2FogEzk2hVmc6mwAo+4YXbELALiTZvDzz/drErk+Wdszjl76gwe1+K9bqqLFroXGRMYQn055hcCqcVTu3MLbgbOFsSp/5JiXvvYFWmvCuKw/1fgK9ZjG9kDxiQi/2dYNb7mQiDmVZ3FWT6LF+MK9heiNnkoGZG5pZ6JaBHZsfAnV5rN59xzNka++3ohQ2ADFaCtqF09hw9LZkvmrvtxSAjWlPoLTo6MKrjQjLEXYEmAIzvwCnbI5+qyhCzvRtDv0SUQDqdoS7Qyhsry8LCrLNQ2A20umx831CYhEC4AbC6eOWAkuCMpSI7smx8ybsanO6WYuBpjJyA5pfNwx48daS8fczsl5J0Lr+rwuyWKnGuCP9ucOYtxEilmcP+bJPSXTQyEXecjuxRusuvFJu8cQbJC0/1JCNji4Rf8rjR0N8+AVGlS1U=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMDA5MDAwNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83ZmE4MTk1OTljYzg0ZmFlZmMxYWI0MmMyMmI0YmJmYzZjMWUyNGUyMzQzOTgxMzRjODRjOGQ4MzkxYjI4NTYxIgogICAgfQogIH0KfQ==" + }, + "]": { + "signature": "QE3Ljld0eZ4HeXlTR1jTU/YBOUtQ17n5xECuC9HBMjWZrN74uo4KjTcQ7kQx85wEkMtjltNtqOgtDOqH5sh16y++StBSpaThTBC38HKFvG286RVUqsPuAi8dpp3TGQX9jLHSlGYWOEfRyqD8U5wMfg3gXkMLU3JUd+iDaG5W9HO50vO5qxvYWIw0lGGV9KtC1z69T8hsDyviS+C8Syh94MPoLD46vDF4ebnkitgYNXKvQdaHQlYsyKPCgdUb9jVj5e9CE88wJpEOQKKd2jqyOYBLwkA3z45PLIVPqvbxvTZZzudV6MMbOd0J7L7iKRGTbsxxF5gdQjg4S+ElTdzIrrsKg9ba+Ju047gXoNwxhRmwaAsgFoWhnR4ZodUVf3tWf6nb+egfKh2+aPs9LdYNYNRsarEYQxOVgVOKDEYOTS6PO1oDtAK7MJ78zFEHo1AvZIXXHWpoHUWwNn4/tQzInubqIKMZ7duvRQqyrAm27ZoU+kKTrYi+UE7Z1a8qRFhgQ/rX1hDTLZ3x2bZo1WLdMJanABiWr1I2K8VJ59kt0KITo+qO+CpYnGezIbHoS6BRZvym1wt0bU8SKLpR7Oy+SE/dCWVRo9EiztVW7zJCftBlkXP0UxkE09DmjDfpzkKei+WbMB7DbnaVedHFxLHO5kG7pRkA1HnhspAWWWzawsk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExOTQ2Mzc0MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82MTRjZGIwOGY2ZmQ2NDVjYjBmN2NhMDkxZTAzMTUwNzQ3MTFjMmMxZWMwOGI3YThlYjYxYjVkMGY3OTNjZWMiCiAgICB9CiAgfQp9" + }, + "^": { + "signature": "mRGjXgFAUdCXo+0vaWTYK+tUKtv04b5pqpjZOSnFtOO49P0H0ndmaQk8T0ZTGpWdpmrJT/hEdo+wGpxg5h+G1RVC06FMqR2HHxP60ROSL46sLWILnYC7Q+rfyrB5Mph8SChFVxTnJU9J0WTwnUUeIObtTismWdHHgYgrVzi3KVNwobh3iHBkAVmuIixz3dh/Dyi2D9xhE1AImLn8F4ppt54VOMzKcQPFWPNbOdYlPhevQ913luHMgblyHS5a+3+Hywg+YDShQ0LDCOWMZeQISpN4tioOBCPPxCeBpiL4WKcVryYTzdbl42TKeW4UgwEVHjj9+G6VjVaNQnrjF2c255pIv2LeVZ1xld3NEgYJreeLTHaK78LDiLSi93fORal1Q8CxTQRuIW/gcx59+4PWHlDS22vUUCgCABy2eoLabvRj9dO0pcgIqBAXOrw+3IIqu1SmMN1pb2LyYf0Fq23P6Lw0jC4mvZHOK1ktcLubZS8I0gALZt4YW1INOWkurdeh7GoM+yM9NUnXs0llU1P7AY7XQcU2Ut+OWK9LtdNusu4hQPvJMLTY9yvxdWTXbQgRRNTZwna6/OSUVf7opv88PfXsehvKyz/6sR9SelMYKiMlGtcAVOQ+TUM4oCmf0K4vlmw2igsP/KP3WfDwAAfnD87J0aq+ePTj7HwMP4mDIg8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMDIzNjU3MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hZDQ1NDU0MzQyYjA2YzdmNzU5MDY1NmZmZjFhNTE0ZmM3NTY4MjUxYTRjZDM0OWQ1MGRiNjE4M2I4YWVkN2IiCiAgICB9CiAgfQp9" + }, + "_": { + "signature": "BmF9QG88qwse440DJZi8zvSfSnmdJgt3we7kXvPLp6GnceD9mD+Q+8oiHgi7wbx/TXQjVAKcJLOGgvychdDXNMZfo9U0/0IkE+Vj1Vq9zPxrKWZWKO6zzp6AElCFBcjIlLvhqcAyse7DUfFCOYAYO9/B4WhjasL/6hSqcj8ATZfR8zs5KiUkSXabiVJiwoaBjL6SA5WsQdahYokwpiYp6OUZBuj8Z+v34lUenufr9q45zMpRKAVUCNLDGNJLguKhV22F1fSKREc17R94yZkp29lqTht2/+mKoCNGm6J10zzh4xaxIyV+uEPyotv+OQDJy401g98UzdXBMPVOx2RPxpLzR1dmfTJWoltV604mQpDuLwyW1F/Fz8yvMD3fXa0TTnmICwVnK5x0w/PTxMcP1yOjjWXL7ei5dUfI/vva54ouqvZ1Qk+oe99zvgW3J8NPvbb4awBa3PyvPDBX50HQ1FcFELsSC048djbG/bXJRWpUnWoMlSOP5bM/DmH/QqZ6rmGidhVBS9oFMTnWsNvnBHgrPSy/6m7GEsKsiDi0PvtqXznIWrL4dOv2UyM4lBdpx4mrvhF/3UmIgwmb3Kw0ZkaJ5u3a9Jc48HSkDCS1CKWojxvDFM7KDp9qv8PNMER3AUXtBet341KGkUDZwdmCIP9fZ3UEwitQ0CgMRFr3DwU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMTQ0OTgwNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zNTU3MzgyZDU4MWE5NzZkYTc2NmI0NGNiNDEwNWMzNjQzMWEzNjIwYzE3MjUwMTczYjI2YzY2MmNlNGY4M2NmIgogICAgfQogIH0KfQ==" + }, + "`": { + "signature": "Z6sFhyiu9C5mQhJtDCKjlsge0yp7qfxdoQPjvy6trBg14ra7dYOZk1AfWgOae7mHC77n/c896JEZHuHZl97NFry3iPK7q5ni7cydLRHbQaM2ksi9axs0TSQCnxYtisjU0UyOXJai4IAhfbaX1SnxSvE8LUxJUpnizxmjL5UUzE9UUfe8vztPave+o/zE2dgTl21LdPSNZDmtKl2JukACCIFkrpR69MMw8Ani739NI6XyAvW3BNfNQK2Jdgk6+EYd7qwsf8Q9jIV0l+kc9U/Q4tfZPy74TS8wcvJfwcaLJjAAQxVKFfI/Yd4j0woQOrz5lfUlzWT6Zc+J2NAje3Vqw0sUJRD70Fojzz22QRnnkBd/m03slOgyV/xgzkVLPH064g+zecNgKvvCOG4mwwx2FnbxHzcGvBboOWjTeQhDulbnO9QV0lbBBeXTx6rHimFuJ5npEOqz1fVCAs0VFyRh7TaDRywneGoQaak7Tz45i77VqG6yHXV4XCtsroyOAiCYSvHm5tK88bxZnxOTrj8pJok+z2wmtBEQTi+6y9csjehCCvSTwJ4Z17xRYkDN9LRGxVXP61Ma1D+XxeL5PaXmXPx1y0vmp1X/chz49jxAx5M/ro/u6Ht9YMmFqRs05cC5gDp78r96DJt4IhGH+eLhwEzRTHAGwx1vFw2l8lqK9ms=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExOTI0MTgzMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jMTc1MWEzM2JjZGM2ZGFjMDhiMTlhM2E2ZTE3OWQ0YzhmOTEzZjEzNTgzMjM3ZDE1N2RkOGU5ZmRmMTg3MzkiCiAgICB9CiAgfQp9" + }, + "{": { + "signature": "TCDyafA4Y+y5CZu//SBfu8DPYYwuvYVMeYo/hbCSOr8AzFh+PjejlS5aP8CDBbJn8i093fIjMDXRZMyNPZGlmcUv7J6mtFI2b01A2K7yHBCypi+rkN6KsM8Gcl7Fo0YLA2p+1QGl5xVqcdMsvHUOaQdXxzp6ZxDxts2oWHcVSI/E/vqpfXE+/DzFS9Rc/SjYh0TLmjOnkWwLYa9sPjknAiaKYQCxkGXy3/eQ0XGjjeylnqwzlvbd8ipdrcsJpM+/VOp2oRmP7rcphPvdgifvq9m9u+6gtKfpll2ccjnO7D3X9SmdJwUv+Zzf6oGJ5tOTPCM46dXXRwFlT8R1cyimE4IqaLuCXj15AolPnl+IkP2ozlFCIs6ngL5V3X97QnNKYTgVJC7VfuJU96oB7uMeY06FOTFi86iud/xyIQCHxZBevdCudf5nHa8oqNNFR9txJhCXPiTJIhpowhLmxc7vylHtYhMmAAPwu2LpO6+C7A9poeSXmmkZUt1pUBqeJXNXgd0VLL7lYBwod37ZdTAEolWrMQsRy5IcAgfCht/us7AXLlUr9v3QpPtuDYtESzD8tPpydi2IuIisRfEndORXXlZ4mNTywpKBpMzoY0hwOGhQ5yOCx6p61t/XwNSQHfW1gszfOrqAv+JsNLvHRqfTl6kZQoQVLoMnPEhKJpV59p8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEyMDQyMTM2NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mOGE0Y2JlZWQzMDYxMWRjNzA5YTAyODU0YTVjZGI5NjU4Yzc4ZDY3NzRjNjRkNzlhMzg4ZGM4YTgzNjJlMDk3IgogICAgfQogIH0KfQ==" + }, + "}": { + "signature": "okYv7kWoP+9NoDvoCz7xc1ocOBdmlTpeS+85wIqnwFjubYi1pYVl04Uia4eJIMSGVLa6yoHKy0manry+REW6GWb8XoEE1qnpNEEsGlVUabfWJq7MKuTbz6aMLXoqGl85rAojbEFt8oGAHIuf1MTmk21GS8zHPYS7oqQLHnFe7uuLiT6dQYe6oxkVYC18qNfozZqKwcbdlf1QEujep9zwpV2X/sg1ovKx5dQE8t1nbyJPOHLBUUFwWNts8BMyZmkS9T6gRQU95miGL4KFVqW5Vjt1e6dSINhCaZEUlqL70qPZlmWewe2bS39I7ENpIovChVLT8CiMrVxv2LBSucrkHfI+JK4ZFi3923zaawIU/bGFzdI8h7bHgyv2uL9yoO5F2gczrg0BpOESZvUfZJ/9WdvO9aacn+E89CeUKKkDLikkUAeXwwggCylU+vlogPhLFYDXRXNkfiKZjI5mg6IC69uVE9s9PWhmYswuGZ/kPiFn7u+gEISOfSVMvYyyVKOCgDiNoeujrb6WaJ2E0POj4KG3EmS17t8MaJnIZkLOHe0Gmk83R4CIg7txF2TPKasNDUj1hpjWFvRzvOffX7PoKUdGHBUd/Kde3BiMuwt0PUbl5IR/eGoEPy4Cd3j6/maBqMeQ9Acg63DbenzWhMWaFANNfewoyzraf3KJGuBYYl8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExOTM4OTMzOCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mZmUzOGM0ZmJkMGYyMWEzZDZjNGFhZmQyNjkwYmVmN2ZjMmZhMTFmMzgxNjY2YzQ4YWNjZTkwNGM2NDM1YzZjIgogICAgfQogIH0KfQ==" + }, + "~": { + "signature": "w0+Dt/hNu2VhI3w0QeNusnt1FNrxmF6ry0Tl/DhYcCkjcp+SinA4les3GyWAbnedHfENUiQddjT00OMPOA3yZYjdAq6yplYXDxs/V/t11rlhMZ5M22k5WM83/t0875XEfIXX8vqZ9mM3vRh1omTW4u5ix2SgxaHoDRlyTn+qCbBZDj/cqbtQF0My20wnQ2fQquxuyNvFyriQSvK5fy6Nm4xzrvPoAPuk7waERj+a979RKMTM2Pi2qhq100psj5VgK0umJ3mZJUR/QwI5AxNfvUdTe5S55bDCovN0a0pvv76CbRCzM+HEGYRSbLKXPM7CLIA9nD7dAVB4kJz9LAG+VGJYE649ryZ+lb5HDYLGzc4qrsrlbM5x57XrJ97ZHk0nVIhP2p6AVBgLzz1mznBo6ryxFOu0Fg8c/IcINPri2FhXqYmRFt/OjfOx3b+ZEPCXXYoFPMgh9aricC2rMi39ztqAFrxB/FNxpY5tdVPnvHlF82GZfthrz7E1s7DbzLUima8CgUtHBXck6oBTD/nD+kfsETXQKi8bS6rEpMFOR1tjGaHPQz8kGexvNN+ndW3vo2+Ln4KlbePriosEp76pFALwAXYcCi8RyPT96vgqZjx8S4E+ndBBDgBI6//m1ZN0w3AGy9gkgv/A/xC2isRBPMnrk/1W6Mz5eqMRzjzcPlI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExOTM1MTk2MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82Y2ZiODJmOTAzZmM5ZmIzOWYwZTM0OTliYTJlMmFjNWFhN2E2ZTc4MGVmNzJjMzc1OTA2ODQ1OGQ4MTQwN2MzIgogICAgfQogIH0KfQ==" + } +} \ No newline at end of file diff --git a/Network/eLib/src/main/resources/skins/chars/darkgreen.json b/Network/eLib/src/main/resources/skins/chars/darkgreen.json new file mode 100644 index 0000000..bbcdb33 --- /dev/null +++ b/Network/eLib/src/main/resources/skins/chars/darkgreen.json @@ -0,0 +1,266 @@ +{ + " ": { + "signature": "e54kEPFmjIeBUthE8HzSdaXzi+kzqWqhlEQnHSvxZgt+gL4dut0CgMx58e9MOk43fxR82fpQz1i9LAHT6AxuqqwL2U/0DGuwsHSR/ReiLzvEL+Btwkiwn7Gg744mk8/am5xApa0HWGJjWk56/QFH6MWPqmsyiO3moo5eNuSVxVbP78mg3LF9hXElgCzYGk/F4XC8omlrr22Dpt+vicW82FUuov3chc0UHOhDNflxGZ12C5U6tO7r1ydTO6O/SzDMJpj+/WwhqOMLTMUngDQKzOKI7LHuHvfun22QrzkmMKmLRp2HXOyWoxoosI5Go3Kg6vThtwwsXoTS7PqFhTY4mer99rlWH9I5hp+D8PPGAI9BoELwloVXpvFLVtwn3in5+4r+EH9SkR6LffuAa7jKORe4ulDj0Bt2yjhYVSTav/Bsg2y+/diaJjIchlss2X3Zc5zDihedLQLCHjKRc9A0WRUFjENLh7Hs/pIqSKyOTG1D7pJsRGKNG1IDaC1pjqMbV52NAVEuYu39BCGgCLq4Kbh88lueoLbE5ULuTr2qiKjFaeSuUCy6ukRk2VN2zIBV0m3G4O1Q7JGK9R2QHuak+lhUKSXSQopmQBQrz7QHrDHLFFHO/uCzTtvrdlLp4hoy1Dh6AvB9AbiuQGY2E/maPUJTKlC/9yWW8j0vNp9ti6o=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNTI2MzIwNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yNjI0YzJiYjQ5MTZjZjAzNzdiMmNlOWNiZGRiYmMyNzM5N2IxOTA3ZGY0OWQwMTY3NmExZGUyODVjZDhmOGQ2IgogICAgfQogIH0KfQ==" + }, + "!": { + "signature": "KRIKaWJnq8p69tRKt5ZbX1eIyr1zomjfgvfiinH4TR6xqvug3fNHuVqhAvTR3hBeud1uEB66Vsju9HAQDjzYTokwdL+K+HMhmoQTTAc8X915XmUcul22BQ8iDh/o/Ye1T3W1wqJoLoj4tUeV9vKLCm86MsdNdfK+xH/XwSvsxoq6c8u7bqajfD3Z84B4qzb2i78QP4x+/Swhkn2y62gHTMDVgzEtqTcl3U2WT7tGkmz0GwyEbMx4EivU4yO0B6nHgZt0MgJB3kl/6amGiQ62dOjpS8gMgxY0cQc5dC5MRPu/YnM2NZYi92vgqaD8MhKM1cH4A0IFXmUmtjPNNB4xu8FZFCbs6W90IIWxKI7H+XZBk183P8+5HZ9VJ7AayeNzG6BFaqdeQin1CBt1OoB2w0cVF51QWrWKwujYHoUH4p00EVY0TouXPjHw5bvVmFMpsdNdPQTgBTUXxg/WTHnc+qICnBYdowDzuyNHabS8tazQWWZsxxUPAzoEvz0UxwosBu7W6n1ltEIJ6TE9/2cfL5CIxCGaxU9lJO4c/wCSZGra6ulz9xyPtdE4OsTTxsu5d89y95ywNQLo9xGAXXEWancvj2m2RTJ8H7JLDChAzUO/jcXP8a8JHYT7f5qeOSdpBrebFtHo5974jqw3VVDrermReReiOao4h+nFCrBHJBU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNDYwMjcyMCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lMTYyY2I1ZTQwOTVkMDU5NGQ0MGNjM2ZiMTVmZjhiZGNhYWQxMzgwMDllNWU3OGViZDE1NTU0MDdiZmZhNDRmIgogICAgfQogIH0KfQ==" + }, + "\"": { + "signature": "Mxcxqk+YmW9jbTn6nUviT+9evih0OhK6GN5HbT45lHWH8/JF5m9Z04JATYUqcsq/UEbfX9O+UhQ+gAK8S/iP6bL5mSkJc/xc5pk85HezAlOqFKSzF1N3NmJxHHrmneZgdHpFMsiVMqQXaXbjhEczDHok3ODvLwTPxfQgTo4bNOABir2Cc4nW3YodXKL0aX5RlJ7gFD/nrGXOOOA9DNiGQ+AjAGytBHTcf65OKN2aUlTb3Ue4b9uGSs3z7iCk3V3y4C2cW12nVvpEsmfmG9b3ndA8l+xSjD5k6N90FUEFtOkYEUyPo256OnHBWJA9Hd/Gi/vHja/30IoxugEb6+Tm2l20YtmH9Og3G5JC21IUE+MWv8q55KJoWPYD1ZVoXlfEFmey6QUR3NkxWn9MaJkd1Rnd4vmEdZ1hH740hZ/LhC+3jRFKZHsQoZZxicJaihSEY/kaBZPrkAl0sHlmiL3gRvkHRnRPKfO9qEfzn2gJahE/t+0anFqqZYCPavBoOUF0TA592bgEBFPkDTMWhKMSHIL3yZFxBKQ5IoZ881ZqGXDdqu9H8h1QSL2t774FbAVuE+utrriHPKyYyB5/fHiWQIvU9lqSE9+VHYTO4k21bOm0zYan7OsNoRxH+8sLow+7HveeX3YhaG2kO2Vk19ZQeByJXvKsVVtjw8rD7WDsmcw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNTAwNTY3MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81OTY0YWZhN2E0YWM2NmE0NTAyZWRhOTliOGFiMzU2YjczZmRiMGRjZDM0YmRjMWQwODU2ZDE4OTE0ZjMyOTY3IgogICAgfQogIH0KfQ==" + }, + "#": { + "signature": "op4wGO8bYt5zgofRbvxKuxdfwYdSa2GM5NJL74LfGaaEUyD20JVDSTy8Y0SymBf6wWJbKWqK7IxFaaidzlCNDgki1iyVg5kDzQkt/jWZ/2bzU3cMBniGMatoWUt34Gh/+fCr7evbew++YCgFWzE71Uu5WzrgF3f5pbxdEFw0MAd6zfr/1KzzV9hnQuEjBUJpWOIqIzsP6tlKoVjKVlW7x9fhhUckH/5M0lBZ6WwpOt5NfQExFou77N6lnhYsTe40dPboi3LSGTXbJkAP0Uf0oOsNOBELSK9u7uiZdswKfrfkW9Qx5dqrrBj2kBIaoR1emBcnViK+WvKzZ+9cNIKzGUi1gziyaD+yrKEZM+fkDpaYMd7aT+L27PufEmRS98g6k9sIBVpGRNyTzzLyVNI/bj93w+EkPjkzQgvrpAK0AT3oCKPREmL70q/VgnY7d711aYCOUD/C3tIWRrNdMTaktvFw/q1o7oHzOyS74G2QM0peBiv3l2YSdtD2REnWTsSQoAzLPFXJEQ90WySnWVdA61PPm6UJrI3PLKeGeHWu7M9XwI7eXyoVnam9ZzOIuemkCTSk1uIsB4Oe7fgPsqyMBFi3NdLWX5v8K451MKhoKN+GX6/+OYXJGQM0Ej6tNiQWwGjXuq+wRSiliiNGcWr3ds0LCB4MnEYmg0FIMdAlZtI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNDg5NTk5MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yODc0YTMyY2RkYzViY2Y3NWEyNDM5MTRjYzQzMTQ0ZmY3NDIzNmRlMWZkNjY3YWY4M2EyZWUwMTA1ZjdjNjgzIgogICAgfQogIH0KfQ==" + }, + "$": { + "signature": "rBIs1sy1Lozu2r6KdApgVQWWL0fAmj9UHJT2QXcyyI9dq362/FTngQJBUUPvvEKJzh2Tk6VNCATSq9l9G/KHpstfcofmB8gL33kfTrFDCkJ421kpvJT4nSQSPe+Ko1B9yIF2TM0Qp6VF6nriOJnD9HqEufi716SfnMywjp2zBw1t93tWT69FLUst16cyzH5EA0IUT+8+60oQGXW+3pBNM5yEO6fOzmZKpy8RyDFxT46nDrvh/Uq2v6tCQj3lgWsicPjY9H09E+HHQ1eqGiMdN1xCQyjwApYLVIEsO1XQvWmrR51oSt6kxD1a2y8BoecYi2BMrHAb5Uzoo2LYi+d6WFiuj7Sg6BHQM7rvX3cfkaDXJxokLYU9Eon8mu8V0hjneC2NJvTHGnkUvTO1g+a4RrwipPwQNLNKMiRrLITUy4CqIU+Fx2UqXE5UfGh4zazFG1PKGjcnxybIUb7PpYd/lVYr7WexBeLgDng7gcxgpfl0gW2y9UtWe1/6DxkDfxvsKttlDgTTDGRtzzwu6EBlo10rTTW4MWJV9lRH3IAEgkyHZMw6XBFrIHWkM7jgjzoGTkeNpoGjO7/nRhQyfPgGUOo9mzOmKJKlAE+NJE3QhY67kdjoAisMy5XWxcxdpw28254Y+KLvWwNwlF6HJBBxHfX0RWYgHH9obqNbyaRyBG8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMzM0ODUxNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yZGVkMjZlYTAxZDdiMTE4MDIxMDcyYjQwZDEwOWNlNTZiYWFhOWEzZmJkNjIxYWMxNjBmMjY2YmU1ZGY0ZTg4IgogICAgfQogIH0KfQ==" + }, + "%": { + "signature": "ZwAwW+A0d9QyM/Rm+MVkb9/4xDkVe6WycmTkLqC2eVLxRex9scRD82RxI76w8po5t/0Cs5WiuXyXIRZA+eH4S8z7FQ2hll1zlB7oOKzhoo2kuHo2JjvaGwo49mm3GucFxzW4mpmDeMJGK3v/NLazkX2zbVNdZyFzTn5fgQQV085p5RAW4NbNeQLfl0gpOdMamIr/VLEhTEJfdfT8LZonVFZ4byZJN7u84UDLw9hMZt4f1HEQH/n593UY2q/uCxv0cs2lwJNPMOvgEJ+HN3euB5oRKAUZ1y7J+j0Da0wrZE4ERmOr6uoor5TtIBzQHRANPL/KWJ2VoJQ03Ow+SOZJt/k4hDom6DxtxgBO3mTbyC3GvEvC60eYGxjETGS65YlCGTya6K1Ck129WV7teU+T/3yP6DNg72VJfqRG5MSqay7BkpQ4TbbmeWFMkv3p0NbNhCpYLQkbiVOkyb4oUSArHP8/daNqTzQf+jkaCsTtqU0sP04RxZ8sjEjgE/6qLcUuKUtFp6fkemCNnBbptAO1QjRL5QcFO/0mu8KmTqdbSuEpqEZhE7Km52q6z5+w17iu5EBXPZZGIeFYANDOvAJ+MMfmNns/qYa+E1nnqqziioYG5j7PAVzaGZgRmsIBYyXOcMExfkszdlIWOCmK9yCLa03GKW+AoA2kW11XbxXPnxI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMzM4NTIzMCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84MDk3MWI3YmRjM2VkMGE0MWVhYTQyZGJiYjhiZTM4MWJjMmRiNThlNWZlMDI1Nzc3NTMzNTVmOWM4ZWNiMTlkIgogICAgfQogIH0KfQ==" + }, + "&": { + "signature": "Lw88WZPJz/Vrcua2/neJV9WgWdbMolmo5xF7EOLWIRB3GMjIyz6KWblumA9aiNNf6JTCfuVgUBNPutJAlvo+NEUmUx4GewUZ9or37sADCRn2VqyQvitmtYw0oJwAvvnw0TQ13euO5g3aA165SUWGBKdPlCHchPOAKwPhf8w7MA5e67Bwz8lDo4Eztoo/bWx5BWVD3iA0rjvXyqXG8LoYNetcd07wn2e1qiWZIEvTBlUt3HMVxpcgsd5j0m8nVd0DqwjnMC8pL3vsRaaquQYDXEWrJqNb/8lT9A2ygYUZPURxbjupSvY4X7P1DzBi6A+ej3xpYJagWVNn8Xne5Ram+roOdiQ4zdT49qVzw28bSXWxHn02uuXhYBdqKDSKd51OI2vysc3jTg3KJtyMm949BjcAnBZT/dbqgtDnb8y5x24QmQMnnEMW1PJVUYuJeVyFfxR6Xl9ZT14VSDXTZDO40nysnzCKQTjACZIXI1TCiD/XMVhswnTbqKXY8Q2LY4uqv5HtHPmdbxP17Sylw08TdhGvfizvl2JAfBtiUGbV3EDuGaIj4cOzwCYQ5xpYJIG5j8livzqMva1HpzMGGKetudTbssYl13+AvygoO8hMwEqcPq2+iAgyI2WSCfq2PrjClYu1Zi3oSqcpAi0AALrcLRAyoov+14oGRdE/f8OKOG8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNDgyMjkxMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80OTZlNTU2YTA0YTcxYTA2ZjY4N2E5YzMwNzIxNzVjNjNmZDRkZTI5MDhkMzU0MmQwMGFjODJkZGI2NzE3NjRhIgogICAgfQogIH0KfQ==" + }, + "'": { + "signature": "Z5KcBNGYEofaEF8/qFv5Kv/LcCmqat0r55A0BJL+tLfYqTa8CYmcORHjzDIEYMZ6ziPiEjEu/s/fvyz/mnAbp/zRY33e9GGCbAWp8S0vJtmT0exzA/rDUzt9du1c6vTDqx+vz0IOI5QruHrWOt4AjFrw7Q21NCyounrR/ZJzyDzd+rTcjjuwqxRgW8TuKtVl9X70cH0KhNO2aQS3kH6Z9Prq9dO4T5WPsuGqX+Ul10C/gt5XXxrw4C6rBP4TEmvwOWs7+DkTXJUQ7KcpIZqN+vBUtLQiyV6nFkkFRAZ3guNYk22o3sKY6cVcle2lzotaxsbfNJF0RKABaPs9E7Ax4NLb7idDhozGj8LVIbscYWnar1BV/UsIw07q5syhAACKJ0tpNyGZzSpFcc9V+oWv6Ocs6kjE9rtPtYPE+35CjUpx38xCyIj6JvVbiBSmzXgKnfc1AIVE501ZgsMf2XA36/pVdZx8CzCgfithtUwyFEpNQup9d5psl+NL/4IL4RiHK/O8uQIab6T8H2Jq3CKcCceX5qH+KWc+IYDyI/+WmEH5M9n5XyvaHvQfYV54QbaCa2pp3MLnhYN58xUjPcpSum0dwCSHqeYhs0mEzs8bV389y4Ea1OGZZxzCHWosnHdA3Snn570fSZyMiHkZC7cxaV0No2mIisJs22Bfbj/qbNM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMzQyMTk3NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jZmQwZmY0OTE4NzE4OGVkMjU4NmJkMmMxZjkzZWFhOWE5YjkxODJhNjVjN2IxMjUxZWIzYzUxYmNhZWQ2ZmNjIgogICAgfQogIH0KfQ==" + }, + "(": { + "signature": "wFfXtE16WXDr+9usCFIJHXJiwinwKz9LH1MZWuJyCEHfLJFie0HFMz0/wusdKm2e9MipRFW5vJeO3+OiUe4q95EhNTS1H4AegNJIBr1U36dSA5CRZVciqvunTFHVkSlxgCzofXOAhJ+ZqqloJuactSnbx1xvFiepiMxkRIugPS46y02v8uoHGw3mppCoDA5ZPMhDg1Nb0TXg/u8GfMLzODYZoZQLsrnZRNblEMzXerg21DkxY3n20O7DFvIDnxSFb2SFVEetKzmB6ZWjEz41CWx2StdMh51tXBEqmX3DNNxpVHy8IQt7vNs4QUTGZkbJo+pgYa+KXt6zi+YejJneQ9xMUaHDQGCgvLZlAboCzgHz/nr+B+hMtnm54aeAramRK1OIsJYnGW2KGJh2lewBJRxHv4WnyfBadelkL5fwS7hEv0mRyNIamDjd9U1sT0MjDQdT7L4bOW93kcPvo0ZskcHu4taXguyTR2jmK+vGdPFVZNzi69FIDv4FBUw5WZR0yxIDxvGIbIR9VExPLFLqyuwLrDvyeOz8DXq9YHroDIGz5je0CKsCkxhJuGAYEbOoJSEx/NsW94WD963fULg5k0Haf98mAzduFjJla1a6zshpu8JPkfsOFbzUPwN4meK4dYMDk2ANJ8/1ZO0ozZMl5d8j5+/BpF7DOpAYYNvnpf4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMzg2NTU0OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81M2FkYWExMDhmNGRiYzIzNzA3NTcyOGIyMjE0Nzc0NmYxMzgyNzY0MDc4MjlmZmZmY2U4ZjVhMTAzZmNiMWMiCiAgICB9CiAgfQp9" + }, + ")": { + "signature": "dxS+wll2v2FyWDfsdc02+1QrJKrf3zgPe39u6LKWTvgar+G3IOhtux+mjOuUYzk9ET3OlVjIDlYN1SUfpxtZsl/j+9gBpIHUQmwHraMGvmaHLU9jbn7zVelmSQDHFOnEK92ODikN9pxbLeaOIZWnoT1o0L4KcJqTCsJ/tr8K2upXtaWc0pJtUZYRCO8hUWeZBu7vR30qCuBmkIBq2U8qQ03Zp6PYzdfAE8h35yBCKaoOpqyxHc/ehYPiqBuLIYxErZ80ra4Lq0CAIrFwZM6Tpf5UmFztAfWCLAFuGvaNrjh9UNOBM+YHVPBPpAmDwViDOAtMglAgaHKUFyvKByrKfalXDB93bG0CSW2pzkY3RogXLVLItff8Dd2+NwUfYOqG/BxDV9PL7vaA0GklDD87ur84KTjqWpzksdzAfuM5A/YNtVy4QLdH51zawNcLvFdIc38LHipOsjHVGgtx+u+ACU9BAxpdERCCKth8GhraocCM8YSU9AWncJM2CGnX5vPop4fb4Z7PCBBgWvDw7A/iMhyRaDqg6Cysb7gDz64tifqNnWPZhGskURQKsSQG1/LyfQppb86kd7wcOOuhCxH/iir0I/tLC4PY39yJJykvsHkICquoorCm6YHaK+NjAw022b8KhwwQ5xso+pnKUqRAv7mingjagJnSIzXV34H5okU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMzI3NDQ1MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iZWFlMjEyOTg5Y2FkM2E4NTRkODAwYjliNDk0NTdmZTk2YmIwMjMyOWI3Y2YyNzlhZTU2NWQwNmE1YmI3YTlkIgogICAgfQogIH0KfQ==" + }, + "*": { + "signature": "S+DkwjdOisaAetCb2tDitV4wCYgSNB68xPwNZA6yrQHVRQcUzbNtIAsriqq9NZk2aJf1FudXDTLjUOMdB3G7tEF54PxIxBHriTDNKfPdX2c0xcgC2DblwEHzL9TWVSI+TJ7zyjor8RS5fpFgem7lVnA8XrH9cSKLi8TxIVguX1XHvVcWP7g7CpRPjbVFSUuboeQRCuyaRVeaw9SDjvWZrnxlpWvsWNOowV+YW9USrDQ0RRGyD2fB3IauZ1d+d6lpc9AJJ+Q8Sffel/47m9v8YND1R5fyr8c3WPEa5QZf/wKEB5/SQoiSre6HL5ar4rwGJRpt3uk9eR0/VEZOceeMEFyErWwo8hQhCcm/4KSQUvFcTCJouiEfNRJSymRb2SZ2kAskHZNWVcj7VnXi1VJBaXbsNKREvGn7RTTIsMsfim7dO6nK0a4qpYvAAQFSbgpZDwuEyt8xR0XYeBEEOuHXkaoQC6X8ZM0UoA1P5gT+1bks9ETevgysGm3ebMYax3I+HGArmL6ritY+S3Kz8gacAajIyyPtM4Qj0zbTaM7ehNhBxaB+6kSuSXE7MLb3s1EIdgLGFRbbWYuJDFS7KkxegGPAIoLSElMJ24RljfH2E1/q8WDtVx50FxN+l/Fq7WWxN72CKrckx1RNHGACFPsmKw3TtY8fOUVqM2p9NLEcFDo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMjgzMjE5MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jZmE3NTljNTBkMmE0OGIwZmI1YzQ2OGVkYTQyOWRhMjE3YjY3MDgyY2YyMTE0OGE5YWIxZDNiZGIzYTI1OTgiCiAgICB9CiAgfQp9" + }, + "+": { + "signature": "sOsjWMaqc1QyZ8bvjPZJ6qdfKwnJrNR6Sw8O8JRQsbQ4xF+0NTBEtmZ2neMi3mGpM0bxT852wW7YLYd3zTWdfpFw5Whii1pc6pfHczEysdBOTE+GyiahxYjfU1Q5h9BS+fBfBmAikAv3MxKYGokaqaFuYptxb8f2UHaI03djmPSDCCYIMMTIyTrzxFDymZB2/FLJO3PgYNgALM/Nfj5rrnlci4V/prdPusvov1HCNYGy7v7lDmCwergI4jqdb6wcoPO5HIw+1n99uQvSfk0FifP/1bdcAYGB8Cng2bGuQd8i73cwf5WfS1xtQmy3QbZcPH2pxMh+ggp21dLUPgDcjZrahY69TG14e0OkkSGnmIeP3EqJYEs6bjQ+FdvsJzieBohN1nDynFjcHGU4LANWelosSEqvqlihHd7sKw9fLM5Gr0g9DTuAyySBbsQm/9v+DCTjp2dLlJqVNoS5PIRr4p3CXdyxcnXl4ktDKlI9Mx6p916Tb1Msxf+zQhMSbU2T0N0RdRjEHfLzTDfedTtnNhck/PeCh74OTKZDj22ZIwrDqHrwSc91FX8DSxNWFdN1yGn64CuQVM+54vhCHp4CtyQ+Ee6lXspUlyknPYhyMZd0GjtvXt/bmGip/0koSEpZcBfe7qc0p02zBqLQUGBaE2K1uhFX7Pg6MIGguP2gX94=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNDQ1NDU3NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82NWIxY2FlNzFiMjA5MmU2ODYyMGNjMzIxNDUyY2RkYzFkOTk5Y2E3NTg3ZTkxOGVkNDA1YWMxZWM4YTdlZDA3IgogICAgfQogIH0KfQ==" + }, + ",": { + "signature": "LJTr0cyniBbI1d/Z/3ILtCGzoaYC1dF/b6uAXwTUAlNSD8cLEX32vzcZws26ALl1mDBdS9vxff3nhcGf342ZmJwPiIvK4bRq9iU64laJWUPW8k9x7HrDgAvdTGxEoONOU3Z3kU9t3dQulomjelHIUVvWVfqh4wBhhd7svBYsBt3WMXun8GtWaqbmZwa3DK9eDSIHDdcDs1YQO/0AdVIG4A1wgTPr4xKGnRdYrXBx/wlDaeACPP++SPIRxjmJgXgdNVpe9l7A5NbA2+rHTtLxC9uY09PoOYVyEFa/TrC1Q2GwWdhK7eydKZvYmXy0G8iV2RQ+PvYerqhLs7DCVs4V09qpwOhm10E1Pp8wV277lQLsWEV3hQ7XLftcH9syQNz6PLQIPr3DQJjSK48AAzIDz3UVnQN/CoXeMwFnx8V0c1Pw6Tr4ZyTjidco74F1F6VEWB/vjeWk3uvPASrxWX9H2Ctr8B6QxEmS4CkGIcIYyB7Ahmp6XrHuYw5N/33cElXJJTcHfuFzBw+0p1Wc3RifdrUQVK9cKroqCr0kog/7HNePCqads0DU91g0171ukK/oTBRsAoNZm322OSUikXsDbPo6V256gREulnLVUW7kNYftvdP5fBkyGOS01KSLoes+8/KZja2vAF1HQJsPbfekdfwuvK3jXH0nS5Pt4MkTrXk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNDMwNjU0NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yZDMyY2JiYTBiMWQxZmQwOTlhYTQ5YzEzOTA3ODA1MmRhZjgwYTFhM2MzMTcyMWIyZTQyNjljMmEzYzQ4NmMiCiAgICB9CiAgfQp9" + }, + "-": { + "signature": "WoeGqMlfwuu6vdVcrtG6TIv6QAyI+H92JzMhJwSRkZGRqep8AZKV7B5GVRkIzoJp7MkaO22e/zrM+h94b1YxkQasjf9WnxLozusiGCa9VzK4XyPZUMsJSqn6afB+hcY1dXFJkaOWHVcEAaMZmaUJ4qwOR6ABovNq/lZSRd0FWoqwZTbiGGAYmtV+Zs0ogrrJAyZjU7z+YyxWNApQScqgpZSW4Eg5iMjQmUTtFHn+XPfWIulpaJ/rdv7UTbwmcSIz+2mTbnoeIvQNHhzOwsU5cYjHbc7TOShJx3ziRTJZPvWRBV8YuNriTd74Za8U52NfpdoG3brxAlN9imQ9X6SUpFwsqSvIntUoEUUo7wRDXCof6+Vo/VE02JJ+OFQyJjjPJdDMETKKKPaUKOPOM3ulnwBvho4ZpEfN1jNcfhe8UVU6XPwa0NkPwjSE4sg5xJNX5bAHrkJ9Id+EX8zVapQzGqV3HXLN5MJOaDu4pNnbg/3m6WZcz4CeWGC7gmC8F9GstVnhOLEddQuVmQH8CrGAVo+zQyR0pIWe/ymcPplwYZzwfVP/0FWusFCT9WtAUe4ck/YJNR+XnpytqdJsgf9rHUU+4h+QJ99lgvITANdLSnfcJybBwONB35ZlpmlMzeo3zGv8gixBFROYKF7h2Q/ZAki0pgbMqn4NLovteZPYk5U=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMzQ5NjE3NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iYTc1MDgxZmQ2MGU0NWExN2JjMDEyZGNjYjNkN2U3NWEyMDUxNGU1OTM5NjgyN2QzNTllODEzMzQwMzlhYjZiIgogICAgfQogIH0KfQ==" + }, + ".": { + "signature": "fbBEFVmyJfDDFqNs0z6lw48G8KVfnFSQRRrVaAAEuQ3Xt4PsMVXzN+567nss8F4toDpBZVKe5SKGP7p+zyapBq4KC8yX6GEf864KWvKq2QqV3V0Iya90Ryg8EEz5cD0U1neTPyEEL/8u3gTt0gvT5id6qiArJ/NLR7WnEbOsiKdWFquUxVFlSMjmpXYYiyzajVvCpnp0zrPeDGAvTXi/4y62688Wh7x6n9/yi2V07Xl4cWQGD9NtyJru2kOfnPImw125hoXS4Gf1EzL0xjiF4KnYXfq9KMwD4r0+RdEXjbxMWTav+JKvv9PQHq8Y2ljupnf+uACjvph3bYBPDmDRjQ2Jb7XxKOC0SIPhndbFOqbowVjBCSFoKODmZEGti69aSHbkQGblPgwjf/6CL2boP5veUgfb55mD1/6DlBESmkfk6Z/9/eIHSuRYdiPXfwlgqYE4LxZFHqdXGTwexxGu59TwlsveY8o+JS4XT72wC+nuxygzxks+qXwD9q1v05QK3x5DS9h/OiU7UONq4/bbxS1K9cxyy5t79zi2BiKDrRBJ46AlNTHScIVV5y6jNWreuDTMHWQIxZNLTE4A0+xRNHQugJXYRDLBVvLB1wkwPYP/pwO0S2OswiApsy5axlgZYbI95blsTMfl3zglz4Hbt1Uk48ogkEeyh6HJZ6ThBho=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMzgyODYxMCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yZDMyY2JiYTBiMWQxZmQwOTlhYTQ5YzEzOTA3ODA1MmRhZjgwYTFhM2MzMTcyMWIyZTQyNjljMmEzYzQ4NmMiCiAgICB9CiAgfQp9" + }, + "/": { + "signature": "MOb2apUhHUFtxiwF4M6eb6hG4LnQXzfekfzd7WGVtZALW0uGeRSrZROxYyclcD59dYO8Xy0ncjiNH9Tfs5mklOe3bx5gBQydbapWHRfV8yjGZAwM4LHui3mUsagC7KkB92nNior8gTltonaE67RYHfW/1FOHFdG8t29hXzSYnspX3dll2gKkkLr+bbucJ0a9VRqmRuygJChbAUy2CxR1atXzsGix/+G8IB/itDaeq+FWCAv6bwayaHd/CfWPgLT7UrHyYnHXXnG5jKji3vXI8A8uuluUIV2cQX5SXREK6rqTiNYwYffgYrN9224YF9DH31wosm2slG1n3ZoAv+LK5zrn17O3cljixHWugE1Vu7I4qipZptkQzQJVmOGekCgw1qWe0hxxMav71ztMPIx9Ucj9I4vuZqA1Z5gMhGXli9+R42xEKLSs+w2KelccFam8WYZuWb1WTRy/t6UQt27joas+W9/v3Ws53V8kDj6e+UeB2/IAeG5FGhklHkmFkGrJYcvF8WlYUp28HD8E4VRR8SztxWwttCao2wqCJtiCZdPUSFfaGp4xhY3Qws0A06mNUw2OwuCe1e8+WOgiwLVqtcWLQerdqlIa77qV9T+eFzhshCHmjwWdasb+w+HF9K3fkb2uixvrCRrlCnUjS5gWk4IYTfdNswbs6EbJ/V8AHOo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMzEyNzU3NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mNWE5MTkwYzk1Y2MyYjI2OGVkNjEwMjAxOTk3ZmY3YTczOTNkZjAwYTE3NjQ3MmIyNjYxYTIwZjg1NGJkYThjIgogICAgfQogIH0KfQ==" + }, + "0": { + "signature": "ucA2V9n6s5qgluM+Kei+FDwjKph8ySvs+SbVYW5XxRxqXUZYZ5npFjUPxWPUH7pTDNW4kJfsu2RjFZy4JvA+KcgfwEbTpSvGC5Mg9ZjI8orXNxHlHVXc2UvgZTRW3AcoIf1UuSBJ/TbFb3Bg16KF3LS/8mN0118O+481nRFJlABTe+9BuDQJ2dJy9ojImKnD98Y54KH561NM68xW12A6UzZua1Zjy1htx1RFAaZFh5WqlXeNEMIFbCdqx6k0Hgz9UI3O7xJo2e24qD7GUFjdOhazY0WySWM/X1NMa019lFRLBLC1svJYhXpmlIQgFvy+xxvODr0eFBGHxGRdSi+kLRQPdyDXKy87gJtUvevEdUxUM9i8VmHQP2q4omLO0zGjSmN+p5JMAHVBf3zlFGib9DTwugRVrPTz30R3F4it7M/2Bb5g5WDhCtR8oSNFJTWOUAYYBJUDTNjqPbehMKACWbxqRQXOE6dZcokuDIQeIxCkTGd8W7gzefl6/NWL4OCfZrvzRTGy/fq/NwyeMX0ZhmQe15/0WFOKM2Lq2YcAcgMa21WTwFKq8kiXo+SkMOn17Q2WKyuqdO+Kwp8hD1XY+/dJHJxkNjw+IzdkmDR30j4Gw8X7lyaS5loAx6nV8n7FiBeessq3gLBCx8rAu0Yjw9+2/gEW5AXI1maZGvtWUZA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMjg2ODk0MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84YTA2OWRmYzZjMGNhMjVjYTc1NzJhNmNiOWVkYTU4MzBiODIxYjU0MDQ0ODdjNmE3ZTA1NzBhNGM5ODNkYjNiIgogICAgfQogIH0KfQ==" + }, + "1": { + "signature": "QdLpl2Fz36hL7NyNf0l8neBnkMZawrLLLOrh9Sr2UHp5vTbHwB9boVPTcTEP8zE59VaaqNj+Wk99+SY4cCLR/cMuFNL/rZFPkBiTNAg13/BIVtZAsTBv1Xh1/5bs4R9+bD06T2kVY4JqyPiw2rU8bVSKaKjGpITBqo3nwz9PZp+q8IJuMvAGcOmlsrHvKAWDvkhcRYPaxdAf/hANvbkO+Ojn6GcY9cVAwiQKok19EuXggFscAkN6mGgcd3cDgTMP85Ah4DsP6ch0m0gEwLzBY7KqaJ3/QY8c/DC/rSbblOLbvcnifRX0hSXnemNd0Cl9egNQuayxMsBHF8VoUuUFphl8P4yc+DY1J2GbPmg/5jUP15dtI0LewibwE+wThUkBgMqmU17ya63x7LmnBhTH/oSqOIlJrYFWVKzD8NuXRrsIvO65U3ouT4LQ1QlXBVZVPM0bAbDUbRgm4mQ8mtb2lbR3lt7a7+zwfBwSl6i0DKTFOHVQmfGwA3H4zqef0nNIpQWC83jx7oicHoqnDBa0YgaY6LPOvfP70Bcake624Y2ykbWdFrlWzJhQv2/WayVMuEhV5o1IlkdvXQNyUtL1+JQqEajxqWvx8o8FYL/dEO2DM0ow6Ami4s1MSdlkYi17YA9Pazf3sIfnYRAHKIZxgdRXOF46zjpfuQgzIQQrfoI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNTMwMDEyNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84NDc2MGQyYzczNGUwNDEyNmRjOTVhYjkwYzBiYzBkN2JhOWIxM2YyZDI0NzM4OTlmMDgzZGI4NjA0OGJhZGUwIgogICAgfQogIH0KfQ==" + }, + "2": { + "signature": "drTSZw0yqgBdkj+4+vhY/K2harid8gTgh1usYlwgO77hbZfRViudy/xBRt5xQInzAc3UH3KfK/PITBHVRIjAzH7uATwZTUAjL6YaoVAm2Nq9apKv/GRpU9wrnkqrP7AKgV0B/4ku8eoaAbIioMZlRynvochymEPq9AQWVzwUXUsKJqGskmk5/1efbKigXYAt/XLKAwobmAUJIjtxqGfU5boKiACumofQwC/sWYsZFuWiOsYdpwBjhObzCbNmMMHsIwf01nGZrqcKgAUuCOKwWOZouIAVmmStXDtuNIR0kjpntZ6ZcSCC+/5eH4+jLifRZDuIhXgG9rRX2GegbIgrUjWUJPIS64fhVWpH2Xjgp5cYBzvy7d5s7G/BCswvOD2wzTJvCP1fszYhbvgvdCohkSZ0QHbf329KPs3zDFhIj7PFtUgWgKHGK5Dg2BG/CCHHTgpVYrW74ODAuhM0j75Uk2BW8oyIcDlD9dX4I8H4uAzL7/OqkEwzyv7az7hI52JORrGZheIPEY++YqXKmvTohlSCSKDJhcy6m+PZDPlsA9i01lgts1sSUZKx6jSlcxnCffNBDezJxp7VIbti4Vydoz0j4S/IWAPz+p2TCQkQg+b1oqCUejBH5hzeepUhTu/Dm7J/8aCyiDaJmN+PTuw4rZUpX8D47eTtH3Z85beZIqI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNDUyODM1OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83N2Q0YTljNGFlZGQ0MzBkMTRlNTYwNDE4ZmQ5NmM5ZDNiYzc3YzUwYjQyZDljNGQ3Yjg2NWQ3ODcyNTg3NmMwIgogICAgfQogIH0KfQ==" + }, + "3": { + "signature": "LLO4rJJ9EGzh4PUcCX+z0NlJfMQOCcS4N9m8QsTs3x6Xc4L4RZ/3uAANmCCbsB8nSekpMyWqdyk+VfV+nh7fbt1/+sd1NhG2mB7fXGTvBnz9XXm9B4eNAecM982+pPnxllo0CWtJWUnQvP4XmYotCvYKGU5lUCaDefMJirHV97hfQ3oG12z4jW2TZqFGZaLw8qf2SYS0cuHIiuwqmfv2XTZKv+PEN+kSpe8CwybI9f1JzDV8hAB/4tNBx26GHpS5cuRFT6j6CGqVqmgBqrl2rOv3Aij3Awf6aJTdH3Bzz57qFj2k3Lt/qrzMj4OUAHhmZcxfsFpQPV5oUFMTDH30lf5xFuK71g51WgWgtJ0E4GMRpuQUrqc4yt4Rk2n7MgqUrnYeYCYVvaW0dyxQtcsZpL/qM1clrUZOHuHLwptNeyB7Fve5BLx9efBDiMASgT7nVYYuYoHwjWHbf2QEAQCFh9debPCyRYvwiT4G1W9VFVL/sWH1vueSNWMHeXJECX2CquLMzRyu8aVmwjHRLPXW0pEW5gpKctooolOgdV1MOQPsQ7fn0ZTFTyhjtzuoMDGAo2nH+G9BoDXICp0SOKfAHDmNXXHpXkFw9srBeeaanKNG5qCAtKtqnbs+pwqg8NvW4XuHClo2XOINVVmG+ybFCrQoNEjsWInChvDEKj4IcL4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMzc1NDUyNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81NTQ4YWExMjM2Y2RiMzEwNzI1YzRjOWRjNzEzY2Q1MjJkOGRmZTUzOWYyMmI5NTY3YzQ5OTk1YjBiNzg2ODkiCiAgICB9CiAgfQp9" + }, + "4": { + "signature": "yG+5tByc0cdcMbt2Eo3bVEhuH635P3zUGdGqGe8NSFjlXXTCIjro/USPIDfGcDtahxOdZmW77y2YjUE4PEh3KqDADEy4k4sLg4tJyuXk8FFrEEbk5LRz8gjUxC24XnLwpP8uCsvv62VwFD7o9cV635Em1U5kzWINVUG6yYAH7Dw1eqd539hQiJblLBQ/0DleX8UtwGyCJ5m+GndcDRkdzWHkd52AHWC5o8D2/j1VAb0fMRGLBYmGmkkshN1Co9f96u3chlRUM8R4ddGsHDD6Gz0+9T/M8negxKw5cOHKtQntKId3c3iri36W5RVqh2zDGw/wrGWAqmeIqFamQ4491+96ejIv4o0fQdovcjZ8xDW8q1krWKin659U4ZM4kvPN+x6ogmZgYgOoU7Ljjh6W9UOTq+Uk8O3y2tnYKr9YEabpBvuxA/Ip8Adm94AcM6Jh5HbfaqjJqpaknO+psMcilbsET9mqe/Dahpb/GiL29oGZm0gnN2AuLj3BKHsCB1BlRZqesmjr2t22EUOzNlX0S80n1eATlUrE2PTcJZmkc8OrLomagcg4+YRaJuj/wCMPXIsSa8EYmrD9PIbzVspjdnDH+V93A3Ht7f90dZ7XKyFQ1foD7V8vpKbsTUs+94Ra/tty8Uayh/16dOmv7BHHrNMJ3qH6SlY8ffVRnLmSIvM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNTA3OTcyNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81MWY1YTg4YzgyNTI1NjVjMDAzMGRlNWZjN2U1NWQ5ZmI0YTNkYTU0ZjA3N2Y3MjgxOGExNDRkMWRjNmQ3ZjZiIgogICAgfQogIH0KfQ==" + }, + "5": { + "signature": "pPoG5utOTFvOAbA2PYFzEwT7n+em23/rIr7mFJt/BW1QFm8oIaijWITobLUYIPdaPdUCHfhbt7vm8gGmk0kJAtXzUwOhkkMSa6sUp3b0DV8PhRqdS42JzrOeXwDCHOFYnt4k+BG9RYgMOhMyjFu++UZS8BzqT3zB7Bs+OkDUYxDw9/UEP/CIENiWjwnctdog2XzYibZXyUEPlPjsW5mLL+90vPsCgdrio7H2yy0mbBi7faXR+UvyC+sRjeivBDp3wUhYHFYmfGP/wLQYQcBIxE5QFzZFVOfLIIDwlVVbf5mkK/XuTlQeArgI2IjmYJizwIErTF46fdRqDPfF0FmRwrTy0LY2/sI1fNiVzux7r9GZwAASGuU8FcrGwnuajV6cdui2768pReqMQatswU7ZYHFPBz2nC29gjqC5Mi3FhYNeT9iLuIGZkJSOrEoDAQdqABeb3N1PZOQGHNWwD+L2z7RUuNAH8bfI1eBngpGbTTj8qOE/l5khIxi9FXihb7jISDLJzCH9ja14p/NUO5y97xtrqRNg9LIisWjGkRgydmTcczUTUQ3ZfKFdbi6xKfaor5XVYyOK9TqPFasQSlHx35nAIj26J3nKut4Oe+XxmN2OLk1R2sRxy3jbfNvJuV0r6tYUaxiMnN6uB07wYyCdEUZIWGNNkIE/qUF0wkVBC6I=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNDQ5MTkxMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iYjkxYmJmN2M2ZTNkYjUzMjZlY2QxOTI5NjMxZmFlZmNmYzgxNzBjYTg3NGM4YzEyOWNmMWY0MTliM2I1NjA3IgogICAgfQogIH0KfQ==" + }, + "6": { + "signature": "OLGUOOMz1ZZjwx8OGbUqx6tL7AG26KgeulScjruGDuXB1+xqpqkojpnlOvKSIvPYHeAjg7AVzgF53+O1YKh4G/e5KHIKN6mVvvvKUU8VjqZ6yAcCUwdV+/1UmDSqYeY5z6D2Q7f28mxFnQXuEB+YL9zjvV//sWkfiAjPADc1Q0bN/ZlBvQsfFqpL4W0s9nHaKNgUoKSSvVy8D4Lo332qWFcm8MybH08gt1UzU0HDRPNjv6dm0kljroEL9BHxw9obSOT2+YOSzYOXUbvbEjZp5qc88cWmgfTS/+0r8RxQjAD4l1YGdWpqsKZNW0eXKludWxfHqOdkTOSHnXD0GZxqYMqj1uCpz4Qa1vx3/RUZKdt0dGc5YfeF3jZjTZQa1Xe03QN3dLR90CKhI2orJ9AQATjAS+xhBG2fGLWoblJEyehAvMSkR3h5v9oOf94xlGAGfkwgAAIkDtIJQtvvoR6rtJ26P6dDeOuweGgpFf27iTuMWErlDZUnMYHLO2JQU/+5lA439nfjt/R0vzTsc7WdPeC3nl7uTlYZc9ZTrEC5P8bGt49Ui6flGOfvqynE2ofJaNBxYxHbLb2XpgdYyzoGCuk16BvcB98RuY/BBh4xDq8oAKg5mEKtQdc2ZCdqBYrXFWTs9OkiDwcWoVC3gAdV0MAVRhzxpegnsFjiP0xH89Q=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNTExNjA4MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84ZmQzODljOWJkMTNmMjc1YzBlNmM0Yzg2ODJkODJkNzQyMTg5ZDVlODlmYzk4MjUwMDlhYTkzMDZmMDUzNDY4IgogICAgfQogIH0KfQ==" + }, + "7": { + "signature": "kLWxGpUV+5KoIF2v8q/9ZsudZY3t2jWQ8VU5z1OH2/wy0joKEFgHaC720uyR6pMyNi+QxbexO+ywRI+GjnBrmRZ70WuUHtzk3sHKH2TU2DYh8iXEmVrHb1lJoWll/OeaoXcHHi114sDeH/Rf4PMKJXDhtCqxz7VxSr+PpcLvI6pP2fhaOlVsK7WZkhjVvbG7cMjRGsRbPa3EAYeKQePclvBhPIMUN/URBAhRIPOoWvyecdopkVKHu9YOoY8VETU4A4dHgFA9Xr2uBcQyi24F1JvCyv1zJI7wMaRusSawYFu/JkcaVzr4FjLdRQHYldGp70EZ73G2G84vPrnUPZnCgQC14am6gwzIzgfS+yjKRU8OXKof0L8Jkw7wRQKnHcVUJkpV8oAwl0/PbNNwGSzXHYIGLSLmO0/NsorkyNoGiN1sTUkBh/4KEt8c+1P40kg8J3MZTw/ZKr/DN5SHua2rq+vC2WKq0seu4iM+ShfPxLQRr4DetHcrCJjlVaZdAVoWJfITZRw7JDKdZU4CnByrVJ4LZ1dIHdUDXrfUmE7Fr9CPa9yCRvxn9oeJyai4ZtBi/UKpfh0/jAaaNsWiTjgRITbBljvZG9w2VtZS67AmRZgzTIkAq5NoxOiL8Q5cBqLhD22HEyZTJ+DQ8o5nMUuNAP6piTmoEvSurxyGaCdAzxg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMzY0MzczMCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81MDI0Mjk2ZmE5ZTYyZWQ5N2ZlNjIyNzg5NzhjYTQ1MDU5YzNkNTYwYWI0ZWNjZDI5YzZkMDIwOTExMDA2ZWI3IgogICAgfQogIH0KfQ==" + }, + "8": { + "signature": "itH5qF2qowGC/PnpM0c2QJx1azZcrdmqDidakHoMM49hetDxQGkHk17ABeHtOf/lutoM+BQnEOdYd5+fDMS1tLRGY4coPXCLvMjXmszKxzPKgcIZTyOrT6ErejkCePs9Y1StQdxaBnrv+U3quF8OQ8VTeAeQsWIadMPb9h7o7JSob5sh7ZKfYo3SkWeiFGpNmbMMxIWBCUSCNFkXqDj/6qx2C/olPMrNAspv8xnVkMqW4itl0tSMDY4e9LajG13s6vP+8kjwjSd6P9dSE4R6ChQeJlHdZidAotmitVaJa2R1lgyoSZ9W63KL9rDrIKerniD5RWyEry8pVINDkSMFfTbhwS778fQSZZHqFp3PBzWkzT2g8TP43oipQAdS7SlIM6u0HPQwCJX55B6KZG1QT0Z8E9GsS59HTr0Pu+q3B/Y4EPtwwidYla7hCM0jgan2+ELzYXrq9oRRdjUzLRfq8ZQoUph+RH3XR/6UONtVY5X8G3Z2YK29BNjXEjAuRE8hBPWmn83YRgJomDyJyCGEWffIIHg6zA0aKpRj/ZvCVK/idmLHw3hqzgLkX+Y00DcsxtfmKKxOnFgK2CYxi8ykmGPYP+1+SQvLx8S6C+0JWbbwQCT3r+nt6tqYf3jR9DmRrorjahx9jZCe//azpLGs/XEjJTgU4G2D4osgtwBr1qc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNDAxMjM5NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85Yjg5MjY1MjAzOTEwOTI4M2JlNjljOTc1ODIzYzIxNmMyM2FjMGU5ODE1ZDllYjZjNDYzYTViMWMwZGRhOThmIgogICAgfQogIH0KfQ==" + }, + "9": { + "signature": "cORi+5SFpSef0QrYJ87rHRRIJZmKUnnfnhT6fRaWCVYXUCrF35QKULPvB3nGfy1YDRrvXFoEiReBP0qrxbnbSn8kMZdtGdSCKt2kFucqiHFIM7unZexO8YY9pJNe/tNTcu/R2RKLuV2et3Y5sSNUmiRTtP6bDnMhLbRMAgsTRekZD7TgorPYweZOw/I3t1MigYGVBJ2/YKzb011366oUqv96KVsacZFQjpraqrxFcAhhP0Y1Md/aMWcvNwFxvL8teiMBvdkZ3YjN8y7pBA3nc7Ddez+lA8AqUp9A0u6AIiVPTBdUnQJz6EZRnf8ScbQ+Zm+WLp4pQbq166zhuKTnslp2L06SyTq8MIxM6gBzxS+D+ZxcqJAGjRLKabaz7qCtNT8G0/CV0j+Qv/EORkeowuFiL9IQq5rmQYXS4cWPnerKto+MG2yiYZbTvdtupUA46taiHILLdpjlt5cx6O1tkQi+nO2Cg30TRtY0rqGn4hCS3WLIFr7ZzCEz7omaBTfnkjvjNlskAbeB06KdhGWov31DRLyM7XKzVIKXfMmJqRJ4jRvo0cgBZbENFtbLbbc8WLgK3G+whRy21TTIIalIqpu7Pf/dfLNIfJZNwKbhxQ+oDu4vcqNMXgYRPyXM3r2LOwG+ghR/TWLnhJa8noteltwzj3KXm41gQl9CEIwjG2U=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNDc0OTY0MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kNWVlNDQwOTNkZWZmMDMxNTZjYTU3MDg1ZGExOTVlOGU5MjQ0ODJmMTI4Yjg2MWZhYTczMDZhNzZhZTg0ZDg3IgogICAgfQogIH0KfQ==" + }, + ";": { + "signature": "q1qy0QGNFtDh+ghQ9syDF+PrbNI4OamyE1lQNOfwHkwxHTVv4F1aBEMtzlHnmW4Usv6rNQIdXapltS9VWqc4dNvxkMISfOm87SMzJ1l1VV4o1PpKiCAMl/b2XLV0NT4vlHCe03WPPrRH40bAsXd58adcRkGMT8A1+E75/6UiAwEUm3uDyMvrVzkpPFl/aVF8X1R4ZkgdXPx0RUMtShGrUf+zjg5blUTHD0BtnxZuuBlWNzyKZYlHIgJDUjIKcILR4e/12N8GeIkOWadlfGnkZ4W9ROD4Q5EipWWvqRHkIHcNg1lV2KDN/Xz/Oh0ONuQej4AGeKeOaOsRyaiPQC/xcSrmNXAa8OivWpjIxut9BPWCZ/e97LoVYVnVa0m1SVe9KCQ9cDewANzizB/MmKirTALFNietk/DgpFAjSuTbZ9twyn/rHJ9dYUVdmWpgHK2OB8tEDblJgbIKkuBe8dXJHS3Vnq+pEnw3+siP94Lq1W2WZ6Hr1/ehtATC8EY8J/AHnpHxx0CF3RyB3ArsYpLsJ6Nv0o7cfkGv1nboZs/Dcs5/hNj86WxNAI3F5fVf1CHjOgPSrCkra7iwMtGlzETsTEjklSDAq/67uTxd5wz9txZVnBy2VMFAuaiPteEZ7Rebd+Ia6cp0tH4wNua84yFkRtzfXfV0MWeE58ORzBV1Wds=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNTIyNTg2OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85YWUwYWYzYzFlOTI4NjRlZjJiOTQ5ZWNmZTk5ZDE4OTk3MTVlNmIzZjE4OGQwNWUzODFiMzNiNzVmZDg1YzRiIgogICAgfQogIH0KfQ==" + }, + "<": { + "signature": "fmm93HdqQP0INAqhYhzJi11Eco7EfB1hHlViQmdN+4KVVdvjvM+ecCrypYN8saacCoNzMWe/q1fpeko+1izoclsUa+YadhFPQ1irVAi+lQjRlunQamGHR+TyqY5nzxBTCwMtygTwS33mug/LGXy4rncDhXeHAv5SdAHhzGK+XVShhbGdRme83NeC4VP1EhKZ4z7owCMq3Vpw37SLnDIIB56DEdbxweav/rB/PES/7/W1IlWdZ1K1f6Ix8hx95+0jLRsA0sMtlIe1nSioOHSbUHegVJCMrz8SxMvxEaX9QNsni3qD+HVLJjEgbGiXE3tMmUO1T9SG6HGoeathPXeGh8Kt3aRjqaFndzA8waL+ScvH8kuB9BAKKWb2R36dKG459itMu8XM66CUhPSZlL4Ak9eMGSGXoE4ejgOwoGsz/TW9geKvEI4ptL+5G53KFNykycWB601sm4KHdR/SiXh4xBin7868EevhJveQ4INippTpBA0hzAZbp3+IVsW8rVAekf78TVC+/iGp38BhiJLek4akiTscmBcPqQPV5DOTo+fszBPn216pCX3+oFgXbSd4gffkev1C6y64vID4EbdWodWNKkKQIc8pPigaJ1yZ616z7VCYWGtAYybBMiUIKDBlJRKmPDi4Y756QmbimB0VnWywa+wHFdTaOEN+JFgck/Q=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMzUzMjg5MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kNjE0NWFlNjM1NmU4MzhmZmVlYWJlOGQ5YWE1MzVjNzcxMjdhZTdmMjcxNjM3MjkyYjRlYmU2YzQ3NGIyM2I0IgogICAgfQogIH0KfQ==" + }, + "=": { + "signature": "OGepOok+B9Um/5XJSRPPLlZ1PCKkIVZVHFawYpvRzqHxvx34H6SOnfSEO0OTowrBPS6FzwvmXSawSEfxnX+xGVIIOl8aW28pZgv/b/adzxXyS+JAXKdPAZaC4RM9qF16XvxzTsB4VODMDrT1CN1LG+CKlQ370MxI0iEhQA+ThW4MO/JKJZZuAktmIu138ccQzpdrL+uTs1uUzO9Xx8/vdTcqURdHXOO8pxY0tYiptYeI/CL2LhydhXlk7asiFl81Ak4U8kuI0fd4skj4w8+BQurMXXQemVAY8o+L+yMD2JkGx42zd3ma92hJ7xd7Q2QzwWpM1Ydj2IxG7E8WFsYjRxtFwTJ8+jVDv+12YLKh+vL1O6wcTSLPdAgjPUqgBTqLpHJjnj07uDZdcnNdKh+0huRWGL9HHdrpDeYyGTa1TUxY0DifJyv3HPYYk1r1AvP18cOM6en18A9KQfQY1zqI1NnPRmXSsLVQBAE4Ue4io7V49vwhMN7D3KR3qD8GgXxLaRibHcV/tpZvaWgm3XXvrdRhPPjqmVuohUlcQTAoG6wQiJwV92SDRbIpeEw8OpIz+sYvNmjxVKMrVcryDAB46I73UzB7omzGOFxlUtjoSTtSsik/Np6N4kEJb6PUJQSDcogEMuek9SuZ13kd02CtgUQLHsxkix94sxiNqBcPAu4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMzk3NDk1MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lYWYyZWRkYzUyZWEyMDU5NWMwMWNjZDBhNTlmNmQwYmIxMjdkMzBiNDc5ZGU2NDk5MmE3NDVhYzg4OGM2NjI5IgogICAgfQogIH0KfQ==" + }, + ">": { + "signature": "iBxR5UXDqX6IC9L94nFkc1raoXQMcGNBMxjeMS4P9E8samdXmKT7ogL5CKZhTdH9tLMmsQg73w+BcNZY3br1PIqFsO9CSqGVl5Aj/S+RuHaPh2855kwIjn25eKuaBlnFH/Vcf2UeiX5I9dNekp18jsBO9bOpIKqiPC+Tm8aXgSgGZUBrCcdbiKi54m8QvGMtEtjsrxts7W4knxaifIhI5lVIrmT5ROLujgBo/tya4ApE1DA5vDgl1hBING83ElZQhNY5iDhIRlQT0BUL7eWzB4s3uLtg11FW7RR6ZT2Iuld1b1S/enyGCxb6FtwYNjSghDoQc/rcumOkzcsxEPo14EIwzGH6zv5TMB6dDIW1cTb28bv+JQMxmy02lq+ile40UZEr9TqiBdpzdufKCtvXSVfoz1DLOuCMZ5TdiuK8T1swE02QWhxJIfgh5zXC1c3ZYXwctWJtkYueCUroW07yGnyii7dd3RjTh8i5prURvVMktKc6Q28r7yoAyBBa919abPh1O4fA2NMjXD8J8BndqkuWPP5TLU5Qj6hnm6p5DuGhRBEhcYpExzpIOmc8pZ1UL5k0Vc1mb3rge7qPGLkQHvtYKeSTsLxBD1FELV+bxY+CqBNxvgJTwgss8HNecrdOxK7cFvlU5o0xlQMHQnlyiAr2cGyPGHiAJKZyOcSbV/U=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNDM0MzQ4MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yZGRlNjUzNWNjZDQ3YWI5Njk5YmEwN2FhZjIzYjA0YWVkZDY4MmY0ZjFlNTcwODZiMTRjNmQzMGEwMTk4MzNhIgogICAgfQogIH0KfQ==" + }, + "?": { + "signature": "j1Imdj2DO9GLsHcyPqDIdaGkUStlfW2ro9LkFY8W+aAxSLCkDSDzMHPhkijcyd3c/eYAs9gE+4dQqUohkDFuPqVuc4xQe/0hg8U4UjpH4BeBYQSFAF2Vq4PEgVjCqCJaLM3ioRopwQazVeNJXL76tHUJcaZ7rxbq8kN5U2EYXnt3isqKPLFK43IKPJebJ2zjGxe0A90D7NAQ3ZD+DilbwilZyaSQ6Z0vFAuLv625lczUhk3YPugCWcF4vuL55ms9/XdYW5pwpZdmk0loTO4lQYisDx0EiXhzJ9JGOuBC+IElj6msVMmPBGLExLLKSiFlco9eZnZ1KaXeZXte4cmoAhjKHZZIvJAXOzwamieqQOhiCWgczuJjvVpN1tkoTN7xpMh3inmhcM5ECmfW2NvZq+yRttlMQaYjZ9mOIysMWw8j4ig1oqAMCLilLS2PRcE9ygewxeWaaP9csZyUcQnQqZ5OHNhXHAtA6bhoe7V7E7/3AQaQ7dvn7Br+D6Fx9ORf5yaagDj47aWsKOVJnGtWxv1iABA3e/eRFwYTV1X8vVzGj37CdpRUZo7q9shhOxNJHN/mqkmstD/4fZbEvTaADed0wtJdC54tkVG+31hexfZyTeO4AVlCjdTE1pmPKr63gLgFBgm2uEAClGQLuVvQb7ZAT5tEsC3DLWDz2mYfR3c=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNDkzMjMyNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yMjY3NzFkN2NkYzVhOGE2NGFhYTFkYTk3NGNmOGQ5ZmRiYTY5NzIxNDI3YWFhM2RiNjUwZTU1N2ZiYzc0ZTdjIgogICAgfQogIH0KfQ==" + }, + "@": { + "signature": "KOVNr2VGOq0Vvt9GN/MFVxOg16XXddf/6FHEIwTniOPUvcZtQyojBwxwrj9xwIJo2OZUKMlHjQh1t7KjZP94mhOeF0cBOUu4OwqiRSbsSh046+uOsIma+EgXTsUgplVW3RgmhwqH+zFQxE27p97Lilg9SQ6i55udBjlrKMLjlLUXSPphjpQIoyfC/7RiMS0A5r+/TLiu57u/DvizmPpiyXEGriz81gfkPsZNRrHvL5PIT5Kux0OOJ8CtaM5O5xWCQyiqq2/6b3u0llwlAd/1S0RH0As6gvTF5hQyaibHB5j6GspiKxwjQ6+U0eksuSS80CTVLYrXQy4/glfp2MRpGKjiwg7rZ9ceEQ5aMI1ABfw5vBr+r1uA6RPPH3r7wB9v5371wUrBt9ZzBQkfyHJcbjC/wO0XT3bW03oCwPktolW7buGrnLzxYHnyfQ0+PGtCMu3dAaaimibB6N6x+h05hBn8fNJ9AxD6WzpneG0vJBgnvxRi6xFGMbb59COcwnK939uV38WROLzOKpNUtDKJWl6rMyA49udNhAXsm4RChEBY4/JdStdJChggL+5TryU7Qqa3eaEAurmqfDHEHAxoFxRLVP7y1TaZGZs8u1cb0WwR6H2HDF59kWsuzQmuuoc772g8ChdmkxBaDNsil+wvVSAdq3qaGC/ZhiB19ZIBFrs=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMzcxNzc3NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80ZDI1ZWYwN2I3OWEzZGY1Y2NjY2QzN2YzMWUxYWFkYjE0OGU4YzBjNDhjOTg1YmJjYjIwNmM1MTczZGEwMzFmIgogICAgfQogIH0KfQ==" + }, + "A": { + "signature": "hP7FK/UmeH84YPgWvNG1qaiYxk5btC4p31jMUEt3Lva/UyV4E0V9b/joWHCAi38BNHXmWuTZ15VEl2ZHuZkXRMeFV/BZD+6lY5atEWQNbuVPLOq2EaAY9v7F0EFo5kTn5JqTCCjqNpIJu0jCmA9X6Je/mtq+7GZpOJzRf5SSn86jucG/pjyt5A0cNQvR6IR0meOCEubt2QmO1vzUNrCkx5/wa9HB0E1u7e1u23Vfvjw9BGHl63V8cwMcH5anQaWUBBrWoY7qLzMB9EOnIxMWCbSjrDoY1RhIpRHSgmru7UhjeFFrrd5RKTIIx+aAcRLAPQ3Xd7t8b7QGJNhXqoawgkHq8ly1cMO7yWDGiguaym//YX8W+p0N3kbOtjRBK1N0q3uKcJB/FyZSJTfbBUAjCim7Y0Aw4cghRISrhnl0jEoLhge1d9xJBNFqBaO+E3GdU5fjUXn4n9G89Zq03lxKYrvg95+UgKZD/mPiUD32sTGk7pKBQ5oYFJdVzXNYG3rUGn4qDbRuMUrFLfFKt4NEpmUd9fy3JKy5x8sTWT7NCcc5bnbexLq5Ng9x2LjH36k4cyzTNWpQhGHMv5luNAqkfKnZPb6KMKYy1MCDmrxz6pah9NLPkIT3ijQMh7IuyCVkTn77sgEC5m8djkkcHwhnXFhWuEvOPrp40h5kRvJcti8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMjk4MDMxMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jZmRmZmFlZGQ1N2JiODg4ZDc5OWM1OTRmM2QzZTgyNjgxNDU4OTYzNGE0MmMwMTE4ZDI3NTY2ZTIzODhkOWM0IgogICAgfQogIH0KfQ==" + }, + "B": { + "signature": "iyrzWKWmOPCsIt1OCTL52In8UYpDrTsv1kWlPCLavsPll+CDXJ/j+ufe0IZrFz5LDr7w6eN/IVkGnToFrX+JSSzdDLs8EhRY8aV/7v2pHwKoHgcVAcqeGO1TMFDtGE5Yky/+co1TxN6Ud7GyxPpbZdqymQCSzA29WDNbo9I1+uuNrG3BrHclmDb42rywS/EDpEFw6HXE82IhZ8768M/zKxfHKTT4DWJTmCiVD7qA9ejY94BD4z8XhdG7cGR2g3xQFJFXwJP+YW8KaLmH2mvasDNvMzk0UtA/yvDMpGtpdqBSG75a8IpJYphyqZ5Ql4bmDmaUobMf7j2fIWiIQCWVFRk66Oma/9AIs+F6whFa6+tIlBnVo93FkjJdrkymVKlOMFXAfulkiEV7FJRmX1+SeCOhvSfuYMl1ffcmgbtvAS7daE2cENV1BsFtYGTJdY2t3nbPjadJboSxngpv48QBK3DviBo7US7TUgq7QyT8Tx8g00NKEBUuex036mgVwbSLTdVzKJtuPDX2mZi+jpIC+aNpxpHkB8PmMJbD1Z75tuRTmKM4JWEcJ4jGBrUQ0By1S3MphF0zUMi/bOf8Lws3fjM6ZNH5bcFCB0yb3edgQC9sEKTZL9u60JGatCfZb6J72MZ7YRtVjRbmYHAJSjsrBKlH0Lcv+633mL3QCa7jvkY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMjkwNjEyMCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84MmM2NzcyZDA4MWY2ZWJlMzRkM2I2YTAyMTAyYzBmOWZmYmJiMmU3NjdiMTI0NDVhMzdhNjEyNjZiY2U0YjhlIgogICAgfQogIH0KfQ==" + }, + "C": { + "signature": "W+vxye/l0hmhFxCxcokSXZHrOyWXM24uRXRlwEgqi89bAiYQu43kmWXbZ79vCHeWuWjawWRnE0kyC3a3k0An1XRTn4s7LmcjU0vwmDYlFv1heWvqRdmpjPxF5d9Ai42tij3C8ubR7LQFeoHx4li6ji7iIim98rs2BQyMSOvRiL+6fVxj6uxfl7F8LRwEcJhdtFqNRinIZ775tP7OtTyGpAYzxy2zc9eA3W43QrL+aXuqHzzeY/bWo2lYQCtyykheXRBCUQrThPb9CBRERoDwad/R/5wHEzal4xYHKBq1SfRMz3ghP35L60jbbeJZQ5SE8A8gWnKh5mG3rnJ8GhimpfyNkkSvWmhvTUxeLuAFHDsMZRj/IJtJRScc3DWkmKbEaYMvK6duK76aQN+0rnMNoekO9pOScGzV2KuTOl5wCUefFJc2kWmI5jpEL75ffmS7XIDw83GSIRb1cyPqxOZsVM22DBxOvHYrDp1US6YMA5qyl/2P4zwQfl/isLnUXbyrp78Pco4mf51jj2Q569fF2DoNLFbq6GimDK4rQxGoT/2OMFSpTf2gjESjAHsqshkglmSLTx7C1rS93oFmrZbEgLZDw9QpomGOe0EaIRA3FUFYgnfaWXCGT3VPIzNmLZQxrkwjhUco9MVTI/8L7j4bu2qT3GBoBQVgYSc1/Vkdxw4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNDcxMjY4NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hNjI1NjNlMTQ4MGM0Njg0ZWE0NzBjNjg5MDU5MzVjMDE4MzM1NDcxNjVmZDA4YjU1NDE5OTY4YzdhYmRiNGMzIgogICAgfQogIH0KfQ==" + }, + "D": { + "signature": "sIfE1HWEX2pLHhNNhU+nAjwNr0/l8CSmi3yYboVA1eg4JJ2zlM9zC/7vD37D4ojoivSMygIfijo6KnWza57ysaXcBzaLKm0Ob7YQbJinZfWuwW/Ws6x5Gp46GnecpczLa8rDC5N7ypqZ4T6QvcVbK0r3KCStlr0VXNqw3b44dWju3/UwuR8mT1KLZ3tA2zat2J/W1wM+IHaLUQZHK2KjjnsQP2gVYsP3vEjT4RuxqGXqvwW6c59fXz4l+KH/tfIOkLEzJtDZLMVYlg4fjvYRgiBFEwtIAVruO9ZTReCQ2ENGlniYMiiP/RG+8yYcve91dRRUhHZQj9SUxCeWufOcF7ZfzYgKmabKWHb05EtI33SGpJk0pCkOCOtmEL6YV51x9Bokluhw7gDsMoqDumTI2mxTVuWELoGj1VpYSMCp5HFDU2YvTTcxc41288WIWZw/CgcQgQasi5NCxRmqw83rO58rCmFyM3nh1cH1FB7fPzBcaqN37tF792wNM8UQHP6y4lYhxS/mlx7WLSPaJOdJE8huUfWM2wE+3ZchMXLGwb1XB9y0IWNV1rrMiBYZBvmjT+SzxOUV9AKnb3H6j0kSlvuojWiI9TZfHOKgmieuuWw/+XZJoyRivglzAYnjpowqMCZhfM7BuoR3E/1wMd5xMM1/sXNb4rei4JP4kQTpK0w=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNDI2OTYzOCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82MjBhZjM5MTMwMDMwNmNjODQ5MTM0ZDI4YTdlMmEyYTJlMjEyYmRhYjhjMDYzMzk5MTRlZTk0OTJmMTg4ZWIzIgogICAgfQogIH0KfQ==" + }, + "E": { + "signature": "FvEyqfbf1SWGzqaLED9VivhYouJZyJJmt7fqn9T+g05eUpq56CHfXwrzl3A5Nyp9j0IQwMcvnYya4tttOKTsZMzTZ45FF5SqGAurnXjjnZEYmeoPFFaZtv53dhMKvbiYIsKyTnK0VQAhmQRcG7c3aK+sIQc5pe+b3uytqRaIrR+p+BPzSDB9JCV69FCGSKVJB4EVxcLg/v6vUhMJrX0UItV2UDhnvKVRk25v4qTF9+Qit5/cMj/V8qBzRbW/Ry7B9cJKPNy2wH8/0BgcbZhUxCuNXtoXFFUDFlEnW7F1Q21N3SBoSbvevBA39C3XD3V0TT92gcLPVQH9sJn6wwRk7VH0HBPR0tejtGNT0MUyTrFSn4voU/LRkMP6P/SehUhP9zOZZFxGqIrjgLlhpCvbQFaQ0P+HFWnCK9Iko9As/Y1omU5kTL1k3ryQad4vWbqzqEF5YWN0BW8UeLT8puzhfD3hLq7gJM+//0TlFveaWXJRvgE1GnwFJCQFvEzZEq1i7IWm+Ul5EZe8Ygp4saD44C0nIMlqSju2kWWQTVKI/YQgCUTA2E/GqhN+XJQAofaf0cB+8LLaaTETH4OSjg89lC3fp/0VftHiHjY53hVDctGI3qHCNyVCc3DPLovzPNX5aWUXwXocH+LbPZBGKPmdAEJJXx8qKT0oW76EFeZCY5k=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNTE4ODkzMCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82ZDE1OTUzYTJhZjk4OWNkYWI1N2M2NTM3NTA4ZTQ3MGYxNzhjYmJkNTM5MmEyNWQyZDdhN2IwNDMwZjgwM2UiCiAgICB9CiAgfQp9" + }, + "F": { + "signature": "MXThVRGuDWpbC+Uk9WpSQVEhuWvADh4PIgi283rdZtOAnFkuVVBnAYGwvhdT9kB9dwu8BzTPZh52U+MaiEhqW/p26UOtb2vzsNBneQeO2zqlk4pmN6rOW054UXZVea2iHOwwl/l+Og+meUoaUrL54InTKpqM9UsP9pPFHhQ3nzvmLSE/qw5Ar3xoumllNP8s6Wgt1PLu6n3wRH23BMzDfZRQ+RDJLscJ2pmu3BjriMVxADKP/di2hhBee/EX8fZzw5Kbq+J4rzBQ/tTpGouPoqXi7qYkSYPbDCT+NFz+mANHxJOSlYnV5zMNKhGqFiWCanWZK9t2ZjAwIwSF9PYyy8UwbrR9/lIXZY8kkzc1mmtSyzeYf+svxSbLK1bLxRuF8Yvsym/5T041OsBe667GZCyaN1o5qb8M13pyRl6AvqkPjrRx4kvrxzVfHP9H83mRdU4EUiaC95cYfWsbj1pypKu9qAmjM2N/7NKreokF3EralG8axQGSO5KYmH42O6Mg0uIpdN5l2LVJjI6Uu+rBJ6Cx5ofOweOHMR1zatIOC4yAHlrpH4P8rONlwJMMcSmiOun9jEq3tbFDUm36FcxXNDmF+QPRc48aFPeiTS1XFJLkd8zQ4zbUwJRejlmB+yDnf3bz07rZAXZ0+rZJDNch5a8UfDkh6qvVfIem4BLuDI4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMzIzODA3MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jZTA2ZDczNWRlNTU3NzJiNjZmM2QyNzkyZGUyOTBhZWU3M2IxNjJlY2RjOGFjMTczZmRkZDQzNjY4MDg3YmUxIgogICAgfQogIH0KfQ==" + }, + "G": { + "signature": "DvmG6KbSDtL996uK7AASGJzqDulB2Ma1fCX8wKoxgrDRGOObyoBPgg0XN6mbkWyWi6Slhb4Cs2kqpSTqZOjVumEu/yJPTfs8559ge+UU5rbt4FmdUhvlRi8SPSAf1JwQITqFwmqjB/fLu17+3Jbz2oehONabUIQblzKzvS05q4rQpiK9jb73rkokQxLOwJg+hz7cba+zYFjb3sBOut99sA7Ef2YN93oLUtnB4BlECz0TVAgKbme6x8l3IRAf0YnHRSjvS/HAFOO7eVOVhRAgObfCGSOJgp94V5e4qJ/wHVrM9HEqwBIMpCR5jqR2NEgxKl9LgnQ3zb3l2I0pZWyDBWhNwTpG6oaNYsyzmTHm5sMQjbjBzSR9DGQB4aAzLoFVg4Zg5M9M+fP3rR/I+AL8tVqPGI9vvUSTS0Iaf000TiyTQHIUrchexSELm2i0iMJUJ4Z85g65c/PfMGTj0Fy/dQM61QlAeNBz1l9NIeBhjdGW+FI1jLlj8gY3JZzD2tuukaWEE3u2iCySJCfN/RrefwPzX92RkyiqbKo1h6xwad1iLOcsc575SYL0bI4VWRYQO2slgILCHt1tztiHKWEpxuEQhGABHgNVzrrqlB5D+8se1UTCE4/0XrvUcQFWDoBVXnYOIXGys2JQfuYjej3QQiM4Q9GS7tKu9rs1oudoND8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMzQ1OTMwNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zMWJkOWU1OWViYTcyZjliYTkyNGY0YjZmMzVhODM4ZjdhMjc1NGZkMmJmMjU1NDYxYjMwZTBlYmQ2OGRkMDdlIgogICAgfQogIH0KfQ==" + }, + "H": { + "signature": "FBqFoPg0+zwsxpxzvHVw4PNYXktxjgI+qC4JAQ7vsdZ7sIHcL6WDCAOGVnoHIlmNEdvu0mGqfwV9wKPs4qHJje58ZVITJRiEMQ+D3RkxqLbhMtYA1bnv+ksN3efi8oYAgB8lby0Cs8UGseVpuw4zCjE5xUjzWFqUKemBvvT5O3sEjTc6kUT9kfAGtW24dwgbu9K+XdIk/uRnCv/TYE7QZCU3l6tWmfejTHjI3OEgRYqJvQRztef4m+aIyDz3LWiI1cU0zH8nepayuSk2RAHSL9Ud/PjdFea53Ly3IKUNtUhzvvAXlohHUAGjCZrvu3Ind1WMeKHYAlfH1x84OzS7IKLdi3SYwG/n/HmeyczEgNeB4c4Wv29TxeZB10KBxO26WLiWX33O3d78p8ptHCCIGrvXa5SbWOnlTeFJWjHE7wrlFIR/LMbXSG8/AIbHpacYLPBqpVv3Fds5x0WEzH6jSPDqKxaEsL8DfBu/f2lOGif/dwXJKgrIXVqaaD+WxNea9dddn+iTYfBs7JS7cpqXZX1ewpxMvXuVu9gQjqjqi8lyn+V1Jk1cQvZcSCk8tGr2/tS2dDgncrZtwts6R1LlMdhf9KECQ0bDmocmF0UoC+EZp5AK9/3s1j4Sz4TLinmbmp5Lf4z0GWlR3QPabmebkzkHwHAZRPeDys22B5olltg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNDQxNzY0NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zOWJlYzRiYzIwZDY1YTg5YzQzODYyZTNjYjQwNTIxYjNiYzQyNWI2ZTRkY2Q5MmY3ZDFlM2NkODY2Nzg1YWYiCiAgICB9CiAgfQp9" + }, + "I": { + "signature": "C4qz10ZBQLJb0uCYFNQxn56G5+kySfQ9mWC/ovvW/r9yyB30niq1e8XY9WuemncPngD4vl+YhIjXVMRpuQPbjLfzGB+zFrFTAzo/iHKxBZyZ0zaGcYJBVqwLB3L6sEQveNbDtL0R4fiWOkCZfMyoMBnJ5noEUrKwe7Y51YtbqBRVXwkGVFydnD/9TPr0ZSYyuhlHhUXx8eJBN5M3fHsHeRNY7ROfZOKn0HN6npn7WhfqKyHdJXkMdMJ730hFZzbAL67RAOnq+OAKebXaC+HH0INFw/Rt621CZ9/R/84ObvtnSQWY09rO49+lnUIezw7NyUtMJInvSkWAV1TWl5EgbyDXX8igi31iqUcny0yDmbV2tiBXU9ZVHFbv30tztek1pgl4UagAbeDhV7y3gjep4TuopYukyV/uDxUnt55xbGbnV8GZGRkYz7RTszl2AW1YT2E7agdM//KhvNlHdasl4nvSUEgXRJukgb/l1uDLukT0G39CxgIxnHz034SMDC9IKiDPxBoMYCFp4nXRui8/qb/XRuxTuOtpAKWuF546wZIFbdWfE6FL6xpEJ5KT+nUvsKJ1Eo1Bj4XW21f0EkVHr4VY2jTpP0czUCImvsE68vlWi6zGUyb9gdMVIouE66ekA8Mmp9avMW8Gbb6of7kCtOGQqnixjT3c69aMatKbLmo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNDA4NjEwMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80ZDQ4OTNmYjYxYjk5OTFhZGE3OGZlMWUzMDU3MGJhOTgwNjExNDA3YTA2OTBhYzFmNzFkYmJkZjVmYTA4N2ZiIgogICAgfQogIH0KfQ==" + }, + "J": { + "signature": "k5oxYavug7vetTnOv3hcWQvIk9b5udbQPymVGBJ4N42RQ2ZrdTDAa0s1AQGdnncyI+6lnSzIb5uiLiexOw4nD3swCUpUPS7+1yUuYmdZUAmMG6jwk2Wso8Jge6jWeCV3NlvBOGh6oQmrcKnSwAUsHUTMSxPXgxCXu6W/GDcgUryk4x4ocQe496Pn5JiJ1hnLLFmpe/tEs6ihi3HjFS2bNedOeqxcQ7sI5lAjIO0QL7vx5EoxtomT6LZ+zlZGB6U1Zwr2NK8r7Q4OGOrZOU22sBoiDyu34AyABrETtRe7w5q1oeuzXX+XRuo4sDm8nzU72IF6NEq9mi3hggwgWZuxMeiNFNxMPi1DNtXlv8LI+SQW14HsFfW/Jpew+r3iPR51DPjcW8qFF/yxQbhj5pmaiVfVsr7DXyJ0z+fO297AfAjydHFdQJG8BjfVMAXVeY31vn/YQfzGzBGskm+XDHv6a/oDDQw3LmLra7TawNuQam8DzWzNyc+kBNVJ1tnR0Usvay3NH6P6AUExbnabfKxto2zm4Y43ivVn8hJSanQdoMUrVb3oscG02gRHEXUZ/Fzs6hYJ+t9+eb7cc2x6if0GZWhlIYMj4AqoMQvK8d5wSXjUVbNd7dXhtxnauV3en8gjH23SqoCz0mEFk3hH+rY8GtquCJHbwTL8EWcajMhr6iQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNDg1OTI0NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83ZTg0NWVkMTdiNjU5MWY3MzVlMzExN2E5MGQxNTBhMGM0NjdjODg5MTc4NGYyMzgzZTIzOWU5MDExZWM2MzZlIgogICAgfQogIH0KfQ==" + }, + "K": { + "signature": "vZxzHtUSuX9WNSp9XIZTR+/3oFdey3lg9oQicf0PnFEx2WiVeVKTmoA9tIC+y73uImtmLewBeyl5iCVs6gXFn4afMFflzFpCgby93seMqgQ0e+jHwRFbtmIzdzbasMXmvFf3E9nGAMiKMZlpRbD5AeFNZy68omeFJTasPUVdbTyuk3e+JbEnSanZ3TkL9t9y552vZ0ieSrevOTiZO/oYZvBs2Hz/gdDzSx5MSDhgfe7eDr6ayePX+D4cOG7mR/n5ku8+A28O6Kw9PvsG4ZhX9+Ydd9qOVPpiY9jD21TEjDD/zfYSX1ImXkbfTS+J9I9XO7kc8RR9QegDWh7uOkhIUctzk+M4R8CwnWr8u3fJ/gmN1k9fJOY12zelXgJik3oPVdnLiyJRXlRNFHY2TCI/B9xujpTmoOwRDcSG9ABoLHwUMW+7HBXSeXBze8bUeoGpdA9Y3DLqQC4QSyPybDHdPvPzRdc9/nsA8UhJBFJrw8h1d400qr0Fjm1/VJ+aUrxgirgACohp5ovT6e0LHO6GLqS6FIfi+aYdp5BHydfhPn9A0VkDmk3EGjfvDqmLf82O9sZb8I2H6jLIomOOCmDOfcfWU06pJpdj3yLLCFOFSeI5ivzzvm+juZuloVvmRaiD9spq+BCZy3sfLT0yz3xL8O33KOyZbcg4NliBIm7djSs=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMzkwMTg3MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85MmQ1ZTNhNTNiMjhkYTYzNDcwYzgwMjk5NmFlMDFkY2QxNTVhNTg3ZWQ2MDcwNjZkNjE2NDAxYjNjYjM0MzNmIgogICAgfQogIH0KfQ==" + }, + "L": { + "signature": "NxMR/opqCLSEvEuHkry4X/yksc1UGm3NF2FQJADtCfLMbtDOZTekJ252LEbtioAQFJa7jYbI3Gre0x5K36v17nDPnFMSKDLvLexZL7wjUMqrJ3ODafQzvVWcinDUuRz/uBuuKZlgY6YS3OQin1CHUNRRHXDTM5qi1+1sYWUd3XSpKf3g/fhhnCNnpUy15116ksGon9qUCvJFTWytHvxILbjXzUefJ/93c6sJQf+u6fzbP1y3T7xiE0a9YpbO4gUs1yzZGnBhcuUBUkI5lPFGEQ7jSH87/VmVXsHdtOpmLHw8vpHvPGVa+2xyUYcZFGwbydJqfAK3NJVWyV4OPfO2x04S4WbYWkmMSQTTcADVMNwcx0Cm6oIBI0DIONXzH13vXpF+1vnB+bvftJl+WkCCZijyDm2GzhVkAxtpWAxsOrudqFsJUlpUr0++k08Xw6ojHPnGYWvz0Y9mVTqh+Y8dMIbpyqSVX/1q86FjL1yrbjhDUYB1XhnpPboU2C//0Z+hRceTkaMFbcCpSPPysUKBLL6FJiEvlo41PklgCpNEfALgEVfaSBRyQynUP1A5XWAZ6GmUd4Ue/TgHL6+p3uXVOOLH+wm9CPlVQt0+vGFsBT5x/Mnuwmvwc2q/xCdI/ufYCJEHlax8JtMp+3HUo9GljTTFOM7xvyd+QRmUtjPbshY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNDIzMjkwOCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lNmQzZjhlMTkxOGMyMjFlNzAzMDkxZTdjMjc5OTFkMDBhNTg5YjgzM2Q1MTRiNjU5MGE5YzgwZTI5NDhjMjcxIgogICAgfQogIH0KfQ==" + }, + "M": { + "signature": "ASZkbq6u/jSLrx/VoiZs4e2GekHmkzNUb76+KYbuH5H5nPuqgvVdsJMJTpwHlksxBhIGkcwfpXntNhY+Fxqr+R4uuE6TV4cyiAnxXROQduButWMGZNClpOKQR2ot+1uO/+56sFiLN624zdohhkOOBNW3lp5F3wPisfjR7RDrsaQjo8fbcjaZE+QsxfOgZwlJh75tMg4xeHn7er/Zz1Ta9/VVzYcScdGT0zTUFrllNP9wSxygBHQuJwuxKvDSZln34OkSx7wpU/h6+IwZ0hPHS1MsU7OXVIg4pj+q5LhLcOjGDsYpufKiHG5D4ueJLY7lOI0u9vVL6sYaLJ2sgJ68W1ZJwD9TiuX6178cTsvuWw+VK7PWnabYab/26w1ti6YkdXzk43PLn9fDqWp21RyP5uEZs9H7TUBWXYdAL9/xMn4eIDm5KKGOEowpn5gFJ8p92sVN9LyZPY8jTCUsWB5gQIrdhDVbL8O8vm81cM0kswhM4G26/8WQZDiosKFSJoxjn4ydZV1rPSPMPgWs+RUKUhRFfeKYBCwPPT0nFVZsL+9StfAE1ujiScHtaMu5Cp4qAryCGtU/Ivbk+cdUmazNQgNuF7qE+6tRqBCwkdiOGhma74pv5PdvYvrhRj/0A2HbetfOM1TBKi5BR5Swxd406EwaqbuE+7xNKw2g7ZB2qaU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNTA0MzAxNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85MzcyZjk3NDAwNDNmNjhlNDM3ZGI1ZWNkYTZiYjllN2U1YjJiZTI3ZDA4MmUxODQwNDA1MzhlZWZjZjllODMiCiAgICB9CiAgfQp9" + }, + "N": { + "signature": "nS0qhxzu1+vLvbqlVb5mj5RkGRYDPjidpmMNbbR2NVVlSHuTiXa9zz7n85EhbeBkD+Xx0FYnMQn0CKZA5AOw7OZZJtdowUFXt7/UjsSQiS6AMohpAwS2EvPm35ZAd5+pGngW543ySsLefnN0urn5H/5KCQXR2c1pub6PjuTWeO4SslgaZ2FLWh5qJe63Gt47tkqrt2Yk6GN5EXnnEvbTxL4kgLcBo8MoziO8We39GIS7nrUuzjVOhbdumZtNg/27R6LYrBhlnX9vzOL69jyYIvVzlMyFBsZ6RAB3YL9vqNz2mEJgPuJfH+1mscjkBcHF1mVHqPRkFTQnkbx7iyMAh8ba1ezn9ARlUypAqKLY/WGAqmCSpNsCFQQKC86N9wy6JAD5QKsuZXrjvbkvYTcMxQUBgLTtW/2YzeYTm8LLD7OdN4qlcATT+yUHRG4fb+y1wmunSddWTKhh04SwKNJ3M+Fm3ADuh2MlUP/nWjJFA5e7wsSWpSIEr9GdAIj0WY2ahuLMyyIFeH94i6DJuREsBqNx2wcxe0/HVTEccUx355hp9FBQ3C5x14JHWh6G1opqCh/y7h7ZP4K5EbW2QJFGYFNDAiVQupiUzxo27IjaofEBCXQAns96bSGhAdPbcP3c15OTe/Qx9PuaJkFIDzEM4ws2ZKVLslp+TCbOBAeQHjU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNDU2NTI4MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84MTczMTUyZTY1OWFmMjY4YmY3YWZlOGMzMGNlNzQ5MDcyYjliNjUwMmY0MzFiMmNiMWU1OGM4NmEwNjg4NjI2IgogICAgfQogIH0KfQ==" + }, + "O": { + "signature": "u1UnZdt8oKjxN1B1W+WcJtFVK9t7kUwPvWpZ8lYZO/VK4f3Zk2WIw+JtoXecsx0wXkFMng5sgDIp8ux6xL7ts8l3x4ZZqD0ehiu58an4l7+kqmTmK8lVQ+DGyNwJnKyqYBAa2Edwbnsp53Ms8QcM7/aMMI5u7dnL66RSxS7DlTns0dnD7quPPPenanpqrm5Lj4lUrpAebkZNfFy4d4JhN1z+Ng6BJfA+yyYfq8J9e7U8XhX8yV1dSw5Q6EPvhWHRTjHRIX5N+3cP9Mhsi2/8qQMVMBRvJyr7Q7BbVQgDoKmvcCwqNic2w3Ghvy+4iPRPeaYgybUVnfmeZYMbLkf5bUrFGTPQDGNJJNy0zCX6dLI1x+mQVIcIN7x+CiQ0Tk5i63ZX7fl9flko5joucdBLt7TCYN4V5I0KjcggoaAZq/lGqVMEkSBaaYy+WwRPkr3oh7nBQCfQmdMH/jGScWWb4OzMyElKM+llNEPlYvvsjIvpqie9eUF40Lg4/a6MC1gQyCGcAOFANHR64mmCw/PGKyKSPr+J4MwJKK1qHvAT3rGQHgMXZ2WzfvnR5dU3/wpKgDkPQTLyHM3lwl8rvs5zCOkBeTR2M7e7uqlphovgz+TQQ0+t/g8B0lJt4gSanhMBTBVBJxmMP15jWBuEKN4pYFMsE77Rw5CsxosSYOTU8Fk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMzU3MDIyMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xOTVhODc2NmU3MzNiNzY0OWNmYjJkODkwMDRjMmU3OTZiOTE3ZjRlNzM0ODUwYWI3MTUyMzgxNzQ2OWY4ZmFhIgogICAgfQogIH0KfQ==" + }, + "P": { + "signature": "sH1O7JrMcJPq+qryiudchHhUj/Jql2p53VLqIsBpqrcrvsz5UZEENwu2ZUdi8BfnT5+BAnbPx6rIgjVHBMfyMiwlgucorrsvO0C3+ln8XLedPk20/KBMdqFQytP0AULmuM6ZogymQrZOugONxFxFzdX53OgoQD+omZwBvc4L3dr7V4LXJ9R5rv5m2zoF3G+KmaX9T8g3zE08koAyTPhNRNUifrm2tgZikhMIEPy8x8GT7xdU5+rPNQGehl8Jns14IIQ1ZB22dc1PPX9wXskw9YFmUr7YPLl/j/S5PWwaQqMCfdKtg2Iys8v6U6bOP804YXKUHRXLDHctXfGZ21gn0KZvxf7nX7pb/ntR5RdZjLhHh+QOEQVcGIqUxJ8lv+ub1upVdv0Y8btxbFACXM26KBEn5xi07Ezj8ouR7wnTJRNOZ7EqbdxkZs+pyknv8TP/ICeqE5HtnNHPzgFhT0eXk1rgQMXPBDxG0f4wutNBevQNDQ5dW4tFkAb/EL9fG6rZSs3Jv/DkYyafvKQ6SpYfnmfxfdm2NR2R6L6hko0Lgyuqwt6FJdMC2eGt9eLQ0PxouHD0FNZpxWo+pRNbvLGnj9CDwVEk/chootDNimmjugRv/YQchE8bC6ociVuJiwuyDKmODu9JCh+3o5IWTIjXGRqHXvGfkqAuRhwD5MjUIBc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNDc4NjM5MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81ODVjY2E2MDRiYjNjMzgwOGFkZTdkMWM3MzY1OWE2Y2U3N2FmZWM0ZjEzZDNkMDU4ODVmM2YzNTllZGU2ZDg1IgogICAgfQogIH0KfQ==" + }, + "Q": { + "signature": "kk150Cd+4BVH5Kod417spFBK14Of33sZxIKCiMqHXDMxEorJPV6ARBOPR4Ta/ME6RB7wNjJ2SQVGvmpWsyo+8UKx7AxzWzh9PVV6hZvALSWBZR7hSfdkT8YjhSm69lrrmAQ7MgjVs/imJrC49Z0ZsVHPkugzDr5QBH5Nk+IltyxmrMNsUBgUcIS4AXS/6uQVcKiM13f/T1QQrTeAVCx1pLZpnPdl/TFFonvmt/ymcq4l3wGSIDNdI7fzb6+T5OTK8XhcOQ8J9kcLqkztmOhgHeAzkjQq4mTUHQLM5wXwv0+Dsk3X27c0WAVJiunYocouPu4v8ZlYv5uSdpa/JASnMAQKhb6WPMySANJZQDNfwr91+5ZRAZBK/QZ/b2mngcd8jGsU4/BzWzlaxN19eN5t1DfB96sNHiyO1MdoniC5jGTSLaLVSROzmE1SfWDC9Kuon4c5xNagDNyTcYSVycUo/UPLcZ6QVm03n8f939C98Bl1sQkOavkXiieyzUnvLDSq4zcB6HYFWNGfZ577K8bTsyP7BhEXlnV6c2fcdDRp1bVA0RcdfA1XIiL8iKFjU5gZoXzVNSZqJaDalGWSg3CJ3K5Bw3/UMdiinONGOD3TBaXGp8zyPaQBVaYi78M6nyLC+8ux8KYAE8js2TbV7TcmRLREQWMDqxTJJGzlqwruqIY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNDYzOTA1OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hNjY2MzgwNzQxOTkyOTBmMzM2ZmE4MzlkNDQ1MjlkYTlmYjA2Njg3NGFhNGY1MzM1OTI0Njk3ZGEyYTc1NGUxIgogICAgfQogIH0KfQ==" + }, + "R": { + "signature": "sXpW7LZyrpJDojkDOeb+5F6J7qFymNJ0igqM9x9mFvLyersCUiwjxjF+ujBsT37wd2kvmDKQPIpyBXkms3cfsFezkuYq+kcOWrSD0hT3IyRKna+D/BQuLVPD5B1hLLMlBXtk83iEB9Xj7AxeFP5E2OJgyWUv0YtCbU/K1LDGZUqeixs33rtwDa85PMSyLb1e2dkGJs/ztW5NHOl79kX2X49Lk4eYvfbDfopDkLra07ZisFpeyfGmX5lHEpIqH0g0wga8K4gWndG0JxB2S65Y09VCPzrIHwTeQNf2rZrPAz6qxnTz5Mvm+eYtSiGUnmRiZ53DkOz/Mfv5RK+qVmAvsHX/zOKwsnLxptnPCRypYZPuPA397wPzg8/tKZCJsZwhb6zI90q/fsTXNV3P/8fGDD7m+hYOVMw361uaFHxO22G1lbXKK7nCq+TTL6Wnvkx5F21G3w6NVlvxAt936EgVHhE27s0Xr+Rwgh/JY4xvsv9IqSyVOr94FPGI35yAZZNyChSsQ9Xxzl98srL/xnTN9dh2TC75+MmlaGhza8vLr9C6DldzEFR17SaMsFzhAnNK5qq26MNZ+VPLj38pJET9MTqlaqSLF6kPcgnZFXdMHxVUzR1FFBhXjlrlbtHLrq0zLv6r641afgx26FGt1N5U+j8530HEhBV479exhhCiJIw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMzIwMTE1OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83YTllZjIxNjlhZGY2OGRiNGJmN2ExM2JjOTZhYjMzN2NlYTcxMjUxNTE0OTk5M2Y0NTQ3OTYyN2VmNjZkODI1IgogICAgfQogIH0KfQ==" + }, + "S": { + "signature": "MSYJeRZaHAKR8cD7r1tsmzQrD67C4t26OAZoNLXCKSL6AahloXQR0N404G9g16BCYV2YoNcD+VRKC41SRxa6MXvIVDvV5rY9fXzD7jOgCcWRptHAlHFxGKwvHulddsskNetpJvCM1kVpOobTrqm4sm2V4na/8aKd6BNgeEw/wWvfmiOq03/4/RMLifWXdZo2S5Wa2thI+EbA6HwZrkf0EQVL+y3LWld2z+e1lBoKzb8I5dL7ojXf/+vt3CLjEE4V8o0hJ4FwjRlsq046abn1D3bR18ft9tV5DfQtjL+lgFl2LAwHSodEBcMTVUMC91uATYB6C/PobSlgwUARaLylAS5XXsUverncVNoAIQJwj3L+vAauAGhmFLqwqLnSQ2EkeaQUFYlVrtHj0Uo+gXUj5G0W26nVhK+1W8bEj6q9hyWIdkhIbAqI+/PTDiTF/2nUcS68vZn6P5JG3VF/BxL87tjAo1Nwf2/IYpTeWM15ZaOyc7fAghLETsVNiw0qvEIYkNdWMwnS+gCybYIS3XPZqxyTutEaOrp4dJxz7LNNRa15TqEAA5Pp7SJJBOaTxCDZThN3WXjYrYIaZMBmmvi8TnrqAJ+oQqG9ObIMmBEUR+DPTr6K3BLSPs3RnzjBKCSuKpc2BQ4h45/NE2vqNoKq5olPTdbNlb3l8BrPh4B8/ng=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMzYwNjk1NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zZjJkYTZmNjAwYWE5ZDVhYjExZGI5ZTRlNjlkYmJjY2ZiMGRjMDM4YTA5YzgyNGVhNzI1MWFjM2U1Y2MwZGNhIgogICAgfQogIH0KfQ==" + }, + "T": { + "signature": "Tj9gn0xpscEoL74kYnIuchtlu4DFn7+vJCDn81Vwti8DwidnA3Ji5VWr+wD0kH1CSz0+XQZ9qamFVlDQmHSQsh54naEbGZZ5wbshiPw6q5GDaQL7WZvu9GXoYKAwqprBetrF/p5ysj2P20q6yuLXiUAJzOmm1MhZOFZie+wF2ICwE+mYvTARM45+WDiOgEt69D7EJKwHUwWRHrFb6GcKAUcjo7HR5wbCFIYw+u7Irfmy+4XcdkOq8UMlh2ay9Re/ZFpeHETGpOhDIc77S0Uf54e2BltYZdhYNQy/E/a0LA8UW4L92SuOURDS4O5+aEjyD1GMbdICgNMeWIu4I47W0DGrLpzvbwAnVVcRy2MKRjzxO3uw5e7mGVlg3DlrwEpGOsYdOZ4pO5xFoVMyaBvm9ibYd1C5ccqqhms5C3IfVluTaHlvoN7C3TYJW3TRqay57EwG5kTAsKLygXv3Jnq2aohnZfsXHJn9bM1KK9BSVc8oZOUduznyWEulWiIwOYGmWKAmVr1zd6ivI1Rbtcb+3wcwzXi0v2atCAsp1LyhtEn1O9FVDTPsm6Fy7/i67JDBbP8RjErIytjG99gy6JkbrHpxxsMEC9ULKGWoQAGEjACbEXOI3gwMeeOgEYsjvfVwQ7jneOTNTxdh/ORNw2BgXeXY13Knm/dRtceOt9Xiby8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNDk2ODc2MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xMzZiOTEwMjViNDZlNDM0N2MyZTAxMTdkNzdiYmU3MTI5ZTg1NzFmZTNhNmQ0YTY0OGU1YmMzZWRlZTlhMDhhIgogICAgfQogIH0KfQ==" + }, + "U": { + "signature": "FBHFKOxwa3ged2OroJPC7tpQ3/7BKyOAs2XagGZkGecIA46VTT3NkhVbiglmZiynOEJjH/sgx6mQkX1cmxX656oFf9cE683VHwQn2tYsOJrnSPPyMnZSngyaTnuDg/FRoc1KH6dvyWxmo5ftPzTgnORN0UMT2/ZVTSrr3UJkRMCEkC/d0MRWm4/qXDMDkNXGBJ8N2HBi44OfahTAY/ywj4vNeIKSU/MdSuhQBoHHZ5yI9waFDUE3+lg4x4V9X6B9BsFHHOj4IKrOcKrSVKaGTxPg5tfgHrSZoj03xNZDWIz6olPoezdKBeo0jkQ8LFNs8gtYB7K+lLDpbDl0r1PsX2+WkaO+TU8PGVvWfPFaP8hH8Hp/IREephlxc5cNxSK9sAmU08ymPUlrG7h+preaIxt/0rKfh0Mt4OW6G5WmAw8xpcFbffdD94f4tRvhkgdrptMYrR5ZnCSDnm7KqGCmgHsGOc9LVs83lTMtszmLGEhPOv2aQV2lNX1e56mjSgbMi4/WTMgBShhgVdzzp8oAKkWu20L/r5RwrhFbZfHNO+4m3VJK5VK1G68pTxlaffZgTTCZHMbxdCO3P6mzfI7UF+aC6tCry71JAu2nPwC/ohZ8A7vu1Ejy6DGA4WuOXojvlsjx+6pOqKDSGVlS8dIDRZkhLJ4J8/SkHvCFazi86I0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNDY3NTg0MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mNTU5MmI0YTIwNGRkOTZjNmYwMjhmODFkZDM0MTI1NjRmY2U2ZmU0YzYyYTVjNmVhMGNjZTdjZjkyMzU1MjYiCiAgICB9CiAgfQp9" + }, + "V": { + "signature": "S7Vmog6pyfI98YRrtSfgdlCMEYAraqWgKST+BVfE6kbrTr47OIm/3dwCe9yhAid9Yc69wU5/5iVenb4buas8VZIbfVbR1rwgnP0OTbFfgMSvlrWl6hTW/80piNYZaR4JlQ1no4zdSVMnywsZQG+LRs9rfzQh5zL8vo+7MHSUMc+bCwVAuWL3foLhc7z6q5gyAHzkD13UrkUQvBB8Y1H+uUR+vLx0nCZLEJR4xC6SBZkwKJ8WLGdbIUMjGP3/zX0f2bxolRXYqoU025oFehtghp2+CNNq1nsfYcvq77Xxu48rYJafv+Ch50iObG3WFv3Fz0OMYD051tVWvWyKuHitYUdvmO9Oc92xlB3Mn5BBAhVqeKM8XDwR4ImmrUspCZyjYiGYAfQYZHTEZo1LDj8UV2LmmI7ddIxibIoLZFMeNK8ucHxjOWg4UHYi9ZfIozsgNngMtdkzur4HneLhkoCxbxLBikhgvze+ZThDizkbskI7AfxLAWa1ozOaW2TwzRGjXk1dl3QsTo0gc6poHtAQV0lXj22N/2Y7BWlH9Zq2XFHU90K9Ywxg2FnQYc/1IPKmeP8QOe+7CPCGxDveGyhfKhxAB78b3G2GZRSjENglkqdEL/lFsCUfezmJ7+hlJS0HN/TuUj5aFtgYpnVoAKvxAw3oHTBpg85B1w17SUU58Fg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNDE1OTczNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lNWIzMmNjZTZhNWM1YjA4MmIzMDFmYWM4MDUzODY4MDg5MTg3ZTBkYzkzOTM4ZGIwMmZjZmIwOTNkZTI0M2U5IgogICAgfQogIH0KfQ==" + }, + "W": { + "signature": "A6g+wDl/hlwhF4KpeYTpweq0etHinpYAcEYcckyaXBVJmN8vOKIOyDNyJ26GULvQoAJl0HxjvS7uIcb5BY9vMBwX6IDa+dVZ0fCuTMN8mhN7U3GYQZEJoYdBDbguh7ddRjkaFyHGz/K+4wZ63UtVpGwn39BBTjvKTr1aqYJWCq/uGXblW8jjGe3kmlso9O591c4DRyXtm6pasmxY1fID7bNijG5uMRXR9iwvTCnJTJK0400vh4LguZIJHVBVYnh8/DU+2nQCrgTKKT/6tIce8wVfi/zj2WIVwmcUE1FFskcgozuZLRUrG37tR/r0EqX2Wga2pDAlgorxwbDZmKR8+6Hd7cUrB2NBhsSthGmezkDzrE10Lcbm9CWbGKGwtlc8u80g+9XURP5Pd5LubXQaaesBqoaEu+SDK4vjpltlvFFwkkHJ1syTbwH6vyMOh1qFu3Mw4zeHCg1UwqllD0VpZHdUDx+xLAgHGOe8jiNR+GrkoPfCpir9D+p0FDUG4HRFovFUEMn77nEa/G1Q0sRn9lcyTqor1JRHUpqjTgx/pisEaWjN4qKdzqhUAf7YGTCMfy2L7IPLSYPySsLbqIoMQ8SBAYeZkSTtQe/FndKl0GtHkn7CeHWHaomaCrIPySKcLwhm8GWyJ8KmJf6PWEyYdh6vNl6UeNhauS3e5S5Hh6Y=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNDM4MDgzNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lODJlYzk2ZGQwNzhjM2I5ZThhZDJjMmI1Y2UzYzkyZTQ4ZDg2ZTBlZjQ5YjMyOTU1YzY1MTQxOTY4NzFjMjc0IgogICAgfQogIH0KfQ==" + }, + "X": { + "signature": "aE8LOmL1X2HAuIJve0x3szqN9Wmg6Y2vosGU9ujRsEEUmNSuq1NF6l155Ecm36z9b9q9VVwEKynXmALIwAsQCBzuxLEzyTHRgfd413yW/RaQ82eqR81gl9mnWfm7lU3vZpf4niPDT90brmCIAYW0Ecc5/ngIPTjUxrgDoVXIrxcNF0B+wY2rkyeh3kPbpFUWNnxzWPn0SwKnHjz1rFBbzMf7LBbxTSbB0knI4SisRmG6f+0iWAZuL5mg54VcxQM7ejYdrQxrwHbX/1XmhZw9Zl12hS8MUza/loeFON8N49vMLKtWH4q6sK9o61wBt9vCqL5lWYKCbeJCTsZuhhp0EncTPKN8C6/P+cLja2SbfLrlJDTG9jNk1cT+FjGuc+puu+hwv0+Vz8PipgHpD211GhILESsAMDTc6CJF147wdnmSy/BVcpogU9RowHVaBWbKmIYy3LoLo+VBPStUA6Kb3vhV45g9G7cI8h384VMRtlnEHaklrHxdG0GONCNbDFkf034hvqJPm0Z9+/WNv5t5/2QP5GG/4DmQPGXUbaxLDR6Sojuf7KQxPmEXuspR0RcMIwah1oeGBSsSWYLd66F3CIhnRU4qP3+NgmaegfxWza2/KlQdM/RCyrdET2qCYIZeuCgrbsAFT3RLCEOACWgPIKDqvyxb9SXrMZOw8hGE/9Y=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMzY4MDQ1MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85YzlhYjMyNmNkMmJkMDJmNTAwNDFmNGM5MGZiYmViYTg0NjlhMWRiN2Y5NTUwZDcyN2M1MjgzMjVkY2JmZjkxIgogICAgfQogIH0KfQ==" + }, + "Y": { + "signature": "Jtgd/3s9J1S1N2GLOz5LDPBgZkTyp8YOM4DhgChKCrNHgUKQf85RAVxQRRMLnwNFGWNDEIOHwqZZCYKcBjiALPPr1wyEvJxSYtyDipOsQx3SBmyPYjSZ3o2FBV0Em7TqGD3h1P0CAfExzRuhUmSKw+frMJHkfJylWwHMgeMQax+zpF0tTpjMp/B8sAQ3VdAIiFtjnjZ+0vlyhabnQ3NpXfGf5gr7FiwLqqRoot4cUmDUcedzLe+hAqWjOP1mCO0056ZdVzVmD5jxEBkHwkaPzWDK76sl9wYOhhZr2hu3jcASkR3YS58B/OQBicswyeY+VKdurZR+ZcZmvhwzH85ODIRvixeeUHkYbWFLvyn8KxrgOMSbdYmK+zPqNHWrb1UEG0rz4lgGwxxITlPdGFDTxPtgVW8/iTR2Ya2fBsoXLN1u09Uh4CSTq6P0nH13aVV81KJ4/ortm+L0gfagHhKEBuWo+EdmU6bfZnzFPpNusps+qLAGapIrN3Lf86U9zvLt+UxAmfrQGUulrHHQ3vEsqHvI2aIr/pdx51ICKCD6V/f18tnRtcducIj/dKwHDo8/Wc76GGKpc/rzikUuBH4pjw0zHBAlL39y2CvxLEqNJ4c9kmeBbY5ogk5MoJGWwExY6VeYHwxB9N0woTJdK+0FKTfUPoJG7SPgQ+eU9iqV5GI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNDA0ODc1MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81ZGY1ZDE5ODBhMzQ4OTY2MTIxOTFlMjgyMTkwNzRlNmU1MmIxNTMzMGM2NTIzMGUwNzU2OGRlZmNjYjA4NGQ1IgogICAgfQogIH0KfQ==" + }, + "Z": { + "signature": "HnTJBHFPkHFSSAs8wHj4JHHfthQs5Wz1WusaReLTOL9OuyTbGVMfbN728ddy36D/vWrzkHCxvWMVmlEwue2YJPu+y6sOrL4nuggquuSjyo37H14j69Pymo+Ucl5cHv+maSHrRZrMlrStX31DCIsdkmsMeERkej4roD79jTUPOEPeI0gKg8BwaHPH91F0xVwKH4QtiupnDGrfQ9x2eFGR9zGC4oClP4//BF+BcjGS9Knvya78Z53hUP/JTZczIyKKxwD/9gDh4xr27NdHmiY/iaSwzKuRSCDOM3tzbEEyewcqVqvXNz4r3ac7wvc2IK8wyE1cA1bFLA6RET7BU+vItP4+jqci21tStAQ+WRqA/qtMMvhHcwRJ3nxmpOEUy48SPOf4ogPmj231H78uF4Ko30qIuqu7E43tfjlMmc6u3s8fkycs0Icyisr+94SS2WIT9KB92SU8LqwCYnraf2oh6iBsN4yMq/Af10ah+SyXYAChvq3uVtbpM/d3+9/d61xYsL3sV5n6hQ3n389Wm93Mx5NRMGSspjZdT2TJx8PL+Q5WNw3uwSV1UD+PVHiTABL8f4zn3sGNTGzxwqea/DxvM+Nm3w505W3JRQdZ7iQYd5khtyaM9NZ9tXfs95QoaegSlj5O3FaouGYWld3f3ZcXLYnBmDq6/bhpL6VcVKdPZMs=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNDE5NjQ0NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lY2FhZDg0MGRiNmVjNjdhMjE4N2I3MTY0MjZiNDk4N2RkZWQzZmQzZDNjMzM2ODBlN2YzZTYwYjUyZDA1MDFhIgogICAgfQogIH0KfQ==" + }, + "[": { + "signature": "OQYx/79IaQUl2080rm8p7mMrkKk9uhHDROnw3N6PM13sAVJQUTGCQpdpeOrmRNslo8FUQAbBvUecbd9vaInpLttJZhNYivIVhnjGO2lO0Anwx8RLpovs2Bf20zyp0UqOPHTrLi7id6wKlMAhn1aFmRzE7HHohc6LfQ/m9S/4ozfVkXfYDag3CkK0X8Do4E7uNFTA29lgx9Sn8BPdbOb4C79ZGVOF1EjOAtQx+dH2D18sR36QEXerDzdbZLbK4rdsY/8zseIJJoGEKmz2eYZsz79oEP6XeTLyeJaoFqVyvPDrm3UulzA/oo5GrVLL4ARINmZXCr4RXibnO1WngkLJVTIk7WL6YgbYsYb7GtZ+DVL8MrRQ4rvqeWcCmK/S5e7w+gpMDm9qRjJlJ+1e9R1UIK2ZTCrSJzIZEm0OEa2Nw8DM4K7hdKFFmRXHURGr47g1rARUpZU9vLsbn6Uac6wTChqus28ZwAPl60VkPzkbkqFsTz+DQoDvdgxk0d60DtkJpbpylgTxuv4c5Ey/k6N/3rN+TkeBJmIfQ1+g5/BspVpYBofQERknvlXaVJUW6sUI3XuMO/c82C0o5h61HG+OOOSucFXw6UQgwoOPRsdUtn7E+GPy8OfRRWRZtB7Crn3fRC3TcamNcaaIKIJjxZfSNIlnQAJpOppBHOzypthnU9c=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMzc5MTg3NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zYjAwNzdlZDQzODA1NjQ5YjMxZGNiMjJiNWFiMmRkOTIwMDAyYWMxNzlmOGE3NWNlYmU1ZGMwNGI5NGYxNDU3IgogICAgfQogIH0KfQ==" + }, + "]": { + "signature": "gJfWn27TLFeCc2OOhzkGUlt7NmNdswSquU9XGTFtJQkM8YUK9WRXGmXWeSB2ODABfGmTD09OWfKu3ZynU04PytMmKeH8IjcuCjo1HNPlPLP9bquOQ9yRVRiFZy4RyPrNdLJ+ibRK+QiWkOG1iWM9WuBbmIAmBv2OP5l6pdHDQlF5U20JDGCjH5KdyfNXCRP5eNXroOmB4hvAWaBR/hQIdpSg9JJFioNv51XH82Zdnomxo9Li+IgQ7DwbegmZAV8CPBbMhHJaHd6TChV4dldJ000sf+X7RxkI68jzNEJKiXcyNtBUllsw35glVAmqNrUqJfngz4NieFKutoMDIzwzxZZv1wOlI6277/ZnOhmGDMa1DPsxgBRswmjM0clSwOAnLkGcKq+IilW9ZdIF6boRk2TFIoRJWq506DVbXZcKskNNfP6TKX1413j2ZBe5V58tPriDSPlIk13DvVoaYN98d98eSrL4WiOEuwcWJTAKPcMBvbALJFg0eJ11GCucNK2v+/kVuAb6tUmvZMGUdnizgmzl6Adjvj9I/wMV+jEhN5cioWKxVAOyvjnfyBinuHAGdV3tsRp5SPOftPALD8YaMDE3LWZOFzh/wYNSukbsZKGSf98Sj5R8wiMJL3gC6yOum0NXw4JUF7sxohqMnChEqXzRDXxDj1kiH1CoB1dNN44=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMzE2NDMyMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jNDBiZGVmYjlhNzkxNjkyMzI1Y2MyMzEzYjVhNTJhNDI4OTI4YjBlYmVlZDg1NDhiOGMyNmY4MmQyMzk1NDQ5IgogICAgfQogIH0KfQ==" + }, + "^": { + "signature": "CouVmEGjm0fyWsG0CqEvd3J8V9BqSSX99QlX8xCk2JUN2kIosapbpFh35Y1mkpck7ol1EwTIc0B6RimwXYSzbTwnUaEGI7hTaMyF4Na02ol/dPHKCHKh2A/fh20p6lW0WO7BxYPDTu6Vvf0uNyWhl7AREGHAVs6se6sCG/4ry8X7o6K4zjA4/u0Pnn1NiEmu3ycpy2DbyAWf0O91b4ZZQ6Mx/5/BIOG118iAHXW0OR9bgLtLcRXUHRk8srdjBVZeWbGM1udRxucBKZbahx9t4WzB1AvZnqpjmgvRykjSTuXrHa4++pMaavkN+bImWdP6spDCFqLMT1UNef1vXTCBaXkGF6bGKQ7xIB6OPwRxUvg+aBVuL0h0Wzzw/t+IpHjacgIe4B/OC41bPPSE/vOlpRHGQkBXSNc3cuvvnqg5mHMyKU4lFCqQV4Tx1ZDen1mnr6AFAdan04lm2PUDcx8AVeg+crobUWW3sxZiVKb/yuWUXgnhuGkuGXl6JwMp4d6DHFaRkUdOrthTHMdNGdil69lBHF7npdZBjIX4B03jeBRJSMYAmUorLbfrjW8KPA+VARqsORJS8+/TVWmOJO0HDpYOeNdPW9Dd/nYX4VgcyW9iI47mHvcXZeDOx541BlvNLVT/jqJ4QOWvltkUu9kdkycM3wul26fk9Ea7B1phJLU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMzkzODIzNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iMzNiOTExMTMyNmIxMDIwZjgzMmFhY2I0MTMxMTkzNjNhYzEzODg5NmE0Y2YyYzkzNjNhMGE1ZjM5NjcwMGQwIgogICAgfQogIH0KfQ==" + }, + "_": { + "signature": "JeuyQ2NPh3aSPbBVn3CU9fGayzro5udrGOtnJ48whQ7OsaNGB2EFCKmuEfBRiEA5FITc7vIyHp4t03uXpojsfgJMabsxSDYPp4VWEVKRXMfaDx6RJWkCynzYy8tG78KGsir9EGnStsVy0VqOnp0AqWYhGr9OC8QN7ujDPIj/UfRX/kR21seF11VyPxrir0M2vU6SXs7j0igo+GqHMdYOH6SGyyNApr6qSr7zkM9P6GxjODy3GMuD0bA2Rk11QbxYcannw8Fhx8/kCYzG9lLvd7TMVJgbLMR+nMyjuMsrA3dBrt+Njwk4xL+2ATqhez1psP0YHXHOlvT4u26QYpACgFGcwvj2TObGzyeRTVqnrTxLpE07VrhmDFY57JNQW0qYktlBf16rJe9lbKdyB6tpwsRdjOl3Nlstm4RYjBkBf7gaECVCW2RyEk7M2O1yZmfQjrVFgqufqGARjC6hmr3tZklf8tzEVMIEz4CjWGiE15v1evjGKgsuc17m45FlVhknMTH6mx/LH2URaFTwa/YWvsV04mn4XvXVbxcMspSqlnq5/3tCvVgl9u1TME1heXdxDhiHEkhKzi/N8RWgDNTgbotcXA/VpinzhAZe9RQ+1hdoGhCrfXFr2ffqekddnszbKGqMlMmaQMB6unVCYmDIlxX89Ti9YrxCjctgo6mnsSs=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNTE1MjQzOCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zNTU3MzgyZDU4MWE5NzZkYTc2NmI0NGNiNDEwNWMzNjQzMWEzNjIwYzE3MjUwMTczYjI2YzY2MmNlNGY4M2NmIgogICAgfQogIH0KfQ==" + }, + "`": { + "signature": "vqkoBAyKbzlK3YO25MDO5vApfA01/a8YpnTWKf5gzEoCfOK1abW4NSWNtqDgLQGcEv9yEvZQcMz12KKyEn4bMnztHfgxQV+8Uvdols8Q2l7eRf2wmPcgbmkD1Oizfwfl0oYVb73r72HGNQ/YS+2R9oB8G14kV4lKnqIBgWciUlqhU6aOYKG6jQplDGPjDhkmTLCrfoAOYoJD0xziOt1MAJLuLhlEJfe2v5/UhzEfci1JWZ4ubxP6BJF26McSEFoOaT8mbiS9mEH8hFsjhEsBYA2CZw2sps0YY+vOvSHdnNkUkZDLOBQCvg5lkw3ViotwZxKweKoNiDR27azz+bjP4Bb3LWyFf5YE165oJeEw8cPlsMX9no78axMaVne048y94N5sTyUXAJUYPADJh2s/MlHD9jQZzssUd6FlcH6OYlGquwoSsmRYF4BFVoy7ttp8YZcNdzTcbqGI+MLo7Qpesq2yDG9PUVoZvniTqbWO+YpCNKRuj89zFrsMfDynZBWvpJHPGlO1KP39YXUVHpvS2Au0+4xAiRt76Snk0j/5ZRaKG8dCNFv2gevibz1ul5uNA7Wcv3zgTFL0Uj7a8w6x+ssfkzwske+YqjY5JsnB4Hsl/7wEW/64VkgMri8JSI1LVM2DhdiqVwmvuJWIs9Y2QPSSCnpkVIYH9Oa90iJS0lk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMjk0MzQ0OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84NDI3NzczNWY5YjFhYjIzMmM4ZDY4MjY1OTQ3NDRlY2Y5YWM1MzFiMTFkM2RkYmU5Nzk2YTJiNTFmZjEwNzQxIgogICAgfQogIH0KfQ==" + }, + "{": { + "signature": "Oh0SNIJ0ceKebciRHMhkkj8MuGkAFtsaZwKhp7FYyZIdziR5GdpFrX/qagyR4RJdCAlVeZQ4cuZFszToQoaKtuVLxZGr5qSMnqKtVVysLPKkgedraj1eR42fy6cSvZFOcI+LNEu99XJhb8CVBIsvaYcYZ5v4t5QuTNda6pJfC4M4sRebnsssV7GaZynU5xl0u+kQqrpzz8EtYsXwjb4u8NqMMVfv04SoORqD/EQRoxnA+AGmanB8zUy4n7SwkrXAMUE3z5Zk7vueRGfwszu12QwhGPGYSSU4oKb7fRaZyF3u5fd8M0Y58sbeZe/lNsPl2xW/QwXE1EPDek5N1aZ4E0kKHVGXB58YYHOtnqROAZBZLr4/jiuhLvrtxslIjPjHvN40joBdETxG5grQsUbctLK9fEKCW82YQFzZm1R1xXEo5ltCt2FBIrgBID5yrEv4Ej1CmPxRpcY0Wg4MHLkVkqqwUe56yTPgXAVPCgXvVgAvAvchz3jKg2bUImlq5E9XucmXuDbcqnuP8iYt9cR2Z6fLJ2eKMKvfvzgTeVej+gMm5t7v7u8/yqGwUTLO8ixUzOLFuPFdaUfT6VrVdKuB+48Mddk2uNTmwS9oKSUu3VJr7MFXx2gVVQ8GUxTS6D6OR49980TXHZHRAk8YjOKlgPjpck11kFnHCuDqapYA3mE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNDEyMjg2NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82NjZmOWE2N2E5YTFmYjU1MTkyZDA2ZGM0ZWQyMDRhYzY0Mjg1YjQ3ZjA3M2E2MTljMjQ1MmExOGNjNDQ3NzQ5IgogICAgfQogIH0KfQ==" + }, + "}": { + "signature": "p3VwrPgpocuJnP8CIa9uEf2Min5GlJ+IM9QKH+ghWZBmQ2UtW31UA7pKme5Ebke1oTdiT7b9mJ5H/s28AImyNLBMMGN6GZEP9n565uuGoe+tVakf6CPZBhsroQ2AW5bDaO3DAWrUXCEhKuNFlXYQ/hD3d7mogOI9ffmSg0BwFW/Y3FjQzxegS7/otrD8sz7tebeuw73837r0sXJWfOvbVqplgFLB4BV1k3jW84oKTTxTQyL/l5i5J8yqk7W0MkB8mfj+RQj4+WpaAyYXKRZoUchUhnvp18UFe7x07+H27gFQ9Fxl+1RoRkRPYjfbAO6SOgBKt7/kBmRDLemSwB9Kx8epHxJmqyDDWR66QFqCilecTQmHabWXdfbx2HXx65cZz+EDz5qCTu7FXeBFQG//ag86nZ978J54a5G3yxQ177swpvTgF7cYdMQOthtMPvmfnWxAikvMLGY5dYnmJl8mZBKXZh91R6tdQUhxsiWYA2cYlraP4YQh0ypIjTY8b0VZExGG3eTuRCfIfzD8j73pr8e9fcCF76dqxh9YaYJyn68UJna0vbtP8av8lR0FnIyLRQe6YWk7v5AxWOgthm9B1c8e7B3dbQnfioyFSSrhJPTL5JB5zS+AcrgXiV9biwYtYvX4bwAqo/82Sn0b3/wslDKjxIC7UCUoE6teNhvNd40=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMzA5MTIzNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81NGIwZjA3NjFlMmMzZDc1NjM0N2FiYTQ4ZDQyMDYxYzYxZjdjMjkxODYyZDQxZGJjYmYzNmFhNmJjOWE0MTkxIgogICAgfQogIH0KfQ==" + }, + "~": { + "signature": "QIDt7cBLLjrNotHfWzAYHBiFTrDTNNSp7QEJ0K5pgEHOS5yXc45m7n0eW/yEOBUiP0Ld6LX/1qjtyz44dZfygOF4BHoYKAI+jSqhaxgY3bFW7kItRMC0q2e3wc8BGEIZiOiEIGK0AZXxDuYHSHMOgcchAjuCXVTPrm43eztFVgQDoxZXE2s1a3iWl5DvkLU0bxVMHVU8Bfzl95q8tIeeDEbaRplgbX/HvOaEuvrWNFQs5blUzvDiv6ZxLHNKjBPp545IWt0n14sWSzn+slzeU/cHi7NQeJBnwWam+7UktxhyPty0QXwKG2sKyqEyuSx7yFRFc8LGgVfzH8rd4rZEpoHXCJ/uSzIriPfB2MuRvYYEdUxCePTm+A3MY6eqnsAK7UizL1DaSWbje+VRYhwDsSnU+7NiRRkXZt1WqKZZwgllaiYKgVfJ4d5ORENhQoGWDOeqgX6DUixOceyLKbfriuAEF1EvVNxsb/S9kwkg9pjAADwYlaufBNCow1Ru6XAAP7PnDZ7d2CotBMb2/zvuF/MilEZAnV2u1Bu8t5Ee6VuqoWJ4SSP/Y6PpGNRjybkSeTI85qPVVymOa3ojByWi/mIc36c1zOtvTdHI5EthmatUPSHPSFl7zDKTrF/azheqQrjtWiH3V1I27IOHmrVzLeg+ujrDFC/wKwNrdbcojiE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMzA1NDMyMiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85ZWZlYTFiNGI5NzUyMmJiMTQ2N2NiZGNmNGQ5ZGNjOTJiNTQ3MjYyYTE0M2RiYjA4ZTZhMmQyNmMyMTI3MmI4IgogICAgfQogIH0KfQ==" + } +} \ No newline at end of file diff --git a/Network/eLib/src/main/resources/skins/chars/gray.json b/Network/eLib/src/main/resources/skins/chars/gray.json new file mode 100644 index 0000000..d414203 --- /dev/null +++ b/Network/eLib/src/main/resources/skins/chars/gray.json @@ -0,0 +1,266 @@ +{ + " ": { + "signature": "LI0hVFc7TFU7mXbaiIekzJb+jY2fQt5TggJDlZ6Eezp0vUjF72uDsfaTRiL+8SqfklLwpDsCLpAd1E7RTD9LX34gjXl3MVQbH9IoHElHwF1y+Uktya+zDWinY1IxJNhWxtnYP62wsuArz1RXRiT4uWFJgP7KWlPxhLNgJ3DMWrzVelHowulj393BHOwp2emaLOkPQEh0Rc1AeexQYK3NQIW6gIxzmqCGg1EwRq01F8PjkTvhX+w6w1ndyoTknRB5/gVGroYlXYLJDib5+vnwwDZ2BYoZ8oWzQegzxWFTy0QWkhKy/uLPhmqi5SC4lSI7YJJnWCMqYylyGikqUv3zM9dSghnsij/NPs9/ai6vLBs8ciFd5nMlpioylpZJA6BxYUhraRBJOqdjho0p8RfWZQ0yw9KQN4asDoAUaUj90Pj/Yf7iWAw2MAjd5J0zheCfULnCaYonhwOf0p4FaRXitcXTVUk5ItUjQNTY5kvWJTpCR6WaAVtA+wnWlm1ip1s6iEGsyxLr+490eNdkr/mYJwWxWJPzTMKIYVUuTgYEu75ewOEnPzNXj95c3FvQ5nUoYYu9mVmSaYwbaNWnVzgzJlLvpzR67yFeCb7EjCj+aj4NBlsOVz05o1a6I3XMgzgtt3rG9/uz49ZdxrOvVLCElmE9ZPuPFQaNc2Rlr2Wszjc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0Mjk5NTMyNDQxNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zNTU3MzgyZDU4MWE5NzZkYTc2NmI0NGNiNDEwNWMzNjQzMWEzNjIwYzE3MjUwMTczYjI2YzY2MmNlNGY4M2NmIgogICAgfQogIH0KfQ==" + }, + "!": { + "signature": "hhHck6EKvHco8rai3xKKknIIC0jEpba5R1hfEyTCI1U2Phys00qe3Kl6PZmx7EcGzXakZ+3xwuCu8IPpyiD4u7nl8O/SlSPzXzvxo1ENqSn+3f8UHbrrZNOVguLpuAQDoHbeKx10xuSztPX8OCsU2k/NRIBNQaVQeMn+s89gI4/7XNJYwIiLoJ9IeEAaiuh+GAGGz1kX/Fb60r3vB2WzvDEJgP54xChOF+Sd8Q2iwX5tcrZolLnFhUT9g5nnOsi93UhJl2iZD/sVCguqWdkNnipTa5Ts2lXQ1+98WTyxDR9haHMl+5hNyCfEg4tLrG+S7KVQ14fFS8b0IRSdwu6yXnFf4nXKF/uOSDZyj4sWlP5LFPBB8U203Xnn9d7K/xAgNsPdblGE080Y1Ar2xzs6nLKJ6mb0AvW9JEZZc5AXYQKAnZVHhOtZkm2O44nYWNq9fV3oVV28hPvPCVDh/kFu+V8F6SDksYhbaQBcpIq8wztJkpBRNzX7hi4vl7m0q+Ph0XZpDN6NpvTvQIHqQNNgtWjwBtvq7NtsEVxmYs70sszxhwpHPwd1wmoCqHaFiM2Nxyrhlp387/gJqvWVomT+rPIP1FDG2FgbwMvIH517ag748qX8VhpWtcwGNJIOVfZPu2x+1Mi8S+p4MEy+ZOjEMcxkghLg52/wlfRhQc1RscA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMDc2NzE4NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hNjk5OThiYmY2NmY1Y2U0YjliYjYyNzE2MjAzNmM4YTMyYmE1MDA1YTUwMjg3OTE4ZTBjNTIxZTA5MjFlNGRlIgogICAgfQogIH0KfQ==" + }, + "\"": { + "signature": "hZ/GiGDRbj+X3BLHVPEj35k5d4AZu7zlaSca5St4XWuzG5HPXqUBlEg49Gx3OhFFb6vQv1GojhwuAb4G8+i3iMabwXleFAEvxH/Eu4IygVsKW6pDGUg7XpyL3VcKNQa/cGZCypMl1z84xS9/lkVA7pIJJZFxzlrwWm4GcO603XsfScwPAYrEknB1j1W8TSWcgLMdOb8I2kWIS0X9eM7yHZw8YKqGlvOqgszzYE4IDX5DZe5W1uzO7F+eLc8IY2t72/qJr/R45kww5KAGz3anEeoGyv/Hva+KyBQpvGaHQPWpe+Cf5kOxqbKsGpPTjczozsbJoJJlWUs7WjwMXQPE3JIJ6g7VrTNQno8NqSGyHKWA3RW5fdDajuJqy3Iu1l2XaMUKRbbgsK8NhMfA2b3Jl+7Pz5VYkj3pTZAzyJ5LVWYoXKlawS/3df2b34Ba99PzfBe1b5mi2U3o5cnpj/H+Gz8Ve9d4sfGSIto+uM+fpZkYwAFMTNMXKn+ydh2bsrULQdDLaba6NVPD1UYBRzHEawblP1psMdQtTmibH3jYh0ZJGZaC6B/+MMu3iGEq+RfDxzqvSAHOui+OOdYzdQEaYSUp6IbTxPWHR9xqP9H8WpkkrU/R+y/uRnUiti8YSk0cRPlCfLpaIkr1CxXabVkLdqC7adECHl77ZTq/NExkh6g=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMTE3MTI3NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iYzRlZjM1ZGQ3OWU0NGQ2OGZjMTM5ODNlZGRhNzY2ODQ4MDZhOTQ2MjliMzhiYzhkYzNmN2E3ZjVkMWZmNzA0IgogICAgfQogIH0KfQ==" + }, + "#": { + "signature": "tdxf4QKk2rzQp3UimPDBjJIlgRFeG0mfhMbq9RC7fMZ+IkslTcmo4XUlmjZVryapQ30RHen+aaZoYBBTDwngUey1L5KkcmOX3CbKcy3UCZy7Zmnz0xZkbbwxOHKEHpTEZgNEfSnyV1JHjdHN3+0LtfZvbPUgOgAm0VPbPh85ManjcL0RP6Jo8qCXfLJ1o1fMZxx82j8agYiyjq1k3QdNduwomY9unkMoo9OMNZIPyjl4exOoj1gYd67I50N0I/W5/y32RN8c2HGBh+9DJOyym+Z/Vawoask7NHn4RM4JlussftaGz2juB3Tqj5zmDDx3EnOkilYlGFT3UpG6OEtindzWMkNu8WuQTNfpH68bq6/AVG/UcE//zdsXNW+RzW2ufooI2GsPHAi0X+aG2LglTwMigeNopY9RY5/0MM9Ofhd2ux0Kq6bbnIbgMkoB63e7rHO9cSkOuv9GAAQig6bajNK9n8rAFwfqf/YL+nsSaFH1ee+dnNxNsSJeabMuvs55uRvf8i/A628u2VIew4IZOeaff7VP3GguOs7j11pRSkW3mynD3M+rGKu9QM7p+jPyR6YPgXUWFdMvYdiqVM0iP/LIiRtDoaB+Hq5D11p9wPfhcvAgSN25qJw7RZGsRd06HKNZR22EyQ10u3n6wGXYBFNHZpW5eCGMRA4za+yzf0s=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMTA2MDA3OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xM2I1YTdmNzhkOGQ5NDFiYjEwZDMzMTY4ZWIyYjA2MzIwMjUwYjYzYWY5ZjI5MTY2MGZlYTEzODVhZGJjMGMiCiAgICB9CiAgfQp9" + }, + "$": { + "signature": "SuemAcVS9yUbWO4ZsQIr3rHN0zmCCldEMCgPMM8baPmXFbcJ0LUSQPqFYgB70P02D4P51bfASqOwwu68sZSWIQD4LGx3RdRq2PO8Obb0zRytRfbgNGi4lw/NvMcqpA67YahphVbmZO++Khpc7XdBOCcaAC79mRQG5ryteBVWFI1i3X0QCUoKMWmvj9nBwOZRfFRd4ZzioLDopH6GiQ1xvCEEi96UAiliQ7fGH55N+vSr+9TwgUqQmAazbqShStz4Oyc9bXhIqX8AGQifLAv4jeBJ5RcsD++VogfbEUIp2bNHo1EJ3ZDEM2ab50bXSUTFAUqNSU+pvomDsZKq9FR3h9VS/CvD8tAXhv0BizaJVZqu7ydI13V/MOo8aYnRyyt/C/ScNOCKdSD3lm24LSXbyXJB1GImYjiUU/nHUgHdnEJrcPpzIZ05848AaN5of8mFFoshr4NdQBWiNQL8q4hI0jkmB76lwC+B1M1YRhrYq7KxZ5jKBVDnyZCVxWBzXW55u+CifpC61jn0jWP+NnVj0wLcua91rx0AXvsgvkequI/kbA+/Pqvs+8orsVb/3s1+m44tAerKbF42H0jtPNi5atRol8EtqpORgn+nO24Unjmd73nBvQpkBLCq6vh1PkEvSn4j2HhCgygIgHdJKcAgG6V1ajW3HvVw/j3R8oL4clI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwOTUxMjAyNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kZTgxOGYwYzRiMzEwMDg4ZTBhZTlkMGMwNzc3NDgyMDhjOTgzZTBmMmZhZTM1MzVlMWEzMTNhNGI5NDA2NjkxIgogICAgfQogIH0KfQ==" + }, + "%": { + "signature": "PLXAFYDG9kKOYNbHeuAby2iEMPZ0TN4n575b0Y98A5nE/2mTiejae7GGBb8n/3iJiu/B0jMQeDUD8u0XOenjfPFlTlqFIDa9kROsGVFb/OKTgeS2Mrl1imEnbWwlkAq7JAjn798R9+4T1/aJzzdx4MDn8uwMALBLCC7pz2h4LHyIDgYnKBzaK6n3mSRHWOVVQu6KbclvsP7JjxDaSPTYo+UER8imu4hfxTTEjR/Q38P04LOcnEcXTOI5pUPzpmlDlCngJSvXmzj1fFeXDSYqzaT5X4ei2Dn+9qC61VXQglXHI1IjoJGKfyPvDXnkQYAfXwGQ+NSzGbwcOn3VTQn7JFcL3W1U5DXIWrMGk8O++cbzjS2GpM6/H+LQfSPrPpakcbehxXdN13t5SixH0v78Ut881Gqm8SL6tmPfrbeBzjYxpVeMgOoIrId/7/GdKFB9TpWHEPQe14BNzwpon0ZEQ5gELgRH1T0Q54BCEoursNkNCtX/9KkqHjLK6cgSRIpjtnZkjl1NTfCap3UD9grooicmVRFCjRMGRmrk6U9GFNWxi8jBJSZJJFwnTSNmDymnB1QuIkd6PVvixWNwR7DlhbmZg5a6coeHeaKuVITevz/LNrw9o8E0hfNwxKRHuOnBFo6Yjlqto3QI/F0C/FQYvSZzGf8AsbccUPnA9wZ9+0Q=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwOTU0OTQyNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yYjE4YmY3YTQ4M2YyMWU3Yjg0MzUzNTMwYTZmZDM3NjY1NWFjMzFlZGJiYjNjMWFlOTMzZWI1YTY2OTQxMWMiCiAgICB9CiAgfQp9" + }, + "&": { + "signature": "RNjxujixwuXQHuXJX50Z67mWwq4xB2blEEoNydsYnW0+6zaTGRp5vGJW/RPDwM8rVIH/vxgylKHXY8jCCF/j/DTmHSh2Vksf3VOlxaIFxEGSE6z58eU3q0OgluEQrK0b8RqygTMKbPfKXgKdXsvjlibhebvZlqEGWrF19L1/mf0trCXAQfpE3Sl4U1SP2esYI1YVulVXBwwWXYXsWJnYleQSzHYMfHkxx0No3ZSRSzaAmI1HMLFZYriqxzUewm84qSN1+rmIBFMeSRChfNnCn8v2VF6GVt+Fr1hANli23vwIqlp/sMBXoFX0NLf+E6x1nss+7XXPIRpm9K84tFJRM5qIaNU8ug/j5AqtqgP61UyqhTbKwnrpZg3eLn7jRQFXWwlSINQvzMdJi+GFaL3zC0koElEfTEf4xQEeHCfRHNrqm4sb3ABSwwyPc2UTnlEYzz6u2H40daHmuVqQpokFmAYwTUJ6C9WI2Vv58f2erKXZRQrMoxhQHG8yHvGW4Dcn2Jo3TRzoPhkX/t51I6ONkdhCHrsBGlnFy+70dYa/OssPh7CPqrNrz1qAu5fZgwtKCoS6GHf6+OUM2gXoemflWeOX3RbcpqbpRQT0Pc6wt9Q2m5fcP8bII5TMpIg2O0hLAaSkqj5FYTLN10O+x/93h9nCg88LBidtuJ9Z3S5purM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMDk4Njk5OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jY2YyYWI2MjJkNzgxZGY5NmY2YmUwOTY4MmQzZGE5MWQ4ODAzOGE0NTMzYmI2MDJjNGE5MDQzZmYwNzZhZjdhIgogICAgfQogIH0KfQ==" + }, + "'": { + "signature": "QFssX8bViOIacRBevCroZuJ71DDO0YSXkEPXDe2SKAnZn6Zfiy8tw9+DAOdNJfYPmA2ZxWetFem1PGSNeM+VjgLpgUxlOWOBP+qMZf06h9nQpT3SMGHaRz7jfDi5QhZKggCbKBDD0sfR+RwhWzVVPnXWK/HL7YKDRwr+l8Ylcm8aIrH76H6PO+atOOz4Hs4+wVql8uGBHccd/NZbgRgr/a6O5UBC8d8lhsecG1e98o9ReSbRlFwGSx91CikpiiGJvRjUVqSg85HtL0us8PNCmhQKnBgjwzBaMjUUdRGyl6dmfmmUb3oI4DWif0Hek4x178NVizQji6563Zzv/0Nxn99h+1d79DEYscVd5qUDPakYpRrkd7LuoQXItPxB2VF7vj1DmmX22XLmGtkl50NCgjPUlV/44yvAegORTV5lQTG6FeDLBcebMx7MP0pX88xzQo/e+j2+3axP8tTmolK8K/9EUewFMCcHwjsmaCDyFcxoMav3PX/ZfYRu44oSn6agnqezQ0Xz9yaPWMIm02fQJdS3ZRc1Igh+US+LuwrTG1pIw84BOhgz1QqsK0AZwpv9uAGe26Q/6X3E9UkZcbP4wHS7zkGpZVmaOmaF627gM2f+FrfP30vZmDSOlsvPubCu+W9O6qoKqSp04KM6DF/7MmJDxpeFVm8Z0twjCaDHVu4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwOTU4NjE2MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zMDMxMGZhNDZmMjVhNDA5NmNhZmI2MGIzZTRhZTZjMDE4ZGI4YjU5ZDBhZDAyOTIyNGExODAxYTVhNjlkNzciCiAgICB9CiAgfQp9" + }, + "(": { + "signature": "ufRcHggFUyUnFJvH0nB2/OQPRnfQR56sNDsBlRO820JFdwFJ3xzswQ23SNJaCYdMUEMP9QEalJv9pzfSGsYBE3T7mZAaRj5lKAyv+Ng72QRLHqH/YJmN2KvgTBG56rk3NP56VEQ+/aHE79QxHK0lD2z/lygFNuO6cetnYPOXF9N+32mmLhR8F9Ggl2sbvM3OJ7LJqTySZXD48JGP5uXdZ6AXYmr0kNolU/yOCP6nkkCoHrjWequbQvQVp5vbSEVnxC3kvyNho9/zxRu4Hcb8HIQglQ1JGZIL7JRvdmBiktmITSPmzuUKusCwL/FoPhLF2w/izV6j1wAkF3oYs2QzKpix1wAf1LoA+dJNE+UtEvDfeEBMopO6xyb1elX/cE+piskdTBPouPBX31UNVFsYNMvnMzP/xafaSxfZd8GHmii76Oyg04LyrOoj4ChxaxLm9TFJAp8hse9dtTQB18o5mBRjOHKnG7TQ/7ZkrXwKMyWgypka2NMWtVZ2n+73KwOzGHsPinpwYRoIKNXBibqCcjixP6SXuZKsihIlynFQEHu3WVWBI2zlwMH8BJVa4OcDrvgrUqX13rezIafVj9qPRAOxm2OxTej3ZEO6r8JRX8stWjXpLWlQDXDMIyKDneFTf90pnlk4IzeN5KnJ6k6s8OrMGqmfPmc0SQlQptWvjC8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMDAyOTU3NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80NmE0NDA3NTRlZjk1MGVkMGY1OGZjMGI3NmM2OTBjYjM5MzcxYzIxYzNmZGRhMWY1ZGYzMGY3MTdjNzlkMDY4IgogICAgfQogIH0KfQ==" + }, + ")": { + "signature": "JJ7HJMrJLbaEhB04xBg98p9WDd5WPJf2teOiIF7y5DdcsBZcTebZHiIq8SbIp+eqgiS2AI/UHw7DT2C8L2FqGGeLqW8yg12KToh6hSl2udA09LmMI4qvJWizMH2s+c+96JJIxrSnFW+474BfiHsQw+p67CqQMxd4/53AWHkF2FZAR/TMKvqxv/BVG3BR25MPTuVFOAVFD+Uf5MY89Ccs92dg2XTpckHRI5t9dn8ACja1RKs4FD5WGzuHre/129+b83rtyYsonpBiWF2dUpBHZKAcmHMrM1eTvh+M98J3ebK8pB+AtKZINCpkje53EjT10uG4NC3e6T0HrcR2evNed7p6S/czg317p1b6yrSxCZRe4qQR67zNPVtC8kdAela1Ql0SoN24Hqfl3d4KTNB9Srr7kcqxoJIaonb88SpZ774NeSBV4UO9nOeFDMEq0ubn0/bE6L4rzSnIpC0Z3sRyF8w98ZuO68t058zU6sfCZ9UOTwGF22yS/vovfI9W8qZuOLF1q3mV4gW2GgGUlMT5HZllShK53ccqhqZLkiN4oDecNRLduNwHfUSm30PMTXma3Nqew/szTAdwkXegKMH5ynVhbFi4hao/6wfstNdFyaYao4DMNYbrZycvXfUIncSZIKNr9MFktJAgng5Yp3JW6bmAQXCc2kgcPPFww9UaLXg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwOTQzNzgzMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82NDkwZjMxZDc3N2I2ZmI0MTc5YTVkMDM5ZGNiNDNjNjgxNGZiZjA0ZjgyYzA0Yjg4ZDA1MWE1ZmIyNzQzZjVmIgogICAgfQogIH0KfQ==" + }, + "*": { + "signature": "fU9McW3+wzGg7xLxjy+e3+qLjFQ2kvMh6HBXEEcbs7Pa5wwypJNi/ICx4KNlJ7J8thdW6qpsDBfFeF9YOC5Z1IKu2lCV9Sdzez6DMujjY3GBrurfuOPMGSiqk+DqsnkjJwqDuXQ0EiEDXCesXLmRWuzaT13AtfI9R5iVbHruF+6t3729lzwn1wXcMNPvFUTzMONZCvBcxwWE6HUzKzMMjf975Vk+wm+YmBX3zB5SMqKnPJzSKAHtg9UbAgGiKFH6wSZyDqkpx1e1EyjZTnVa9O6bQuMfSmem8EZmK6uniLe7+LhX0SotuL+cH0NdxVuxqVH+GnuMS5+In3rlZnA9p1UTWw4OwryV3Y+iSm7lVMInqYB03l7VWCY01vVWcqkcI5u8lPPMjMrDicb4sd792a7fGq21eDngyFLnYKNoYwEe/vZ9iVfNVIZ+47YF6+pu21L8q2UukhY1dhcAfOWqmudVCDvm/qj4lkt7SnqapBJg7X37Ew8Gc2RFm2ZYAOvZn99OetfNauvkgnRq0QA3o79Wth8vzk5i5MxqLlTUDfEDuT1KkzStj0hiJFJ9Ig8EslO3g+UnQ2EYfPq2OVqoVhQD6oY9iqQvEpMzKncz2jQ3YilxqNMFIHUtczSrXCuV+GspkFpdKMClDiATViQq9u+W2bm1aaslOPPCSAZKBL0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwODk5NDcwNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yMTM1MWEzNDc4ODVhNjhhNGRlMjkxNDQxYmVjZWZlZThhZGE0ZDA1OTAyMmMwNjlkNzAwNTJjYzBiN2Y2MWUyIgogICAgfQogIH0KfQ==" + }, + "+": { + "signature": "Cw1mfV2w4YO+GewOIP7lvFsbvl38cosI9OyJo5zi58W87Sl9MgXylG+UY/x7YI/+VSyMnk5ltDcLstjjMNa9PN6IgTwZq/JsW0lXozAiIq10mfj9QwcIvbBGoL/GkD4TbjFP+NSSPJfIZupX464kyx7svxUftJaejq92FpB/07YtLidfV9a+0ixHTb0/BGFecbo8laB3VLmT8GdEtYtK7X8WCc+Ik8A6YRxXdjlpYnXkpgxlsdBlC5KFkkaBWCVLrjNXmfLIe1Y9JrpfDMNeuJj/inNnKDZgkGeDZF3vZX7Gns/RjTdn8T6UKDEFywHwSxNxsFmVszZVQYam5iYGfUAXp+Zp5TDU7we1DjJY4q3MX++GKzSyDNOhFOmjJQ1yKoGexu+3mOeWIcJQ6BAmPYBLGRNSwcz5mGhDFGuLUkQDdRpgYDrnsr1NFhaFEAXb6vP2NRSyqptLkXG8eRE2ezSRo+6g+KSb8noNr3hE8f87puLpvcPItSduYQQ43d3jpT0M5TLeMKzywhrMQZWEC6N/8kASbGPADmIbqHv3Mt8miLXuNqMcaJ4waBLfC/jrIK8dcQLNSXnQ7wQpI7SGvyKTARNphvSOdm3UKRDgAt7DysHrSAHKG7Zx3oguMvR9DqRckz8YISY9p8xSsYM/JsAxawTpV1LYHkyf04d8HXQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMDYxOTk5MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hZTFhMzMwZjM1MjQ4N2E0Y2NhZmRhMmY1NjA3MTk3NDJiN2ZjOGE0YzIzNTU0MjRlMGQ3ZGYzMzA5Y2MxM2Q0IgogICAgfQogIH0KfQ==" + }, + ",": { + "signature": "KFz33FXo9PEf8vcffqIyijNLKwyw4laQ61szA8yXwSt8HASRaH4sYt5bTCNQ0uPN9VzUdtvlJD2P7KyJtiwctJ8WLCc2/SZs1U78TuN0VgyCtFtVfXgzS1qm4B6WJkBzVo+byhSBOKnen2a+cupRI01dCAUxayKBXK6JxG53UmajiNS3KIKEOwvY+Uly98LBA1+vovO9qJrGLsAyiyMk64MC/5EiuOamKJ6MX4NaM4XHAYsrP7rtQFeg5LG5J73Iz+l3RO3Wlqjt1dB6wQqivgXKJ57rD28aWQHnQuUbgBCVIcvUaYnUPUJO1Mj2V2Dn5NJo7nNIed+rQOppfF7P32hrjUVvB+ONLtICd9pIvzz/2wLRVDteZdCyCPN0VyGs3yJ+6om+zIMvtV+mm/w/x/sBfnBJtgkLqBLagA3u1oOoVvS+FqV/hA3hJJSpVvJm+mjV6nNY3qGUHD/7rz/HVWFIEjHTL2P0c2cf5IRhACQs5iixRBKKnmPO9Hqz9MuuK/qoqYDPRgBH/gXBS1edm1zeRHm7XUjcNbbL/xwJZfld+puXQzNKwZ/mL6nL15no8Hlc7oV3D+NPAL8rX9JowmCjpGEJ0V0WdOXZQ3uyqazqkyvpgA/ZtCyBQ71PZ0BzPIxnsQJneURFf3cvND9lsBINKlRY8uvLUEhAmJmLDMo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMDQ3MjE3MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80ZjFhOWEwNjQyOWM3MjY4NzE3MTc2YTVkNTQ1Y2UyMjcyYzIyMDZhZWIzN2JjMGYwMWU5NDM4NTcyMzQ0NmViIgogICAgfQogIH0KfQ==" + }, + "-": { + "signature": "G0L2wcz+CEgVD67pxclBPZJYSD6jb+rx7EZTXeGIXtzRFCYhbU0PoJx1P9xFV5me1gJSu/ke2/G9EPHKIrPDb7bdWPggbB2zoJQ2vueVEycKEsxSlKqZFB68kHRoJh+yOrKxVQrn2gnxN8dv8dltRpXotCG66Ed01ptiOy/54d7EdVNHesL8awZnGto/Yiir7RitUnLclVRnlCfDFEJvsWohlf0J0YxGJxIDMTMd8nXVnrvstUU5ulJfh0CgZQZJv0QvKQLfj2G+7T80eHHXvrdCXKjKWXnB2UWEP4xevHKCJ0U/nbJSRTjq/Q4ZZ1AxFfyT3uZTGXOt6TMXstQ0gbTz2WSw9dKiUruqnrDaqD+apc4SPVrxQzLoS2pKJ8zqORfLYAIKK2u00c1dKJk6WjtAoIURe/sIu/aEXy9ydQ9crJxeB0SmRb1i19mJQxhl3Ml0jSMuut5LIgb0VCTUuN2IazjqHWQ8hh3ntFMuGzONAVQpiunEeVi1CEIw7xY7F8qZM575GXm6rV+dfGD3gjRwLPhozUxgBS6zn6+5obOFZ3SwWCZijnKBFz8OCWf3WH6nqPW6UsNwsLZi7PoGhRxFzR2uqFT22LbgbGhNjZW5nyT7A8qcNr5GVbkdDMtZMHUrwDaf3kdbBbirm/6NHg07hqs1/GHxX5xAHuNYz3s=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwOTY1OTg2OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82NjlmMWMyZmZhZmM0N2MxZTQwNzJiMTgyYjVkZGFmYzVjNDU3ZjY2NDVlYTA3ODkyZTFiZmEzNDMyNzNkMGMwIgogICAgfQogIH0KfQ==" + }, + ".": { + "signature": "h+gmeHw487bjBENKykniXAIovkY1/Dq7hR2TTCzSKffdpTI9g2TKbsmzgFAU2TY02YOeC9mIp76jaiKmsNuZd20wUX8JfPBrtk3mF7snoR4v/IiLVckc3U8krsJNzCQQ7DDNj+4PvW9R3phHNsZneh0r/eW1QcYsRsHsSdEjPtUuT5h262+jzRO7NtXHIqj/h0iH8twlW2a1ki6BQQDkDC6aa1hG2g4LcUxZ9eBpe/u0dFdc9Z2V+y71bcxecN9d+Lvgkj6PrrtyqjbSomdutJHMFRWFC78CEOgLp7SQ/ulxUpVGPQgKJ9SVJJjfaqdc+gVL4Ss+J5ssxjOsvy8Tmpb2M+J6HDeTq2LrVUm2rTxx3W9NKYyiRBuLdpgv8mdeTzoocV04orWMmahfaS8XdJGIon+DD1W1jTDrsJ/6womhxV504V0Iv8jVbPO15iwGPTgedEjOdtr/jESiQRN4msqcUn0AOWuRk6eJEndxUwa9VqucKTv0RX0cFjvf/EvmHbTpqQ0POHn559F4wwqbSPBepc7TxqP7t9da86DrZLWPqBhfEjbx4FJnTBW5n0CZcfZvbSS06KGBwdoXPua+X1bKKZcwaZn2EMAPc3yfvKPfiBGx0yrfqFhOUfKXU6lB3vEsANUfCVYcouQcGxKFIFH6pT2mQfghCBD01btNakc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwOTk5MjI0NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80ZjFhOWEwNjQyOWM3MjY4NzE3MTc2YTVkNTQ1Y2UyMjcyYzIyMDZhZWIzN2JjMGYwMWU5NDM4NTcyMzQ0NmViIgogICAgfQogIH0KfQ==" + }, + "/": { + "signature": "p2qTD/MnYe26nIL7bZb524QumDq49EbqSj8HtZd6CoNZhff4656fmmWG4lLIG9hT/qTVujZbHfKhyEgfpG3W10tpBvr08fuwshdIqe8ZUI1TS/4uEUHFUx9fzu3hmGskvdoTiqvp00khNXNtaggryMKSP6fExADLNdqDfwuPKSJxIEEAPB8FX4kI5kUEue540CJBhG/2+s0ATmYNsVKXqbWO7dHbfRCWLg28Q0o/EHfyw+p/Zzl84aCvQ+A7cwZDNijSiwf3tNKAkmaXhyy0YJkHUKB3yl2uzazDAGDnSzgw3MjfFdG3FjpbW6BcViClAOcN7H5cB4O6YABlfLI/5M920+c2SY5gYcOsN4vv4EPvtG7/CYMJd4Swk29XL+uKeDzqhTOe3bZwaCxaxFnFnNd4+kvSPNSsrmFhqP9tcFbFEzvhodbiN5oheXDeDgqiBKYXO8J6Q8OYTj/L2LOm6evjJFWPQXtirijEuGyHcD6Aoa59xOZ9FtVay67250QHtlc89lNATamZmvGRI8BgJAtlQDbU+9yCwGYdAJOxOUP9npX1qlzL+kzyN7zypJQub6joTMZiQUTCiVqymnZUIxbuU44m/oc69Dm0K/vnop05DahrfpPR9WRKY0jTob+88XHFQjmagDxoPJlgeo114F95KjWvJoZOeMPEoYdgOdg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwOTI5MDMwNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mMTdkNWViODY4ZDBjNDY4MzAxNTc5YjAxYzk5YmJmMzJjMzVhNmRhOTgwNTZlY2M2ZGVlOGE3OGIxNDZiODA5IgogICAgfQogIH0KfQ==" + }, + "0": { + "signature": "WhCE4LaH4quR6oPZVOIHuPOFiCznLjNKfJ5UmU+cgD/kjM6XFeAkI3iJzcyY/S6TUd4HYANQNPmJSI3NYJoyPVjPEUACzAUzi7MT7YDbsgV39mViqpYy8jTVjF/Xso98QuW7Q/MWSlcAabCG2fsUKniqWLPXrl4gCzUmNHljyLNbWNDBN8M3Q+VNKkNflgviKSyNoD7tvMvdzilVGUd/cYcQ5OEaQaRxvSjvRJbyS+xcqwiA2RR1wxAwbrAUKCG8Gn18rJ2dw44PBlyIx+QwgQ7suf4ZGHlhFYrMeTFf8NT12QAQTEU1McsX3L+60boJZEgILSOsRJR6ohtx2+1MAWFRlRdoMjwSoTsQ+MYN91KJ1mXsMecMTXBwdWelnnUFjigAMpZF4YUHZuGMKyf7JyT+HuSjGHtekMgdiWzhULYcYRt3+8qGGFNPXpENsqeaSh0dunSO0P+uDkz/d2NL67BZNV6qN1TPO5wieyfHFtGaXP/teZyBmtvptIxSamQcLiBuqJSM4ILN+v5OLeccRFIRnpDlmCg7pc6aB6g48q+cLTO1hJ1HJIuGQokRRdoTfg7qaE9wE6RZKf1gCecvJCwVa+zW6p6f+uYsxxFECSuBNe0AHgRKyigcis1ll0oAoKXK0SLlkBu4/tHxI1Otvqr1Z80ZMl+O6RMSv4+8t+k=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwOTAzMjA1NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jZDU3ODNmOTkwYzMzNDA5MjIzYzQzNjI4NThmN2E3Y2IxYzdlYjllMGQ1MWU5MTJlZWYwOWY5NzkwZGE5ZjdjIgogICAgfQogIH0KfQ==" + }, + "1": { + "signature": "pjWN8QD3nKorSrrdlzemoStPtmGPnRK/y4fafVtCZi736WaYlZw+O1NdkDJS7Aff14Ij6QQr92dyqjvnevnUpoyVTozWLw+bQGxadjoZfTGKfGaPMkTWp6cWC8g29jVj/O5zdCAyptwnq5vEfC9EFZIzRME8mEIhJQXXZtRX4Tq6MSkA6AFL5ihQAkfDZF8FXJDceT5k7Kk7TSWVkTLKitF8SZenSRGuJhjnBav8u2FLPaAicMzXtt1PSfRWc8TVIY1OhkB8I6or1WUV1gU9Ahs3a/hwafGLF3dnwiF1WiLQR8DkWoo5rQuL23mVmQb4DooQ/Xpc+vC5N2yApVrXLbuz/scFkGrW/scDeiCU7cr5mkEAwYKMFjt7ArR5oz/NcijtvLn37XX8lHRgsX+TYt8bwdUuVd6Ijz8Jo0+P9Hoo4wcSpaaSvfnLQ7HAnIUpRSfIKNwVKzGGzaEYfWgzhiMj7DRgLmVE5xGR5tFPJXP3xxFP3bpSkgvkhIMJ3bi1Qzsg5A4I1ACLADLVc0tI/AY64DOIqId0kubQ27G+Fa1h1TBI86A1CL/dGHgoZNzSU13IU/S5A/8txvGm6tbsWjQzst16h9suGMmTzKpPAdlJJWjqnivkA8GaBur2s8EoJwkRzdqU3QdPutK/rQ6/viGnWU6p2IfrNDuK9PtuBHk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMTQ2NTc5NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83YmMyMzMzYWJkYWE3YjgxMzZlODk4OTYyZTQxYjQwNWMzMTlkYTIzNzEyMzRkNWVhYjNhNTIzNWZkOWI2YTEwIgogICAgfQogIH0KfQ==" + }, + "2": { + "signature": "gS0/lzyLNswz8uAqNt//OvhKcQGmNIUqNNCqTeFfXa7oa8hVXMIDKOT3eG0rM9yj6aHx6kEYEEq/OxiTvzTqjFGylroHT9NqTSvTl306nWQVGRVHspzTos3zaM8qnQb5hqJqbKeep6rhiTU/G7NhYD/ycWje/hNBlOeLMKzM6ft2B2pHLtQ3/TkNnq0BgpJouRvcODAo4DBIxkd+BeGj0yN18FTCrY2X40WjIi7Qg+Md4xLy9LvPrZiBU3L9FXYWzM9LlL9LawO2146fr5/iZZtwJiVRv7JllvwrGWarEKtkBqTdiEWlk0Zr6wMPrwc9XEz3JAI2b4rh3M+F6I0Khq4Se/6DbvmJq9fUClbc53TFMzWsZS+xeA9Ufzj+iXWaAuQDdtXd3+MMAIJa35ofd8urGn3sKoxMS1sn37vcEIqhVWLFENcuePxQXSrL0Gwa8/wpeBfqFf09l+Yl9VM0uO/iGpozz8EbyEyRcOqmY/WR/VuBvadoQF5h8oNRuHYb11P5aA4LFDmUyWvd86spFPOS91nNzLaIfw0ZHY2BayQzbFMmyYDSk+Y5i5Qp3EalREh3+b5zBgAsoEzlG4bvUt3sj9NYB8NFngvQMGECoDCPMwUQafEqvUmiwcE8W78Cf/1FNjlMIsskbWcdTxIkqXIbjO/+MRCOJv9gpmaeFlo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMDY5MzcxMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84ODZiZGUzNDk1ODM3ZWUyZjQxMzVkYjE2MzlmMDRhNjllYjA0NmY0MTJlN2FhZDFjNDYyOTg5ZjRjYzg5ZDUyIgogICAgfQogIH0KfQ==" + }, + "3": { + "signature": "W8EcGWPYMBfxwKrsSKz6NoGbJ+krQ08HcTFKSFUO/sPb857qJ+uItF0M3fynnZbUR2D92t++9p9h/wP73u2OKBqfDa2jPh3dyNQ04+ILqZ1qP516aGOn2f1Feso0vG9g83Vuv8M5aFdJWXXMQj8Zp86R6qTRIcDibS6TaEFUSFUeo9si2+Aua3rhwFiFmoAe4JP4dbaGy3XqbnDdiiTCRJXGaHhaxW6dMCL2pFQNxhhyR3XWx1WWCmkvxcCne5az8lUqJInkohErntugELi3gulBoJLp4d3z02SVC1aKgiQxGIF6ej2szEWkDnrE7xRy4PXhyBCGe+Cxt3UojHbyEbp3pu5jt+zG4X4L904tczv+CvMzU79BTcmMiWM1dnbmGLLT9LYtoLHpHA9Q1Dj+qjVAUoAogUsqjkvVyxFzICDIPq3795dvK634MwIxocdpKvZSEPXj1Crk5NNh2Uhy8yp7ccmVHMUSlvOfJGBBqKHLMhN38AULi5MFJx8mparzefiQQt37B7OHmqEaJHmWvs7T0PxyK93QkFA9sZGm8WyYmYZZG8+y5rWI00Cvin2piUFCs2BNTd/m1KZ+M1M0/YuQaArN5Ff1e58TnZPDhiBaNRDWEtL0j4kF00/g2bDnrSh3+uWqvMt+Zhdd14/mlBs7/yLl5JvVVvIwwn+8AMY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwOTkxODUwNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81YmEwY2Y4YWVmZGYxYmJjMTMxZDQxMzk0NmYxNmQwYmZlMTg3OWNkNzQ3ZDdjYThlMTlkZjYzZThlOGE4ZWYiCiAgICB9CiAgfQp9" + }, + "4": { + "signature": "ro5PXoO2GxGTpCZLfMUbOpJ7BGeWvqz/oCYWC2dzyt+cM1g42BWeRw2Dwr3q9WJZhcNIgh+YJ4QyPmaXadpLvK1t9Rla8xBVKHLGRrReDJgpOsPdkGBe3gvt6VOULMoaDjyBO+1aefl+zRVuFY5dM2DKrECjc/kP0j6AUcGCLZpTacGRc2xmawpOAugaD3TjzE7rAzijTKryv5g9MN/zvJH+LutQsmFTcqTSJVkWG0GJ1Kz9r6e0yRGwa4gIR2AtHQoCI7VJbv3Fi9rWPBdgKYZQ08JoCOstDBA7rkakEPBLfqqBEHpjJMrMf5QV7GoSfLW0EpDxy8d6OhBhJuyfPKdfEmQE/OteWv/raFlAXPBaBJ8jPwTReXRoUeRP9GPwylFqb1OMgBphmY5/G5Q/UTQpGvvQyr4HUJL59g+UzKM7Ywn+EKsoSJw23sNfocmgEOtVmUel3HyXD0kLHRZNooBisRO7OKP8Xz9/XJ2D5ab1Ysbo5b/zKt4ZVKESx9SOIuctLdDpw21My+9xqmUSLBPjfJscCwNZ6rlFJZOOYB2nSC/R/nZBsQSi6mgRuoA3mDbX3A6nwUtPHlrZMjWtW372LonBA/0JPjbptEKoRabrPmmma25EQ6SYhcm0Nc9nrSCED5SQ2FEbrjRdaNfOjUY2UqN2bOziIt55bvH1kFk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMTI0NTE2NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mNzZlNGExZjAzYjEzMDlkOTNiNDliMjMyZDRlNjFlNjM1OGZiNjBiYmM2NGVkZmM2NzBkMzJkMTQ2YWViYmU5IgogICAgfQogIH0KfQ==" + }, + "5": { + "signature": "XmBzi0FRK2kGxoSmda2rCOkB8ieAuoIPlMYCMry5xOkzrgtOTipXGDXXN7mYhbLyEPOVWqxcxdSHidxs2G+i3rtAPTPTKYRaOqut8gBZr27xEEuduKSRIRuL/tWhhQ73zI32db9TinePAnaO5E/n9HYmG9UjFZ2VtXEHRcO+5S5u2MHexe9B9rpVGoHdd5a2Hc7rgzM/XzJgLzrwnQHkqCdhsnMpbRDMHfJoFd00b3OHr6WLKhyPAVnfMGFwMyeIw4TodxjJa7txC4cZJcEPh1Q927YcEkHQlWni6lba8/ruZWbkk9zaJ4CsUVUcIH5mLI1xDkSKcDvjxYKYzSV+0cm091TAVmjUe9H6cPSOe3RWA6buCihC0Vr6chhiEqm+tXtDhx5GUq6N5g2Sy/AA0I5BOjN8h7BGGdLPeShIWConHz0e9cQ5SB4Dvue8t60nTMta5fA3vdRjfgdSV07lvHFqu1rEfJQG8HnqNYF315x2RMsxkXNvVOVngGX2E6XQnMbU/Qe+d4CqaVwxLFhtq8YCYWonrvYgfo9CCxuMCKHZHieWVu5xJ7I0Vx9ArgZMWTj9MY/iHf9vT6Zj3gDPuVku1ICu0IuYadYbe64JuF9GD1XKLrV0aO8yenhYaif+01dB793btm2K6eJhLpjOXkZN1y5uc03RUlm6RXF6/6Y=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMDY1Njc1MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83MmUxOTk5N2UyOTZhMzM3MWUwOTZjN2EyNWExZGM2OTY0YjU2MjU0Y2RlMDQ5MDVlYmE3Nzg4MzE3MDg1YmI5IgogICAgfQogIH0KfQ==" + }, + "6": { + "signature": "pgFYVsqqnBhO3jxjntbP4pRYpPkVx0P0T39kd/BzvQ5+PIFwIQEjAy87lP+kTyCwHCBuSNSnn2D6TyHVWkdGkkAAgIUWVY/lcGQP6apYMQIpIiZqdlSUAAw3SIbOYGTFh4KnoANTKLwNgAleVE9lcIiYVHh+zE86jucyPw808RYLEUmf/ttUO9OTjuFY/vyz7hxRklW+YxZ7O5achjFxOgkN0quF/g778LwknBV3ei0i2i8E2IveC+n7AXTHIGOPcX63s/oZ7cLEro+/hKsQPKKlNvlLoDwdBzc0uy53yPbOnYvxll8MsVRr955ayTisAGO5OrzXWxZCZdlD76EPgs5hknw55Ewz3aG2vqa+rwxP+jEgzs4wMF0aJ23HrvfIAbku0IeZf5UebsQUsE0+ADR1vH0gwL9enBjLoOEoWAD0eFD65UiKCra1aIq/BDvciPLbFTP+Ej+6h2E5xe4LPSJzYjDPu+c+/yFzKnyu+dxgQwx1b1ygZkJejHmKJIfpClTa/CnFiE8V9VRwzkUcmL1wZQfBCanMVol8GvazXz4PVhtG9ug1wB5fl245OMIUe2SMniUz/uQypYEVZXtXKlp6ml72aGIm/qgLz1T9m12GRhTlryitvY8SDw2rjoPQJHrXOROG/wnpLgeyXaPwMsG2HSSktf+Rjm6GILwhjT4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMTI4MTg4OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lMTZkNzU2MDRiYjFmMDIxN2I0YzZkM2JmYWYyODJkMDMzNzdiMDVmZDY3YTY3MzA3MDFmZjdkODY1MTU3OTA3IgogICAgfQogIH0KfQ==" + }, + "7": { + "signature": "pskCbp2eHCnS9xJt1DDucM25MWWkX3+v2pMVTjwfzNpbZzPlgrLyrHa4mrmQ9s4J1SKVUudPjG/aLvnb8MN/vn9zK59uJ7yy+R+NF2Uud2O1jZLLbDL2sPBCPYdskwa/k1/Uo/vSqjQwuNOCPmP8UYlNJqfvDpSk58rN9sbgxvokyRObq33RL2YpbsH2iSyMc+Og2SI6IsEHwU73vpWep90WwQ31aYBZboFg61h87pDFbqF1JnFeRCIguTZbfSxB5d0y2/5KIHF6nK6n5bkgrNuunEXvCbmind4XLjqTVdcmYPVnU2g/VeGBFRsHqrj1axLsKdgOxPm7TcrS0Z/GMP5eWYM41lKdxzuW9AkGyHFo3gHviUIkvhrfPvRA/2vs7AJeXb7D0gaql07bzFQR9wMW90zVmV16WUpWIMuQUQGBPyuUyTdQovc/yeyCxbR7h49gGzxmYUNEZE9mocBS7/vI3jpC891jpcWomn6tIvbP5hKejxM+CYt2x5jRhHlOKeHeuclm+uHMSwE4dBegZzTU2Bj3Ce38pKY1esBws8RLW9IaC1FC6FB5mr6bZfUW7n4vvy6erfMInI8M5yb67FJ+zFLQ4YxjeRjeDkr6xZe2Itr3wkdxYnnuHQCbUSaxhnfkLNkMNPp2WovRmWmxRlB9JdVCjc58ejBS408hKsA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwOTgwNzc5NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85M2YyZmM1M2JiMjJjZGMyZjhkMGQwMDNjY2ExM2IwMGU4ZGI0ODFiMzM1OTU0MjhiNTAyYzlmOTY3Nzk3NmI5IgogICAgfQogIH0KfQ==" + }, + "8": { + "signature": "kkjF1B2xxPox2+fbhSj+SvktpYavGknissMVC02xHNORwkMqZBEHL4vVBQPgP64MVPASqPOkujVYJWtYjHNnvV0svt1Y3y96OG2oszm+5HJyiyDYIuokBp779CX6e1ENxMzOl51/NYS0J3OQxvxui1utQ2OxUwBxUL/1Ogyvce/higRpApODWXNqbAj5ktXmEg7JGqc/Cs3lyrx+Rei2shmT/KOVHE23a5zEegEwZHB13JO/Hy5FocYS2T2TJ3EJC0LSw6WbBjwV6w8Qq1t8si9yH5SGTqJJAnCqd3Zn4/NtT2l6VZ0WqhA0U6dg10B5OMJ2jbch1iGG03z7V94jq8o4Iba18+lTp4ZnmLuMC+hg3ed1mHFkw7wJboGyi3FTlUJBR1zaaYHhcCY+l79VTtJPQA0qKzp3BjIkJEPsmrGykJZigbNnxvE1K1R7Fi3vD+6tvo5PH/TEyBaKxgXnd/xawf2vwo4kBAxDU38SBFi9AO2wm2h+Ci+vXqTY+23fMnTeXkTUvzLPPqM0y+dPZeTMNlrxVSVhhkXV/qKJ+J4Av5Cqh8L0JCWGM9yi4xD7GiZxX0YCQFAVymKkr6l6IMy7BPmOuqe+mezP9yTabe/Zgj1BDJ0iX7j68+5bljO4OVjOFFn4gw0Ji1WC4N81YchpE/MzPWxySW0F6s+RysE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMDE3NjE1NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84MzNiZTc1MzIxZGRmNjg5ZWU1MjZkMDVjOGZiZDdiMTRmMjZkNjI5Yjk5NmYxOTVkYTJkNGI5ZDg0ZTRkYzc3IgogICAgfQogIH0KfQ==" + }, + "9": { + "signature": "Q8SjfHe0Ov8BJbJEwXvzrXunB7uN/STi6Qvm+T47Bbtj/fgHQP5rOJH+E5Xfj7wsingD9WkN3SS8JAeHAImuLIlDb6S6pDMgbOzCmNjrW3TubmtYVvlPEa0O5HyHSqCSRBcePSA9LeLxNx0qfU5d6krn+ESPHmQ6E0JPAx8tL0azr7YVJf1PF8h6uhZl9LZWLmXdoEOr/Eo5UUMtwETvyvzagiJVPUZ2OP1apcWCTZG1mpz7pvQlN6R+tbVRJg8tYOm99DUvdNTpiA1s/9wgLff13PH2Wsnf7OVP/MGMhd6ZRqKZRsLUPhbrOMSmvSV0mpDvix9aVZ1gDhuVFHN6qJIMRsSRYrNqSo/948hv2u7dfmQZnQnrknArPmNt+ms69pJUfVWszpVxoZmQWY63CjYJiEOCwPPhubjqJytczdROwSqtiJmW4e7K9UZwJLM1qhZU0P0scB/Ge4jgoVJKPA18I1cVNvReV+1bIiU/UylimtD/kas5X9DUq9OJ8fniaHdbGNm/oJuQmpLEt80K32v4RdfM25vQqIRIuZ/gA9CRqdBqjzoNMqRsMAlFidQ417p4sz9g5dkEtxruZgdxlAAC3m+mZJqYA7rv6k2xXyv993u9lMNF+GMbCLPOArfeE1XxJZYZHUvWs1qCYh+Gmjz6uTCfX44TA1GPdb53EYE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMDkxMzQyNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82NmQ4ZGQxZjgwMzVmNWZiNTk2MWZkY2Y1ZGQwODQ4NTBkMDRlMTIxMjRiZjk2M2MxNTJhZWRjZTIwMzZmZjUwIgogICAgfQogIH0KfQ==" + }, + ";": { + "signature": "P9HADJzkxDcTSzPJVMPdqdXHFVnb5LkOz57UbcjfBqgMz8I7YIOQSiFna3TZS9+CUgh5WaKxZbqhdTVPBgWbvtYJH/cGn+g0696IhEjlyEgZ8n7/Hefizd5aLuoE9qxzrVirXwQZTX3HrWn/OFa0cIZZvXcqur3/7S0z5QyzRu96p3zi7gXFMqtR3nrp1yxm7Y2hna333aChZbM6Hub19TTDtc+RBCJ1mhhAtoaV+6Z8zWVzLaMphnPeEbaPG8IZXrDOmwZ7lQNo1hq43nqlU4r4cmirtw6uRnOgEo0UZeUm3Q9b+DWmz3RtRVw33dqkYh6B5KYUHyTKrXbrXScSI3i21TaqO20RF+rcgHVI9RqQaBGJzjU8CV/xcQJkVwdVwQWaGRi16nEsGbSyWRuQOxhPBeJStFYQEsOKldq6s7Q3MqreNs4Zc5hQ7K122Xtg576ue+axapiNakT4wXWNUC6UwbfN38rRBPdIGg4csrb0xMi5CNABNDHTI5Ra6tMlF3W8hzpiDe7dZ3AwX7yDxaFGMOKQvqmh71q1bn+xC6D/YYF4OuXq71u0E2LF4NxzA9CWtq9BBc1bovUOrKIKsWIh7PYr1fJwbR2j1QeJOA4aIgxCNWCPqVWCr+uAN1MGfEJcSMpODD9HmiCJpeQcIb8ERodYld+2Mr+G9O9+fIE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMTM5MjExNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83MDRkMTMwNGZlOWYwOGM3M2NlOTA2YzcwOGExNTdkYTJiYjI2ZjgzNTFhNGQ0YWY2M2MzYTAzOWRkYWQ3MDMxIgogICAgfQogIH0KfQ==" + }, + "<": { + "signature": "pCSWHvYjFnMv+YS4YcR1uHFaFnnDh2fgn+DBRTMeVczjqSi+2gqUE646Fy8XU8MtVcRoJCyWs8+RrV74Gfh6WLuqU1bpXP0gusvEgZmGsRU3aVxpJl9R6f1o8bDHNMnnOr+/ilnKMG4BZAhuqemH3v5NXslKKjTz3+MUIAMVYLqgLVGloSfSMQnOtP2ILFnXydfcV+b3ehiQ8LXV2Q/BuEXcjKmx5kpXTVZR8dDdglT21CZ7hj0nngrow2dwLYQv61IzqpKGMhkbt4n8p9bfIjXidzjjXuuHIExtL09qlGa2Skk2qo7TYL8xi8XBkHO/l/l6+gnescwBDp4HCAxzEwaNWeaCeoWP7syuBrUbf8cFDb5XZ6l1sB4Bk/An0dElo+yK6Tjs9mvP65EGEnKLB+wf0UVDW/ReTC1QE0r2cLDCe0FzLxB6xY22QtWNt9V+ngrraAAbpeokgHlNPQ0H1LhnFxWTEM5V1b9XPFls/0s53ue6q36YD3DsHYqNhpLIlMMeUHQD8kiu+O6OAmf/axTAmHaIAb9WU18IVWT8jpTJFaTv9gAx/AheTIL//ZReOA8v4Rm/Lxu0o7thY2QaMh5U41G3eFCC4k9f5AlVjZza0sw6W/GVxGrH/EAp/0o224C8kkf1c9pRwniiOuAt+8DvRkHVl8ZLXo3J8g4vdvY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwOTY5NjgwMCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80MzliODQ5Y2ZjZGE5NzA2OWJlMmM4YThmODBlM2VjZTM4ZTc5Yzg2OWQ4MGE3ZjNhMTc4ZDY0MjM3ZDUwZTlmIgogICAgfQogIH0KfQ==" + }, + "=": { + "signature": "A41FbsNzUQgM9PjVbis2cwLnI71cSdw/GKRFDegauzf5KzsxfXOJXJqVhTb6AV7jCLJlH1yyC99dxyRv5LKPvZ2eaA2U1hGUf/qUu6WrJW599F3S/50tg9mgjf/d+OXeeUXlWF2FbaxZoqfJapROpgORVJIrp2YG6wrGOSCX49rN4RYchi/IsZmtlSnUf4tPRgkEroSMguAh3Xs6H3kfkRzi3LQgxY1LArOqOll+zV5cAIn7uk4Rxb9iQU5ehaklnmlp++LLN3B0yo0+ZJUSJAnBSAn2n0kmsM0Y3dTso/SmJk76pX05UJswVg2U50pRdFCAobjYLdulo9a2jf0c2mPGRJpxBxOBQ/K4y22ojgUWAl30dMkr0gzOZBNAUY3HBUIjhBHeYOLja9CQNO6y4jCHQpeJHp9dtLl+APrZiV+ewSlmnQrOzWJexYo72e7ReHjXqoZEbLqBE4db8/CJvgzBlB0ckuJ7vwR4U6eFGH7t8ShX9dHbHDr2S+GnZbbyMESWS++XcQe+lR7MCI/LS3pYblz9m1ainHQ4gg1iqXll3NtUIwhpZPwdjc5kWsCVQFgwTOx922znrQEErPa3D3G0S78jnooAACrLNNj5gnxEWJL4vH42c5KI/ywstjcrgIrb23Dv2zLC5w8gsnA323T30id91someUAXLaR8/MU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMDEzOTc5MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jYjkzYjM5YThkYjU5ZjczYmEzNDkzODZlMTg3ZGU5ODBmYWI0NzllOTg1ZjgwNzcxYWJhYjg3Mzg2Y2QzZmQ0IgogICAgfQogIH0KfQ==" + }, + ">": { + "signature": "tcKbOAHjxx5Sss6PVdUxm0ieGA2ZLMby9g1KrqLkqYcs0VTy6+ASvGV5EitosnfIuNPLFx/TgE1oe0r6dZ7LQYr5DcQJdte5KDOVe5R5Vlqz5lnRRYw9f5rrWiYo8unhfJx0M32B/Cly7IpLpX0DJ8qUTP4DGjc6trJfv8Baqe8FnYm6z345IMo1OT1VLEVM7VrH3OzZ4t4hJoQersjAvXb3RhipWNT6wlO+lLJbSAWiMvITJqlrzYS1SotaTLQvVejGFPVWma2HJegXN2yhZkQ7MsqCZyu+XmAWNbLjq1JNu7H1WmDRgOSiDfHQmROpRgan5KOSD3qradegfJ5oqQ5pvpQAqF4WnXay9DKYn7J+V/hzNBJhObCDg+EkxlvZQoSHX5EYHiutwqLqkysP++eHucjsHT4SCQQO6S17kXDTmoCJ/Ee4QAIycFi7SZHKvKClFGgS/171Kr9XjbtsZ5KOKY0v+MPL1VjicpxkXgAslu5ZwpgSUiPbS1ikI59SNcER1d2hokpRV4YSJD3OH9p3lytpt6UDSu7UHv/IszxhzDdggd1010kbbACiZyQy43MqqPjH55QWnPtdNvQjNegfxSVbAMfTgKH0aejl7E0aTJTQXBU7RodHP2hy1RMBlToiaokucsl/WnpMNxl3KoPI/D0FNZbd6FGxx+k24Yc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMDUwOTUyNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zZGE3NjBhZGNlMTJmMjU5NjE0OTZlYjM5N2I4YTFiYjU3NWRhYzg2MWE2NmE1NjAwNWIyY2RmZDJiOGI3OTRlIgogICAgfQogIH0KfQ==" + }, + "?": { + "signature": "O1hld/OlsSFZPMVBhSYp0EqSF09oCxeOTu+921NsLY1yNzYkzdSSOGHXFY/D3kcKdqpqAJ5l2NIXeI1GiJFNP/WtrKzWSHs42K9QDE29mtET4h8YgpU2vxb4ZpITZlmN5wHyytABpxhy1uTXUSc+eu3JCPHY0MQsVvQacqBFtZHdvu2LaEs/81vPkMYQEW00Ou/33a2itaKZc1hs5pBXVK3cyj/Fw+UsLPjXJUvDfm6GAphRj+xGpETMV8NmWzPGeLsmpDcJuNtqW7J+lzndfDDy51JXphlUzOVc5Z5N/8TiO5VjRDf61Qu6Cshlg4saPAhrYyXlOz5XfIyxICnp3swTUVq94UqW4BilDdyxymQx/4YiUCYzWWTDEq1aHPc+Cn96e4lu67gZfsvTMVTe6y0oHwJOCiwxyUvy+fYMQMSw0lwx5sjKSeUhbNgcKGPoDa3sEA+DDtdQFgYRt48/km8Aaww/fxssGHkeML7u7+NrmqyNtmewD3iXo/1b91M5RvmaCZKWHbskBTgEAB3GIY8s0OJtKkHh+cnLbC8xWg+ZtWkRG2ymIhWMd8tM/Bgn96AHI2woGyK9ItuGqjh1ibC+R8zcXBFKdqtQjFbu4/ate5l7FYjY6gy1qty6kaua95LrA9rMgL2xY0IsZtI7V3TILs6d6LBmwOngJImzUzw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMTA5NjkxNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kY2UzY2NiYmFhNGVlZDAwODIzMjNhZDA5YzIxYjA0NGYzMDJlZTM5NWY0MmY2NzFlYTYzZmMyMTk2YzM2NWRjIgogICAgfQogIH0KfQ==" + }, + "@": { + "signature": "IYbSUrh5NuSuL7bHkNUwEunj4uE8VOobsD/AhDQr0AdIkzZ4CmUzImjZFeruVxuBdTByvpHST8BntV1AaTVS8pn8+uR1jJQ/titv2TY4X9CcuSBCXpHD734ysrF9HklTNxKfPE7F9EiKCltBGSURcUdtPFwaSmOQfMb41K7vYHkkxgN1X4zrzwhVyGffUiNnkcOXITyO6e0mI1NCYr6sE0LjVCRWMieAuAnl9sOnY6KKPYcxpyc/JkXnHJF37Ueo5F7XYEr1xBm2NrvnLINyxo03W/Dii5kj6ZsF/h6/LjZbAtUGoNkm/N5bTcbrOjUM34suRRUc8uOpEo7JouaAnFz8xI7A1gOy8+6HIrBTp1f7dzrIPgVtlLLKParMRBnk6hEipE3fDz2O8wUsl74E35uQjkGqlOWNPe1VRCHi5Wp+5J4k38tiVH1cQkUumRwyrA+28jycni6AUZQq/kXVcSbDze0DdESO7Wj2cQXxJscCcSB4HVg2arYAMqpOvDH0NKqP/iwG8pao3kujnllnFUwvDpPO9JNqmiRI+gHaUB8pUpKeoIutV/f73CCKnAR5kV7Be4D/66Q6PMFKgyBiuEnr9gMeBQLnihDVHFnd8v2oQXKUt3x6qx4U61vazmBXFj3X6LfBRuxF7bvUxZOHey+KoJucGMNAMGlv8VkVh+8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwOTg4MTczMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jN2VhMjY5NzdkMGQxOTE0M2NiZDRhN2VmM2NlMzRkZGRhMTdlMGQ3YTc2OGU4ODI0YmFhNmJjMGEzNWU0MGU3IgogICAgfQogIH0KfQ==" + }, + "A": { + "signature": "NwoDJZvE5PV8MrI3G0a9E4b3UijImfLm+z1Hd86QcD/FGsrJnrgdU3PHUNz6XikNp/EEH2/J3Bh6xYYK1RdUZz6SOb7U3HQxSVZRYPNFCgfmhT9NPhVyA4Kklcx3/09Lb4/A1FtzEbOB8nW7FS+nyOJfYRgR4vaX0H+Ml7VsFHEURj5xZDOOmjp4oiUTlLeCjnxsVWzQhyM0DIqQuw4vS+UpK8uWoWtGpsu8Hcezce4NHpvDKftoPT5bx36kXyyX/CGqAO4HnKVZbGWOc9IHXO+C4uJJa61OwjD1CTkEkjHlGawkMy70zcDlX9Z2c0xKD0r5+rf1Q8Z8Jx193/qYvzZTwjQNoaBUJJePfktbZa8fyuzmpTeo58uyZ7Ee5mCu1Pu+sNofiE4fAAnYFUc14E+Iuh6S946XzFLNTLfbzPFjKfWksyGhHG8vugW77RYKqYhPDcBBxcdygVIKkuRilUox37jBta2HQ31506Y2cAEw0yUJ1zD7eo+YmlqArSh805lwPVdW7gZHfC65cMfueodZF04OT/kc4gzGL5Dh3Tu5pu6+mGCVkWtW4ZOpwEezCnX87P8zHx8TUqIpUhd1TWjUZmU9aBWk5mP+z/pHKbv99/HN/GSv73S5Ppe0jrXfEoVO71uxUIKTBPq7KvkWLZWSb7aQsV9/maSR7h6WCnA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwOTE0Mjc5NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hODMzODk3MjU0NDAxNjNjNGRjNmE3YWIzMjQyZGJmNmU1ZDNjZmY0OGYxNGFmZDRlMTRlM2Y2MDA3Zjk4NjZkIgogICAgfQogIH0KfQ==" + }, + "B": { + "signature": "PuLTYsSJlkJIZ+AGuQ4ihR/mE7LIAGkTtaoZD+3YUWNu80PgAZ+N325Zpn7SWljZ2ebNlq033rzsi3Vq36EMgsVR2d4J5HjoV6rmwsQPMYPTGajxfcAngral/Qwu5YYgi0KqlsoZxx4BcDpUm5dK87zIvUQK6xsBTb5CM+oiW4I9amiDA3QUPj3BdQJOoT80gOCtZLahMTJDvYc/cRCrCSy90K35F0aXHuNqdZTXgKeFCSsra3OaUXNY7f8b6/WAI2TPME/4q5q52QtSMU9QL7B9aaItOtxMqcsIJs1WgP4/XYs8R7HdY/RsujhyAEsTtRVftBInySVDlCQOZgxOHNnQ/8OnDg1PSoV8/CykyjwB5iZ6fAXNos5Jh9woo6NVFPVPIQpAgRXXRPsUpIzAG0XETssr9jZHa6pozLTdpak4UynSiYF5dbfTnEKiqXjLl+sE2hFAIVtHUEKf1ceNGERCk1TVZAUe0JpEatlriQMMXO5LmNENu+N69Iis0MT14lQS30eWecCEEK48aEB11njyKVC8+p0zTnxvAjkQexKzmppZp9u063l3IFHeoKyBBFtrYKlXTjDB6T8aNxj9ZMk6CBpqVK6tvwg620S4beb2JpcpgQbogQpMsn1a6qwkH47P/yjBafG7Y1k8rHsmI57x93V45pVZ/Z7Hh9W/4K0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwOTA2ODQwMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lYzRlMGYxODZlNWMwYTJlY2I0ZGQ4OWFiYTU5MGMzMDIzOTQ1MzliYTcxZTMwNjU3NTQzYzBiZTFlMDNmMDA2IgogICAgfQogIH0KfQ==" + }, + "C": { + "signature": "tpFHAQjwRMsRAIaDg+JKNqmXvYztzT+3uh6LeaSndQGfvDEtlJ5CWFbCEwaBzUmfCPNRr5RpQEb3mj8B9bwlTxo8vYCmsbS/VELd4UGp+ydIMe/ELmHYI53IFyfS6vj1uFr1fuA4Yyc228QIfJNXCj/1pO2Y7eDDdfseKT3K684TQxwqC4Jgyg1Cq0GJK39hS1f6DotLWjk3rcPXwgnndC3Nq1DoXQD3an9s8af1okSleJRk772tAmMv2B9A2UQKw2m7MYmuSXWkEy6hRg7G5uJJM0kEKvD/NX6uRKMD5gPF9CLVRxd5l9CsoZmiQaZkWYLeJ+1vcH0+IAp2vyxvtdZ3JRGHtJK41ZHb0pDtc2bop6RCKqCjdlm+PUm1wViRrmxTSF43SB3yemBoYuGYv8fjSFzSQW/Vl/bRUBiY+d/BWmbseuvIFG0HpTZjW4+ACHtB5mqlQ2lS0leCaDfyG0jkQ3j9XvnPDb7hBgCnSZsBUgPt/u3PB2ulCqE+DvLX0YhV6kwg2z6gJZrPQ86pjF0YKQTcFNXDw74oB4n034wkq2j4cMp786a+UkXKjFooIdfLPc95vR5acBqfBTLhi081oFU/1+/dqs4HhbdgNJx6WgMOmhhdlxezCF0vw44r5iWSAeX/TiJ+4IkuIJr4zn7do8P1iIcnjkQ9nWy2Kbg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMDg3NzEwNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kNDdhZDM4MTNkZjhlMTRkYWEwYTEwNjNmYzZiZDdkZTY3MWVjNjA4MzcxNDk3YzcxZTliYWI5YzlhNTA2ZmQiCiAgICB9CiAgfQp9" + }, + "D": { + "signature": "s8BNxtQwKTj1Gfod8mLfPgTYx9CxP1UZK0QS2Y6AvX9jgI1q72IrNslqoJwULej9GlSCLuBlpkgwsYe/sF9JXIYhyOVEgxzH9vXepDTyEJY9CWK/zmsAsoiHj5r+8eQTgT71QLqoLTNzacgzPrwtUoBl9KGDpF4Wp7++cWC+Tw1HruwrFw9kMsp6Oj9eGikTWQknc7hzaCtjiwq6RBrAoTJDxeZb5U987Z64l/VqG4P6OVPEecK49tARvmgr+PPXi2u3DJF+wqRzNqyR7iq/JEwH/R8TLGa/n2ggl01ZgO28D05Xx7z5Zj8tFZdg/ledxV3qe9P5lLDYnmP67uXxmZabR8dZusZoJ9hCPbTJF0vHYIdijrvd4oWTkEpIIrsWaDVZakkaEvq2GDJzNqZnrijwP9DYWinWSuoZT/1Wh9rb6N/8m9uLdbwB5nEi7kaG6gNSm9UjuVV2AvWkU0Xnu3IITGevMN/8fRgEm0sHuS2AxwfHKKxG3BSNs6X5VvHYmAf4OqrhJD8mhtGNeTtYseGjMqSkiD0IlRaaX+PNxNnr7eDZhD5TGUfLocvzzmnBGRHEjqY7BuKnMcQ26sLdbHKC5Z2oeCjyWBMsxEMcTJvLJRkJXa6drI7Vtr60/JkiOfRe2iHt6AgqrDkfiP+Uau2WfOKEEWEKRkr2AvrDWNI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMDQzNDg1NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jM2I3ZWU0ODhiZjcwODY0N2Y3NTE1MTNjOTM5NWY4OGQwM2RiNDkzYzJkMDk1ZmNiOGQyZWZmNjM2YWIyZGY0IgogICAgfQogIH0KfQ==" + }, + "E": { + "signature": "UsMwUj/o47NxYTBnIye059CBvGeM6t4zFCyUhNcuVTqwDZt1TWI4k3Rkn9OxTR2mm0HFPfMnb5wy1inARS5+3QUazXATKvq03VdH3HDVBzkK/NwO8gNIxlgNWdlf3T/EJUsxnPVaM5GJII2i9NGykA+Gv832Yk2wZSOXqx1s2ZB9vzNWSbfKUk8f2i+rzdUsjALZ1+yJlrkDPtZmKJU+YVIBPGuslcDJPDBIDzf/GY33sRIDrJpdPndq/DYuAa8YJoVC6txn37Kk/fKBYBq4fCO9PoIAlh7BEH6w1QlbXGAtjKKxt0dwTY2XWdlYxDB4UuPiLBgIU9TS06vfiKqbpe2afTSdzKrNuiGFcZ7ZiJcO/zeCxfhdQ6mIL8QHN6cPRz1xyLAZnunUQsF5TCCs7agTFhcSrCoHcDfPguHVAG7U5klXr1y3Odrusj88oKwltAKL6NSHy66SXDvHq5iHiivoP/LeWWsikWG+LqFdFRmDXCBXBDsh5736Mq0kn9tIn+3LiqOklqBIAbDEBmbaQwx83A7nV4E1OChi+mvk7NFAHPkqJPVl2j8kZxZyp5TisIYxpO4y5IcHT+0ta0xTcy8HMOU5/QyGnS43MPR41MSQ0RrB0f1O38gGMFZdK+3ipIk+Qt2lMx2mafa6fmJSqPkaGfNAgNCQ3SMeY3PmcuI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMTM1NTM2OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83YmIxMmMxZmFmNGEzNjM0N2FjODhlMWVlM2Q2YmUzMTM3NmVmM2Y0ZWM0NzBiMjZhOTRiN2MyNDQ1YjA1OGZlIgogICAgfQogIH0KfQ==" + }, + "F": { + "signature": "CvXb+zazYhvlVJ0kM5p7gNL9eaadiPlPEgYLkmsJHX5uuxAEgDeRQQqpXSL3Xc+4ZB0IQhN/r3zvkS6NWNh3eCdWEDS72/AN+rBAQ6tuCVUxRlEh+inWvrrrwsMC5bI1asEGrUdNwKMDV2aIVcySHG5uqTQAr1v7NfCOr5g6vNaQhVqYmxHdrYOQzQJy8rG4nLE//FAe9PjJJtFDO5cAv82XvH61UpS27gfgd34uteRZ+0BkvLxN1CF9Zpmdp9Ab+20dPjCtcVqD5XWqVtIqxVve8FEe+RmzY/0TZyyJmZkQBy4oXWPSaPIvvwwNbC1Fb9fNlc8iHuP8t+LKFXi8KIl0tttI42CQAuV+/Qz+p270lCdrgA7UqDt1oeiN0YtaUKOc3rQ18aEWie7Ve0dOMfL3lzJc64jNArYk/xk3Ezclsbk3e++BU2UfTrhMABPXcRnOr8yCGETrUxXol14RwO9pqpt/0CExAQkhfFT05Vb6p12DZoNjWoNrfcrXpH3guO+Vpd8W/dDCjLWZzDps+ulGH2MUlWX5cTuDnsPCM4RRcEMUG1qkbuCO1Tqiwn+1Xs7U2TchdvovLxD36kcWGNUBWvqjMkxlSxwHMKdtMw+zw2fYU6nfBzKE4DU3BFa9rHrkTujDZHkxJKyDbj4azhK3e5xoKjj8r9fx9Y7ZZQw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwOTQwMDUwOCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83NzkxZmM2NzcwMWExODRlNTZjMzgyMzUzMmMzMzExYjI0OTY5ODkwYWEyYzQ5YzRmNDM4NmFiYmEwM2FkNzRkIgogICAgfQogIH0KfQ==" + }, + "G": { + "signature": "qJAY0d8xouKB/Zu+Fn0QSrwxNmAR89Y3bQC9QVudB2QmUgZKhmQpVluSwUiPzjDV40tgQw4W7flGGAqfXgI0hZhiA7MbZtOQ6l0l9cnR5rZwGQgLY1J0SgdQanu0vGSAdsK8CuFbIH0hjXI9eXH7lcuYw3RvEfwmyqWFP+WqVsBvJmKuhYQf5gtMiBrgcDIOHBncPlagEs5S0UBxLZD0vtt0LANNO6Wgyo82WueIDAZVeXQKaHTF1G6CdL3JJoGwCp9D9yChbH99hd5LPPVirbCCl5+q8+45FXdIbRxAYHYa/Bk2ClITWm0KQ4y4JyaYvGm6wwBtWXaE3nZJMDccNGsvzdV0JMM9B7gPDniezHBrHSjZlgBA5cY8fxs3XhVOKALprvw/8Bh4hsFQDTDPK9aEes7Bk75tPacAKQKXtjwfQkHT8S2uP+wVXxO7SvidHxWcbz46ELwFW5hRa+WQRY1YaW0aslqCKXBd4z+42c2q8Mle9OjdL5A+HBJThR79AKP9FCZjsXNowerMa8Zt9C6b1YvCOlUOqFuwYFt1K4vhZuIoVs+ZOvBEECgEDLyAonGlMDqADY5U6nIMdTjUMnoTFKP2UruRUQn900nmGtKVjdlIHbftZ+5s6++VYdyldkYDy9gKvnv7PLCZYWDGKZLM93u2zQltRw8Dzg25ZuM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwOTYyMjkyOSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zNDgzZGFmNGRlYTQwMmQwYTgzYjA3ZjFhZTZlZmI1NWEzNjMzZThmZTNjMWQ1ZGY5ODk0MjA4ODU2ZWEzYTM2IgogICAgfQogIH0KfQ==" + }, + "H": { + "signature": "lbVsNU6IPBQYKpVukcZrqoq8X9zJUhdpdW/usNBm5lO12y3fmtJkmatcva7uEaniTI38pAaUUGl2X+UiNQzb1rKYKnnt4CP2/ijyHdJFtZ6QDIqBMPTfZsn8dsVqa1xWyCdxbivfc1VblSU31BwDRDI50Z8khYevjoacXqPZDpjYRzGRu21oufvg7m7lXQljURXFwR3+lfkqTJtHdVzyG9YtZifzJ94gGIghd7a2gw+XuhLHCq5xtbgMsHDKWXimCc3SpdW4gOUlZqG+EYwAskE5MZPY55ZFDWRar4gLjLrW6hUfdxSYB4NB/UiivffMFCcO8aWtFs4Xjn/HEltAb8n2m256hqe9cjfy9e//3m8ZX3J8/SW4eaDb83q3p7ftRk9cHG+UEj/V15pHZ90UNXPVkZ41uegWVbI3krQr9t7qrTS2IoAUzVSdZE+m+FovKvARQhxnhm429XeQfJVafcbWEinTpZGSML7MehLMRsQ/Phd5llaLJjgZXfJcKnYwyOjmc0R2Xs03hvaPthXb5d7EIge+UMNC5jOwpo5bCx2QEftd/4EBiXVDqjP+yWkO1He9iIOSEm6+mG0ED11DMCAIDemUpaT0QaF62O3pF9Yj0EgeHE4yH2Bq9rSDo4ikFqFDoSEOZxnZMCmnsUyAftM1T2dEwGN95rei42JsbHM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMDU4MzIzMiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82N2JlNjNjYjE3MTM5MjNlZTMzNTM3OTE3ODYzMmE0MjQ0MTBlMjI3OGFhMmVjZTY4ZGYxMjUwZTA1YmNmYTU3IgogICAgfQogIH0KfQ==" + }, + "I": { + "signature": "lG8Rjmbwwfnu0Ususz5iiw3cDGeTAE8JGPoWvLFj4yaevZtcEd+X22UXWjaL04MUS36PybvKwsg5pnTq9uegVTps+wJWaBrOMtgJsG599e5c1BbAXqCFD5M4v+jQTJyVv4UvqIyiXnj+00CsaqXHy9PSsXde2D3ChFFSeJlTxsgSBvyKztRn0LsftrH83wFs0TUmnPJGU4kRSlaqeIY0sKUypz1xs2pFxJsZa1puMlO+jRIDd6OjoTbeOFHZeAKXtVtVDqL/UImT65y5q5fSvsyN+dcWvLLLzDZ0lhFdSqM2seL8lUHllN/Gy7uIUEE/LFo2I3GqNbDyKfvM17J5GO5Om6CGv5FFTcdHwGKkrEaHzjJf8VQ0ZYOjOCd53SaSdgB2kC6PbrWbR05RzzaHP/RXdOaAPlOLChi8y5ZYB82yPNhVfpx2lMre1cdFRsGLz4IibyqqzMTWTYRJhz/5FeS04ksS3xFk2JanhtFVivyCIL70/Y5apg22jC11nLbXLP3YWKyU6KS7UGSvtfMJnat/2JWOzGGvf90i4+tKVMUDYRPiL83NP7fdYdlPP46dht6StI3UoiSx23ZYBIeOW5LrkE0hiESlM+ruTWRVScN7Nbd4Pml8XYdIpVFIMJXm7z/2mdsaGyNVNr5z8gZ2B3GvajruBWHT8aFexBp83BE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMDI0OTQzNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82YWNiN2E0MzU0NGFmM2Y3NThkZTdjYjM3NmY1ZGJhZmViYTAzNjMxMjg3ZDY4NjY5MjU5MmFmMDFmYTRiZjE4IgogICAgfQogIH0KfQ==" + }, + "J": { + "signature": "bIv24s3/LJoFWTj5i+ddP0WQeTYp9AweGzk1kKm+XY4fn48isHkE6Dsd3+nyIi1kBazOp1LCwolnpz4GPH7icNQCSCixfKWufh3nLUDizB+HUbMXkyF2Dx6QWlGu+txELOxtDT1A3gqhEJCbB1FX0PFrwItNvonnmw1LcPA9nsamzb5BUvJsn2QrbibVUSwIhkVHSr8HEMqHuCUqu7syIdC9QX2K1pMQlIY5U3IV2pTVhEnQccMEadCFQHL/7VU0DOyFt+1pEz+v5apeUttaji0il0ExUtG54ccOR8EHmOeZqNLvY+5/F/TSubXjWytw7G93pj/WCRb1+EetPUCdE4DlYYBwSCkIE78b/pRhUsrS6wZ7Yc6lgBftPjwz7XnR7HGFaH8cIpDZ5OWeanxAEJt/Y78pE0HYlrmu3a5oEcCuIA/0DldRIN3KWQOnAox8BIs7v+eYuEAJsS6IecTmHVqcdVVhrFMqBptx52EEVvRvstwn+H3fO9Wuygo8l8NdlDIwtqJhs7UA0SVX9r6xkaaE2u3P6y1Wqk0o14BKl2x+1wSPsdXoaS9/pOc1MJ3zm22oNqcOBpqe0Uc+t6x1IudYsjlH6XOheY25vdTuN/QK11COJ3MuV5cyun2kmrm3RDpyMnqZ+Qom7bNRs3+1Q7BGgK8SdGJeTGJmL7QhH1A=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMTAyMzczNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yM2FlNTFiMDQ2YzVlZGQ2YTE4YmU4MmZlYjY1MGVlNWUzMjNhMGZlZDllYTU5Y2I4MzNjOWI2MWE2OTRlZWE3IgogICAgfQogIH0KfQ==" + }, + "K": { + "signature": "qZyQ9JPzCvk8+kKygkpwkwVA3MQOUQUo/XddD05tFngonZG0/h3jdUg2wUS1XxCk12oXURXYoTMFsgU2eZ+Pb+WR6JIqgttxESXLZxK0HCWc1UzgCf9D4mPBHlczHwc6qG9m8IMnyS/o3D6Xl2TjqSU3V6TbFxGH3llrU4KU4OmIV3UEDRk3wfYLXV9nwfULOyAR4VVhCPQKvdOvk1KNvfUTQEzR+DL7+HaRh2HP1DKoYibXUl94yHp/4anrt5mvKyeHtGhyctTaso3iZklii9RrZqZro08ObmmPdyfkvztbjXzYUMFuxTvx7pBh6xqED8xzNaPsgHxXe0RqwzTfvQXRP9YVGSrRVDEM1tUYXn4tV4k0y4X8lFt6iSHDI8KQhwK9U9/K8RHbatdNgTTOyUWuZY/u+H/GcBG5W1kfgodoxo7w8fNuIYvYcqy5goUik+aUJqXyP0BQ04noyR+RSkkFNMMhEU/DfjCtzG4DtP17jE1RwwakrkydMIdNRj9YPunnsfNDGyD/o1DO+ZhCaAhTiKbiDrEhIz56l5wlddBgx9wOUOoN58TEbKfNmbYjuCGTccoZzBEp0zD9yIOlbTemMph4eCUMsROgKHHIVgnS3pbQyfvJIdpXrdxM1VR0FXSxpy/xNnX+alu3aHYPi4TuUgy+OYviJl46AjZE588=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMDA2NTkyMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lNDU0YzZmNjZkNjBjM2Q5MTg4ZjkzYzM2YjVlYTMzNTY3MzRmZDc2NWY2NzkyZjQyMmI1Y2YwNDQxYTE2MThhIgogICAgfQogIH0KfQ==" + }, + "L": { + "signature": "hWLxt99rIZv5KQvl0m68+x21duMhODe4wrD5Un9+6mavpndRSDIxpYB51n8UTxUBtLScxpWXn4yojWQL0MIInqPDB+L9YFypvHxgF/je8MzmT8g5PdOCWNxGF8xVQw5gzFV1IuqkgY0I/cXH+rNptmid8TbalhuesY3M2gFOm75+TIwntDohy4ucFSPCLEBOxR9Nu6dEqD4XYiABS0sJE87sWkOdr8i6I0dS+ZqyeB4BicWg/j2lngcB/SBKhp8UIKRblUEuIoA9rhMUGVOY0W/9I+xgeeegT2BWWO2Ha8reWyBeY+fpul08GYQLUCw2FFScpvVk7BPJepAKjouQZPPgXF95csamxDFRqCKdBSxLn1m8DH52HLGzYpeefk7/ZYbkyMsY7O/w7wxVSs4F2fzSzA6PkJdEuEuLMfmE/uI3SUmlI/jFoaiQCEbFekd0EJxHGKtJutql/FxWNTLZl5Hwo/HC7+I2m8dzCf5uOUtZAXXsieFb7p24SmwidL+ZIv9JaLlG0nHeAUI3X5FrzHbZCmpUvYsspV5AKV9RSQ14sjPzzjk/X7qk/g+FXqHVNw9rfkLlijmR6DCPbENe56FVJjwOUr94+GkZTBdeVNKTzBMQgxd2OB4vUaRG8fZzP9CWsq7STR3lkMTyeqkA6A71PYzCmonVb4+9VmyhL5A=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMDM5NzUwOCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81M2JjMDMyNGZlYjQwMDNmM2U3NDMxYTcyYmE4YjBiY2UzNzA2MTBkMDBkMzZiMDY3YzMwZGEyOTQ2MTUwNjE0IgogICAgfQogIH0KfQ==" + }, + "M": { + "signature": "q8PQhqbChHVPHuaFVPdKGtGQjY4UyuvBHkXfKVofxkgiiYgNgYKwIBdvTFsIfA2jhVLIe9PQkRWXi4z+JgzfEnDtLwwvMCMcSbDKT5gJlaUkz6Q3S/cCswdcHVh9VoDQFvrody7rPFiUZCJtZ2CDcFuhUduJr/c0dPk06brhhP0EygjqGyY784sQ4Vm3gWkNeC1YkdZxdA4Bo3NVz10q9DgMgCWb3qRp0Vp2V9leKYa9O7XD/BpsROeqAZVFmSrySDIBJ19aJS9mqL+WFFAFO23yL5fYg43Pazoh+UEhMB8wwKiL73HbzUmQpDmfPRxFIFKQQe/7UB52dRNgdUs4s9Rg/t/qxpHSuKedgQieTP48VdZowrsDS7mWwMQhRshascv8g7vwyevGnV9bdailqNoPMPzbN9SPZ1DxwdFxDquUzfCmpxNAMKGsxSfHCFBrDhCuDXvuNNMzsWcFEuD+JMbU1sHz+TtBpZwzQnqyJFuA0qmlypuPPkVb6i5BGosryQwxxgsIlyq3R7OjJmqmCDpf2w4epKVIZqXHoynuioCD9uAblDTiIO64RthzRK5uQBil2a9vQTFVBZizooNyK+2RfwRIr6keWLTvZEVVuPkzAsX3uTqRIoogg8uR6RB8Ea2Zwi+KLFSTCcIV9rHE7ocq/W3WoRrJbxl5Xut0Pp0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMTIwODIyNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85ODFhNmE0ZGMwZmM3MTZkNjViNjhiZjY3ZTBmNTMyOWFjNThjYzVmOTcyOWNhMTk0OTJiNDc5Yzg1ZmU0N2VlIgogICAgfQogIH0KfQ==" + }, + "N": { + "signature": "Zcipre/tnXC3Kq9Kiclt/DFj5kWGZSzbNhuOgyfNkHgega2kK513T2HYOQ+IrZ9trZcuXqeZDkoKoOqPEnEh1VrgGxKrlI5ps/UKYSWKPduHGC44BZhtv1EtV2R+i2RYtLXPgG/zODG6nMUzVqpbdGGHXrVrIbEuFaTvWd1kJw/AcHhyQAyTUeAswvv9c20Sy70cMKk8J4SfvczQgaL7gAVa3S+Do6b0SaLEVL+QBpycWG91WQkow/jASLNbmoqyFgKjbUs8TtfqbdJcOOq6UWzVXC3u2X9Y1pPeL0ho7rR7QIxFEwIPkJV1Sp8PvZvjWUK9PUxBRF54ANa9ufO3LuSLPMa51prDtreN0msaTNzs8EFZ+SWKGuKWilSrLtTwKnTj4CeTDVVvLBJgrpsBX7miVmnKY+pEN1MtFwFqxUMaGbouIWrqPrZ+AnX60QeMOi1VSOt8gWpUmGmgtHVur1iraaEvw4p2biYEl+JGXmrqgym1Mwv19Yrde1EugheQ26y8FKMzLoCKcRnbKrkWitx3UnFDcDATmY0LxuyWeQVo/2kvKQTG4KOeMqZNZND3an6pVaqwCWjvIwzBsO7s3wV3esvIHqV7C3AL7yP6vDeOIWHAecytBhz8WWn9OuqhwWNJLnMLWEPCXp1VX6rtgLFi9405lDPzklzhI5eywrI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMDczMDQ2MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84MmU4MjYwMGI3MTMxZmE2NTg1ZGIxMTE4NzZmOTM4OGIzMTA3MDAzOWZiNDdiYTMyYTc3ZDA4NTk4MzQ1NWJiIgogICAgfQogIH0KfQ==" + }, + "O": { + "signature": "h7WTUwgPlyLE1EOm6MBSs2Kw9ji+17NxCkHsfLde+dublhgC1MWTzaCfptoDaaQF4X0DQi+hD7L3jSQyVycwX42Y+SSFTBt2AlbI+z+0nenoUJWx6/J1Bg6qUQ3yAUw8z7WTiGhpMPW9YRGEgWxhatnfh7eaTs+S1cTyI3wsP50rptSvo5ObwO6nU91rilBXVHeIhQ92d31ne/onzXzAkQYi8jRS4sRiBBA+y5HosFZQw4Z42ljWPgBG0KQLhNzFLX/X617Zg9Kcua8c6/SH+ADXexwsmX7g80gCMhXp8xLPaQ1a64o79xRhSx9NJ/+t9j4bCrukAsLQAuRopNuDcE3s4sPnfCvoQnuMtF8i49wctwNLiUsBa7K1wgcosHhRKZ49sWXzc9IHbupE1aaKM6gnlXaP1sqhuXS3LAHNYfnIaJkyekldeDeNrK0+nf+44ceec4BWxkl+Xf8YUhbWoEglRp7hQVGPJz158aDfJTBuLWwjCr9uMoIvK3BVmNiS95QPH+FN1WSmIINvp1bEEB+n9wjNKnL1UCXEfINHhc1KDqx0vV9ggQODb56ydn4ScsNnpBsvsvGWAOo/9wOoGx1arLkRUxijaicDPAIaBQrQnHJHDvpAImEJOaZUmUgGnURXfnoA9MpwKhltiLsHr7/LDMLP5gc03SjwMNH8BRM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwOTczMzUyOCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iYjI0MWI4MDI2OWU0YTllYTA1ZjA4NDdlMmMwODZlOGNlZTVmMjQ5MGM5ZTJlZGE5NGQ5MTJkYTZlZGI2OTg1IgogICAgfQogIH0KfQ==" + }, + "P": { + "signature": "JQ4ghr6FAPHfOTa+L/DMDGUl25/hZ8DE8e7Q9RmvYW8OwCr5T+mRmTZFVbq+4FsA5kXvSkIyCBH0bJVo0flnThF9NNIEjR00JzPstBoV1PuD5CuJQA7NP3C5P5Zx3OHODeFtxF/axpqWbkTFXcmVpO7E+8fb4/iGiCpWfRFYjx2tHmRqRZ+GnqPSua5H4FcVdnA6Hd8AjSfViycgU9GMuMIUGGVunr/8OVw0/EU10+qrdhBAAx2JLCFRhiGCAW5ZGe1D21rEzZ8L2K124HjQ+Xuko363W/EZPeu4cdXF2QKRP6O/vtFnYN6rIP2gEgt01ERwnXUUQSuODhl4r2ztCq2Vx2fDMa57XjR9CwpITkzWF7H9Jsy7MiSG0jqhJtq6YJFeG609g1PojFQIlpeKXojhD0A9BQfSXsy93QRF0OdQ7ork2ZDYv5OYtHVm+bWwU9DDWRGOHkD1w0zxPLFGD82Ai7nzdzJHiFeMI1SLFgCY6kQcSUe58yD5zIDHCGojchkIahaJLNViN7ox2j1xfGjppblUHFSegWevSkgzFkeCgWd2GTYIDK9eDpDJj3swApG6sRGaJHTvTjuXAYuIBjp1buIkuJNBVJqKhvY1LzRgih7aQa3QzWlrspn6s8MHu11YX3kKbIgPG4kVNd1hI1QqS+PF1qx5kdDg+Zj6OSQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMDk1MDI3OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lNWY3ZjU2Y2FlNzBmNzJhZTEyZTU0Njc0M2JhNTk2YWU4NDI1YzRkNzQ2NzEyNjk0NTRmOGJhYWU1ZjYyMDlkIgogICAgfQogIH0KfQ==" + }, + "Q": { + "signature": "JZ4D44psYjQO1Q2l2RUbG1NRuBQ7bDAPFvvkkb9br4QRzDGpj2dOtmPjIjY3YfZRup8SCGXN9editwXKe7jCYdGMGeZzmF7WMecT8ZPEmQCfC0pUqf8RPUkwzh9aHBxGxJltH7s3iptrCZLtwJoRTKLzWPVPMzgo0PTaNUv7f/TWqTkGvfxNArdVUVXjysL6xe2eUbLXDvJsW0WFNxiKV2KC9YbrrvHb2DkwLS8QPlpP0SnE0HsioOdAWaYeggeg7Y4LoC43C/vlub3hfLYzqA8ZCD1Py/Fw3xDlL6Qlgz425VVfS+6zFx1ZTclUlsb5sj0rAIcEj0i6YbTAGLtaxMTe3AFKeZh3XVKcULrKZynXZewNrw4a10q52JZ7dzLnUT5vTKrcYjxS3GGnIKpGDL9eTkii8EifFx9QQ7UkfXYE1Ed8bVv2Sav71DGMPV1hFG5wzaz7Z/Az8IokE7o1UIpt52si3TxvC06f29l6YqU/Al5Y1kIK8UJGl71MZuV1cCmHSFgCGhBwXiLNOVW624JWP5f36kLtkmuWndvl3ZD9s95sDW4Bb0xSacZl8qlhPtfd0ZpnW1D0HLqFcn9ctgnvcV1wWrg5zSplEz6+1w8p0T/P1RGfmsElFL7vXI+2ckdgDThABMnBOKjdB4LLWrEDxNvpLUxE8kB8Iy/xeaI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMDgwNDAwNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85MTdlOWMwYTYwOTU5NjIwNDFkNzU5ZmI1ZGVjMDQ0Nzc0YzAxZWRiYTU2OWEzMGE0ZTdkYjc5YzJmYjljNjU5IgogICAgfQogIH0KfQ==" + }, + "R": { + "signature": "uLs9plohZNR43whvVy7Xaj7pjZsTpa6oZX18ZJNCrhPVYwaAhCNBDo51ShzWm+herTa7mPfE1+Mvdophr6Ni4e9oprwpayXKtj7+Xhj8uzrn3nuAHYFg3QxrobEvLP/guR97dSZBUgKaBynTvrIrBFoUyQZpF5P5zPdb3eg154j1ka6x2Akgi6QkTrfz6rfSLGGDPTNmw1dVegijfiPh9mBO4BWJ/Qpqj8D7ZJh+r+cUXAvMbGGgOAyX866iGFviV82WqbegkX78A7Gez/p/ltyAXAh8hLtQKj/9GUBbtDFX4ZGbc0PP0DIu86LMwb/2LRTUzNB6/OkD9j1hMdZiqQiwG6wd3DdYkTOZSp6Rf3sYW4R7tpa4dyUJGFODmSn36KMyCWmiLo0tjP4hUElG1AxADcbdcXHmvx3ZirYNfX/1TI6FkdOwiRpHG37iAZDwGA8iCeETuK8g6a88YATulK/cG7r+bG4GAF3e9x7/mTx9L/HnzEHgvyjWJrLSozWicPMD3nq2LyfXkeATF7xeCqRqHn94koetlUbDzDgjB65MCgxr6F7+HdXagm97NFNXlVwXWRS+VjLFBRLSlz2taBCWPbG5tOYhq+q/meBe6GhzgOk5sbMTKyqjFYt3EjE8fXBQp2VCNq4tgMyk0kRWc02iU6LfpW2NK/R0vY5bmew=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwOTM2Mzc0OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yNGJiYmQ5Zjk2YzBjZWY1NDc0ZDg1MDY4Njk3MGNmNjRkYjJmOGFlZDgwNzRlZmEyMmJlNTIyYTE5NzlhYTlkIgogICAgfQogIH0KfQ==" + }, + "S": { + "signature": "mH0UQgcdfD0RC/WnE81VT1li8z33MSz0lXxuZF7zOotV5RRxvUWMn5THVq2/jcid9tPQu1ZqBLiwNVQhamd6szyS6WZi13o3zR43Qxomw+yVlME2Q7jrEnKpj3muB+qgLEhM1F47aCQ/XAu6LqD+tnAzJqc8CS4bi5OucoKDGgk9Xf6M3w0+Dcq4oSc5YXouySSCJ+UBi8BAH9CLDB83pgIehAuPnF+SKmMI4ZNh5ZkjJDD85TElJqFjtP+fHoam2yceTm1ZqsZhdDqvAONO5viGy7D0Sclni9EZmS5gPdLHJnmCXDoC93iLf6pUqCUwvSR5t31o/3bLMdIJlfRYfYrndyGoU/DhlwrW/RwE26igMgY/dc3RWnCfcCwjiMUDP4lkAjgxLsF3reffBHB54I/Z62a63wl32WFcr2SQIcMuMC5H32dYyxiyrtEkqHDnKm55CFS6dHG/RDINEFA3m4Aem1VVcTjyrvpipydLlgOEgxGv7Q0vsdJFE88JrpoX8ETrdWIioSFuPSVFKhER76kxXaaHQpwbQxG+mxU8ZZJnR6C8NTxOsDPd2hv8/ZLsrJzDtvU+yOntRemGZSySEVSIvdSd6kBkmh1doa8KBk6ToRYEfLay2pKHS4DY7bDvXsRzCdxwCzz8BYGl9+UEBZv5EABz73Sduz74kwAZFNw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwOTc3MDg3NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kZGQyMzZiYzk5NDQ1YTlkZTJjMmFmMjYxZWNmNWViZTEzM2YzOTlhOTgwYjQ0MGVlYWRhOTBmM2Q1ZWMyMjE1IgogICAgfQogIH0KfQ==" + }, + "T": { + "signature": "br8hhaJ6NfzNZ24AJWfUACTGK5urHgiMnP743wdRu9WzJ61mnJ672fBFhS5VS67K+tiH8qTyDLbllEqbXY1Y4BnZHtXk5M7rDmopVMBkjxIAaPt+js12lbS9CATrok1n6iSVPtaQe/2Di8WCOiJI/iET16O03gBqNzCbUHwvvj33wstJIL9ss89Bnw3Oik41N+9h7I2q/vHE7/0BMMLZlncOZ9iS4TXALS1/xltVqpNhhcj0rVwmmeqIR9ikPtbtSW+cLRx4DhgPJJQ9YrHug+/0f7UHGX0uq4Wq/7c1i4Irzzc4aOPqIcvXRjQ0ooP1KBSJdgkD1ERJbr3x95Z4KbwE1+yMNcGIUa1KzgOHwHgK6DOba5uvlNarqWd6ntSVKK/9GzoP2O1vEjixodaqEm3IODZMZXWhiKRMPtwNc4mC0EQQ3tXfB8WE85BUH0GE/xdcMoqwoW4/lx5l26s+6BYJtnU1XD6EVw35jmR7E/aik1n2HSsMMjpUdPlYETPPHbx1t0xDdgBAaPeHaBOckJNDTdH12z8+RrJ2c6SmseA4KN42JqffjJdkCmIJWyelw6Dsb0AMSIzcLlU+66o3+jkO8P7fw2LUXv90WvsSikhDmg8SkVlWWEoacU83yRsN5ACtJlHP07QHjn6oswq3z4jZujlIL82ggBWxveFH/Zo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMTEzMzY5OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kOTZjYjIzZDA4MGFhMmI2YWQzN2JiN2Q0MDZjZGQ1MmU3NTEyYzE2MDUwOTNhZTIzYTc4ZDI1ZGM5NzYxZWU5IgogICAgfQogIH0KfQ==" + }, + "U": { + "signature": "uhlLVDymy/VJtl+rnVKTXsrLMV0Rs70Oe5iRpOmoq+HxvgWKWGp7tPa2cf861gwsj7naIMfVPNb45VN6y62TDVNlmJCcD6+J9QQDK1nigvF4fd8m7fv/c0LoTLOcYOuY7+iGhP1MH2bgGx7ierJ/MfqNTzPC8tlhyxRAOl0ZPeV1pQTxbgg3BFqXO5btLORaH8Q5cv1H1hRpuPHcS5+GOzdrtqjdQ4I5j+EfkR2OiCdl82vNsy01Ba1AbTxwQaQR56NOq8fr6WdhUb4hDIdGzVIbH6iUu8M+a5uiG33NmtIc9W9XaETlL99lOoFZbCEm4BvpADhezfmhXSjIOZO4isqsv+a4E3COHkf0hWprsnHTliDwv8ukSY4rNoPiOQKhmlPV+ItWr7UXLOzYIal3HzXEgvRJRGB6qVYojoFMGXK+7nw/qVee/4lNV0Cgzyou7J+nzXsrzCds02dA4NkdkGnq/Qzro5DM0sng/5PDz6A0NHnYwqUe5rDdddeC3hbUCqjUYy2EI8fx/Fs3ApZe+m2f0OEXX2P61ZqTdbCPg2cSZh7nk0CzmEDGsN8eETJJyAe8l67BKCJgwid6sQNwpjbIxqc7gUMPPu4o/PQp2Cwo2SLw0XIaNAZ3/2Vz5sK44gYFvizKTIlzXvbWqzEWXhUxl6CkZghj3dSERnL2dbE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMDg0MDM2MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xMzJkM2QxMWZlOWFiMzU4MTU0MTE5M2M4M2IzMmIxMzQyODA5YTcyMTAxYjljODI2MDQ2MzdjMWI1ZDI4NzY1IgogICAgfQogIH0KfQ==" + }, + "V": { + "signature": "Bk30pxEOeYgwaI7Pn1fXzKxGr+7VtW0TlDgprhXysD6d38MEH9aK5bJlS/f71vu2QwaJrztK/hXqfQq8DEgqu9QJNWxlZbzO3DffA2STwxiPIDV8E2DjpXfnQuAucKsYcTwvgTN9g30UqbWliT670QEnaYAxPxGzvXJ6RIqTjBj3vDhokF3CveUtJG8awdBtJAwycu4XYElycQdJC5LEcBxhz17BFtDyPrI2K1nM6V3hUJLnQhWcGNRhXsfXvSlHayRXJhDWThhsaGUkrXUlw3Nwwy1lbhbDQ1DHzy+0ScrTPcXvjsdX0tKwRJnBg23ajvRaoPHVi54XI0lMGn5mZGlO/kiWFnUUgJbNjSXGIjA6AHo9w0SnQBTmbaTiD5vKbY9QCAL5NRv2dJWvOtIAJpSQCO6mUcakrgqzUF6w1Z/EHi1svbjk6QbnBVmGTAVde9ZPqtPSmQYkyRm9o/iWCI8qHKI0dEORucO+BGkH2rv8uLqHnAHeCx/rHEvBPiudRmJgu009tE/OoeKxXNMeOmkzofo7uLNNOVwIb3+7ZKeC69JWzvHQK5Tgx5Q7rFtStFVDGTXuXt5Nz9GMae3m8YEP4K2MHhoYeb6si4MBo1S02JNU/mNqqDzJCwe4BiJRDqMUpSnTW4XUaKRwtCwxLCl4HFbCjCsgjHhUd7OpeE8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMDMyMzgxNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lNzM3NDg0YzE0ZTZhMmRiOGM4ODYzMGM3NmI4YTZjMjc1ZTA5MDU3NDEzMjQyMTgzMmVkZGUyY2ViYjI2NjZlIgogICAgfQogIH0KfQ==" + }, + "W": { + "signature": "o8Yx5HIPERanh5yS4qq2asyQUwEBODSa9j+urieLUVnvTMosZUe+aqWbWzuPNJFKHkgbL6D3Sh0MvSAEFYg0agWTxYawjOY4iyIbCK/oAZwzE4Lcqm76/XU693+vWKG+LGBkxgQ/aip0+yBGXKNG0K/duC6TMjP0jFUnuAWacZDeAvjf0YaOrp78in607tkU6FqiaeaEeV+vZM8vdO93UHd7AS7ceiQm9G2PNNqvqIyq2KtKslq3QGFNH9ITmqCT2LXI76gsImgbEt135JkEuvqXrzEiwZ3r616ynKvEkWz1/NiWlX3nPyEAxkGnyO6/SdtcpmFkFpxf1yvU1vpFaKssyZF2Hxj33javrmxmYSvFXPa6+He1eGQZUWIZYnu37J+52OnG0Q9+qSdg/VOzMlrr7mW0p2az+URO6PmzKN4t3ljc5iMH+3S1UbLMsHVxuAZlutHQaxedemdiDAr+AajkNI6CuVQY9QLXeLK2MgD4ZARDN+OTzuaUmFQ7a2QUQ94AbR5nIchFW3ZKanFeCrLXhdgMzp/3pV6nQodNyQye6n3luFbeUc0KnLJDRDDXGuUs9q/g39x1qJU1GwuFQR/o7Zr8Opi++gFbFDF8cCKm2Q2lwoz/o/9X4emDNhAQ1FzxXDRs7k7CCr4Drjhk0CBLpx9Znw989iyEWkZAe2I=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMDU0NjQ4MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lZTU3YjRkYjA4MjlhNzhkYzY4OTI2MGU3OGZjMDNkNmFmMWU5ZTY3MjMyZDQwZWI4OWMzYzBmYjczMDQ5NjNiIgogICAgfQogIH0KfQ==" + }, + "X": { + "signature": "Oa64NqwD5xA4RGY3sFqYEQiYfdIGEoGATJjlB9iR7j/nQr0yE/UGc+5HVfst+lg+Yt2XfHifRcqSwxkTEXkIYchYjqdoJTC5br7doSvNIxyKR+lQ22z5lqARI1hO1ZKkm69hvXQRCUefOMBySFNStteU3kFAllimeoDC8q48M9lM2sCHLaCqRTxAzG+dkub0GvqbvRse1A5wU7a9vSyp/U5iT1gl4btg2QgO7z8fitzpld4b4yC7fr/Hu6dmrmd8bnZm3Oc0l4gYmn+Yjfzrnc1xIIbQTtGs+lLnypPZ6Y9v1Q5aKAL8Hp+0SUmd46zCRnjq+RTJ7lqEQdRvu9UZ+jHFCHheZoLX9ajxf/iJDb9mT0l3Z3oCCLeFi547/VtcPMQ80jkWwaM6lMifuyQDlkxOfpWwpGchY9LwemxncFDTpJaK6ps680YnbRyNbl4QaspagBeCAR1CsAQpP0ZSwzIh9IRNskANxfHRMDybTd2N5E1BmH9DO2f3EL/3mHRV0dL23PwSOzsVC66bR+x2iUEtoOl8bPneSIVR4d9TW9lKgPLEzTWubDirvosRdokQQAQPL+b1pb7hkEm8vXw+X7mHzvxC9Z5CgVaKMZBzucfDPMOoQR6Fx1c6NxyHJy2QOIqKAe4fO0x24HIKNU1pvphWFh4LskAnwU5xosBIPI4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwOTg0NTE5MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80NmEzMGFjOGNjNmJlMzJiMmI0ZWNmOTQyNGU4YTQ1NWMxNjZjODhhYTM2N2Q5ZDE4YWRmNzhjNmVmNzJlNDBlIgogICAgfQogIH0KfQ==" + }, + "Y": { + "signature": "M/QmmnF2Cztz5bb6l5Fkt1DKpPPmSxquK11MaZGWe5GGdq8xRdrMMrnRuH8y+ROKMj2cPJg+dio0QpSGr2S64re+wJlRRyG6tmReuYv7Zi/z9g8+Gy+NQLDvdhMeoTQyCwjLvicg+p8MOlWSIe5AtWJp7z2mbc6+h48PLvdCymhJfE74nBc6oegim4QXfLQWyLpi0ltfMtpOsMyqfK+m7TU1uR4kjoHUb722hrDgbo0TEVmSeqwQWg+d75rOflYx9iA3pDBoU1+iMY8zDG7uHkgDSQFEbkn+gofSpcA7vvxZk6TkBIp5Cd6ig21LlH5WNE3NKlvPIHonRTBLMBR5AVuR2puXQ1XSig+NbMuFSPg7hlWqCxtcyZQvrxw+fUCDe04F6V7/ZDFor3pdGF9fnPwcKvOov+0EoSYhyBE19UQHrpMDCGT4WeMbCJAzcbsBK86/8XOjpiJWKTDLv1EUArJH2lBjv+OcZ9iBztOFcgN3KSiaysaFJliQ1thJBXDbCcKVT+KeAilK+vS0uwzHLrw79BsR25dbGIi4AwO+cwqg2kUYne9b2FlkLmGFRaFdNDO4F6TaWNyVPBERIN/V/lsRF3/yTW+kAjJWsDO3dLjEB3r6mIBUCv4EHhqOO5hLdx9gHlDgDj4ZKEyNma/Bmy4vsF+Cg7jbi9MxSHiHrZE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMDIxMzEwNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84MzIyMGE2MDg0ODdiNjNiM2M3Y2E5NGM1MzhkNjAyZDI5ZjUyOTRmYjU0ZmU1YTNhYjczMGUwZmY3OTAzZTU1IgogICAgfQogIH0KfQ==" + }, + "Z": { + "signature": "TJ6vlJtS1RjB/haws8VZIYm8e1G2rw2uAWqWHJnmhMZLLVcnvW9e/JPeTttJdKYEd3hV3S39FVA0RTPAwxsgd4ECMcXBfoo7VFZiX2xHuuhzYpo2pO7yfmNGMN98afGbqkmTcKBRDhcIA7n177kELm7rQ7b1I6Rw9xC4s0Vxu8PVNej+rARKtZKCabw1Kfjx0/vlOGb/iSJ+7B7YQmmh0EJ/avug72FmXUrwW4uuJ4B/Bu9+BxjV8RooNAyqtSdd0h+7RnDohHhWZpsGzNzB7BS+/dnYaZYCjSlsrnQLJtYCymVJ2Vw9l1SExlyiLVeirgiEyqHfwET6J0cTsA0/YucOoxaF26TZ3+qLRt4Nv0uhPKNdTKWgjxKXfYFzVLv08zdCNjMnpHUN9LI41GKzQgdsoGVywcfV7TABXLRn3T5OgDTOrPOSu1MUPO5Gt4yhJQecnvFio6C+gmHl+iySe3OaMKJ8L/Titc+g++864mvDa2NOs7EzMjOH2rcoEzA+7qTfdTrEyoGttYDEMhnHIiz2gxN7nwqqOaMGx+wMiPCfSWWETdlpjZRBUuu3ciyFDwPafXmXyTj/UC8ARExvbuZW6PV8YbFrL9qXoKowgHeXSmooKmzpE/HZShrWrxtTEjzRPkrkEeTK5yMVVo2sHugpc9TN+IFnwualeh2O9jY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMDM2MDc4MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83OWU2ODEyZTU5Mjk5YTZiOTAyOGY4YWQ0NTdmMjJhYjczNjM5NWJmYjM5NjE0YmI1ZDc3YjVlYWJlZTRkZGZlIgogICAgfQogIH0KfQ==" + }, + "[": { + "signature": "Ye2OZRAWAQWIVK5+q6WwqEDSZ9zVPfqGbaFQSdNC0aphik2HbY6O3MqLOlOl5tBGgigKsamDjHCn3JNiwpEeOWuUq7w46g9eOx2isyj5hwD/9Ejoikkt8InmosuuMw5pVPH6Fyq/7fuiVo7ajVW8BDSzRsqkt1p+12H9zbrO3rR7SzIEf+hFS5kVCpo9rLYXeJOGlJn4X1pB7buLYGSJ3y0RwQAKMIdcKP3OIlxvDxs/XUgkAgDOwOpw0X8a50LoE+2mOaGVROhyTFoP9U02GO3k2GWBwGL3tdxSgnR+aqW+JhmY0cPsaRWUCRJ6jqs52SCIeWVep+x+/hEA5iOIvbVxAtMVlaQ4RUVv0Vy7LbdqQUP7zrJZyNoXBjWzzpRbpaqiRvxf48nIMUs5nAxSPd5KPp7F/dA2a/EKTKfpBrr5Il1ZJ8i3jVNQtzzkFI1WxWeliHdVusRCxOlpRKHraYVx1tPx2Q4XYp3Ps71wXtVyjzeROpiWdCVR9iuuCKHRtyxlra9nZO13s3owfnBQN/AAOJ/RlwD1OqDp7wqP6JCgg+zF0bYhqSEJnv34lyk9g6wsNUtHCjU9oL2s9SJH6q8ENzS3RDEjiFIbooAdgT9L3F/EtyAjdqPYJPNoPH0tTKU5cMUQm7p2+HkJR49PnuB/qY+lFqxsjdUojKydyHY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwOTk1NTI5MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mMzdjMmUzMjYwZTk2YjkxZWE1OWE4MjY4ZWU5MDI1ZWNjYjljMDFmNTMzMDcyOWZkYTQ3YzQzOGViMGYxZDBiIgogICAgfQogIH0KfQ==" + }, + "]": { + "signature": "THai6uaBjSKpScjpVsEiLD85Mb26J3CDkAODPayy8jzP8gSNE96MwZux9XXkT07uEb3b/rS3WbvU2MrxTyhsEcl5kqDTQ7caUEejjS9RO9EXQDskvZ6DgZL/mmGabmuoCT/mSMGw9jzC12tGcNEjgy+EuUMj+ZQ3x+4c61Ez///nmCRrj2KygzfZmEYf//O4rD7XgjgUR29q6y81HBtYPzmVlsrqU9BAz8Wyk9tYMdM943GosPttG7/+bJ0qQ9+g86p0HUTW7cxXzIsiTRXRu0YCelEsn6rMwNaV64W1BuYWNW5ZbjSQqNBJS5Zxd5203V4XljRYOIQTU3UtqxxfyLv3LW8AymLnKsaXPTEnAL6q+sYM5UyFySv1KSkcDfUG24bh53hEVIkmxWmVLAvbqkuPx1MJETbIUy/y29crlfT6v+3hJgcA4LBoxXeJrUyEvNLKsprCdoR8/msofqRWd/lLpLfzzLSL/VScI9C4t4Pzu3JRnilGlLAlOxkFfa68kpSHyTrCFiF+VD2sSK+gFhHu9Hu5SCRujSqAZGT+/RiNCjpJjuEDOQgoh7vgh2/otXnRoOM9/1sRJsRaU1TKu4SUF/djG5eO7TzjAvVPg71t7s/DpoudgbFhvnjr+tGqDR1QxuilJE8S1hGeD4QXR3losf5ermM4fOEEJfNnE+8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwOTMyNzA0NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82ZTdmMGE0N2I2MThhZjk1ODcyNjQ1OWM2NTNjMGIwNzdhYjU4M2YzMGI3Nzg0MDlhODk5YWM5NmU1NjVlNjg0IgogICAgfQogIH0KfQ==" + }, + "^": { + "signature": "N1H5Z5nI9YO0lmFeUlbml/SoW5POzF9vI5xHCdKqBUlJibTUk6H7b9Mb5Hwcdt5z9zPRV8DTKNgFBr32uxRz+XFL2xOvSdbcuVGEQJj6VWILh93fkP/+kVB+gNZM7A4Bimcy8JWshROyVaFdSYYQ5BjQLzC3NSFFNZaLqMVC0HjB897XpibMKrm7qEUHm5bfqFi8aN+nNbzXf5NVRcFeVvMzQydfTvMeS95HnpI5Q2VNoV72CoJT44tfHIYLZHAIzsaIXJ4n+ovUEmQakRh8PLs3fwLdt6M62aYC0XzhGW6kz81W20X0i0YWn5yIO1RtueXK/zRu/HDZZVi8YvdBSNUneYjpFhI60ai6XW0JJEIhuMGPPKKCK8EkTdcpFNGH9ooPOLyJHHkENnZtQkZTMu9y0pjKd+ao8XatjQ33wYi9Vrqhim/4dR+Cp00hU2473aGnkeM+RZcr2ffgzKxTwh4gwRIwM0kMdjpXYI7Jma2/W7XDbU5N+IHkoJBPqOQjGYG3+fdYLce1ZmQD09w3caz0akEr5fTldarkOuBkJCvZINCBu1S2XwEbNsfsdeSFUU003LMRIjTSG4dGP9RgSW2jpUzzLMeOW0NOasduXEY5gzZ3o7P7LjDCrnnUHwHbxutpM7mbYxQiFP3psBv3UBGFursfT8Za4lLe4MxicNs=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMDEwMjgyOCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xZDE2MWQ5ZGE2MmI1YzdlYjhkOTVlMzEzN2VhNWFlNDVjNjQxNzk3YTZiNzlmYjM2NGQ1YjA3YTE5YTBlOTk0IgogICAgfQogIH0KfQ==" + }, + "_": { + "signature": "p+WmySSjtqpdB+4g29GY5SMvN2whRuSvh81Qi2p/HP4BLzXyvwgMLkEglXJPlqS4nce4+f3d51xxIwR8whWta1rSNHbpcVZnHc0SUFp0znNoOv0hjc4/gQ6mcXZFSRHESE45o4rxVN9eVcJHyp9SOyEvJmJqZjjD2POMy8bvMEX0QX97WmBT2on4PT+tPe8GecBWbEIjrjkZlHEAXNYs3KMc5AocnuDiloJwlpLKIjOdOF3JyxJORXRUJsQbEdLRP8CihI0tgPg/spWGn742Qp2ylGjo/jFd2/v+O3nm+LHLhJxZbog+LahAP6HSw3uF8jdYsJOizusY9hYS1+HBcL1Q3Sv5qDwHCAoo2OgqWISxhusNWJOUT+P2sWdhBiL5HYjg+gMLhLuWVj7cxUOCqTQRgOwDMtcbDqDuvrFvpcsEVcE6Nwp7usRz/dHHXTiCONwzN9PBDZG0/BJfrdvKLD0BroHzdNxoWzjKfN5VSRHtyVThMm9wz8lDXYKc+WcX8XC1d43QPG1zNAx4Wb64LngQEm0koEve7mOoTjoh3mGItT52Ax6TrcIHN0A1c0JeIgjTzbd+/ZQpwdfq4ZO0t/dbl/FVSM6rC2/JETwU5yLmUnm5JX23E9qMY8/pJRgCNNL/pgF8/dnElnhczU77DvTJBdry4Q8J7a0dUj01sFo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMTMxODYxOCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zNTU3MzgyZDU4MWE5NzZkYTc2NmI0NGNiNDEwNWMzNjQzMWEzNjIwYzE3MjUwMTczYjI2YzY2MmNlNGY4M2NmIgogICAgfQogIH0KfQ==" + }, + "`": { + "signature": "GEl66Mmcj64m6opHcDZK9zanB9LciRERninFpkq7QPUE+6hEUV0swlflLL0wQG5izX7subewXbUD6csQkGmzynEaAmnEzRQBVcBi9Es9bmyQwDCYAyNNde5UWqvZ4DGD6kWY6j29axWmb5zwQxMrxcoRyjZ7dW3mngEkwvFaJeOltuWl25DGWfu8HpWOvAqKMnY3+mlQLhcff10F5iDkJE7npSmNzrTaO6cbaR3bEc7ZY/Rq6sHkjR77tg1OSNemVmMtreczEqLrtPETqsMj0DQO0mBPqHtVsQBPMkgp9e/QpomJ8rB8RTHy1jyJ3Ggo3XIriFRobzopkX1oofgHqOMbPtUOtaKuIc8lKT5IEGASZnMBuf/+7R/IvPVTO3orb4J/AeGjjQX149e2hbl6PfjNH/tbybilMo3/IeVl4JZAu0SIoCUi00pTE/3KtSzW6g2KXR+bgEiySLJci7o0rQp0B3S9XNefaWhEBzdvqJvrS5LORjudhoAiRCnQGQdIi9O75P4sxYLujsEB/fBF+hVtsDHBhxL1rhVDdLr98JA066b5rdWJwgaOSknU1jzzSZqRRBN1chjJiZgEAWTdO1M4YKfA/c2IKbIkf0MhCOjkPs+9CzsUead40xxP+nd1HVDawoa77oM6JxxfBPgAPX3eLQTfwi+6kpxQfoE7Ymw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwOTEwNTg2OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84YTRiZjk2MWUwNGIxNzQzY2IyODQxMThkZDZhZDYzMzk5MWRlMTYzYmQ2ODZhNjBhZmNlYjdiY2Y5MTI4NGFkIgogICAgfQogIH0KfQ==" + }, + "{": { + "signature": "dWWhO3VhooG/ga828OyA+Hkg7TEFBypqZkMy0OwwOLpELoSnfF1g53JY13V/MRZapPtG7DWWRj6bD2rrhQEW49Dy3lMJE2ZQ6/oTqscTU56FlYbchYJKJeEzUc+d2Y6v+9L9sh2slVcVBOns5QWP7ow3Unes5TlADddEJuHRw6MYYF9uis5XBmkpJkSnFYaCJvHYnipW9hpaehqQ2wM3/IRzBgHShtYSflxwgysNOJRnEYlfdVBl2k4H3QqibEvbq4gazWrsaIwu4tFMirIANOcqMnf1SCP/MMxS6ONHM+QwZ+2cXPnIGDdWJeaz/9LGrxwaJf3qfLG+8dNoVjyN+u+Q+A8sGLVI4SBdLG0oOZms0keGE45aTC0C3v0jikJnXneCVjHJtb/EEhjiU8t/S52LT/oI7UuJcl2j5HTJz+a/OC+oLrzsBjjDGCjhTQ8IVSsXQNBhCaJB01dVjzvNSabmXlPo4s8zqWHHXMe38V1UrsOdWR02I8ayCc0qDAxHTeJQw1o+3PpJiFBC/lVo5guZPEqzpRvHezFybkvnu9339U4ZlVQEE0gM5R8PTGwmGINjWO2gGnC5+90Ie1duusXW3w3NdfWhwZ2vYcSVxWla6lIDWES3Rcw90OgDAEZZWzrmpaZ5yPGcfVsuROmKMExpAcYZhI7u5OutBSfldIQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMDI4Njg5MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82YTFmYjdkNTRjODNhZmFhOGNhOGU4MWYyMDQ2ZDBlZjVkZTg1N2RlZmQyMGZkZGJhZmE2MGNjZDAwYmFkMTRiIgogICAgfQogIH0KfQ==" + }, + "}": { + "signature": "fLvH2qO0151Fer2KM+K3q7GAoH6g83DddRAIISkXshoFzTJFDvhKzKAdkfNL/fVVu/vZuHj9GAF3jFBrL+5SDjaYV67AZ11PQTCIU9EZlOi8aVK6ySgKNAUBMWCg6GLkEY0tWdV2IFfENSXkwdetAjO8/TeKYZECHSpR1aaalYnpebOTx0bYUgNLy9+MlI3evefuhJvkXI78xI48zWcKNsmf85NqKlG+1UWh7uH9u/Y0+X92B8gT8KktCgWV5lC5obXAfcO698ihO5fwFmV9FFzeL5tpdPDuA2w0SUnvCGnsmIFPvL0z20ypFjdCkwkXhBUsxBjI22XJxgPtroaWygsPoES6Q88v6VaZvuCKnx4fHD5xzNZCu82tKtXBhANoCJ9sgHuoq1twu5i0z242SmCamINfPfBDpI1es+aMhC8YIek0cu3G10SN7e1bXT3CbBXfL68bFihMxAnsGphc84xmP/DsxNuGW5ZiC5+mYVYWVHhr6+w+RDk90hNEq4eW7nWB4psBGoCmxg+Hd07JEyOvtBoqg+PBW16Kjiao77s/HQokxfCKvoesDTGpNFlnAn7Nv9AFMttZyzQ4Bl5GHoyB8CB1+ugPgKu7e2nOIYN0jaK5qI2Cd//16eP+B2O3Z+9rf2OLGz40p/DDsitW94aMC/u7z4aL6pALDci23SU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwOTI1Mjk0NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lN2I2MGNmYTRlNDM3ZTljMTIxYzY3ZDFjMDI5MmRlNjA0M2IyM2IwZjNkYjQ5MjNlZTUyODAzOTk2ZjI4NDJjIgogICAgfQogIH0KfQ==" + }, + "~": { + "signature": "wmQ7+MmY7NpLh4LxjEgwnRF1YncjKyTnMvA7TDlvUNzwLXUTD8jXXDtCeKQwwYkC/03syotDvvKJv/vQxCxjSMxi9vNDd6l048f29OYAQnlPZVVB/PxrW5q0VUQBVq0P7Y4G+rkzuNJ/v0+6ZbgzHx+5e0Y/HzijiVi+3pWIVhThaWvxrUJ/ONuVsUDgcqDPKDtYWE8tudqsDjo6iosCqMFUTNrplm2DcqOZVBquPqHWbMW0IqzE59k46t8/qnIHAuL9iBKvHupAwbp9YMjVDGfIx7MHDz3ueByVwZt67v5hQJyusCspPAbsoakCgCNnpEjxl3+cd4l9ZLmrcdsrhSP1rWVx5Au2EcWl1RsrfUOejS0i7KdApIwRy2Im6mwDR7+YmsP4CNy95+GjwSTnKZHMo6yD5TBsbK00FXyV0PRCMvNKOZSJiCv4R7HPrP3/hMtn51A/MwwgYxoFgi1tiO6cHvMzcshx4uSK79/7nnvpMjAwORRbo6Waz8DqLUkP0D6nHljS+vP97bujAy3WiW3FKKWUJo6x8xfAvv37ovSEMsZZ7dnuzYeyfSntQB8iMOta3kdSoql+pfUpvyZpurjycYjo0+nPQvjbxy2P3aBaXRpT3O2JPRKK5ABIMXfC6Qu/gRq1ISsFutiKj2lQM9blPnACb+REPPZk3RJKqoY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwOTIxNjA2NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82NTFlYjUzYWNhZWQxZjExZDJkMDQ1ZDkzYjBiN2U2ODZjODQ0MjdiZTZiNTFmMmQxNjViODU1MjU2NTIwMGVjIgogICAgfQogIH0KfQ==" + } +} \ No newline at end of file diff --git a/Network/eLib/src/main/resources/skins/chars/green.json b/Network/eLib/src/main/resources/skins/chars/green.json new file mode 100644 index 0000000..b5f74b2 --- /dev/null +++ b/Network/eLib/src/main/resources/skins/chars/green.json @@ -0,0 +1,266 @@ +{ + " ": { + "signature": "bsDkc8HQmpawdWfuQ80B4CEagMhLe7WMLQ949BNTwEx6xUU1LXq7JdwvlRksebwxCvM7gkOasmHaURMLXCYpUSTEvygaf2jSGlSpUK6ljcSPavBfxQ0o374iXdhSawLbo+h7v6r9jouwK8tEVyEPCzL7Eevu79NH2mWhAtgMR2lfa6Im5Awdgo+YlbZSfE8LuJ+ooKNT1BKxrYEc5yGh84tOw6KZQY7Kt7+73nl5/VCGA02sgjXxbhGN9k08Rvz0k3kjvut9MVxeoyIm8rb18DNei96RoopRSaP5+3sR7ZwNgKq/YqCTZZlOTlOfZ4omEgrd3sDV3YqGCdBkX5/kegzBqFUp93CqN4W0ZvEoupiAjNxyTNcfKElhEq+l0iebLeNlRr6cFTJIGAuQnTLLesNR+9gpdiz9zzQP6g7GXn+sq95v+7kZi3FmiFbHuLRT/fza9MXud5MLxYuH/ks4kq6gY7seP8QNS14DQmaTqrRl7tA8MliTS5QFK5loSo/a9VGxwd/OvDs9u2rzrDAVMlkGaxp7dSdlZO5FUTX97CAL8qXRLGvpN/CGE6AFkP1zIk/8Gug0M3fXLa/PU/NusdMs7yUKmGYJi0DBAZIqxDKOXJuyvgshBB6EcZI0y0UsyeGZAyFrCmNK88Vo1lNegGOUHgCGI1gs2LCrhkfXBn4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNzc1OTQ1OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zZTdhYzVlZGU1YThiOWUyNGRiZTk1Mzk4NmY1ZDVjZTc5NDI1NGM4MzA3NTQ0ZmFhNWY3YzhmZmJhZThmMmFjIgogICAgfQogIH0KfQ==" + }, + "!": { + "signature": "W/O995A2xoL5JdttPqyAQjwBnFB4GvWW/y9P18PX5BtmD8ZDQ1HwUfMnsgdZnWr7bSv6Nb0iFdVWU1EvmXel3POAXckqAvX5fpl9/IfO+g1bE1iSaIS0hFMC9ns6tfyPBpXfr02NDQcPquNLsM+yvWrRvp0D9w43TPkPDfq2lnNwbQ3jzuqIIRS0jVXfv0e8j15SiAUOHczSbVJiTMbHncjKk+2NNLTr+UP5RkhiERemDKtCbVYpFqPwiut/P7+5SI0yerRs8k6cCw+FUkxc+Q+IS+MYQycFYJXPj/ygGeHc0Omb3mw4+Gn+DGTk0Sm/flGYt0CjJoYnWEl7HuqzFo8vLxMUeTSwfh//AwWbC5YQHsm0dYSuvWs805v+UpPXQvWI+v8n6d40nRtVV9p6G4e1/mRdTLPG5yrHjRqm4/KVNFPdtAFN8Q6tCsD8edbdmCNDRc0wDrEviIHBsZWsHRiTllFFpZWU+lVl3xNyM7fJ0tmP6PTnWHy4WIyLktD1n3ez/eInDuJENWPj9Yv1pUd3sIAofJf9+R5KHeEhyOK0OkKYEZopwDyicLwknhepZ0P0y5My8C9uf7nL3DfJdabev6c6/7g8ozuhFoW2VHDpsmedCI/sAl8pK8snOYcflHk0C5wSbfSFNWacc0EVR8z4AvZMvud9SMjAk7KtN18=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNzA5NzA1MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81MDVkYWRkYmQ1MzMxNzA0ODY1NDk3Nzg5NWMwMDUyZmIyNTk5ZGQ3N2ZkNmQ0ZjYxOTgzMmU5NmE5ZjU0YTQ4IgogICAgfQogIH0KfQ==" + }, + "\"": { + "signature": "J+2xz66S/VNCE0nGchoRUp0+kvOuNSVGfT3kjKadP9zsjK2+c363dsymEk9nllOIcckk1bsIimaQZLVtsRRavWAKVr2UT3MJ+qtNcSKea6OUShIaKPBIQhILkGS5BqXxorszEpwfqrRMbBX8BdtR6Mc9CmI+2hLQhOrUV/DoTfEenjEkjUNswp6I0rScQ7iJoqv+ic9gbSYe6d5Qi6rbInIpep5NyzKaDMLQa1VOiOe3wIV9a2dR1lISjwi2I9OSf2bKvl285Ag9G9P6AYohyPczu4VvUzG42W8SOm5zTTAJ72ldorITPceRDfMYuzTVClx25whfBtB72zwjXy99nBTdHvTosqAg4HdzVI1SEPEwc0AGPYXtIV4E57411LKIvlBN7IOfcOCsgdrNmRhAnFYKAQu3XLILBmVu8z+VAHiuFOALsbJ4RpnlDfCD0XQTuDsJe8PPCvh5lrSC2JuLYLIsK2W7qrW3zCY1F/YBQnOmrHwx2UPDwhjKW/qMaOAmdWFAHg6bLbhoJXPDmNa9Ojr4XX9oTuEjYYMyE85jCRYI1yILYEHTp7KX/rR5lmyBaSSIuH73+ce5ZFafDx0odoVWKfnC5J4QCHksaXXyOfrLBUt3JZ3+eYu/1/cKWnY3S/RTcw62RD2KnZHNsuNSsl72dEOhtxviN919yL7rX7Q=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNzUwMTYwOCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iOWMzZTU5MmNiYTllMmEyYTYyNTVjY2IwMDYxY2U3NWI4N2JlNDc5OGI4YTNlNmE2MzE5NzMxZTdmMmE5NjNhIgogICAgfQogIH0KfQ==" + }, + "#": { + "signature": "IZtX21fNTdwmw4RJGjamXSJWrqFUB3ZOMDsIjjKSkKlSJ9Cs9EpgE1y1GO902hX+4yCqwlrNZU0/yqgHJOcfi3yfFnEXrXdE+LoK0KXZ7UQ1S4r0Fi2Cpxx/E9EZE9DMd5OuJS/iJXLnp6SioUkgc0eaTivkw+b3T+yFAGDuQUH6witBRFo5AHw4p/KDGlIzMIMEtppBxZrl9oUy/BjdjjbH8DH7ABQWBlYmTQVBO5aKD0vCGBKCAPnS1uBTGHyFsw/I6BB2wNbYqY65kgwhkpUl/Ifo5am0bA4mMbo//+4NFNikdTQsnI7IuR+exXuAdGQdXTHuSjdQerDTDLZieVyfFTOkwaFKtevGMp2YLF1BXV+VTzzB9qZh6V/V3kUhsK4BKPYHnkXoAHFfyV0v0Tz9mIpwjRlpvzuyFIbuNSfy7TzeT0gvYSSZPyoOWI9S4J0aDO0b1rx+XYciWwgbp2z8Whqqv0G1ipYoUwl84XKXM0pI4jpL7tB/TB6VYQagfFNJhfhe1U1OyTLSGs29qRiYbSSmB1vIalLcJ0bD9YK4G5YZQdHOhtCxv7QJBzzzCqm4q++ykXxBkn/yWvkkgVdw+4EgZKfy35hdd3XwZqCAMhK8PG0/mEbj72WT7HP9Gv8DUZSPR046a0laz0PMX+BrTbgDBElrYRztc73WEIs=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNzM5MDI0OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82ODMzNjZkOTdiYTZjNzA2OGU2NDQyNWEyNjZlYTUxNjBjYTk3YjRmNjA5NThmYTk0MDFmYmNkMDRkMGE3NmI3IgogICAgfQogIH0KfQ==" + }, + "$": { + "signature": "e6izFVu7OFtab3qAN5qC0C0QouC3PJw8WLShsT9nOmrKf0Fjo9Dt/6PjPP8W+tedZLe3F8mrXyIg2ZdR/zlZegqiY3+UK4GVw4rXN0AMBrLYwLzgDyLw0AniZ3SVOn24XU0C0aPlZL156xcE7PxC47mPmkI8b1xctDxUM65ao7QwSo3HAUOHLEHqrnaa4nIvsnBXX3nRsM+BnAtNeKB6m8AlvX2FZMVHLV47dO6V59dGr5UAKm0cR2NbXmX7dSWM5WL8L5pKyc3KFlSk7xS6sqANJJjWTwxPxYpg6So8vA7yKqFyXCZ8a0sqZHASrhCYdYyV7JzlNMqv0Ie4/YbRE+12WhT9ZMCYchcHsHT0uAS2rQBBrcFO5p5xhagqpu4dKQLO2E5McyqSTCs8J+PBl9XsN8rO0V90C/zB2vTsrQQfPjGbXkA295+ofQdkJEmBRj4MYqQVAUccyjGKGSN5GuVDp/akqn8OKB3nJIj8NhNWSGTyw5MLoPb3OVkejfhZQ+CBvN/n+bQmPpBidcngZYtwvrrsy3n12RgB1k9TcWC1Oi3B7jtT9Y+brcHHwXyXuPT7l0TZubHZ8raZPCpl6v07ntl9f8ykDz3X7HLJF6tAdR91XZV2i8E/17Q+gZ5vYWCeToRRRYdDg7Ip0dAy97XZzyIAg9+URaqOajPBFUM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNTg1MTgzNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iNjIwMGYzZTBlYzI5YzBlYTM3Y2IwOTM3ZmE1MzM2ODAxZjZlYTU1NmEwOTVjMDJmNjgxYWE3OTNkZGY2ZTEiCiAgICB9CiAgfQp9" + }, + "%": { + "signature": "EIfda1qhWUWU8hlyjhmXQaIJo7KEnEB+4bMCJm0aEcBnMfPM+n2dUNuC1VwvANwhfSFW4jGk0luMxq8hDlLKaOv28uc08YlZ1zeSM2XySvdAymJtaMGNPzUbLpVEUXtvQBBR/6NSlNLMtn3YsRpwt/JZDiGT+fYHtf8OrPnXe5OoPYj35nS8QPPP+Xv+YTMM9kbypjdYJ575N2fjKD14vXpAm+20U6oNl0L2tAAp/46hk6aWn5LktOj2LB83UjcNl4P5OaDi/ba+mfXHsb5P6bgsB8UyJax4M6BuOr8jAeIqLSySt9JLhFl+4XpcOMNjVPo2hwcb2QkqQfXDwEgIXiv4jMxBSuyb9WS+GJBcVFiL/Rf2IzVHldjtn8T457uaR12wiPrbB5uwifF1hjPgvgwEwYiJoPxmB+0MFkZzuClZln4+IzQ6Rma+THdd2J67nZILAqtfeKE5sdA+7Q/nsQqfKL8bNsTz6/kALlr4Bjwhdbvp9GJmUK3hRn3z7ChWjyl/CRTBFsHG8mjs0aE5bFPaRNyKYYfM5C0M8u/WrrYBQ7kmX3J9/Uy6Mpu0cgf0Q6ot56Tp+BbcFgTgdBm3CLojPLL1X7eRLLCAoZK4MEhfhMMruHgOMK9Ms7tyw0IwA4VbHIh/mQv/6DFnBk3AdUUHbkdCkElF9gOieC+SfaM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNTg4ODU3OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82YjgzYzI0ZmJhOWNhZTJhOGVjYThhNjY0NmFmYjU0MjAzODNjZjUzMzVhNjI0M2IyYzE3YmUyN2FhMmVkZmE0IgogICAgfQogIH0KfQ==" + }, + "&": { + "signature": "fqmcl7v5BpJTFlJoBgVIufqFaMUZPoKOU9GNZSWvMHAak6d0gJ0T3QH3m+wMiEYuf+dEZHiCyJB9sH+gfBYHjw3rstzt7ppKrryE0iNVTzOAhNYuKAQ4WwNWENa/d9aciW4pXt48tWsUaSAUlhOjmCmU30pkWdVc6J8/4IymdL249v8CJQjXSefVU5+hTr6WXS12SyouwEUoHr5nFtqlV5+82vuWlF86IBuejAmdUzONlc7jPqauCGt/jsf5Aiy9cDNeDEp8cMW2g9MJxKLQgk7kv+6P0xzjvFFqPVpa69mMdTMAm522kSW/BKFRK/EA11Y4M9v2CO+GlFXrVlcDdbX7gRx6YUk3LtV8TPYm4qip/3ATJlCyfL+DYLAr8NV4pHfj8PZ37XzX/mcvsFC+sma9LAGvifAg12zaA/1BkyeY5XFH8giywa7W3TVYZ12TY0nXtc45hezWuxQIEX9gSigWmMpZiOxPfjPMRkAnCE3rsDCBKLU9sxA9wesnidABBd8aqQx1ueGV6OtVFF9+/1bJ6d0Vg7+iFsLGUZsUkCc1kU9AMqCLqN/cRQXWDRi8ICTxxdL0gyoJ75zGQjllYuvemkYyEPjEULp2B/n+TcAy77+27muchb73sMKAoK2jib9HcSh9ZqO6t8zCOD5nq8niDMMAfyGC06xMjLyFU+A=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNzMxNzMwMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kZDU0ZmExNjU4YjZjMDJkZTY1YjU4ZWVhNzUwZjE2NmNhNDE1N2QwNWI5YWM3YmI2NWZmMmM0NDliN2UxMzA2IgogICAgfQogIH0KfQ==" + }, + "'": { + "signature": "TZl6PTEiV9g114Oy5PYbhyyq1TUr77QF5Xx8Wc2Be2AU3Hps7P+rq5jQimZuHMTKIwEySxWK5pwc1ug6gEwJm8de/4OoxhqZvOcaBstZxsPWfXWv0D6n8XD7Rz4rFk4n7GsfQUxzKsym4ypGB85wtZ7xK+jqGOJt6tHqIIBfJnkzWP/hBBVoJN5By7VsmOT0tFXCQdwtApBUKYb3LbIRDymaDSSUWW0rmN48lUTLvX3l9StJZ2IhZAlX6gLx6R7uRKDUZkG4EfKFugO+NI41wcR9R2CRvvA5cCQcmoTB8LLyqdvROBxHKJjlPfbzLmigGbuZFq68PJzdSVYJKY4n3ztY4Ir7uYLgDCNgp6772+mWqQQCqjsOBCoLCje1lHVS0kWA3EeLuUfhiIktqET4w4pyu++2+WIeA0nkuy9/TVMVdeQaHOMCc+qULDiz9CXwx06EZ/3nbD7CmMhqv2Km91quYo3IkYJQX3N/V7Ov8QRy4+8X1QhOEIR479XNr6tqrsh+4sd9WXaZqbD2hDs916/dh2tincaBCaY2wQor/fcVA3iTm/EfnmUQ4f+Fh2EQygdc3RWwSePypkSILHEEzdbTeT7ZE8Hdlx1Ubs28oBYaM//0PWIa5XKiaeCEQBHJhZHF0mzFlRJluZi4y1k3KDxZT/zgTcaCHRqIpsJCocE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNTkyNTMxOSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kY2I5ODY1M2ZjNDQ3MTQyYWMzOGRlZDc1MmRjZTQ0NjNkMjNiYWNkZGM2NDA5OTA1Yzg0Yjg4MTBmZGE5ZTZmIgogICAgfQogIH0KfQ==" + }, + "(": { + "signature": "xxn81oj7NhjZO1DqmL8OQ05yIpEjqxJZk2LUnZXdO948jsJEHMh3CI1f6KQa8D7+qxiA0LG5btcOQL7sitrYi5P8/AFrMqzYnrZtJ1P6IocNf3fF7AlkbIWX1o2eagW7xcKxTbhMwjLYXtUGvqdL6x9qARVse63gOsSj9OH/Ya7ijqr8nUaum8EPHNY3jkH9b/THZoZymbHKPJr7RslFsPPdYrazWh+Q6knMHX5eviPLIOnxoGiVSanjIGTeUYITnkyFITG1bkE37H6Xlx0M3BQzZlUlmc44M91k7OFTUM7iDTC6YKSpKex3YBiY3EzMzBee2Zod8RVQz++szm4Obz6xNTau0ATq0EUHpK0euXv+VwagBALL2zHC6gb3GqvwTMV1YNqGMk6yiic0yVtROgS56gVkkXHW5xUyeViHbHCmFq7JexdSxV7ppGR9q9zo7gjiGh6oI47tsjeS/DCwehZE/DUNJNFU8kSA/8JLd8mrLFeNNS+I3FdcdC4tOqPTQGQBK8+xAJ4kZGke28N2m3/STr3BkiK2UFocXssmFSWMyftvVx/5R7/iM51b0fNWf1siCa1ZiexbVNvSytL9/WaFPo8MyUtye+zAfaeJydKpB9nUWNYpnGg62FEg/RFR4EJ0SgLUsUD6OflGlPa5ry/l7Udcy+AbH07IpHD1YAo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNjM2NDk4NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xODI4Njc3MjBhODAwOTFjZDJlZGE5ZWNlOGZmZjU5OWZlZTAxNTAwMWIwNzgyOGE4ZDc5ZmZjZmM5M2E0YWMyIgogICAgfQogIH0KfQ==" + }, + ")": { + "signature": "kWc4vW0tHT0mLRmd67vAtKo/ziNR/JORpf40+GISdXZp3rQexnpKJolWRxcIHvS3fi68fCyhMda1N4mAuddOaiAIQLDCdjgxApDeg74D0d4Gmj9H/8C2mHMRoAvRzRXeSpkFgEn9igfMb3Z6Oxt3CplpVgi8pP9nZiQ6z9iBm7n79AZdhUZN8/Bl9dDIgb/S5qS0sncqocyjBzYHJCan+rfI0mkZ5NIVmfWFTq9o6ugo8iOrIra+PJarpA+PCI70A4keEjMPTUpnRXbLUDP4glO/oiGbdHmkmpS94IyYfBqqMB/AU+R1dAiwOdQuDiXknVCEi8yWj3LuP6+TTqoNdZ/TkvK+hZhHkExJpk3b+FX4RfDWvGXxGlRx9HR6GjBMumAm5oaWiJjxGUkKNznXpPf6BqKTjapjZoQXwHbSEDxhFCdphOTl6Qwv9j18g2Oe46zG9J/25vxU5LnrI1gwgzsqvi926rcLvED6E7OFEWcURdJaRKWE0MGZMj5mLEtRGc3LW1R9zAH380cfSJ3ZPQ87tv7IzgzlNZUb/6rtm5Exw8zbcHaMZridUqCe1JL8KqDaI9712iN+DUfCXGJrokrRNzyde8F9abs47/AKqTBXxo2NirDXd70KESog86HenGCnrwUpW4Hl2nAGGj3VmSpo0ldHtoxRaTRYNjt+h20=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNTc3ODE5MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85OWQ4NWY5ZTYzMjJlZjk4MTVkODZlOGJjMTBiNTAxMDdkYzg0ODhjODNlNWE4NmI0MTI0NzhmNDkyMWRjY2RhIgogICAgfQogIH0KfQ==" + }, + "*": { + "signature": "wKPob9Ibp7ftjI6qu4zVeJws9FqEtPeIjRSmif6SWCrzD1ADmFYGYyq50sTVWLT5ca8MabGllkv3KofQXDlvUGG9LP3fWLOKu9ViEIU5+kg7dfrInKD2nBF/ehABCiMEoNTCVaGUVDelKsBN3tZC9MTnVxAcUJnYmnRNrRe0QgORMd0IiuCHPzQ2jszVAi6UcGy0UJ7+AXJ1ZYNU7GifMe0r/q1NKUF49RV+7eBRuiiG20sztHkyt+GnzFlPPAPeRIyoYUUyfh88Cj2r6BksE/5h4y0xiWqK9PCMRB9XPMp4eGCPwfowEysY2r7fVula+P3kpuO9IecYMphSEPMiB0xa7xz7zxaisiBn6XAEEcf7+2S1sipLzFxRHPSeYefQHRWSfkTV0ZkWmerS+0X4Fp+/4B+Vw+3VRb3L1yOLegtpnZ7vGvrf36Ts6HUjWemrtigfq3PdpWnNjK4V3sCz8SHl+u47ZilWDnjeTh9oCCqXX/CuedeWBu3gDxMi6XmuIC//EO2g81OPL+eA14P+sAjjo/4EyaNABj4oZYlqPftEQMqrzL1nOSuLiPOEQ9koeLUEJPRASFNMoogM51f2ybqgkkgg39tQKmW5J59iFp/KnVGZIcqAeKeujTmDYvGV6SEick9z2PFgfoh0PGNdHKqNGP1bTj9rZDqqA38Vxh8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNTMzNzU5NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kZGZjNGM0NzZmZmM2ZjBjOWE1OWUyOTIwYzk4NzIwODUwMjU4YzgzYjhjMGY3YWZjNGU1NjkwZjM5YzQ3NGIzIgogICAgfQogIH0KfQ==" + }, + "+": { + "signature": "Fjhx3eMVdglCO0TH4ldiqzHi639ifw5hhjKnwzRdA3L4C0BlvEN8kw/lhPrQndLblaEkI869QYBB7OtGO/Y5e4pVdPhCDYDbLhXnl9GiSiuz/0+2EcsxAGFeyN09fQeX1AN1GkH8y/wt1pAw9kk/4Vj4ueQNY/H87TZL727+pUOqXisQSGXVeXEO56Wt4PJCEoB/SVwKZEEWbFMhcUa7fFVenaUT5RIWUbD3FxP46vTaGu2lsOB4g6hwsXC7IfjMzhPXEDJLCR86iN4+20+xo9mAmgBKIwAu2i9zbAFq81rtixS+ztxPTSg4Ypaej4lJeEE/2WrOaZkAU27YHnj2po2EXsxPQIla4/22TPJmHH4s5kqnDfv82u2ZsH4RVJ/fRpy16LZtv0WFP3bV1lqnvVY/etptjaB+BHeJkGJFXXeIxsh1HOriURgWGzdrMukkJZxMSpTN54BWUk5OZckyNAP++myy9tfQazbxMMfDqzp54vzM+0ZxE++KHBYqXNAOwguxVStpYYJa3mut0KqchR0nVuiXDxRPdMc8RBk8lc9lQPa9pbX9V6CtptrToqCdZygLGJ5aZ6qKiUnKSq84gxvEv0IgCNBEE4SNhUAIqLdnMAEbIdkjKHENWYwwFGPJBvfH28X7pzBV9HmUWo3y/XlmFzu8uAstjcc92ZUtMxU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNjk1MDM5OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hMjM3NmIyZWFkNTBmMmI3OGI2MGFmYzllNDg0ZmNhOTFkN2M0MjAxYjI4YWMxYjgwYTYzODNiZDEyNWRhYTdiIgogICAgfQogIH0KfQ==" + }, + ",": { + "signature": "AGT8qp0GKXmGhQyDJF9sKw9tMmGTx+fkN9j89sszJ+0qpPjYaSSDhtrzCz6sh+LBF9GAwLropQK90F74iIBL4AmY5r8AgFQ7PeSy5+3WRDvMOXM2dZiPthltv3WPTmoa7Q9PA2BZqCfUm8ta8JWAcFYSQ7WKh6BS0a7hapTZhycYl5o2xZ/e+ZcML0hmaesi2kpqy6o2+hsfCLctpEagEsV6kHIXGusn3x/WY7LB0Ax9ZALLydYg426a6zTonACTemnml062gnovk0Lzd6KwH69C/xHS2CRqdSqMblswyOc2i1iuWH4RSjzKl9XNJlWfEFfwuyDaLPaczg1WO9BfBlWsF1zRja551F41BP0bgvigVKb4G5R5KrF451VtSJDdHXE8c19YH2btejF1D10JRapeSuxZ01SAA7mSgXJg/i7pzcJKOl/Gd/8w6/YdhA3/aaBLcCDTI1B9Xsl5QkTAxLWknENDrGIR4iK5CUEwrZEDw8YGvRluTdHrRO2XLkISNwIIoiSvf4WADJVfOGYneqBPLi+t5U4LO3LZIEQvtnFt9+fIqhzXExeY0c1fZ1ZbvTV36PQidITp0EP6oec9p9NDkCWlv5qsX0JYHooEa77f5f7wuHFhaxWfX8Xm/aS3ZiGttGM1gFzhU4HnuYxEMR1176eJL+opmL+4yP5b0aI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNjgwMzkwMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kMDNkZjE4ZDU4NmUwMGM1MGUxYzllNjgzOTBiYjFlNjliZWNlNmNiZGEwYjIyZjY1ZTBkNzYxYjU3NWNkNjA3IgogICAgfQogIH0KfQ==" + }, + "-": { + "signature": "NCnDQ0T42XVcQ0DBsanKK7oh1uVgJDcYMxpbm5iTxWe9CjHGjEmKKPchPmQAKMm53mGpSCAciWuGsV5Kof9xvGtfxgEikEvbkMcXlXEK6qWjLdbcOX+p9/9Z+Ojt1TQmNHkO/CZ2UoCtj+m+xhg1TjX189tNFl8iiGInmvkbcPrOJ6G9eIPfi+rUIyt7JF+GGu5Hl6mFMesAM6OJQrjfKj0KzGD++56m6NEbzslk9ROrLeedAARFNolK7kgbuCeNDdenuobg6Knm4NNQGqNH7PDisCO8/UmtHjztzBoMRq5k4c7d8z14Ez0FoSlbUeHJtO6vQE7JseSocGyH4ozRjvBf1wOeXiK6s/zoWuUAJ06WBUsYyIDRH3wC68pFNvfT0+vsHQu0v+RYPb5IrF0HzPruxLLLkSkMwvmtUguA0W6bYCtYPFzwF9KgkRADivw6fSehYEJ2rFqO/wsEjBhPBffG5BPwbuSTcjeklNJhwawEqt5Tom3PKOqYr3FbLhbwxRpR7rrUCsDA8s72w/MGEaawa4sOyM6Gt6aKvOVkWEfZ8e8rOYFTXoxMQ+M43FX9L187QQInH8daXP9eP3JDq65gT4SenJwZ6oP8u3r1BYNw7iKcct58BMVyRFfRI+N64Mlxsm6F9gJBBbWy4LZ81oIvc/soD5H8hMMEiTqxgns=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNTk5ODQwMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kMWY0ZTkwYmM3MDAzNjU5MWNmYjJjZmNiOWE1MWU2OGE0OGFkMGI1YTc5N2I3NjE0ZDNkYmQ5YWIwMjNkZmIwIgogICAgfQogIH0KfQ==" + }, + ".": { + "signature": "IvXt+8NQK8cWrpWGO2+UBrwvRu6kbheof80s/UYhPBobSsQ4ZsF4ZfCGQffkCvn5QgPFHwRrXtxeeX+M1IGAhMWFedhRgfPY1EmJ9HA4iE8n/g5bl2N0lujiMhgTq6WRR6BQJjOecJBRIh2X6Dr7ipl3HUq9JdCC29t2vtABlI9A2+KSzYuCqrNCR/HXgCjXKsYGnHrk7jTO+Ub8xb+sGe7wSKqGuNbvKTlTwJMVvdwIv9JMzqOI6Gv49AaLtsnfjDQRh6ZmLt4KZA3a7BH558QKAv0HzVmY6I2GzcoGKn6ahSzzKgoF1BAWZ23ViKhKjJ8ScUIjRAJ5LZsXGHxSaEMxKnou3fzXKqeYhZNhhbpZjO2iQtUXtmQ/hl8FMsERipFXVFpyHTR9lbkI2F9jP0kNIZwkrgmiMMk+TGIUtR2AqvgBHwJcpbcx7UAgXe1S8NSCmrfaTQxC8hSGHOjj8ILR6n0CzY6tRyvqyjucuk+5mCtpXf7BuaGaRHYLWbJajp1s4JXcrQE1QlOHS28Xsjn5u7zVOdOKUkzwRT8MONNDYCOrIRkGAQnvPUJ5lmlEn8oU6BCWOWfaLFT7Q54LxN5fe6UffdEpKaUsr5Rg66xFVD91403eIhTdBoLYFDuFcRdAruXlE64/ZWNn7t3gafeNeasFYpkYfI3VfjuputQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNjMyODA5MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kMDNkZjE4ZDU4NmUwMGM1MGUxYzllNjgzOTBiYjFlNjliZWNlNmNiZGEwYjIyZjY1ZTBkNzYxYjU3NWNkNjA3IgogICAgfQogIH0KfQ==" + }, + "/": { + "signature": "crN6Y3omYJmbdY8Wyr/dvMAxvtPcaLMwN1ImQbY9p4cBxzuygXT5x879BfZ4sV2Smwk+AH4PiVVwrRMEl6UysI5FaxxKC1kbP4x7bQl0//hvQwPqykiuPyi+qKxs/iM6MXdvQ72TvQCtUabBNLZ+EVx/svYm+vUFUOdZfvgadMblZmK0DTI/33GLPv5pzq2qfLNJXseX8AKqxbbTLdHp4VkpbGeiff7p4rC0eV9VxLYTurJEUP0RilApSOjo715nB4wJMT/7n9YIcewrzbKJGgwS1aIb0OR/Qr2CZIREvADfrSFkuTbXd9mgOMkdCbk7o8AgRynDCrHvNo+RT6ewZ4+CXow0e+Af7ECA4w+goJ1C4Km1hnzIwZfJgnIMuf23uPyqNnT41T3Y74KCEfrzfKy/ailyH1VZIlwACEXQ8qw0Npv01xoB3YU54gwSgURlac7qV/BxdebhbuuiYNjOksrNVhb1YC9MWPnZInbx3IE0TcUutqgMo1QfNUCRtMl+KR1i49Csfa32oEiij2ZsEBm864451e4ndws0dBcwN0r1CWXuppxmW/lNy6HmSNWcMxPxOQiEL8JvNLcoW5sbUvG8337jddlnp6JscHl0AK7qPTk43fkCZtMDO3cGHrBjmx3BESSMAfCsA2/NgFsiUJgfSX0xZU0NEDOKlxD0YJY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNTYzMjIwNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85YzcwMmE2OGY4MzZhM2M2YWQ5ZjQ5YmM4ZGFkZDA5M2Y4YjlhNmViMTkzZmE3ZTYwZDI5OWI1ZDMyMWE4ZTg1IgogICAgfQogIH0KfQ==" + }, + "0": { + "signature": "DQLS5dRdzVtsQD0NLxAdR7LFQCO8Efl3M8ELVxQL0b56XlAxAoUc8VPVlA/+nlrEi9JEp8LcnUsG7uQNEPl3xuiMzgYLS4ARhnzHIatoUbv7k6FKRrzhzpf47xb8DsI/KtDMh7mowHgoxsFKK0tQ+P8lTsagQ1HsGmvEIhKk/W7Ni822oOogj4L6TJbf34gmigtl8UCh/a19AZrd/ywpz73+Xw/ygV2PDV4rCCdoHU26smL2rdaa5XClQhBI42CJKgdYUQEe9L/gkijjLkeKADw04W3tCi7gfQgvtoQ33/avidMWrImwo2rOrZ2OWAEl27Ui6r1hhiljxg2vI1Om12wIUMXrwzPatAl6yLXYdnTusvKpKuCH/71XfnoG7ggEykZ1csIz4YkMH8kYSikmQuCulOgwDcyqD4+KHdvvGkDsIaVdx3H7PVT3Y/zKyzqh2a64WYXxbzY+Mboaky3VdX3TwbKv/dGFGbrdaufDDYnX40wB6DUxnDszn0ff+bp0f5JuXJT6VLYypcKz3IFVwD4TlnLKLWGUSzi/cVvp7YYDJ7ie8pif2XXsfhlIjDKLSM4EW4/Kn0vu52lheHQtYeEVq/fv1vZQDEsR8/wzTs6RASDYM5LvLxhMgsmxplsewNstguQDluQnjP67fMAoVccyUKmm+n2pbcGrRQUAPBA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNTM3NDkxMiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85ZDAyYTMxNzFiZjUwMDAwNjAyMTlkZjkxNmRhY2Q4ZGIzYTQzODRjZTAzNmViMjhiNjg3YTcwNWFhOTM1MmYxIgogICAgfQogIH0KfQ==" + }, + "1": { + "signature": "leLgB96Gp6gT2O4VMgANwQt8w45FfLjGckLLXf7DjbpVVKEGOMqjIwp4OO0xJNO1gNy/BYUXI8W3DmNMklt2awKJQdJqTOHjmJOxTHYEhF088m6AlD6FS2j83+b9El1sS7OG3R3B0O7zSnLlGy6QGO3DwflN3ZAFRFshl1nry6hIbQsuLW8vw5UbcE80jnOXY8kMy3QJSTd81eP2uMemhD1GtKLZq3kxKElCFQs6ZFNao+XMzVykC71ZP731T6+uRvA6aWwTmyk7VeygUIvVSdjR7tCZv324n9v9hbSxv2ndInLBFrju5ybHsfy+qGDW+0m6OLUOJ3cB88Np4H5st5D+ZEHIuO7ZWUxHoJEV/m2wrJ3/nwoQuuN+w+6mz9eccS7Rj+YsB2d+TazDBzFfN59/d03SKXDObRmRN4h+YRmH4bgVExvBSzRaYVil28GsO+Tf+8ezjga7O3yaEOo1zngbQH3qe1JpYhXA22IOw+6Q48wiMir4J8/sEl/XU2QSDUpdND2NyV5ZHMO6U8hu0QlZUH9Z1xa9C54SoK279dykC4DL1abKqKCRyMEPcEH2H0LOZgKsR8Kae9Zlx2IjsN5Da9PzNllN5pCKKW6mUxE5ujXZp565idlrD1Bxe+qwPdlbF+BNgKzG45GajRXL4YELz1H3frZJRQyHTcDokGw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNzc5NjE3NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80MTBmMDJlZTI1YjYxMjA1OTE0NDA1M2VjZDY1NzYxZTc3NGE3Mjc2NWFmMGI0MmUyNzBhNTAxM2IzNzg2NzVlIgogICAgfQogIH0KfQ==" + }, + "2": { + "signature": "jJ1Ytywy2pjjYzkcO99iDJ8WaORmwvn/3nCUrp2VP1aP9yA9RTSJZ0k4VsY0ZvJgTG5t4Ny2VTklJQWSv3GRs3DVQ01Bz3U/NlbFeM8FttKqdqDbcfoOy4C5B4w445nP4SIqVMkfbw2K5cpMW22Se3JawQPTacz7LxR8ONs5kDfOWDzbvWYP9aPIn0e/eNzju31g7a+ChoP/GpU5FVlc1h67QvX6sA5TH3+fR6PTnd6Mws02Fzc9V4H+OQrF3kG78VwBHSWcDDiNaaSV7z4XAI+lIVAF3sw1a79ey4oD1iBsC0J1r1TjTQXYtHqBLXopOWtNiFeOgsYXzkW+8wpNmF9dHiust/rS/95tOZ7nt2142ob6Bx8WrMqRWZO/wTr4S1EKhoPU2Mq3z55eRMeMQ6CgdLmoezdwROZeYSDrwcq8LhU15pEH0EOPNvrpj/ojhWh1pJQtreCelOW7KmshOU34j06l5SZ/2Kjq3pYaCiFYcoB0nr5d8vq4GwGOMwe6jNQSAqOFxajqdy0jeYntYFRcesv/+BrXI2Zz0PEaP38TbKb1sQ5ywMiykftXnvv4GdGyrMDjmCGL+InkiGayMT83kzLcEOxZ+vTLw1Bz5iKk9UDIqC4LmGvx+xPrMwgPnARm+4U8l/m03eez7FuU2JVPf6TvWBtxASU4AtIXUyg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNzAyMzQ1NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iZjg1ZTFkY2JkM2JmMWE0YTM3NjZkZjU4MWY5ZjE1YjM4OTAwZDNiNzc1YjJlMjg5NmY2ZThmYmQ2N2E0MDkyIgogICAgfQogIH0KfQ==" + }, + "3": { + "signature": "EbvezpRn11xn50kXKY72U3pILwep3j3dkDy4OJNbRfnXA92Kec398NHp5wlHpCGyxNsu31I+BiqZ8brsdsz8bUWLvc/vGaqx7wZxMbDoEmrLw2Rn+fkEM6aUP5Y1LUliBk2Hal9I5a/Vp1TEs+V+N36iysdgXEqiX/SR2F2pqpL1dmdmfCzFu0W9v8/qA+FccEikHoHArmEYrMC7fqwA7SL2FuoZmHYgU6SZzNC+ObG0vG7dmF7Ca4tPzemVjy4cpDeehjD+Fv2Sev+oG3t69g8Nf5lUqtME+4UnDjwxZJPWOxPg5L82Q7wGT6nGAi02SkNW+ArHQNe6s2lhsRwmSM/v/7y8iqahwHxmUxIW+rf9m3OR852+k/Pi2VH+7oG3LcIha4evJd3oyl9LX7iNv7ytkyGzpLRsf7/mFZc/h2NV699D84Y1NwmHuhdBkMMD3H/rYEdqT1s9jpRK6cvwJv4xfK+PpjDfqBV6AlFHDiDsZTZTuH1V/JSYY/i6WJGAvYWnIo1bBXnYbASy1AWblWTtysRhHxkBuvR9MiRcDmHA2+bKf+PPKsFa0eGfVmIUWhaBm0i7kcLpZwADgaqSs0d6Yp7mV9e12RS9o8St/PYGbS4dGDfk3ZBuNnhjfGJ+OaAnvG4CWdMy9eZ8mVv3v6f0KF7pdPAYmNS2AZ07AZM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNjI1NDQ1NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iNjUzMTZlMGQxY2E4MDBkNDRmMWZiMWEwYTlmMDE5NDBjY2JiMWE5NzNiMWYzMGNmMjJmZWNhZmQ5MDRmYzYxIgogICAgfQogIH0KfQ==" + }, + "4": { + "signature": "PPp9oJceqkACwEgEYFTL/VE8iZNa7dqVtPMrUw1WqUHkCMt7O/cHc1XEJo2VoXEjEUepr75RWJg7Okz/XWaVfStxgrSNh04lryoaAyNZEpymrR4Ay7Gbu8FoReBMK2APyT+50kkzDNxcZX8EvjSvO2nYkYfFhV/gnb/VBS9WbNZB7++hHssNbhw+5zrVgMKF+i0qpldMAyURWKfSYiLwtHoFnlCXiMiDA7Ac1Pzs/+IqnVteY6KN8/t5R9yWMwozoL4iFaJba5+s4IlUBL93VtOgdQ8ic09hiB1sh72LLEJg5XEjfGLUgYbfDR9jzBs0uCh6zMI7t41NFWqHReZatrF/tbMF2jf4QYd5STJxEIbfK1srQrbeGxfFH1wQsJaj+zliVSGjKVb1PNqFMKINArAtTeqqzWeqxwA+oJ3X7BoLqzm9iuPwWkuxvjVXAdHLwgm7Nhf2Ludqee5qz47+SjJiICfLSc3NmLzIZzFQ8Kxb69lbJuXr6dQt258rBOZCZFnyzwDZ6pY+l8tcrWZLnGmko0HcgqGfbHFGNrlS6wxWT4wlzH4/237CU+ca1g+K5okYeIOQzpZVmq3juwsX+LR2srRiYFeUTJujfwmnjSGa0+I0xX+NpevfEEUPoqXvMyA2bQZLDFR2ZvrNvmckFVr6Wi3b8qp4l4eguBTxKVM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNzU3NTI3MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xODFlMGE3NTA1MDgwNTg5MGZjMGE4ZDg5YmM3YzRlNWUxMzhlNmJlMDBlZTE3MjFhM2ZkMjUzODMyODQyM2RlIgogICAgfQogIH0KfQ==" + }, + "5": { + "signature": "n0ILwpoxWZaFzs5JPsxbZljHk6v+nssw7IHCNWyOzw3i2fPcwwquRtsM+bIEzfDqhWK7FURf7Pmm6dFCNurVHc5K+56ixxOxDkOIG7fmK2EkYGE/GeSL9tq2MoUYd/7UbpWwMf5zIhTxbftyGpKLsX30LiAOtJPovyDXpY0euaQ2myQ2+CUfNXO6Qk5gNoJlWr99zTMkIeBOo//5FSRa8RL/AXHdFwIRPSi3SanpVOYbWsPsAsL/FnFayjzzq/WxuR6VJiyvStG/cZuej27hJU11APRxVcwDnFREZHyqYo7PXlvZEsWVDYykmHGNbIpCxz2luYEvL9yOAYFqNhLV6XLhf0vzaBJuD6tmUvMoJbEA8refUvCjBg8jfIWIcSByMJYGZr2deKnCXC+VvONa9JU225dnpN11MXV07xLcqS66eJ9A99dD6zXMyoo1VUwEdI/9HSjhLLrhjgWYo5qaHAm8GZvvHVlBUw2MRdN3NGBG/XWUtkVSybAwniZLhLKLnRJ8a+1bn1hGd4fs3HuYQr8JDIh21cuQOtUGOdBI3TJ8IY4wMytTwRVe4GZUoF8cLwJ68N4t1OX8j/GTm7zzz7cQ+T/Xcy+0z9AxSCoECqG7Or+WRsF7QlWH8ZoLZtgPsnxBIMcYSRBDsCS9F+LK1/loxSECm+dr++hCdXXNyDI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNjk4NzExNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xMTYxZDNhZmZmMWEyNGQ4YmI5NWJlZGZmMGNkNWNjNjllNWE4NTk4ODFiODYyNGFjMTRiNDNkYjBmZjQ1ZWFhIgogICAgfQogIH0KfQ==" + }, + "6": { + "signature": "ZdlH9B9cy3V2j8q6OooHmoBvFs8UjDw2n/TiHYvlLTZX9CClgHtxamBbwmvs6UXIrJw2UG+Wm0eXTGZu6FlHxq4tjib8Ln0nkln5hrSZ7YM8hUqtW6qriCj4xZ49WCB6i3eaiAU6Ow+1R6D8B71aDC28W93apbAgPRYK5kdqMZLt+1QRIVjmf/fwM5eq6qWIjHPgfdNZkCS/iOFngtr6PfTrlwPmCXopCeB3r/nySqVXMOdfhcKDEbx9qIQvV0BPpVKKyIeWYNDBWDaL+9AYXesaugB2pHJE6CVzG5nZvh3gTgMr+nwM5dnts7tQIZXTo76+3BM3uMSNNhNyx5vYU4INpZtDcNN69lvAcWtpGr4QbR2VLim1z0y/r0qqwRa7GmTR43H3ZynevbIDDwYijqOEIbOy48+IiQEBLLoFwymB1TEFTbLtiVM+ML0CEwTynEkDuGv6OJMhyPqxHdG0SdNVlGN54vtsxb9KVeq0lWlyl4iLQo1uFgJCY+RUf6lSgIgKiU++6sYmrnIAhG2LL/IqRC36y0RBViU3FbQQtWwvNWtNiwpoIV0ODEOOad4pEaeHK6mwsJ++6Tjvh5ST7BelcPBW8Qc7uC8MN4jmcZE3BPB3N+rwrQNeE71y2JNo0KLL8aBd2GPvuf2co6uG1t2vmUkgwIRRjZ/7e7p15Po=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNzYxMjE0OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80ZTg4Yzc5NGFkZTFhYzU5M2Y1NDQ2YzEzYjk5YTE2MWY5NGY1YTExM2QyNmJkYjZmMjBlOGRiMzI4YzQ5M2VhIgogICAgfQogIH0KfQ==" + }, + "7": { + "signature": "ksLevMctTMax2Rcfz3Ga4sevzHTbXpxQmcqVb6skCA0MSpxHFZWVfUXbtMAbajrJC9xzuYS/QVw58JEY9VLwLPPJ60e8pbjk+61H+ElIN497PgKapo0ocsiegWl7ACWPdbYU9/AdDqMNWqFuv3aweHtU8RWGVq2te8wdS2TZ17HGbg9cTnO2oI5ya8KjsVDzUOyI7eUqlE2sPompQ00YkUENLa84W0fTEWZX2p7BCmhl5iM85CGwg8zzfiHRdljtABIoumNm/MqzN9sSfHziaRooO+CxVQOM6FSLeptpzFZ5IRtx88Uv6Ci1vkPgN62eHVdoUnEZ4miIkme3Kf7B56fP46Om5MvD/ljJh/DxB6g6BTMqGLYNC9zACEWV39Glr+H3dwJhU65O40ztWtm6Z7GWN2GK1XiIw7zoMoXAq2rXaBwXM3vnck/DVyyAKZlWFd1lejiRTJC5oRURCfT8poq43pLYztngtg8nN70pxdkq2itQXxVkUh/BSSIsKa6J79xJi+piBlp4StcYqkOZzxnyRraWZQxLCn5nKuGBAHTe+9k/rkh45fXws6qB5vNXNED/0DQjHulmXwrfMkE+P84dF3NJ3+s72VRaZrQfq9yqzHwOcKxpJV3k/4K94Q32YZNPB+ef29a/0WO4X0mhM+wY3uoAoNj8N8Ep7KJgyAA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNjE0NTE2OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81OGM0M2M1ZTQ4NmFiNjA3ZmYxN2U5MDlmMDE3OWU2Mjc3M2NmYjFjNzFmYTZkMjA4YzI5Nzg5NjQ5Njg0ZjZjIgogICAgfQogIH0KfQ==" + }, + "8": { + "signature": "LvCks+fZGzjxjDxRWnETw/1oWIraj7M4+3TmCGvexuztOeNd6mn+3QbKEETSAb3McKPzgi2X0zM/EbNirztyZUHHYI/FYWpEmSdAUTqvuR0+KLBeGVxUGJyXJ6TLdIE/hVwB48DwuYrHHAHdF8rw/Vi2qTr1mAW1AB/iDTeJWvjU9dI9d4oEFMz21c81x4Qj0j63J6DfRzWJ9sOxA4XzNPMQ4ZDxNWTxV3izIbQdI5bbuWQ7svkMEpqv9TEtiOWRJgHBYXHdDR7g3Q85NGacl8JudR/YEUuz6yznTypgnqbTZlQAG6vnjmhL/OfE5+WE+NUfGBwG0flB6btd9GIytTOCiashbmV2/WVsb8kzrCq9RDxGxINprIK0Mu3NFZwOF31ZVwk17p9kSgoHXqwiVs+YxjCZteEaQ5pePsC7SGId7JEd7JvqogMH0jVb1ZuhJ8ft6WA4RjaEvPoG0yXTG8T+gIh3gYK4inscvHSGBGQf6BKrKGVQYNcL2AHDC463adI/aDLXu8mt+9GJp14X/aqLAlyDvp3x96wSGNmxJT4zdpcdqVDmSoLVKDFG2sjvuzfCh8fqf7zDMOYrWLShay9PUtpLNVvpC4jgktSxsL05rq3uBcUL0hnMfI/N5qnr051SkoklHYCYLgrGwZE+Pni6uMpSzERLGLwZH0zSMQs=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNjUxMjcwNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yNTU5ZjVjY2MxOTRjOTllN2U5MGNiOTFhY2JhZjQzNTA4ODFiNzViZmVmMzE0NDYzNTZhZmExNDQzMTgwOGQzIgogICAgfQogIH0KfQ==" + }, + "9": { + "signature": "L0Rxs6e3TtW2K8F8AQQgUA+sC6La9bhaTl1flV6U8nx6xxkrzNfq2BAvhFbIz6dh5yyp7dRCOlD5yNjy2mCgDGDZtOV2gcOV1po3whU58E7cNtg/YWNh758L0LheVWE8pfamL/3zmhKX2fnpZzZ1I9Scbp8asZNgl4HMbYdK1dMgCJu6E+XFgrmiWP59kla6bWYHtFNvUrmBDZm2coX13Yp4MV236YPbqaIB1gYZ2FAjJDsGlLA6M9NCjM8Sbjw/13V5HHMBr5Z568oJXZgnj8Ax2yngF80aHsTvulitZpLaItd7XcSt30+WBMDxuq+wlXXP9d1hAtYz27NvlA22V9h9OIkHWr6FwXBpsFCdto4zvqNTDIe91zfz6F9u5w8JXg9zxj+mQAClpSNHxfCy6P9+nhVIBy6O5sObbqpihta7Pf3+b0rL9+oyHvNaLg+ddbQO49H3Q0WYHfTjv3phhjLHGaXJC3pCKukGum2c+DrVW0jOmzGBcD5TE1J52LlLDnHgjOTfLqVjWdkV/HuaJchZD05iu5DXLngIuLxhWS925gfN42EXbLXznIlhrXwz0QOHG6LC7O7Uv/JrTHulNFJuEF06Y1qVtQLQK9ywND8ohm7rCgS3mLV4GCNW1h9XiNzpFM3BsxVNxihfPiw25f2CiOjklsCLdqyJxmvYXyk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNzI0MzQwNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zMzUzMTg2ZTcxOWQzZTlhMTlhZjQzNzc5M2UyYjEwNGU0ZjNlNDcwOWZmOTk3MzFmMGVlNDc0MzZjOTE1YmVlIgogICAgfQogIH0KfQ==" + }, + ";": { + "signature": "XBVMdWcM+oL+PNcNmJ039ObYRuRB4qKnpQBzsUxBIoc0WQXefpw1igbRzcsanNUh9s56Wa7ZokBgrPe0bSr8xw00TqZx9rXq9cFHKWhowaJBKTJafw5Rk2s3X0oNrfcRXLHlyuHWnN2rSuQbyT5hzbzmjvHWX1dPAvLwjhT5w2p5ZKA3kVEZUiveE42cQs32i/TcKsIBn2bHIpFJ4yHt0+ubwXRWOgkb24NjAHpKYIwyGKMCh5VxvYd9EWpTlo1Hlur3BKRRyjU/lCg8mE/6eDBr9OHZvUG17yg6tJx2Jac1UTP4FnyZGtHgEQtVPetf9J4rC3iBafzOy+IPSlbj4CZJN8hQDCH7Z9JqPJB6YBRLGjmqwJoj3M5cxlCIx2WgWaVyK0U4ogQkC6/fiGlWihi9JLRGwjI2L98sBkTut4OVnwe7SKPCmBC0e2+Ft6ZvQLu7cC75LOdMfGxqrazJLElUKsUAR1EPoXSuUPvFGIjHAG9lCf+/KocTlhbcRsV8TnAiFaqyWpQB0msNxw2cFCtC+wTQ4dnjJ3SAXBfU43RWl294UtENDQKJd46R9vn3ULQLR4zZV1BpAkE58BhE07SjNw2CIM88gt6PAdU0U2XL7HhWhe4YMYI0rj37sqecygo6dgbPlu/8DoV9cYl0L0qFSaHARIjA5miBg54j63I=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNzcyMjczMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84OGQyZDYyN2QzMTA2NmNkOGU4MTZkZjRmNDI1NzlkZjEwOTg1Njc0OGQyOTIxNWE5YmRlMjdlOGRmNzAxNzMzIgogICAgfQogIH0KfQ==" + }, + "<": { + "signature": "E/TQqU73jJ4dlxb7ptSh8bbWLCkeFPKmo4aZ320MNwBUGIibRc45dVQN9lcrYwsSTPVw7S21qr7PoNKVIC0bXARkivWD8mjR/FTSG2K/BrMpkfvuNblpuEmKlVai9Yi4/iHLTRhU7Vnly9pBEG2TbOFwB2hx1iBTYLnob4RCCiaSvNP+47xQ+TU27mFwwoTbeWSs7vphiDRdt3QkTeHFZXgcDHhmkM/1ax7XTnFTtBYdHwdK14Y2xwQDKKCKFr6tQJiIl8kxS+jLyuebiIqyTVbEmepwqOKAq2rSiWjy9eJ4+pmgYR/yO0XZ5AdqXdJ0cWJ17PPw7mFBuj2Y6nuxnp9BcC8FY0etP6FBfrMuhYtbu9SAMxGfcDfVZsluPnr8YlQQZ/N176S/H6zhi/RumYomY/nv0XuGKC75ru8kU/lt84J/xbV1ftmwJCC9zXm5+CS8CLbsB+c1rxGeC9nSDfg9jbaRCN0nVoQAFB/bO2tFHJmEHEQNT4XtG2ObGUdYEtcV4Vbfphte172qCyJZ2o8tjAYyzWLJ5eM8F3XsVqPjr1H6aWnXQIlMwzcawdDuW+EesuBLVZm1zjVrm4yVNdG64LU+5s37DyQ3jEDg2Wo+ai4Xfy63yMalPNNOJCC7qW9JE3BY3/Hiar4ziP2b0N7e0E8htH8GXnWHf6eVCt0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNjAzNDc0NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lZWU2MDI1YmM2ZjcxNmQ3ZTA4MjBkYTNmODA1MGI1MjkwYzA4MTVkZjRiOGIyYTI2ZGY2YWVjY2M5N2Y1NmNlIgogICAgfQogIH0KfQ==" + }, + "=": { + "signature": "Gn5JDaKEXe5R6oAubN2059bh7aziP3alPxSVXrz4MKRor5bUHWGKn3zWi2Ph0mzvynAH7pJr7PKDL/eZwNuMulmo91kEn9vDan/KET4gP8/98iWDkEiKD/3ZEhX06bvt5bmE9DakEEUfKUFeyn09bKqClnZe2cYmWhU2gVmQ7kLlZvy7P38ESv9OGgTpEaZdqTu/8LDznnvZZfRWNqL3ld3tINZe4ceuss2GR3P0nI1qtvqhWEqabLeNttmqFxZey2TRHGQU6/2eY8Ar4pm95nKQojDeXHdRCCMlnBOIg/Pu/D616SdDRnPsmXUWO7VPrubDSU4GWOGgNXOpIrGdOUIHd2/Hk+RUSFsFu6NDwauJEnCYAOcSqTKvqYT0My+nB9+9YhnS3gvvMgjW59s9dcfNsY9CsrIqCJvefxlL20zuj4yvtgPLyC4CP82xP71vAwbSAUritngJmcgYaLH8zamzwssu7g0EZ42EBZB7kKzb3O6hCNJ5xR+Xq8W0+XNc8e099KoJR+GOyKnr3fn7JsDmAtkM8c/loFxTV+HJSILICSAt7mzndGvNqRmqGS0jSbpTtCgAFvjM6oJy7WKQGj1r9tOxYGjAC++wOP7YIaB98abP2X56L5miid6rIT46buA7cjta0Nqxsw2kzPcnlNO44KQzXmyIJI28wp2oIL4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNjQ3NTkwNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84NTU5ZTcwNGZlZDFjNzFhOTBkZmY2ZDMwZGUzZGE3MmIyMjI3Njk4MGVmZGJlODg1ODY1ZTZlMzFhNzlmZmZiIgogICAgfQogIH0KfQ==" + }, + ">": { + "signature": "N2isWiRkDWn7Xbaa/LkC6XFupiziAWM/f4WSpYKctxEmKob9VTXkXYz+AtqyaOWploCFTT6+Vz2M6NjpaeNSHx9/nwYvcOXe02JApUpUZAy1EQCMHbbaCzGMAPnJc3lzeDmZPa5Wbjkhp/JJYM7VPrixTRz++TRPkqcZWk+0869KCssNub5hQjj79bKlIwbH6GaTjEJc3kNbD0dnca49sRLIie/fUjSPaZ14Zlh0XYs9AnjjWnEUkegOiwFaGO9weDMd9Zt7um3LA8DuBfT1xmab867fDPhfKiHIZDlFXcgUiXMNUWTuulkWrF3596JKGPKtKN6Zf2bq0+jgV/tsYmGSM8uN4jn34vDwMUzO3dMkmRQxWWURwozkOsaCpr02ZVEuREsVQ4SHFSXNFdDrHFeM74tftb9pGTpvcHctaHlWEObwi3LC1YakwdMlpQ2keAkGc1GpquR37pbzKi+04EJxPwBGItvw5rsVp4sB3wHPWX29HGZODvPJ6YjDqPZIqIbvhRhylvXAPClyAjMBmu/TymnDxiluM7DB1B6vbxkRjTrL3YW/L+C9uQsYR8D1sjAYNiGF1eJJmzHECge9ZnYXWtZkldnIm6pU7+b+F5Nwzau8Q79S2WXLDN/mK14p9K96qDkNCq000D9/OzU5CWSie0csLH8m9g9LzeHI16w=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNjg0MDYyNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yMWMzYWM0OTM5ZDBlMDFjZmNlZmJmZDkwYTFmN2M5Zjg2YTA0MTAxYzcxNmU3NDc0ZDUyNzQ0MDMyYTdlMzllIgogICAgfQogIH0KfQ==" + }, + "?": { + "signature": "NvzEMRaYwS2JGMPPoZpev2AVOo58seIs2DpKBRNRRPo/vAEZ/DYNnzQnoB35uA6Wx7d9R9a8Q+b67HbLnQM+Rs27NshDv7wP3WZAkz1+1gJgrLyRdeWR3dE8SMyImR2tRtYq47BWuTUc3El/1bCOp3yoFgWRxBfHBviFpkD/u/NMZQh2+WoilvP8vhBgyuWsCXZiK9Q6nsaFdddkcTbbMCOYob5q31u7u30Ew+km+v5SdywdG3cRZBQBvjyn5uoAMrqZE9suF39rM0RfsuLkMWXGyoWBUlwClEJ/9aFKhRF69jHOQ8l0gHTPp7jxDGfqjwoXs7TSDfiepYT/tXXQ2ZmwQMvF4YcpU826Pi0euIeoZMZOFM0LHT047sbu8dHQxPEJUIdiPwpBb+5CPjxs3/gsSEZyuegbXnscrByHlQ+Pd7S79veQPpJlP1JUQOY1fOWGmufUHwx3xnDdDiHyvTyBOfkn7nJWumk2icKg3vkYsO47Ytk8gqAN3zLWlG6nZbwfJFG0Y/Ym/eSgvVd3jOjeqRhSAdsYP8ERFHnQPwavBMI1m2Cs5EjZo+HH3moB/5J2b/9/dcmfZ2mWi+gHzkZ7uRVdxYBvhOr+DRibU0WTljBZ1LnAY+bnH46rhcaamyMItA5E1KNIZda3SN0u8+rwH6KA14FdgDOAK6FW9CM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNzQyNjk3MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83MzE1NTNmNGEyNTU0MTUyYTFiMmMzMmI0Y2MzYTYwOGUyOWE4ODU5MzQzNWE1N2E2MWJlNjM3OTIzNGM5NzU1IgogICAgfQogIH0KfQ==" + }, + "@": { + "signature": "buVpHpHMs3fFycuBIPXZ7rZtP6YFfozy+j0h8JxvozjtGd3budwTKMasEztEcMOzc1G62qkTLUbFAKjE3rro9D8lrcSRZ//uk5xF4InEF3O+c7SWeTO9w+j2X3de8Qy1dsycsPnfFm7Y7wcfT/ZgXVQbPXMLiAtrkSFRVOWOT1FhBJdjFce/Z9N6AP8ZMe2E44U8hK8DOn3JqfCJWKya5qDXP/YRdiC6VJUV6yLVLYXiozGgtc3gUAO+qZbX0Xi7OfVI/zAwzeB1T7tCU0/xn84MlduvMktxBs0Ce6y1nNlQG39zG23wb0bdD1jF6kIIQqjkNZVOWt+JXyqYb8bLd5iTGtYsQQQeZAt9pYKTxksRSqDh2dKyeAxoGze9BdfEYfZrisUw94bc9Dnaet3LaAZSSYbc8bIjseZsB1qbv1PSuHpxdI94r81uf2dxojPTpodAgVYYtOumIZZgsUwnvPheVdIqNOfRMcwa+QcHKq83MmVXrMqC7WP7oLvjXaKo5UC9LDXiU9BVKzUI/AxPtZ1/4JzBOPw9CcE/3dzoCg50pdz9OQjhJ+hZUlc1ty6iEn/mSBlBDsznvXo6mEt20lg39Td4qUfjbDIvrMqIuJTyXvKfJkV/wXB9YOeyjMDL2Dav1I5iwVi52pWCZWDsFzsh7MwRpOkoEsubko2r6ik=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNjIxODEyOSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85MDRiYTA4NWRiY2ExNzE1MDYyM2YxMTMyYzUyNmIwOWFhY2JkZjBiZTA4Y2QwMGNmNGQ2MDg4YmJhYTgyOGIyIgogICAgfQogIH0KfQ==" + }, + "A": { + "signature": "Sc9zykn80sQxUhD2QWnwYoivN5tKef1+4lH2xXLlY39o5c+HvsYQ/4lC0wA2YiUDFgw91g8bqzrX+MX+ymzDvWXNJWAvCdDgQ8dfSaGmWBMJWatKK6LDTVl1l/eAD0H0+030QfXDdjnoBl78k29waw/4DbP/5qyjuYSEPH7nmYt2ApI5nlDoMaD9peZ42Z74QqjogBhz83E/MevaZY9N0LkQ1rHzeHI1oVpRjQM3hh02u2AGlgNI/wHNnXGz7Af1TFlsjif/6z441XuvN9Ug1hlGtcM+yeJt/D726H/5qB0ZpPJTVkuhcTFz4Zc5uE49WqP1xRX2r1QbT4AwXbPDYs7idzBYATAjWxAYUokiDA3iM2mFcM6gn0v2BjOIeUDj+Ha1el3PxPcSPED4PoUGdTXREN6cVRxG3t2QBeffHyabdrfO9NiWqbdvtyFKRRPPdimf9juljAtvbtxQn+LemoWt+F450cX2Nr/caOGsAgxZTEJx7pxfOlxBvxJiOMZqjPjP+uUdkZU1MrqZs1f1FVCRXjHOsr9jklA/ptOEQUuiCdAy69K6zjAKXbc1/bddjDnZMsHt7+5IDQKoRvH5S9Go7fspz5DHEfrAiZLcyDiwEwknfGJv3C62JLkI3vFyvZ6+pdD2jOMe05rNAbguSZl/SDPeCrPrwlrujUNiaxw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNTQ4NTA1MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hNTc0ZTJiNWJkN2I1NjlhNWQ1ZjA2NjcyZDVmMWUzMTNlOGNlZGJkOTk5OWRhNzE0ODc2ODg3NzU1ZTYzMWY5IgogICAgfQogIH0KfQ==" + }, + "B": { + "signature": "YE5sFQWbNpQPWrm30LLZsWPiFb98o5QQ9Qhvk9pvpH8uxDLZ30Fb95a3Yd6u8rkh42nXjWFfbTnRnIDsMYLN8DFfLz4wftzAzcUZEnAS/Yalhp1/irFi1Mqb4C2fZ5UTJJ5iOPybmcTP7a5WuaV1iIDFw/Ub70qbQfg5cO7h6aLhaCfYk4FY8gC56sgZc8kF4unuEGEBhshXuBtzjhwHzLGcUw7Lh1u6V7OhRkqTsKHjLpoZty6996jVLnX6lQggyU4Dl5mLsEiY0ihQjtOm5Zy35xj2T1hhZKwWMcYy4xs5w665g9WHpnB2pBv/V1UX7dJXG8GN4PmfaGiJCrajI12/mI1eSmFtkPVmQ4DYL9kCw1m+HmJcI376JvxGLZNhepFAUF8q2ggbEF/+77s0r+Xio2WtVJ+A+BV8pPCRJxpDttJ2ux77reh3IRaj8gqxmCkyyT8Ui3AjFzh15QO5EAtucS6gtQC5jUChsd6v5gBhK/hmlLDhmH8oJG1xKhK0lWDL9W3YjJLQvn3XNq+WvHi21+jdi5kWvl8qvV4hOq1out3jj2zZcZedKzucXtbeyvH3qilplbUlYCvCyAPavYLO0B+yYOhx3OgRktrzFFV64huVxq3qNHFv83Rz1CCDLXR3lKUPP35qOyruMhsfh7khPjPVyWwXXG+dqLSaW/4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNTQxMTI2MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xOTIzZmUwYTAyZDg1ZmVjZjdiMmFmMDY0MTYxMGZhMTRlNTE3OWIwZmE4YjY1YWU5OTNmYTNkNTM0NTI3YzRjIgogICAgfQogIH0KfQ==" + }, + "C": { + "signature": "rlcuiMuLLt+fonc6X8qpKp2sH1331AoDOnhBz2fS0wETD0VNJrNZBNPtDriqjIEyWIDeSligEy9HfkcJsEAY6BOwpi2fNYF8KV1/1/dZ47Q1ccDGp4EhjCMThIJHhZ4J5FSeyP3Gobo2q+V1Ln4GF9AJOS+yLtgpvq+OoEpnLGyrj4St4W4tFg3hVtGkRxPnD7BY/0/pY28v3x0iqQgRgwqO/HLf3YTxzrcFpTD1WPXnL6XhFS53GI+/uJZlliM7yoEBSKnxMl/4MRM0TVGYkR9XdnyIn57BPiZtz8NJQjrI9CGSV7r6RMGG5e4jRAQXRAUokpZ5Gpk2l6Scyc44VBCzmUO3x4MsjmMyUQBW/EKELh/0E+rpmcj4bE2U+zLxyzeICxIuby01hk8YEE5MbO3MtUCrQO31i2kQK2KFZQdvykWNgDwz9yqN2GvnJt4pdscVIpAUTbW8/LG7NkRM+aghfNP4PqS4DRvlbVLm/J86TvI936TgkzSciFU9U5dN1ySdONs0HhHSwgRCyNpEgC8zaXJjAS0xM8iqCM1tx8LiYX3wMki9LI0jTrZVOFYEkl0sb4jj7pbQ75cx6S6PtYJAd+TQ1WVQie9VV6JHqnF+oeseqefO3zLDJALBg+mDjHG8WPTcLCgG5FPxkuWRhWhnUDLqS8k/+2KVL+niaQA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNzIwNjUxNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yYjA4MmRlMTY4ZTVkZTVkNTcyYzQ2OTlmM2FmOTUzMmQzNDFlZmJmZjZkYTI1Yjc4Yjg4OTIxZjFlZjBiYmQ3IgogICAgfQogIH0KfQ==" + }, + "D": { + "signature": "GWJXDggBLuN0ljAzipz+rOO+K48YToP1YmRUw0UPNa60TbxutHWrUXLe39jkac2wohK95ZBKUgxUNAUdmXKKtlsELMBWbfzJ18LIqOXRyV7Vp/oQpUJe8HHhB2ICZVIdsxWRtDM5Bivc7R3XmhDOZwDVIrsd+xYVAHrgRruulOImEaVafkm5l+3hjOGKNQZubQOS/lH8++7BNUs8ZwbVDMd1xqQVbrFhylw+6E1va1c3b3ilwxyigl7OMTUKYdFx5L+jkY7OwBFHeZ5zlCsqCNulak/rKaWn3fJhmQ575T+H8rcBACxdGRMXz/dREpQDR2G2AeKGID14dpcPTw8BJ4fvpK3bNkW+WEpxNVpeI+shwht9soKSG7BBLARMFnO0ygIlz9fdY9k0HydbI8kh9jC+h0tMfk0FWtr4D3XezTmhnr92Rcvf9l6qKXYKTn0J7AkJA7umPcKKXxeSO2NKHyzGiRQLiE2ZyRx9g3wwyP46sodOSBFDJZyWAqbDLdr4BRaIwsXpzDMfwIDeUy1c/JYbSLzs1vEC+9LlNamh/B7pzJYazApNB3pKYYvSFX0JQzmVdK/MpdGQR7vBssiwsIBOMDjV/ir9XRCXNqwNIRAUHXmrRIgkwl8fk5q6NqkI7026EGJesJFAS8Lj6kBcpesSzcgzCdxA0K+NW6bMP/I=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNjc2NzEyOSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iZTVlYmY1ZWY4ZTY2NDc3MWUzYWUxNDM1YzdkYjE1NDI5MGYzOThlMWY2ZDVmZDg3YThkM2JiMzk4NzA4MWIiCiAgICB9CiAgfQp9" + }, + "E": { + "signature": "YUaf6zZAN9rGFshZZhLsa0IzVEEs81FOuPN5jSPIjKXymMMLOd+KIfmBhsmwDeSLzkvo+TCnBi7zZdm4l/GGb6OD01acC9EC02zm82aDQavQ/cgCfNb/sSd6Ro3McrQAx6+tpKkLaaVxv2MtpdHo+3C3sJ3aM80yr4crI6KcWVU7XPfMblpkH0rguwCyn5232/ebNXV20yexl9M48x68fXfewK70Vh6tgEQgbpDgUmwS0vNzJrzWo9OVXuX2jP/uQrcDJYKm8iSBykP3Ohg3nIhzVL7rqCSIjEntuEksfzxhSK/yoWPwgFsyrarRj06YNmXQgejX5HwZVP93Ns+/HwPpZUZ43w40kwAb8BOihoRSs2gMCUO2V6zM2kDZ4jydeML7CzNvdlxQ3iW87CqatmnhEY9M6QjtbPenkvzQauzWg4hGQVGHKobDNgrqZiimUO+0/6TF7kkytcBWfK/joHp6/z6d1UK/Ma+DLftHEMsBR9N6srbYBvCb1x4hAFh+crqK8NBFG0YMBrRWmaEnzQBAu4BoaxeFqwuZ1JzjkMLeZxhredYajJ7f5BVPlVykVc0DHF9AC8ZtRDegeq7osOdRxaHZ6RHLqfaP4654d9YuCoZj8t2dxcd6IlXFZtdxpXN3R6E6mmsyuhDWFyCau+X98rwJPh+v74Wfaf6J8k8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNzY4NTQyOSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84OWUwYWRhYjZiN2ZjYTc3NDM4YjRhMTIyODg4ZDUyMmFmZTc3MDg0YTc2N2MwNDU0YTE3NTkzZDBhOTc0M2VkIgogICAgfQogIH0KfQ==" + }, + "F": { + "signature": "pNmfKoFriMEaGoCIPJLl+GKJAy5VIJ68sGffrqFlOwgzg71XgeXllHA/QbCQQHli7Q6Y86LlypFQrOirzp1ZpTjCkH8WJEFPV8c02++9gIeYy4r/kactl/6H+9Gw71PGT05rVnosMe0GxIuwV5U2XEXC8hZbN0/Lky54X3Vt3mvWiocSYte84wij686pX5ypFjRIskkchqWJEGq6SHTcWT65Ff3hhpcsgvwHJjI20LnHisORNsT7tgd0+po52obfgAEJpmfZ1US8ixvToAaL5VUm+2wVs9a8dhQSNIe5Tc9BEWvUvoAoGFkFeyaRxwsWpLtqINVXvU5CW3mSoD/R5YWT5YZBbGqtvoc/82u7lHQg6yHyX/HFprjvnqyLh50ZKJE0KO6Ul3nMY3DKaMgfH/G7H9dkD1ZuQoI3byEgC9gLXt3PHjJirIKC0uPpgCBc61V3MiWZh/REqKWTB6SuV4LikN4bvlOOhdWExwYIT3hxzhkI/kDPn2to94hOOFePaiBsd3mEbuROt27RE5dR486/Eg6GVybAo2eyFhBQXQ4sxx4p5eqYH0CFTcGS0d8pOe+n9UM6uPo5l357w87h2GL0Sup49TqQzUb7ok4JR3UuRyhCjhPNoYiSY9NVRFst6pwGe0+iJjaalW25ZKG+R+J4JlLmXF0LC9NL6CQiex4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNTc0MTg1NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mYjFjMWNjYzhjZDkxYmQ3ODBkYjU5MjYzOWQxYjRkYWFjMjAyZWNjOTE0ZTRiNTE1MjZkOWE3NDk3NjM3YjY2IgogICAgfQogIH0KfQ==" + }, + "G": { + "signature": "rwmjbfn6jcNdgrbhKwRl5gEpKoDiFTkIVRtnUTcRmpclDTDU0srDuGc9QhAT7qGiJAv4ZyPyIvME6phgM7FFWRoO52Uxr9UstCT1Inson9o6lqSIXbI0bek+Q0nDfspNC/4F/Pd5RV3mE+8cqfpgr99BYaAyQ+7BwECGIaawcxIHB0ddGIAezSUqskmgfSnwQdJeV4/9EGDZgF1G7GEMkAOt05I4oKZro6FDNqUaHzAxkCvNi/uBOnxs5BR29TkhvLXcXrvmmMA34TEAjjnPSAIMVTDURxmQzeCDNhEIVmu1EpIqDRM8A2jZXJwW9phY4PFLDZoBwpDa77O85fmqSVcPQ3LcUjTwwWz4IUj+bS9pAN4eQ/4nmxeUYHKSdzQgLncGrxydq+zMZhi+B5j0ub9XxnOVywYbxB2e3nl9q597oB5UvlaUqBQ5t5c6AAroC0EzLC8Buoyis1AHskCIewN64lZcPldtTrQMiE51TQ8CGDUXB1KQKWYvy+g/rsUgNo6oHU5EXMQjztXyeX/P0KaajOpTE+0b5GGvBuUex+57OUUQYKBDJgwpmsHtHrnQl15eobjHdioqWFJ/TtlxtWXnq1aF5kFrZqBuP5AtVCtXt9rCZo6v+gQCpMazpgAKt0ibAN47ka7d/nB0Yz8267mf27O1NmfnK5LyLaFqi3g=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNTk2MjAzOSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lMjk0YzY5MTdjZDdlMzBkNDMzMWRjZWUyZDYwZjcxOWQyYzBhZjMzNzQ0NWI5NWU0ZTNmMzQyMjQ1NjU1ZTdjIgogICAgfQogIH0KfQ==" + }, + "H": { + "signature": "e3/sUuykeLPP1zBvC4ZJR62QXDX3nR/sT/kWd2arYtmO8qGVzvbR2XmFeZxRY07Bo0YVOKQBwbqJEs3+KEeLdhJSb2fYFNtplfK0TrPiyQtztI20dG0SRVzzovXcDN7A5Zvz3QA4jIRQGQHWqHktRVExILVy0/gu43zQ7USMXoGT6MmfTG8IaX4ELh+3EJBUj66B3sVOiJgeLNMqMq5+ZRmSoCvq5HAfkg4w2GC3HY4A2Uo7WHNhgbnrWH6UZhpoJBWLHPrO6aFdfQkHtC2Bez6Kv966oKChXPxMWnnEWVDapupSn1aNBPwTKKngpNkTM6SxC8owtUGoLcJzPES6XuLGf+C6km7R+00jRfMEQNhZu9pjk8p3i9UIqDTXzp/j5CgppsZP/HpZ5TZhwZORkNTfE8OcC2P7gbQjPhQNLmeWL5c9nG6L9WvNEtHRkBmOSX8aO+fPDOBXV+3EGkerK6YCPJ8fm19tbt5RgD00I1A9sJa1w+TSdUcPRf8XJILw+kEQ5ZOGwdKNNeU0RKgHWsLHpb+YPc4xFsgu4483v8kM3QAqK6BBVlOrrA162Ng+gEO79ogBmEZL355aUlCtZMde2xtpyWZZxvKcf7/NkRLjAN5K+uAf3+Ub5Gx1OzTeRJW1JUkprinQC5fbTUj4ScgKEaSQEa0GsdWvwiI1srs=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNjkxMzQxNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85ZjZhZTAyNjJhODM2NzM4YjMyMjgzZjQ5YzJlNTA3ODgwNzc4YTFjYjdmMzU0YWFhMzdlZmE3ZTZlM2Y5NTRmIgogICAgfQogIH0KfQ==" + }, + "I": { + "signature": "Txeh6kVSt2/6rr3rmgt4VORUvQEdL5yGO+IkKm1p8YXLRM6miOLAVaXJ2HtXyLT66UY1RBY5MfiQIWoqm41BXR4mE5CxAjiftSCxrDvyVdVaat/EXr/k17SZi2lgyJHyTrZkRap5+ijL7tncdsF6W5iZDugyiA/GiExbGiOxl0pmQ3NpxuVbvw1nxGA6NuGfw9y5ZWWBCTbaj/cq4/QMeKsUJjp21a3ykrOV60RiLtPpmj1SGFNML3yIByRiV6ZjMieaCw0w7qD8NgLDXbWJtrrUKu1VBgWCCKa28R7+AfDrpxWl1VLCoK/yMcrF0rOHGFJTLVZBJ6pphA7oFT4TuzTjsYypFiX3EXEsl4mtLr2O2mhvZMHVebU49GnfPOLO+uMgpgTPL5GL5jWtTS6ywfC00Tl+gl4YQ7nf5h5Ise0Cm/3Qdvyoaxb3eWKrtdeYsMgIWcAzzUcqwGO6jbsGukEQbY3Ff8Y27iMx8ga1A6+oPWnjYSkrPncw96ozBJ5M+hCkBotlDPE8hVgAjBVgrwiIFlb4xE0Z7dGdpuJ6tCoCVowy/RgZNIGY3tGd21GK+f/lskBdhFm4qxCqMs2IaVk+kQddRqINNBSsfXftx3JNTLdJMWtaj/LqTPneepaS9+yZEqAHjo/xne0ABAxQevphA1l6HEvzQdLsRAh7anU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNjU4NTQxNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81MzhhZGJkNjU3YWQyM2UyOGRlYTZmNWViNDQwZTQ5ZTUzMjVjMmVlN2E0M2NjNjFiNGIxNjFhY2NiYjI2ZGQiCiAgICB9CiAgfQp9" + }, + "J": { + "signature": "E81AEuqWryLqcY0QOJKJfayDj4LUm+QddfwmNB2OELlBBqT9tu2l6iObS/Rw4Lk9yg1i2G4YGHjD4bdrt83dOCiBn2hv/o6f4lYAhEfRHW6LUF3mLDd91+zDjazuJdeepMwzGjirtmNHvMdRpFUz2yD0G8mPSV+6P2/XTivGNtHT8tiXrRVpuWfnt18xCn/aq3Jm2LBUWj8DH63SI+nNmI1edXq38XhHF6ynis8ATbgjAOlbYJ1/SR5/DDXc4KIemsN9GnHMR05B253nFQLUuR7/Bvcc+N2ZWgyD+BJ7oFiagEt4O3sdfvpVoCxrJ/wqdmsrdEav1qfFjU4dJ9IHYcV9qSb+MMpwcHxahI6dKA3GakWFD+5+4NJRfDsjtwG+YQhCvJNLFbKVs2AvEpjt1fSzS0wKe60dymxCNZ2qi0TI2n/lwpm6RBFNnMZroq7Kk7JcNsKL8103af/MjwNc+KLDseTacb/HSnB5KZGkcgZwA3i7nMjnZ53QDNgfdAF1w5v9Ia5LQqjum3bC/anQ7SR2BtHAG9NrT1RQBzKoZceFtdLP806XMLwONWjpHZvhDMTrQIzm516w2wxSNyUTRmWscnysBVj4TX5OGVblrlOPY8281yoliZiYGy8clUbFJV1ClasmD57scQgNzkf6EH1CrkEjFbkVpHTU5uhNTHM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNzM1MzYzNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hM2YyNGIzYzcwNWIwYTUwNzcyZGIyNTA5OTQyYzg1Mzc3ZTZlNWM5MjgwNTYzMGQ5NjdmNDc3ZmFiNTFjZDc4IgogICAgfQogIH0KfQ==" + }, + "K": { + "signature": "uTrfjujS20V0JxwysRZLb4jJtebKoLkRi6HXIsubqwV+ZNqUr8EHFmuyngyU6fUI6yIclXN9N66UKWokTtm7epNgKDcVoXNEhMDA2nXfgSRZ0GzQl74LgVndDweXwrK4mnQiqa7kSgrEw8wlwBxVogArGAtlc//NQDFdshpHa48mBJ1bCSrnql3jBmUaFIkID04v3Hj+5it/SLLl+3Eynz7lEYdLWc/u0ioIHGyo96WvNCLUVMq0em/XaufVaCdWhitFhs/DAGuNmmUw+0jXijTJYsWH494NDHpEFq0Fw+t30KDWvv7qn5kCVBLxj4KOqenKZjxFKjlwlLIVfPgD9DeMWjdANw/RrPmVy2QQDhXbGx5guulX1KXX9BWuSKk0FHwZ1dW5z0rmWZi0sw57HqnjiNFMWatoDSUxuAe5nsv7a5CYEGZIQbXL23tUBT3A9UxNik4ldRblvrBl1Sy8whlbfqW1bbZ62pP//CV+N3tmylCCTqwwaNy1D8Q+9Ti1rOIlFr8T7TK5hy+cDg5dOTevFodJdSBMF9ZiNmPPh/y9BZ6RxO1gMDKdkcxpBVI3jQgA8/hLAvSAIIekvpCXyBnLhtUHFnXsigmOQTNpMGz85JjQCnzDEOa0EGZXaoE17XHNsywDVFrJWS3tYcn4qfQQ8gnaAqWf1VZQAbVR6bg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNjQwMjMzMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xZTEyOGRmMGFkZTM1NzczYThiMGFlYzZlNzdkN2JkMTdmZmI1YmU2NmQwZTg1YTEyNDZkMTU3YmQzNWM1OGJiIgogICAgfQogIH0KfQ==" + }, + "L": { + "signature": "x0jZg899dLMiPi+BGtA+ANcf/w8KYFN3n/H4H5T2Czru3tCy7g8y2E+c7AnSurOiRYFaXTBM0reWRKZDkA4ZW7mye/Lnu/i4e9u4HtL31hjdSXAQcmLNbq9TqJw3qEXICc7+hn4iI1qrSoUfmPP8A1mnZ7qp1+vMuDEG1R26kPOclMGQ6DpR2Y0SVp8srZMiNnz4rzP/sHyGZtS1xixP/kZP6G/SixzhOyPK+abER/Wkuo+IF6vb3YLlt5tcrl/5jAKRyxiX8DjQckxjQ9ebsM39v98+UjnocKH6v6E1SZBwUepO4DMiqqAS+4OX2ajKo5DP7gxC0+O7q/OMTfvnwrpr+Oxk6xJ44goP7cBg5YSvOwQ4ZNNwLl9iYOBxrGD/qNip0DqWwvwa8EWIlnywXl+15Z+VvIqELREiljUQigYSjpizRskL8yXsSerhfsrAHvvqErTrYwO0EgKkdGfKvuJ6j74AjYvE5ZFBbGAw/iXuX0unm6qDfskFfiOkn0ytLpmD+gv5ZPyQ0Ucqy+7UO0Tx0syuCnKQ+AQar/zqsAWrg2/HC8qthcKMRuhJlCjq1U+8VMnTn8iYDLuvfSBqCiLKCkaj1BeISLnQ0LQ9sdlWsisIQeWZo1SzUYu6OyQuGJPRvlWptLpgUHbJES4XvOpVCwvpio5jXXf/3XySUeg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNjczMDc5OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zMzFmYzQyMjMwZjk2YTBhMjQxYTA2ZDAzZTUzYjkxMjkyYmM0NWYwMDE0NGY0ZjMxZTVjZGFhN2IzZTYzMTEwIgogICAgfQogIH0KfQ==" + }, + "M": { + "signature": "TxeoOWwcwUZtRTCLQzuG54BRmka77KadbcBp3ekLRvJKyUnP1o8S8wC1f7yiNU0vChkenG6POs2f3nDCuErtBzBYcGZ6i/ixAHLyoC4wNJ5+3w06H2LGpjUpzMB07ixsir6/3cEM51XiQCUvE0c0a878yMsFP/sgOy9E8wMybb1ueg9e1CCoYzQrDMPvqm74MThF+UipekWl6rMmZtzn2KnsR/46xO4+f5vm/hTAv0G1PunraBBvl/akdm18+RWGv02+aQ9KtxaIq/TBxMQyMzR1R3iu2uFebOwoe749ar2ik119tB0a8ySkPjlmd3fuMKlEOQ1Prwn0NyiZd3Yiq/N/TZebUeW9BNaXYBg4YA7xjmWP6ceejrdqNHuA8yRo2iqY9o6y3rFj0y5GM65+3Y6OO4b3uQVt/ymMP3+X4u2tjjpWhUyU+hMoIissIN624h2EZm3X95tm6BtHySGNJ45oP5laoD8jIfZJIAbnre54LqfJ0K0hu3RNxSj/TULm1Sem6EGsEmT7vYctt8XQaFfSaXlBRmphDQm5pRa9urcdxH6FM66QY3ePldqWd3vdVKFRg4+nRsRvX2fEZqMQscv7ehHgvTsZ4/KY2pcmdzRFcx+cKnA0zEyxRAnfXe8PdEkCYNPe+5B6SfXVldd/BHvsX0WzIyEFGpycPG/FlO0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNzUzNzk1OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lZDdkMmE5YjNiMWY2OTdmYTAzMjdlNDExMTUwNTI1NDdhNTkxZDE2NDg0YWU1MDRjNjRjYzljYmRiZjU5MGFiIgogICAgfQogIH0KfQ==" + }, + "N": { + "signature": "o9XJGhXBYgrlAir8h8VNctk29wGgEV1VBR6aEBSCw+/J0g/m/1sfYiwmj34T5y6mb160GAcmMsXSnHO6IKExSK3rNzDcFXXgPdIbtZTiSeC9xJ6UE8C/KKXtnFG+J/8v+ywb/2L6U9cVi5RJaoSxd5hw5LZhEPSn8Z+YL5GaYfzqKNULZcn7o/57ozlV22n4DUDqqkFO3WF9GW6agLhd7/tnTH9wR5Joqt/Hd1jHPB1XtSukmkFWWcNEOM8yooiMZsBUkuOkq6vSKVtV9Pm0dYZYJhgUvikkyoD/1vaYLqB2GnWzyzz3hoGsRl5SkxSjOYI7W20rNdo/sCCEKUmZLAsUay38ZMuJ5w3GByWQfew+tNUZoAAD4JAaE9qqEVoyNJJ2kHBt8XGoI8ItuFubBE/pZkYhNdvlYYSK0HvTwEBWfHFB8VVlh0g4dsDCGlbRAHAw4mtqvvWezPxU47u1plxXyJ668jEWZv7X788Xy8QCcc7CW4B4yXC4k99I8gR2pYz3y0w4WK9A2xqH8SFRop8VX+R1Iq9Pzj/BVKEeW5KQ6hb+/Ps9KOk6vqqrBZKx93746uxqT7NVO32EYCPBPEcOSEiikearWqb+zPH7TA6RTBdMRH6iuyxfbKOgVKG87X0SP7dtmkBtNy9/tYfR9fbnjdHD+fJVHi7vlmeZqHo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNzA2MDE3OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kZDRiNjMzZTk0ZmZlZWVjMWRkMGY3YTdjNWQyYTdiMGI4YTA0NDQ1OTY4ZTJmY2MzOGRjZmE1NGU4OGI4MTY3IgogICAgfQogIH0KfQ==" + }, + "O": { + "signature": "jTymlcvqEY3dXWpRh9K0DptkZ+SfAbl3HgHY/Xtljzk7wotLvOZzoCMJ+BGssDHU2wDDKM5un1T5rgeVmvZt0jkoYwT4LR/D0P9htc5hY4qqs0GJbXGExLkkl5pKlPKPHRSutofURWno6EGO8FaqFO9yqMAzV15KZJNfWX+dG83UAVjUMZphxUDswSGmKqeOntszzSmS0B37L7Q15PoKDRp1K9MRF4T4cdPPzPwwoWVfNb699uS4NiRqA9J/JHTcxDO/mV8WZvwRVaPSQce+Mnl8LW745zWiutvGFHkEFBEJc7wMRE3oJC3Gmiu4Eaaqp5LPnyiUE06QImnKZ4tPxSXsuIV7e+KkcKZ+n1Zsy3GRVebwNnBs0NuC+v1nxOlW9T1bQzzWXe8kUHLbo1z+1bhyUDbJ5quSs4kgAA81gkTuCupX0OWRitPhKCeNv6TOHtxV56ouBwlKq4JSk4yO0Ok7mkRMMuEXJsxwQ/jCQctibjHyF4y1hWbHSrvnhOW4oH132iJd3McrUIBjt/Wcj6zYeUBO8weNm/Y2icMOAOQjzHUk67RBmWNo11YpJ3agtQJdSDdLWW5O8zbn282P9QrJFfETKA5xCU0NW4tFc0gA9rFgDU2PHkfGJuKlh84W857ukYs7ccHhPTVhc56KL9CFvsk9w1Hwtw7O05WOI0U=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNjA3MjA4NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lMzJjOGVmODcyOGViMGY5NmQxMTZhMzRlYzAyZjdjN2ZkMzJlOTNmMDgyMWEwMDFiMzRhMmJhOTQwYjZlY2EyIgogICAgfQogIH0KfQ==" + }, + "P": { + "signature": "wV+4FZgBYOcOQZKFM9/A5+5vKVLpcZNLbRyqkestKS0nGC/qecZJaUDSyX8hPyWZPn3nPpT7eIQEn4NjZl0O1TBazOg2h6kLO3DZfEjchS04tmVok2JT+/MVPLXCU0DQfdJ2DZbDADiVLzuyf2VvpG1mKORIhFx8I9NRTC6ORx0SLjJ+rqglQJtciU2DBMuuOlOOn2o6nBbY60OJwa0dv8EyLoqHscHFn1/KMBkaMGc0Iy65hxl16V+qK48E/l6iR45AwPfqH6I9Mh+T8CfmrQcTMX3C6wi41q06f9WAHmfu0cRi0DBfsdqoWVoX7+vDnY035zlam7lp8ra2TDJUcSSaSZ5jpMdIxH6HkcCu5b/m6lBVRbl4SBb13H/0dBMk9vUTytwpqJVTvZSAfWlZrdBKVhvzsQQCulfVYNDRkUfz7DCtt4ND5ulDR3FIaSe2/02wXmbtd8ws5DVgazRfDBaQidGutzHWDBWM+BjskGgclI+kVKn5V/fI/TCJMb8P1KIDkQNFhhX/OmbRO1tihrtoUeUixrEUsmsPw3Vr6h9PI+NGqQizGYRNptJ2R0xjYik/RFAbjxWUFO9dMEBCG0xx9qQsWW4+WsPcLsgaAIyhse2jTZG/eWqOw0lCj8shja91rWNlCGRgDlI3Ada3dMilIP0vhc+exxUUgwYgLuk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNzI3OTc2NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jNDM3MWE3MjY2YzU0MDY0MmIyM2ViOGI0NTliNGMyNWQ1ZGU4ZjA0MjhmNWIwMDljMGViNWM1ZDY5YWMxOTkxIgogICAgfQogIH0KfQ==" + }, + "Q": { + "signature": "uitezUp/fIi9dJifHBIF0jyx1z1GaAKh0SYMaqX3wcnG9GxnBcutVVMQHXrwTby/lWdEb7CNkqm542C/CHgtIfrmN/tKlLh9TQCvecElPqVAcsQNW0gMtM3wkJJrdqmLQ3qU2SGJiHVjUUgil9IUOeNxiIBT99e9BJWnyTn8sdyM+jvTdORwdictlyPK8YyNge1OP4NUNPPtFhRRtl5VS8fJk/WNSb0t5nnEJdJBdnk4BpLQ2W49ko6+ANnQXgvxEypGt/xwAjKg2ctBgmkZ/i2f3vFMMZdbmyDkKd9T5jbhVnYSx8dHmnbRPjeENA0zqPmbsJ3ZVeKJR3pwz804nvSV08omxGsiQxlPEMSAMdnnKZSQizMzCkEwvJ0qt4o4LU6yDBYcp2Pe2myaYAjss59P+D9/RgQgng4EHTycW4PcZlmrbwewPvM30vrppxXYJG0IQWJ7Mot2m9dWvFmMcGw/B2+ofTfZ3ZlXum/hxh0eDjy86w2+s7WFT1SlOIVoBGIThkhIMPHvcC4KtGiqSCaG9vuYU7ArK5YcBn4/NUspunAYlq2AO5iQXmWGjSiLelpBc8m+Nxdp6RdC/EEZMJZe967GnwTioDzIgdf8OzSNPPNHSnTfU9oss47fVq66U3phWaJkbr2Wbsrkb5uXlWPQdaor//Ak2cVftJUIL60=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNzEzMzM5NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83ZmY0NGZlYTRlNzkyYmFhZjU3ZDg5NWExZjRmYjkzNDk1YmUwMDBjMTc5YmRlNGRhNTEyYjFhM2ZjYTk4NGZmIgogICAgfQogIH0KfQ==" + }, + "R": { + "signature": "B8MXxk4/r7mRDAVRnH1F7CeGt0PFQdIdYZiVKZK3B11GLfXQFXV/2AgKQ6VAWU+XXMz0Lzv9+kJEC+Am2hIs6xGcE8hdYHYwvgnxfANDB7eDlIUE3QnOw2JLF3QWxK4YoI19rZLZyfzmRJus3iDrZwjfulXRWQLe39v5oTB1CGmeQXS3pn8yEC/BZmIAgKGEavQzh7gS6+kkzlbfWezCRzUD5nNR/1b4V3udtT6/Wlx0bnUHaj+Qjjpwb13aIHx8Xg0yRGqTEJmjnKr7IBo9mJf4jlr7GsYPuk7O6fdWXGn18A+eUXAzyrVD5aOOPrEVaPO2W3aMUv45d8QZNNLw1pozHRRI6nWzGVU0mi27Tn8yl/LV74wNsO5rLvqjoYvpclKiqfGLEvBosNXFV8+Dl58XgZSTJ3WBKKeI0SKXQig7K9Xs6ECJJbSkoNXQN3wvG1vAg+REEjsmw801tidbNU4oX29d36zvvE82ZXqrFFB2FMt4jYWrQOitb9Li0buzMBAc6vZCNrzdQegG/V4CWgK81b2PCSIR39FNY331Hgh8am9ucM/AEg721Gr8xNPkOa5F7mUaDfQnO7NP4tXVSJcAWecqkbfGlzKxgJTcr+Bnwj5VjxDhDTw1XeGJtXzKOKys+oaOQ/zfHlAZrXVd5NpJATUdFhxTE4OS567M5YM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNTcwNTUyNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83YTUwYTVmNzExMjc1ZmJlMTZjOWQ2MzU5ZmZlOGQzNjZhNWM5ZjdkNWQxNTFkNWU3NGU0N2JmZWI1NzZlOWJkIgogICAgfQogIH0KfQ==" + }, + "S": { + "signature": "ok44hTkVLgM3/UGFw/VUJDfOhxptk84LXwWa9A3mM5KT6NyI7/jZY0nucVq+aGj9u65vT6TgX1lCTlSxD3UXNbr1YD4ONP28yl38mUI4nJGXBeE8xmAiyPhPhTSUTUMlYSg9lQ94V9H2pmgcH7nWVsThmcxVxkQiYl5JyaWzGuXnXPK3UgJijVrZignFcxHs2hRhZ0yTijrEvaQclXSuM7ERM5OTHKOgbanAszXGvrqGDe5GkOGq1omzCae38rKGgznbltpvWz1GDPqbQ6nSl7OCIKPTRw1qB/ZDhX3eoVbVH9RQdfdnD78i54TwDstitsEiDXBTEPEQ4OHn500rxHPF6bcziXw+FTkXqvTqJGrF8Ox7fjgDsiKq65PLXVCQkrMAcYw5dzLTWO5Iwvmyku2c8TnOnYzIGaDYYSyzbzq/QCUej9lql9qmqqx0TluPtdTJDaonzqIQtPkANdHhnaHH9HpiiM4HfWbLYuqWbYyx/Ak0IvHWKSfqujM8tajqFXE/WMDJu9IYaq5MnJ+iGMsZh+pgk9NHx1z7xJnaVY1u1cUs+ZkUwWlQXNDTRIK5fcL5KuyoPpQT7zwI7ZOKfJw6Hcaqpyp08eNIuDJgn1GmlyFz5C+QjKmfROSFCRvYmdpXTUefgra5ojW88IJQ6JK4GWwuzmA5VpkHosVpOy0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNjEwODQ0MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zZGJiMDg1NmFiMTlhOTJiMWRkMzBiYmM5MjdmZWFjNGI5YzRjMGMyMjlhOTg3YTI5Njc0MDc3NzBhOGFhMzgzIgogICAgfQogIH0KfQ==" + }, + "T": { + "signature": "XpwANbts+hYYJK6+nO97UXwZHfHHoyyYJCQaEvREfyDuiFPUEaA9wulRXWKMdp4zF5pE338GXWTorcTr2lqu2FDKyqKdcE1XOPFuHki+FvowX8gTgF9g1N4UHw89qzQ3/ClqRwNwv+yqzHpSumu9o901hht6f2fN6U2BtzWLk3tRYGhUyArM4K8Znq1LSWW6jGu7IVP7apjLzdYmnNhucBP2BA20rdIcL8dXIRPumwLW8AWq5xl6eglJS10cnAz4J0P2oaIN4X3AH5nzpoyM6o+xG//TCbLxroxuRXIP5NGWxrY0lGY6pqhegd2tyt+Wd+bodMdpGfBB4SNcNVp3qXpyoY4GOTIMfm+4CW1BrMgMt/GhWgzNporNDvHaCULNufmkq90apec76Ad/+VxqSKo9SL7OKq5imwrsrnyQ5qnXgG5UWN3SEc0DhRb+rasV9+DAh1RxWnb83qqBYiucbdM5GvW6vwaDS2KEgv60dxbpKYSsVWCy9Es7W5oJA4/QMsypbZfUe0Yo6Ucr1FJfhaJzKLWFDOWNEDTUiSS0ivF9sL8iQ0llRijvsicGebQr4HssY8BI6Tx9yh/CY94XuZFklRuu3oQs7t1OuabHjUJMxbIThzqg3bhgNk3XubZoCgcB1NdsbLiY5uh+8oG5xKKH1weZrA9Y9DjiCyoZNyE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNzQ2NDI5NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83NTM0NGIxOTMxZDZmNmZhOTNlM2ZjODBmMDc2M2I5ODJjNzFhMjNlYjVmNzg3NjdhMmM1ZWY1YjM1Yzc1ZjEyIgogICAgfQogIH0KfQ==" + }, + "U": { + "signature": "FKGL6MFm/WQDX4rC9tzxztSL2TqMHyF1I57LEcC0HW2E/g1JDC4s3MSnRTcnNyU5uQBxdsg9WEjMymc6D46N7C50NlmRd1me7PGTmEUZJYGUubQ8RGttl/7JkQPSTEiCG5K4qHr4aq4nmBSrBZZuOj7YC0sDDqm4dIFAY7p+zdzH6K1Vc1wlTPBbkxlpNh5zbX03DMTAUv+kgjEV6RlfaKs9Jju1YekVwkuw0dzmLeob6Cm4wIotQ46YNCe4s40NZo6GJYVWmOf/i76lOVglltRmfg6hWKO8AldUcieGKnRjkDxhpxxqIYYRLf3oNLyQRrorbVw2b7AiwmCnUxcXBwkCtqsbvMBR+/dWzUMXxCXEXf56tIcjsXLwr9CmxEbzjkYnVr2+6E5stN9RGTkdv9bzH47BBbVYc7Z+Qnnhcn520ROYnYUDq7w6MxQOXmGLqKvvnRBjpa0p+1e+DCIJLUXF6/A+I1HxwtklHrPJnz+HHm5sSVErfaUiLMcpqvPDvntdRGh/h3LMCWbfh2Fw77tbH1qMggP0FLqXqRmrtlHdANfNrUvRz2mwWBJnTTVRO8CQst4ecviAi4AOZto8juGlPPcK/aZf8Y4s4AMVfK5SwgaFSRQqGFKT4bGidgZ7ejaQtiTLSEnf5qDFbRATeZF++6Ul6Ut0zet0Vm2hexI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNzE3MDE1MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hOTE2NTQ3NTg1YThkZTEwNGI3NTVlM2ZkMmRkOGMyNjRlY2Q1MTU5NzI4M2VhOTYyYjI2YTVkNjg1OGZmNzU2IgogICAgfQogIH0KfQ==" + }, + "V": { + "signature": "VtyYk/HwvdAqdHFpqywY4ZTQRss2y013H4dcfsaDdfUrgrAaDekHSWoM7gZm21zKKSMXIEwWsjy68NAPdkSXHUsiavSJdeIQPbZ2hMwexHJCt8/Cj94tsSX6r7RK1M7qjyYb3uno9mov9wPbuY2K6CLptBAPr8DiKVbMdkNZI6KtYNs/UbRt/ZOl+zXc9DjmpPBE5kWbSnPqk0v1CLnwr3oL+jLDNM+hQ3LzqqM2ZoST5asyjyxlyGCx/e/ULe1ftUiBFbNBKSk0MkIey+N3cUWjFr+LQcTqLAvNWoI2BhgPAloF3quCJawDKKCuaQ62pMmdk2eCPjkZVMm6z8KqX+3ZE1igBNozT9NuBb8isroX14Pc1qwKHCf8uaq+1GIMHAsN4NJh1PgZ4Ra7zTK7dEnqask+SCe2Hb3EMPCEbKrgEEzb/ui3WCR1k75nJjdF4VeAk5QWpDycBvUhbJVpKzGVaqXztSrGR0yN3IeMpEPHvKcu00JJYiSqsfqWZ0EL0kEQ/7JbwKCk2UZ5ETrxF7mYZlTzVMxJavuIzlSbZO/7KMZEjbYrGCI4liBCF+QHOOHxMqBuQaiRG6WA7JlpbaytQ5rF+FqnZY8wr9TTIyJYQWkXuaBCDWwQP5RcFiFAN72xXR+okrcdxJCvMmsWczyOXdjFN2xnhhbvxc5/xQ4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNjY1ODExMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zMWMyNmJjMDcyNmRmZjQ1YWQ2OWUwMzg4ZWI4ZmRjMTU4MDdkM2UwYjE0MmEyYTgzYjc0NzVkMGI2ZWMxMWNkIgogICAgfQogIH0KfQ==" + }, + "W": { + "signature": "j8b/N8C3T/s19eHiwEUIU5gR5cB0pVCQAqFozzEN8U3FCJ/ig013wj9W8r8DnQUoBr5eoqdpPgPTAzZ3HlB09sawtz2lCC015j/NmnUrtlo5f7Bd61wJk1ZLl8y9P51XXwmc1GFrArUQFDf8OXFPydlEPcny+ZTvbXPWasM7laQtTYw0dvQSI1uLLB4PyPusKmtcgnaz6s9sAa1haqcVjKFWRBXZwwqpv0hwYjHnYZ8fMmW9Jxb5lUc0aFiqWyfUs2wg5cBqTJtsikUordsk4a6ENVijzTeEkQEHGx2rSyQs7A/uAemiEPTqqCiBfaw0jDU5NbuQ3cKQXoJJngWL3eWV2B405OpOafK4GgAqg9q5vySi+mi8vAqiZRjq9FQ/a7VUIXJZTAxx7blkXo4YRRbdIIIWLzn7wSE9CFHu1Tf43qrobVWGWok4y3Z4MHD95YZW4HuC0wXJAnWn4HRxjKtfg85iJeZ/rXBJufYqlleX3lqb0aIMICK40yQpM98XnFle8LKc7PkFB1a6JgJ0nJNEr0emgSH1dJ7YmvJj/djC2G6gexTy/HqFyCGJ4oquM47bEorw5qXys2ALuazuwADMOUgLWa0zUfdAqx8LAftSq2x1xfQSl69EnW3DLPDG+cDh5J78w3yZb2WSJWwP7KCV95yqNVoG5zj/6rFKfCo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNjg3NzA4OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82MmU5NmVmMWY3YzNkYzJjZWQxZGFiZTMwZWFkMGNlY2QxMzk3MmJhY2MyNGEwY2NkYzQ1ZGQyYjdiZjE3Yjk2IgogICAgfQogIH0KfQ==" + }, + "X": { + "signature": "EvocownUp+H+dJsBN2+2vabyj7BxBOKUM2qL1GrDK2YjZdz3eV9ktauL8K990mxaSYGlV/NHaGNVlNklUemSQSjMli185UO0nlOV8adcRw+Ews5WNIAkQe3C9pasGvgN0845+2EKYqqzqoMnDurbejEXSCgWaSlQOMHMPLrw5nzsd5FHATJef/d+Q6vpM2p8WU09/8saQ82fvGsfadG3tfccSOcl/CAWZDluIkM1IBb4l8z1KCxSQL9v62zpWoD4o9wXf2F7EIn/SUVqdYQhIj8PuOOstUTkzaKmoXoeoiFovDI5TqV5HZneO70EkhEGd9nrTz5rlo3/StOF/yEdPeSEdfNkH9J5vo8TnlHDov8g7ySJMm3J8iwuR1xnrdGFZ2FVUcNBAyTTAbXYhCf3SSOVXgp27740+zJ/Jy8e0Be2loTc6A28nw3Wi+nm65akUpMXKLqRKHUpDyac1Qx7/Qbz69UPZFleiNZkYGfuAc7NKWwWrmSapiBA+dpr4v8EjNN3mG6CQRL3/Xobxl6kdlcaqJIxyzN/J1iZjqHofYVIKwpPwIRJ4zWIJuU8zOkJFM7MwMheQHF7YZ2iGi8Nj0HNHqHHz+OMIFxJwZvyB6LosUZGzXjfWkMA++834yN1mysmj7FQriux1C5jSgpHW67klEP+aWIz8svt5VfR/bs=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNjE4MTUwMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iNjE1YzcyNDM0Zjg3MGFmNDM1YWJlMDA1Yjg3ZGE3NWMzY2Y3ODgwZmZmOWI4MzMzNzk4NWJhMTc4ODQ5YTY4IgogICAgfQogIH0KfQ==" + }, + "Y": { + "signature": "IEAj6NjfZkobt0enxNfr6SIXtRvGxeqpVPHNV/Awi6kF+qZissKFWZlraysN5pxD2dpH9IQkCLtKjgBTKclmThlzmcIwkqMZKK7ITbZSyWzUdAZkwaSJMAXwBnmDe/Nape9iaHhtAmk6Sn5UypSCKJ2m1Q96QLlW35HFxMUIG9vBWn45nP9nLfhbi0w1NDJUnCw5b4GdwOdjoSTdc/fYXeU+qbYyehfILgQABKUT+iHi2BZRKJ2Q0jkXsXX0eHLQr9zrMn1PqhPBW3lvxKABkoYlOU2K73RHy9it7TMMn0I8BBhzNAOo+KuYj6Z+IDPSizyDJffKkObIL9i61J4KKSKaM+QOj4Q5vy75A7xXtcjOv+1Ua/6Qr54GIjXT38PEkn6IJKz1KZqBBZs2drAMZjW+jwL++j411H6dHlTwQ5gzoG47bpRD/NT/cORhrjxlXtn8fGWgdIKxnEHNeTiZKd3/ly6UZFjzpW9ChRXgdmI+VcSbTdET2XboyT91nqe46pX1SOTzsVQtflLd9odQ6aYn5drA/LiP+7bQ9M0TBIr/z2LoShGbT8jId1xWHXhq1guNlruPRaAXiXMsf/KE9DOOCeNzFavN6cZ8hCsj0Lkeyn+58O12cfbyIyvmCrFOb7cwwisTuHPeSPFEo4Uy+NI+cgBUJ6ol3EQRUQDa1Hw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNjU0OTA2NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84MTU0Y2QzNmU2NTRmYzZhNmJjMGEyZmQ0Nzc2YjZhNDYyNTVkMDAyZDhlOGE4NGU4ZjY2NTY1YTA2M2Q4YzAwIgogICAgfQogIH0KfQ==" + }, + "Z": { + "signature": "iQgyiqmoQ5+Y9LYV96o7aln2oXeJ31l8uq2t+8o1MGQnN7v1jtRLKDYbYJ+tm9hFH6GknKURQ6RUJ1qYbdrmD5wwUhR5+dduR6Gw6QGDaUxlWGXlYgaic7z38BiroRlKX1hpr+zaE00PS2cB+huESohS4KrxebMCbcYY3QwTV4v9NnQgwMe7XXiRZ1f58Piw7cS9M6nLAPsETsqG7zOc/BZ2xT/8rlMixGc/XCIqRGygH1xApAklDKqh/nLeHAPKxYlVkTRkLbc+jjoZnE0qo6RHYykkcsJzyL8LrZ1v2+E7Dn6+GCUhikB1nMWhaOhdA1cufTJsVPzBYp5adndxB+lhYCNHiks87EEX39qzKbt+GwsS3PF/l1wtr7UBL9cAU7RwncH4vsKevXWpNJd5bswqef+rL8XvAZZljWG45ORPY7hAFxP2pEJYUQBVq4SO6TBn85omzuva5oVcwbWmC9Tn/omUnEQLQzwkodclP16wrzP4GfgwcRVUM5lOdssS6fbrvjLa4sBRrEW6kxMd4AXX73OshF9CdVObypuOJ0+EJZdbKCGKSLERvssjA9S9+hghbo/CwmIHb+Ev40mRAz6vgouDjxD9ExDDjV7BdyuL23vGbkdl0JH/Nv+vFxHiPpPjqcb+Z9ZA1ogpp7e9nusAMzlHo6+cXu5UqwouL70=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNjY5NDQzNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lZmMyMmNlMTRiNDYzYzQxODE4NDM1MmZmYWFkM2QxNmM4ZDdmZTAzNzdkZDk0NzNmMmE1OTZiYzBiZDYxNjRhIgogICAgfQogIH0KfQ==" + }, + "[": { + "signature": "wTKgbe8hMsAOJ8QDWwb/fEIAuiYVKZL+7Qyvyp5pKJCHz8av3UguXeubFR73andJEL5cQ1aUcSLf7CnjcQwn7nTzvWdVqI0RtjRNkyV6x4k0MqzMHWwxw6+PE1mN4KzsJwVvxXxM7b3L9HHU2DzRusefdQkqIoFmHwmri/Or88a9oXKS4cpn+GLC7gcbQ/ORrQS78rzxoXwXhVZBDvYKWCtsi3IU2zf3Cx54zE5/M3Uy/5WNjiugQTPK4aLE+iHnpwn9z/T/YA+zUHAPzCJSi1bSkyXOvCG9grhEQdZKwsGn8v03BZ+1wJdof+Cy10AWZMiNV4yX9DeZt1GepTfixq56xxACYKi4Td+lQmU+ZexQR49Yooqq3IScJctTEnJuo6IO2rNzUf8uioev8Nv0w0cCiGp0hceWWgVqL9cSddFe96NCXfMVJucf6GgpVsuAgHrPXZU4PZl5Bp0g7QoCBEFoQiaelrQ9baiPjUyGJCqGwnLeHy99CYRyqRw/zbn9VH7WevPXR6HqCTKcsW7Z7ykcj61w4mNhwSAJLxGxRx9mG0RfRqEaZlpku1vss68U7v+ywL634oHQl6iLTXMgq+m0KgzHJK/CjVdSI5AogZuzlRQPX/9X/xRZ5PFipYSlp8wUWu+wge4gtxtzLoOWyFP5Xu0A5qYaQJ2nmE+7PsU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNjI5MDc4NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jOGRhYjc4OWVjZjk5NjUwZWFlYjA3NzhlZDM5MmVkNWI0MzRhZTA5NzhlY2JiYTljNWRkMDYxYzY3OGJlYmEzIgogICAgfQogIH0KfQ==" + }, + "]": { + "signature": "eQuiAVji5ydGuJH2P6+9O+qWDs+zRYGf6KzhnR7NAcjBUI4nJj2JNHCjmQr8Uix+2C2aGyPyP0/k0deuOluJ9PEGB47UbUuMDn2Wjc0O3mezGEhJcjEwwqWUREBxLncnp7x/kd9HP56OPDrBt3V5Z/FXIDdonBk448YCTFMbXsLe1BR+AWeryMj/W8JDXVioxg/JmtOMY8sR0tL4l8kNYn9Naf4n+y7y+v5EPNdCJ4ep/yOAxJBQGkWuABRwMUoTPwgkQQzu2BPoMiH+IWVZ2eVMKm75yQCD142oeLbbPuznMTIG8Z24IttkHMqHhOExBYpV3E3spz95u2mc4h/iw6EeL42qMZKUNNBisnRy6eqS958f7Lmes9KLi0/XlaOGCvb+3nN1bgkl4/L+1yEvU3wtORRuKjE1NTfY9LrL4HtuuPtPG2jQmPexxjj9mshDMJWCtq4ljHE3mWFw9PRrY8Ad641GMT9w9dMii9gzt5dp/3uoC47LdApMJrrFEeOJpwFdXCHp+3rjTGsYJkR5PYz4RLxrOSPBG+9/MXve+VwQXIFzxi7HvrIHQM2+3SZLiVkr8TTO4RzedaaoXCIu29DpZkEcZYA+U/mOlxuzZs0Dri0Y1idf8y7nnwIV8bsYjdRHK8KS3R4C4n/r+T5EW3/PtfUKpAuA5K2mUY1GViQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNTY2ODk2MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kMTM1ZmY3OTgxZTliN2Q0NDZhYTVlYWQ5ZjFkN2M5ZWE1YzM1NTVjYjg4ZDEzNjgxMDVjMzA1OGY3NWNlYWVlIgogICAgfQogIH0KfQ==" + }, + "^": { + "signature": "SNePYEIqBpT12h1wKqVNENcrkIej5hXV819+mV7BxlG9kOe0XrleJWYQDEgyrFzKKDlpEAti2ePKQF8vGlkM5ZbWnlTBng5CPvhW8WS6ReHrZ7r5oBfph3ExjCaQGUhbMR2GTtw6A6f/NOab1EY9bnm6MRMScnV1rjgLF9U0ovZBs/gVf06kfW7VvzOjPccRphRPh3pRx/+7AuDPicrx0/BAeFHXyQc05xcE5B9c/4iycvemBDNY2ydHAXU/TvJxWuE+7u7vsjI5HnGRC75UeWxGNHsTlgWAswy44lDI/05QOtLZr5MNpm5DJu8oB0kpoW9OcDRwlOgwfq7pVplpzUmzbXGpjuiu0fjDGuQHFJR/SdS07kzK4IJcv1JYcs5wDxGbRQRhcWG2eRD932F2ufncYxhA76rc+Txo3H6aPQCfar1NPoWVik90Ek5ADPA7LRj4GQvZwDAwvrlI+SB3pUTLz8LfT2dREUS8EPvPwcFQ9fOfIV5uIo1beYvxvAzmgL4rr0SeHtkGDJWCrcGHKcSBPTEVKKpiF+fpz5ZpHIfACWVKPLXLsgWvOxv2WrSx8G/mkn2qMZkzsJ5of5RKDnrsVdfAwHjLUC4DtB8DPfStDELKST3wjdfrankDhs3JOnthoHuL4n5RuKFmpn1SdU7YHE8KAfVgue3dlVPaFPQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNjQzOTE5MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83ODI4ODEyZGZjNjFiMTVmNGRkNWZiNzRhN2EyZjY5NGEyZjg0MzkwNTc2M2E1NTZiNzA0NzhmOWMxYTdmOWNlIgogICAgfQogIH0KfQ==" + }, + "_": { + "signature": "ZdwS28MCpU+1o1rBDakHV052e8XBwaFHqBqSOrPE16M/ovlGC6ivSEHPoyjSk3JHqKr71mFdXCowXJSryEhzEJCU1/elE7l4cnp2NE5VWnKpFff58A7EcrMVFXKWpKyeKj55f6fSE7e7pb0EI+Fez11hAp/r7jPIBM7cZ7VyOL6bJw99JopZFo+Pr6I/5p2x84nOd+MiFx/d6185GgsM+mwUkv4RqORbVsyhfW2bnpzoEyxoGQ9UW8J6AZcvGzaoQngpqCw4+3tjIFk+5Mhq2CigRRxzW1bWq9V5GNK82qwEROU6Sq6MkmOE30A+qSDr8DJ64VirVW2+1Hj7pcySpXWzzGiSukzBv1wpATO+K6saqyVygJLa/VLO9uaZ8R+00l8m3QE5R0AY9DCvajP39kU7AY6qbS+p77M2hvPNMDDWpXJnUGwKIgfTYNrxmHRAVE5Ai9UGHHv8Sjxxca4k8cGym/NihqQv6y1ff4Fyu3aig0zZQEw0+p6OwyQPdW6Otu0oukUDr2UGT+Hf+VGbdXvRoOLZ15zdKReCl3s/DAcLosYNlLZ8BESr+64qFFfzIDyfwadE2by+Lg1l8zl8MXsqh7pHUXbHQOtqDZtrvNMPM/U2VFZ0Ynv0tCAEug5qG5jPGgGYJ3t9gPpdClMMG/JPT+kdrjfN/C37Hg1noec=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNzY0ODQ3NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zNTU3MzgyZDU4MWE5NzZkYTc2NmI0NGNiNDEwNWMzNjQzMWEzNjIwYzE3MjUwMTczYjI2YzY2MmNlNGY4M2NmIgogICAgfQogIH0KfQ==" + }, + "`": { + "signature": "SJHTD/16dB6QJqOvM2lN/sIQ5d424btvnqDwL+vTwMibBm2DUqthkFeGLm3KKEYOQm0SVb/Zy6A/uD3r+wRGgEeCoPTxN1Q91yEY5+JUGoe4ie0DcbEhehVU/bzDVD3fkLPDUMdCWk1fwDRb6dKNbVtB1V02x79UldjarmmuKDx58kj4bQ1svphcSjyCuyOPqCbDVFBJlS8DCDvBzY55FrSgjcGoVVqIM1ls3nMcwrO5C0AVUR5OF0MYw1EzvufmsR0XxH+HUVJUHpFZfKEcJOg180L5AEmgfjLqosSsAbyuT0AHBgAY+bgyQJRVd4zRYZc3m6PCkOddBkVo3yAlmgb1sRXGWTwffUhI5TWdw1sNvv6MYRx5Z/JdDZZeXO0Y76N6CuGikzj9wxkHSrnFU7jC6mQc/qKPuSZlv26v6QnLuNjHMNEhfEOH6No/Qj4YPAfzY9n1BLaNqrlNR7vL9AUwD+POEqMWspsJoXperzYmHMspR5Abih76vSP6tNRWLZnHWpv6jgyEgopE32i+fPx23kzDKMQBlszPuPPXaQRv9t6nsJPz9H9a/eE6bVIW5/jO3c3slCB9vCP+JIoYIPZcaozMTQQ10aobQBsw7aLP1Mr+ImFhSlbgW5RD/74Z5zYCgJxhFfdp8oLnnPsD+FnU3DQSCPeiocLlgGo2C9c=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNTQ0Nzk4NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85NjMyNTg3ZmE5YTU2NGQ1NDQ2MzdlMWIyOWYwNWZmZDkyNWI2MzBjODBmN2ZiMzljZjhhYTk2ZDk1NDg4ZWQwIgogICAgfQogIH0KfQ==" + }, + "{": { + "signature": "FaIwZVaxbzwbRIjYoVk21ctIpSdAS7HxYXBskMHiG10DAA5eeyLjiF6gemV4B75IkTe5Q7QbsyZIQVYTMRKvWDaO3Nb/NvaU9fgHCJJ23Kuv6Ktl1aXBkYbbn0a553StYWZG8udB8LGK9d57Wj/63Niy4pOhvfQm/Rbz+QSz85s9yq1b1xuR7LNk8IsOGV4o4z8KSKCi24tqoTcP9hB72VOqRbwE+grSXFtEjjQgXeaEnd5NTbR4uS+9WIGZF+zZfLErOTYvYvmOigJX2AZZqt76eLi7Lb6DsvpawI7Z9VCUyrJXvl9D3bYqUhYpsJk9f7aRasDdE9ju8TfjmVHswr58Lxf3I+Mi+YngmFwIeTq4ywM0rPuwOd2IFz2/YhoH5/JgSRsb/U1ovVMbugvdvQ70WcicNsfFdCOnFi5NksxI1+GreKOjCef0ZLwoa5p3o+OcVFnCsC59FMpcTSD0pKvdsTB8r/25pyYzovPKDzQNKnhvGnL93+LtILWOrzKym4HUsaOyM3GhXaRxwSRFHIBhzDOCt+4oJKk7bG6Sl0/UiwwP3cbERbJgSQQMl6Nv8fPMMtTJTg1OjOyiTK7nHzcx0qj/pkQBDPUAococn5WGCjk4JsTcynxRK1n+tJHgeSeHIdBUWOrtnaRNW4CPdmbAVRDQK0ZC0OHVyQhxROk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNjYyMTc1MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82MDhhMjg0NGFmZDJkMDdlOGQ5OTRjNDdlOTcxODc1ODJkMDYwNDM0MGRkOTk2MjBkYTU3ZWJhMzNiNjJkOGZmIgogICAgfQogIH0KfQ==" + }, + "}": { + "signature": "NILD1dGMDToDf2xhWFY4/k8x/LLhSsiJ3yOQKAx6jwqjQj9GuA30w5NlCwlvqE4u7gHSP6xOioipXfa/nwHNIKaj91ETHJFJtsO7+0Po9o+77iHOPODlf2y1g26iIh61A4d57xOsEGKQ8kGtoJX6UfldIUWL7A6NvDK5VqisQR5iHN8KU6d56LRjpCfxCx9f988h2l4/9WWgfdhJBR7IEZjKxU35phQKDbEH1uQdE821dxzuDvvoOUwRYldS2oppzu/6QEJWnEMbGRvu9RuJekWK8KHXb4xIU7lweyvKOcmN3FV7uxET+hWN0bGqsPglI/WZem9T4v5NlvyuoMXluH0lls+TIKqvfk6jW9vgKrJoFhOLVaMU97qMbDY0MFPh1xK5dvrZWtVH7RLcCxu5B0ojTdojg77GTo4t4IdF5w6b9qNGSVCeDX23b0aqkjZdyb7ozdlAcbyq/a4O2aoh51U4e949qkpQX33JtrqUFKMHEWONkwH2AMvXjvXupVl4LBRkIhRNi1lnPYblRuDTuH6fJi5WpIbrM0n39r4twPBnJoLj/MBcXKLOi9xlzvsrmqGiAnqwKkg/43HS8YtdLod1Q6y0xXOsiA+30m5H+kIVaPFo6Dn04Orn56v0YWDnplPK0HrmYqABY088u6Ud1W5Qy5X56nKgm9uOjdja3f0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNTU5NTYyNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81YmJlMTExMzljMDhkNzcyYWZiZTc4MDRiNWQxYTViN2RlYmE4ODBhNmM4ZTExYWVjMTU3ZDg4MjA3ZTcyZmMwIgogICAgfQogIH0KfQ==" + }, + "~": { + "signature": "SBNtz7W8VIXt+XeF4izS9Q7g3vWb/K9nA+ieA0l2qL7YPTfMDqVE3u2MOAEOwqRH6i3TPmQ9HdwRVKIUXdh+Q5QWyg6llUoP9pdqGz3OgmHGqhc1QJZk9jKqxso8s2zOrRgQgUBnr9q39O2wLjYwM1wzSZYu3SUBdF0y9OxrTcquLkPVWdmPVsrsTw+utBX0BtknpsjHKFjN3XMdbzp0Ir32Ppm6/yXlUxE9fOYp3mr2k3s1ar6j4FqsiJdVNVLQUpVS2r/2jqtImTEGQ/mT4DtFtBRpXQhlnPDJEnYvFWAtZVfhCxjLXT0iUFipZolWx1tRSSU5GIs4/aSRyIWtbB+iy/jzmJF66rjUNXRgKLoAIvUlYBbMX6KxMqKA5a6frzpjSxzjHcenDE7kk9zjLk4WpA7eHpY6XYbfa1u5QygBkS20sBCTz1Bdq4NttwwTCkuQOKnz4313ELl3Jc7f4Bix7PH2soN9RGoOTQtfGy9i2BkjwU9DkqD/jbeMgGv6T6F3BxQXYvvor8noEitxF+7RpvtUEeGlVidbf17gSrOKGbGSnEMEMGtKlLOofI8s7v49KWByzpkZgXBKv+IeT4rfPcTV9SqwMMANMre1V2ROb0QxXOaviRrHVbZGWCkZh5doDTr8G6JQo8NVMzjyyxDpve+IdAfHq6lWsv7/RTg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNTU1ODcxMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hY2M3MTExMTYzMTVjMTQ4NTZkMDYwY2E2YzQ3YTM5Nzc4NDIwYzM1M2Y2MGZjM2RhZDc1OTVhMWYzNDkzOGMwIgogICAgfQogIH0KfQ==" + } +} \ No newline at end of file diff --git a/Network/eLib/src/main/resources/skins/chars/magenta.json b/Network/eLib/src/main/resources/skins/chars/magenta.json new file mode 100644 index 0000000..b8e7697 --- /dev/null +++ b/Network/eLib/src/main/resources/skins/chars/magenta.json @@ -0,0 +1,266 @@ +{ + " ": { + "signature": "W6lWf98kO74oOYFcU9TrzX5e8DUB8XRwPhqnWZzfa/yEDROdPWCLG9ZEZQMX3ibKJbegBUUZdmx0rX4RNaE1ODm0IkCYEUKA2BMl15GCdugMmobIcKO2+QzPcBo8d4g/Wo0uo/LqTJ7pKwRiswog6FKalnqLPh+wxw4HomY/QPq59a/HsimWybR/iCVl33bki17RwlWb0FKJY4aoI6ns27B2SSCBcrra28Xy1WAuKLI5NEZ3E2+WVQ77TOdh9oY0+HZpLcMzMShkl56Po9DtRGQ2NSI5MXKjQhgMrBUSr8hkXz2bB/SaK2rVVIZWoaNsW3aQzMbqQYCgC9J9W8Eulcz1VFOJPFESW4tDGdywNHhn8z+JTfCIFuhD4jBdrHuDYdlh/iQFlsjuYaT7KEqEGo7KB17AoDPyDG8EmdqzFoHsDJ3gjikZKGVlAdA0EaIrul/Ave0bSnZNnkyGBKySb59bGQ2Yh21yhCUOEH7CXR/cfUWMLHeeSMA1uh3o5WIVrykw4bmMLKkm2Rmsd//EwMOIIgjIKceVxo6PoRGMGy0nLHCvRxDIQ9hMvH3XWDsXeTUhaetk0KN2PUfHxfYy/Qdg4He4ZvN1h6n8zsVHq22kDygpQHHGcr7rg6HvENI0sYclDtOK9+ZvPlOU/Qp/fT/THAjjF/yZDM9MpDNzhec=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMzkyODI5MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yNzEwOTI5OTdjNjdiMjg2N2U0ZTcwZDU4YzUyNzdjYWJkNWQ3ZDE1MmRiYWNhMmRjOTM0ZTYwZDZiNjNlNmI1IgogICAgfQogIH0KfQ==" + }, + "!": { + "signature": "rRzkxBCY5UqAT0sQvty/kkr7Om8WnNEbgm4y7tCwlZKFZQYbD+Znc2lvcU3DPzvl2wvR8RhlEmX+iRT9iWAVGZVNBIygkIKKPFhFxO4zVqkjCwW2LU5o4k4Bx36ikGNgMpy4TEBPpMA93bRbPCEY9o2KY/ARstdnbozYo+i4Vcd0FONPSznm7yVOsCer+Tzq5HjRgN4ltD2edBFe/zBqH0EiTaQw+ADuzYDxkF0vBP2ljBnwZiOEqiJKUJmDU34fYij4sWTfyNU0KNeWDAf9uzOmo0D8bbMffNlIIHgjrXlBhZWJUo1pBpPy++tvUAB8DbkuoMFS4EZLn6aIrNk093e0l9Qu2e9Rrqt2dv2lOFR9aqYnKoeKNDo851omzLJgAAxDLVo9afq2B+drJw5HU//3cwsDMUTXOswX77DDQy8jQ+nyqnK0HWjDeqp1/PwJZIzEW4GYn6lB3ejrXD3Mf18FVf1rzNiONe8EeQyIJnGY58KY5BUaYDvrbtp4DIkfO+EscPTxD276hu/YZo2XoyHR4NA2rspskOdlbr9WEbl4+J+o0QFTIEQtRmAMYU7XuqmN/z9d4eEu8nyRuI3T271W2KKWX9vxMI+RaOFan0D/v/OWkx67X++zP3heCZ0OAXayAxL2NMQIw6LvdHetDvTyxJJfpZDnGtoNtxZyKSc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMzI2ODA1MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mMDJhZGNiNzAzOTkyYTcyZTY3ZmY4Y2ZmYWYzZjdhNWEwOTExOTIxMmQ2ODIzZTU5MWI3MjkzZTBjYjBlMTgwIgogICAgfQogIH0KfQ==" + }, + "\"": { + "signature": "UIFWWLJdyNJsb79uej1q3KxCGMzcBtUGnQTsTV4LC0PBOL0gUTr/qqKkVdAetXOtOHe97PaG3rGEgAB2PzmFvO+R7mz3lpipU1LWaE6zAjc6ByVv1b7pXxiF46h1f2FzPOK7eKR6hYsne2iziwBg+413jcaMtC4vAUHUwEK5VPEPJkkar2rnr5ZCs0ApSxeqHw/2p3degbg6gXnicNHecAYSUEUPCHsMAMQvXGcOWqBpj9W6eMDgfXyYes5PPC4lH2oOSPK7orwrOeDJmrA8rUc8XgXLpLEZuoY9if+3qJrRzFcg5u/4l7d9k6S9cEwYzy0ae2H1MsKZcL6qEAl1TLNvrc8O7TPIeChBZpkRby0B3rsK0FL1smrnMdXoEcac/s8686vf2kRPQWA8xXhZIbgkhpoTgl7xKV9QqbR/rtHxryg5W6ZZ3XDwLRjhsLr2lHH3FV9haCtOyIAGJ8F4OCqQ5enednvd7vGF+LWrNgOam/ALKVDiRaEhiYlKGnqc6l26d3lShm4G3FigC9hERsMfGWIiWTsyexWRnswjpTCN3TO0MO9fXvq4xCdYHbZSqcGkd9KmZWEApO61aADBwkqxMZ+Xiob87Mz0BMSsh2ZLfhrZCyQXdpsDZtZoy9JltNRAyn3anf94H1Xjp1rXORyewOzSOedS7fTpm1Tk0j8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMzY3MTQ0OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xMGRkZWU2ZDE5YjQ5NTBmOTIxOGM1YzEzOThlYjljZDAwZGJjNjQyMDJiNDZmOGEyOTFlYmUyMzYwMzdkMjZmIgogICAgfQogIH0KfQ==" + }, + "#": { + "signature": "RfqzE2p2wm1sfxAuQshQEf/i3kXJxOCVMqRRD5+1iivbkvkWaKTsbW9pKJd/lLuYEQoh6Fi3+hC+wxDDCq8I55CzYpU3H2OdUZjbM/nWU0iMaB3Mxgl+K/zKUiE0pzNXpGePt9/pS2FoIU3biuf8gnoONhDUSuwxBFe10YvwOyMae8yK3OKS9lbGR2p02nCIEvGSQO/oLBKB78Gqonh2pEu82Cox+bolO1f8Co6FFXA2Y2zdCHo0+Ceh7RlqL/NvG3D+7C/U6DhWOuv2zc1bAAXC+fhIprhnwjfBjukLNKIVSmrlFmo1kjB/UqlRETT/eLanq+2+3veU7l0rX8diWJnF5AjG2Dt7On00Ru/HnmLtib5acsHstjxXzv0VQHtHz3J2JKMtShFxRC/NxMhPzR59+IIjbk9X5okNFouGltWbS2nXA5jgMmXeOw/+AZC0m0CndOz3ahqX+D03otst6wGZwCiknTh5HGLe9huJDrK/EMBCcXnojL2Y9/PlhzlUhtV6LtAZEOZmcZlQgU2s7JLIHaiDgaCLVOL/70JTXHA3Q09QdG8yPYc3U2ADmqytg6clXnmEjmI7idgkr8iOQmoF4krWWiM2MICFcMDhuIlFYvjwdHFhjKaOMVmpKEnwul2ZpFJ2iU/Bd4dxWHj/3fyGsMznbRW0XegiABSaH1A=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMzU2MTkxOCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jNzVhN2ZlYmE2ODE3MDEyZGIyZmRmOGE5M2FmMjVhZDZhYjk1NWZmZDk3NzM3M2NjYmJhNmJjZTVjZDg1MWFmIgogICAgfQogIH0KfQ==" + }, + "$": { + "signature": "vd/3VrK9hGx4kN3ctNGuNDk2WMprJJj80iZnnrl7qRbJ2zPa9OZQOJYC0mOJQJdhSGS6D9vwf8T2O3dJKvyuyuKvSStUlghJ7BJ0eKB4exe7KxWcUWBvAaiZ5083lhTo6hYvx54KAz/K2+FhpxO8iBYfo6aEzvztsDcbyx5ZA2iRiJXoGIxn3kujfQ1BGLnvdrylPvOBTyT6KNqsWzhDWBy5MJCOyp/jwHg8pMAq82NSxYsXNMvEBMJpWScPQs9da3g+QWYRwG9PDHDo6wWD7hP9WN7SGmtADmCjdGrANHdyZ1s6in44bM+ZeIu9cA8EyxLxJyfyA8N7unfNZo+ERkJImD6QDKR1kst0o36mRppClsTdfCQ+eyUmBYk+jffnbrbhjkHg5xDYQaPl3H2I5klHlkLOVzT2hzudOrkPlJJWNfAYUAyaSD5CxYEEH4LbaIWmQ+J+LU6U3rqKVn5U/GhvetHMJirkbwRbUayUxz/caYIbFQ8pm/no0LIKjDbFwra5JK/RykW4rccxKG3gvJeWDpF3cVM2FqKTEj/APGuvlH2+exTVe5UZJhnACRJibTv9qtJjJDNo3oIJaUytccQ9jybVkulQBt+n0Gz40BtMnYs4tnVp8qvwKWx5nXH1yLMZ9UOh9DmpaLxVei34HTqhSd80L4kRZe24Sw3J6Xo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMjAxNzU5MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80OTIzNGIwMjE4MGQzNDlmNmU0ZDhkYzI5YzA0ZTI5MjU2YWJlNDZkZDg5MGVmNzY3YzQwNjc2ZDFhZjVhY2VlIgogICAgfQogIH0KfQ==" + }, + "%": { + "signature": "aY6PDE+eGz60IwDn0jPXlv9crQmLCPP1yroIs5PlJu2oaPOoSvcHWPqy0u0TnZLtVAh3fAlGf9abeBTNi/FHQpHoZnFH2f+qT+m/mRGBNnCIY+xhGxTZAASGjvUrdO1gWZlpWjzloX/abBdPq9RtazvL1DwQy6vjn+1Lbor+b5kmeesBMVx2zwm0OARj3i+/+qcKssQdWx5PdZ3jVqog1msthQ0NCrWzatRcuUlLiEsHqOFr9G+Y+Qn+PuHnj8VItU82jVABGwyRDpMVbY7kFRVg+RnEgWe31m54NQ1C7jvigiVuVZDTCCKBGl+FUkh4Ib0bTuyPP2Y5Hm8Oszb/G1/LmRYucZ/J3YTcmWneVmlVgmL3gJNCdMyAmTSZ0E8lAP2NoZ5B3d9eqbsOgOFj69sdKFLCyXnvAqBgGVYriD2u19AVhO8Djng/LOyHOAIfmNBwJyYTfveLpZPo6CWpbAEO6OU6Z/zIArfxCAEFrjByDq3W9CbWJEB8F2lMlDHc7ssHsuEadhyJMGXWk6Q5ZolPeJmgNC0E/WtkGkwEcE/lr2K4QuVf52HLfgPNBLONFVn2sWPmkEgPf2to61lNXU14cDLmDO5CABKP5rmGkfQplFZcsJSbf9FPwRp5xMQQXOX9SA03d1KFDJEx234XyLd5nP7/nc+v1+b/SpIj4Dc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMjA1NDg1OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lMWVkNzhkZDAzMDg0YWUzODc2NjJlNWUzMzcwOGRjYTA3NGMyOTgyMTg1Y2JlY2VkNjEzMjBlOTQ3MGQ4ODM3IgogICAgfQogIH0KfQ==" + }, + "&": { + "signature": "ZA822k0Xwmpkxhr4sPhup/GbPp9J3OSo2MEyqOTVwokcIbSYM+gTUPS4qilw7X2lTQW1sv6Wg5Neo9DmV6mWncvVF7uuMqxMcM9qsHBIn1nkIE5nOMrRQUIbQxi/Uu1XnVeSiagNYOCSK/FfIAd/VFQSsxec5DRxytZit4p+tZ+e+MElZnmYInh3LHhe+S9ZF8tsEEyG4b3meCaMAQcQXHuDKcY8z+dHJ7PGzHgmFwauCKE8bELQePCK/DFCSHofkzkzifmmMgYc8B0oMil9GXVlzos1G8uNyeKTG8F2Bvk/OZ1Xg06Oiot7qaVXl0vQR62/BfkwkfhFc+EpF0nYI5Rn1ZBBzdZB7NN63bOzs9f5CDdGnyR3mzmD+ifn+FTOy+VDBWBZnOEimIA4iHbjDtJfuUS4gMfpwQwPLH8wooXhknVYk/CI7CuT9eqlacAgacfG4jPJQ1Oun1VgA3qhp7w9JBABOer8cxulgag+UgrDm0F2efVf+eT7S3RUo920IkSvBVHrVBg9cEizJ8jzByLjT8e35RiCjWpNpldHYqxPe62IaAlXsjl261jabciG5me42uM73r3bf1EZlfMmx6ncMesA2Axr2eGiKEQHt1ReY2TEqoyMK4orIxCPth6G1MXvQdPCjWtTk4V5DlK/CGpVDtyPo5gc9yhXfH6jzMM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMzQ4ODg1NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yOTUyMjAxZWQ1OWQ5MGRkYzQ0MTRiNmNlMTg1NWIzMWQ0YjIwNzYxMzgwY2I5YTUyOGI0NWVjMjBiZjQ3NDYyIgogICAgfQogIH0KfQ==" + }, + "'": { + "signature": "BlMnvRFDYLBUgAD5SpMCbB8SfRAxP0GFHdxu8FtfIJG1GWpM485lWax3dCsM8Ao0M6l5WNqKnPqo5CihGhQE0EMdvDIzLMVPPwPiLz9jGrOO5JzREexefUQsRL1TK1SpWE32jySzHnBofb8CBtMtn4Ae08ekFs+D/MN8LjtgDdV7DHcEjeKCS7C3HVwn5Lv53BsJRWWAT7KyCvV9G/K0mmOR9UwG6r7/QlTd/ALREr7ESL6FlNrAVdL2RQtBinzjcvED26mKkTFYQ2RCs6x42/sAnXl/corn8Q+9J8M+DFAcPY3Se7aNQCh8l1fZyeAwn4bJTClpAbZ6pE6L062XS0sIJtkPm0F4W6LipraWr9+Eu6vsnFSZzjXCbvShrXxZCV6Yn99173UuStf2+hSjFtmh6lSrK3o7uwrbpiC3JFmX+MWPQpLVZTBLCUP/BRh+huOLdkDEMl8jEawohutRL/eT7gyUWwcJoYcdA0LTtCakoISqRzvlXysGgyOX0MiWznkmV/WZHqGoP0C5iXwsPkiXyn+BbqLA1OIShpTZna+K+nsnxvpo1V7ps5N6c1ZoBSYR7qoBRmSjzKQtvptYcW5Bb8J22KC6mHUOdO3jiYjsYATeL0/uvTJXWW8kQoirlTyqBrrZZmpSv/z0NDtX0a6xoktp11Wfv8sPNdg2y/k=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMjA5MjE3MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82MTAxMDkxZWRlMjkwZDJmZmRhMDU4NDdhZTM2MTI1ZDJmYzk4YTE4ZTUzNDkxZjAyM2NlOTdmZjJkZDU1Y2MwIgogICAgfQogIH0KfQ==" + }, + "(": { + "signature": "uATUJJ/Ts3w4ZvjFcQiSZl/UnFcESKS/Xo1xJ7ZE8rXYVOKlXH8sh1sOo2n5e2b7tK4d47wfApteRV2fcjfPuZ7H+w5y9+7eU4GMKzj0XmNnHtZH4vkez8fSpnGtEzfuGJc5o5Krq7vbh3VhbD0+uqT05URnQDvoR7WmkUlDxpvvW1yMTuJGzUAZKOjaH0VPHjnETjLOapWnRqmX5kWLn8IFoGStUxskBwH2qXXf057krNI341P4URpDlGWfbdgjRkLYg1pR3WEBjEXUaLA85mk4hkFi8Ml5A+d8M8p9Ltk3dQFmdGS8ZkueKVSrUAIRlRAZthF37adoIlFTusTtIzHXyKCWtn9MGm3WIHm6LkI1RT1iVrCCy5rHY/1MbRT3VkcVaULoj+HCBCT27dUq3nMuRAI6wtDDhgs2hFTNI2r2aCRrU3AAiATjQVuX86se6TNNzrJRy7WKxo+TF4Tl/Tez5BVl3ABnOe8+v/5EKNpiFo17kggiBhfQG3prwsPbu9YNkf/AehWj4lD2bSHcaLWrvwxMz68cdu2U8XwGswAGHHRtKEr3bSQoK5JHe+i7p5fAlQYjBvp3E6H1ZgrBKMEMwGa7brDigYpj2wiqPDJzzc4y5HCdhrUZoC2dKEQ+NoB+y8dTgxKmgenMS+PNO2p+Nx4TYlm1Tr1ujJ18cbo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMjUzNDczNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82ZTY3ZTY3ZmYyZmE0MzgyNzNiZjA5YTYzZDlkM2Q5NWFhZmYxMzBlZjIwMGM3MWZiMTNiYWM0ZmMxODE4NTk0IgogICAgfQogIH0KfQ==" + }, + ")": { + "signature": "LOkVL/JiB682gjHyyU8vb/AD0aI4Bdgc4ASz0rv4aZYSBk1PCDGtehTSHTE9MY3Wm9Bu3ty6GMCxNx2CMsA70IRMMrUqItaWXHlEmM9GcPIC3K7M1hNN6d3zUWc/3/xPyQFNxtshlL/1ASMVpqTf1M5uFkimQJ9lqascEdHKwhOfwxhAhd9jQ17PYwvjS2ar7X2jC4KVYQa9nYuRUY4s6Rt1FgpwurHwYG+ncf+iRxvCAVhOP8/3f5GifIFbb7MaJOGz06BVLOC9rk5giQ9mTymGGMmJNegSXrwpdtw3xXdoF34e6UnVjV4nkxsPFnNvqlhDFMbqtriedX98yTCsu/OXiWLjeGmpUZKEwusaOU/sOpgFJc6o2YsAMFhJI/oOkClY6KfWwhNRl0cYj36LADpS2oK+tXtZE9HUYl8ebBArg1StRJmYF7ng11+APz2OTB4eYI1rWfkCVLibu2UTz1fbZOH8ehZNx5Kwqo38ZElxFwfvXKGuHe007bIAK7iRp1dLL+fyy+zmhXZ8Rm4rPdLLMmG2AVQ0gGBoA2w7TcHJ0d+vor2dJONE1dmfI+bAYL63ZpbD26/j7kzZchQYNsPznjWZ75svZB2J4+6BqD0Oq//kcMP+LM6JWLbcaRr9Qp6aQtdHuZ914hnVWclFiavN2D967G0g4sB0j0DKjkE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMTk0NDUwNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kMWYwMWM1NGYxYWE0Y2NhYzFiZmVkZDA3MWExODY3MWRmMmUyMmQ0MzIyODI4OWFmZDdhZjBhNDgwMGZkN2RiIgogICAgfQogIH0KfQ==" + }, + "*": { + "signature": "sGRob9wfQu68Gi9c7boZdTKF6pqSjMMYFL6Jnu9s71JWeX5BuiU6rI+1ksB83bDgkrQg1eBc9nzJSMdBfOKHiZCuabRJhOr1vh4gQByfIsW2/QyzyXCYbfkbTSWczBt85yi+XRvi6Pbp5MUWyfVfFGcvP59EaRlUzcazSBMSohJvox1Jq8Qu1EEhRKNM63hl5teWA2wYQYlN31XwgXInr5JUOTe989saAHi7uskzYWYn8UMQWsUTysTlyBBT/6BF+7q6oTC6zB+4lrGHE+0AAvOLPgshZPLYjH7q+T5Wj+WOl1kxr82NfC0REoHVETcCQHhrIQW/Zvgse67FBzye+ioDPB0/89TNbRo6ppYoKqkFlUhxHIi4cn1dffNd46eH8sjyEjyLyEUK5wPiPyKNwGsq3RAG61vV7uoAuCnQD1Ea5NKHRECdqK6LHeV6sMR2aUHmJFfwgRpzwnuPOW/kInLtFXCFWHis64M2eX927/cbQJkRVhTGQxqOk9qVMycTpvSWfQTA6XbLPOgZdpEFWYE5vVBG5gaLqVA/z55WOW/XNYfyxR5to70BP41V+fkCMSbyYN9iuriOlUtN1m8vgRJwbBLQSjmb/8wxrtulSRolX1G1H3pSQc/UB8HPwbTg17yIDDD61e0qCchEJPARWUUDIWto6IQFu8B3wFnjDi0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMTUwMjUyNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iYTlkMTExMmY0ZDhlODFlNjA5Nzc0ZGUxMDVkOGJjN2I3NTJlNjgwZjQ0YWFiOGU3NDE4MzlhMjhjNTMzMWZkIgogICAgfQogIH0KfQ==" + }, + "+": { + "signature": "VRGV1ECOKiJjA1DueeE5zV9UEGfTcX9lXQ8NTozl1Tmaz4e7SxFb02k0cpDtiFxs3ZXP3cUBCtoshmAgUTDQQC5x2NRKdBxRRWvkhPg1033FAvIliZYAyChjtoAqh2ep5qiFiN+3h7Vg/ZjkCMN+XSGtveRMb3MB7FUvDp8ZtMhbMg1CupwkPdn5y26SRxAGUmojtVFfL/27pTTf/v8iPvmafNstf3IGGmEmZ9f97r5h2kjYsvmjyC+w3i2qGK+4bejZyk3oRJ84Or+8UMFh/6lpNNufNlbdj5cVp0mEIAdSMJSt8dJI4gkhh3tjoXXAxez8096cgNTS1IBEi2MgtERufy7sVh3NkgArL+Rgp3UREhD1WiqEVEXvjafvGDSg2xRjE2O5Ee3X2jwj1DtyNu/VOoJsHB5v3aQqjmsPnczRiYykGiFM2/4MzUIPBeeEdT4yFsRpQPkyIHoKp2t8A4PBqkX98tcb2xXM8pABRbBWa/FPzPmuXlCFnmlbkgvU6LzsyBlYCPFHprfmZcNL1aF+k7Xas18NvoYZZdJ6PZy4I/2vXFdByjq1fPWqe/VZU2mSXn7xWVgpG0BzuF89LeTbHifxS822quuriCjQfDazUxEK6ZpxMUGctfLHrbpRQ1SAxNlxt9lF88PKtFQjH6uYgR4kdRyhZAnv1ahaDdE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMzEyMTc1MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kZDg3YTQyNzAyNjI1ZTJmNGI5MDFhZWQ3NGQyYjRkYmFmOGFkYzE2NTg3YTNmMzdmY2RkZDBlMWQyYzhmMGJhIgogICAgfQogIH0KfQ==" + }, + ",": { + "signature": "SjEJcnOGHpvjYw9VouO4Lebw4YX2SVBnTVgyzePIr6JikYT2OkRuDhqeRlqSi4tFqsF6J1+0lVlJpHT2NB+4l7Ce+XqiQhs3RHjskaqbtZXKSB7Q56aMJxAu5bZR2AqTe3gsHXHCaTntU7J2p/uF0HKtn7bhE56lP9F0PuFyJn7ipC6QTseTtbWEXTHQDpnklmYX1538uzgFI0IUyJdNvTf//JHwH2Hhe3mmEj5C0mHjS5bx2z2klH+3EmIk3TMr90pFHugIHumV8Lr0rZmu/phm/5eyAiWWgBkmU9xiprK9YMnddGz7PfYGKK//uboMAZRLFjKERXDCWBf4O7Ku6vX0beSWt3UfghBZrDjRl77FKWbAdySi+ibnUWuwRfwmnZNZVqAZMWQYDh5vmnx+o3M3Cl++NeyISpFcVS3DDKqjsZgBrmQVlF2GHimBimuirmPnXtTcnHkP1ih4EeyXWyrdUKB1TZkl3o7M701/6iSof929DQQ9fIdp825wdS9Wo5T5JyKJDOpGd6TVGkTttQCU4DHM4y2/ZIloKcRzR69hqN6nmgjJa+xgqop7zcc7/CqSyUKRbv7l0KKeiDvKURNgPM1KFIFo7SXMnxjXPtLgQCRVjrOvjuymkApfk9Y8qP5pL9xS5cytCZenbAIdGgn0w/KfHQl4/VUSlmzdVLc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMjk3NTI5MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iZTcwNmIxNGU2NWNlODlhNDdkNjYwOTk1MDdhNDBkMDg0Njc0MTM5YzRjYmIxMDk2ODI1ZDY5NDljYmQ5Njg5IgogICAgfQogIH0KfQ==" + }, + "-": { + "signature": "IGRNQ8bdekWmCALzupqg0uKP6vE6E8GOaehHDGw7lhtg9di1VBg/VJOKPGjGoGG8iN9vKtHcBpvqWbXn5INKca07A3/kytakExvSOmN1mPKmR0LBowCa1SX4aV/Ez/qtbM5KVR2oTW58/NKaoRyEy3RifebMStscCfAg3O7h3iKhERTiSGGvTlVIQxz7rqvaQECxI78epn4oliFd3jSoIpNDDxayvmIl5FbV8hsuO4tvgVAderWOvuysuiCHaH45TtQe2lqbM6uxRA/daNWbqdPJMvoM7Y0dCIFxp+5jm4hgRodJrE5hFIzSzfbGEmLCTrmPWWNGjs/lbooD0jggtGSPdZI1QQyH6VnDjsulldVUAbyECiUWqn1IeZCNV5HUGPlHTnecome/GIpALKuDPNXIFgFzMBYtsLeLJQ/cyUpLvMKmntF7x0QDw7kktHOl4mkw3VmZ/CUdc0ldnUKDKvbewN0CeMt1scFpYZJwrvf29n6E1M/Pz+V8XBxPCsU5+9IOkk8ulBxWdiaTuL2sppLTVJbKEp2fZj3QDlcz5Od+l7izv0Wywa/QHpu7MfjWMtGoyuy+ucSH3rHP3Y0+EQYovHP4GKy39JW/jfifuCmkljOOE2T/EhAA/QcSM/bDT8TEKAMZgF6dRZfmVHx501qPfZXwhRSN/58NpmVIBso=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMjE2NjI2MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82YjI0NTJjZjY0ODYwNzAxZmJmZjg0NjdiYTZhMWI4OGM1YWMzZmEzZWM2Nzg5ZjUzNTEwNjA3OTA1ZDY0YTk2IgogICAgfQogIH0KfQ==" + }, + ".": { + "signature": "l+RZnSONwVNSy0Zstirjsr7yWIZUt5LzjWH2XhEh4jx17JPKMfF5lwVwmLwaAa7Gxmw5KF99+xWvS2pPw/t+QEUrQl/n+DlAqRmbK7B1wr1+8F09lUHjpyC3VBIug4n+tQehpneRAS9+yyv2c9UOM47HLGO6KxDH4wUSk3JbRr7+0kLfc84ARO+r25gVueEPFCltMsdFeKKB2GzkbfEuhRSX/E1/tpxkaf0xc0stjOS7kR6NOsy+3gpRUk7ivPXSqpmbBAjDorQR1IumJkBRPNoGtkvPNi8YgWk0iT6eFjXAaK5otnkiVfL4HpOvSExIKi4VdgGsvFZ4JzYsUEOAxJAiWZ6CnKJMHjgSrdCxBBvrStBc8rm9EyZ+YoSQJ421Rq49W2vyDpPwUbl7J9NVfNfOP5bTWBGvnsEKZLweSzwgRiU+aTtXQDtEntijeIWo248bKrvDqm6xWnum87ARxQwVRnktAXHZdeauN8SfmfJqKhYSwutxpL1z7Op1NwIDo2yjNQPDVyacjE7SZqWN8gI0D6DC7dUvARK1pHEfJrzinXOH3T6LDQrQHcDu3xsZG3p8Ty9aZ8U6Q6Qafr/uaXpPjx9gLKX0sthIY1kl3NdFqHkNiMuFnCLkCTisos1pz+cOo/1DNW+l8PzEZ05D1fkUm5IQzKN/oIQjf+J2GrI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMjQ5Nzk4NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iZTcwNmIxNGU2NWNlODlhNDdkNjYwOTk1MDdhNDBkMDg0Njc0MTM5YzRjYmIxMDk2ODI1ZDY5NDljYmQ5Njg5IgogICAgfQogIH0KfQ==" + }, + "/": { + "signature": "GZnkoCnhEbUMj2fM1OHNy58yuDbtgjPuP52Z7B+amFmT8TEvq0LVasW0R0X6xpCAxkVhiCBe5bxQIGc/XRQtfxwWLyOhDUtlsBmaQqY3fSxPQG2Mjh5mkyCnOBHRTdtqoL2AXPBse94geH3Vto4RRKDBG6qcfPZe8BU08CV1isBVxpOehcZPjWl9ov+PK4EaTXZcqfAXdCMtnFEcH/69OXchTJqRpSWGi0SlDqIl/l6DNoTnS5yY7rfRbO+CE8HVIjTC4XhKEBaJ28d57cmmcXEd3OyCyJ8X/vN7Dt7JH3IbfwNOPErjxZljpJg2DKAKyy2xDTZELI8sulNTe2H9q8cvWurA1hytmgVAzwyQ0Ieh0NfAGPj1hLSsu75HjCtF69+w6qF1iGKoWbnnqk2+kl6swIh0+zXF5sytKSe8En0OC85KFKoI1cBcPkW0CrGIll6+X3yejA8rsuUy1+HPIIxOofL3BucB1b8u5iZL3TDTkiOBcTxEWWScUhei05DcKBZdhnDLHRHwI62mRsZsG+HHhcpeLMpPABtl8iBKbSX4sn5+VZ4gvzI5/yjnHr2TVmesSISVcgWeAcUl4JUX5wqUth2Setb3K+zvBpAsE+b301jV6KfcVcRUAvDkCwW1GHPg5caUlNNLqDT+2M8TedS6fn1Oj7IS12buDoR9m5I=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMTc5ODAxMiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84YThjYzdlYzI1ZWJlNTdiZTZjOTJjMWYxZTRkZDlmNDBiYmVmMzFlOGY1NzI2MjdjOTAxMjdkMWY5ZDg4OGZkIgogICAgfQogIH0KfQ==" + }, + "0": { + "signature": "VKx/cBKQp8J8g6J95XYhAt8rRMkVqWQeNXNGurRxW6iOZcmspTz6b5NvFYYBLO5WBCh85J3Sjw7YbeQ8D7c9DcXQz7z1Q8tZq0+YMbns3emWwJ9bRtgTJpvF/NbIpioZDYxY11tIxOtL2JbgmRGv5r8JV6U8h85B9YafIY5MGvClSxdaWVTvKgEJQ4VQSzYsQj6q0TMi6aMOD6ELzFmo1m2Z5zHySvvNYshQ0bkk6Fi/6xAO6jbEtaBMhIg3kEYtfrEra9di0FYOnnbyZDcWYeLzbM+yGSWiQYvc0UJh9bIyJMvkO6RygEQ6aYwhzf/cgRacohkZMRKNOkTiIZ3earotiNkbdywtGKWqdI3OENbRUvlZfK+kqWbg6Ni14dp2p0utiDWH3QBsWH8OuDoeHoeeMNLOh8eWeqOEhrQm8DFRalBlPdG1hYHjnFG3y+e3CmW00mUGurdFwcCOmqaUIwNBGWEu17GQ4IFRXj6fJ22gXuQ2i30ziOYiFwHrIabP38we8xWW1n3WYQTTusunDKSRVAQakJPivaVHvgi8i/n8wI0JVlMQDSVYaPiY2FyikONtzrJVgZmWVd0eDRv3/BackZZj/Sn0awWx1k+FGj0hXG4QVb3ZN6j+ozF/eSeyWlUjnbuwX01Y4p/nTK5kk/OIZPydaiFMd3icAKQaImY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMTUzOTU0NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lOWJlZDkzMWY0NjE1OGE4MGM4N2QwNDc0Y2FlNTNkMDgxYTRiMTMwM2QzZDg2MTE1NGU2YWFjZWZiOGFjZjlkIgogICAgfQogIH0KfQ==" + }, + "1": { + "signature": "OXpfSN8gl3ZCFSSogZDrqDLy147nIFsOueWpegN3ayHf9J7adww4BKb6udE5CgWsDPAqOBTY1iooQoOLx9htVGwh8WGm3OHOQvBdTdcwybb7hg1aF4wlqhjZfLmEFeBi1X/YNnZLJemDUWItj1lIJ1x5mOvkdpR8HbArQcpbrZJMnJe1GmHWXgTKMFMos5nJs1tOkgbBVjNufvLPE+Asyvj+NW3StysQjVp4LUluFVPxWRZ4wvh8aAGiy/3o8YLsLx4sy7tRNp64GqMBeComtEr2ligWWx/JhILQTf0J/TJnetc70JrOTA7jIZ5Hwb63vMQUILlO6tBF+00zIHceWOGAUnbdQnqCafDmw56M5yPVrrW5cwmofO59VkCOyw7PHIkCuysGCKOHh1M3zYfBjqaftY+e88LNc5PT8sIdxHwnwgBRPp+ka/8fJBtYgApGTE2Nx2MGqPLPiGtstggOE8lYIiwn0eRGzNaV5zFsoCXGYjsxaCoDagCMtkqvxy2WhmxQz5xWl9Rqhqx2Y/XNHv5dhc4PFiJR1z1OJpGZ9bMgcmfGfk8EEbKCz4Ed8OuaRVMfFK70I+c0V3XdH+pXtSYV+qRnLz37DASucdtwPdzoueWkQ/YmYnQph6cuqXTw5x9kWR0nY+OQWopZiQMYb7lhW0AaJs1FLH1Nyx0XiuQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMzk2NDYzNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jZGRkZTlmMzMxYjY2NGQ4YTYwOWE0ZDNhNTBmM2JiODljOGU1MDg0N2VhMjVkY2QwYmJiMjM4NWFhMDQwNjQ5IgogICAgfQogIH0KfQ==" + }, + "2": { + "signature": "EHourGqQGfsxQcgH/euZ9rutEjNzrTtZeLShETtVj0aF/LB0EMnHM9Aej9R4T8NyePXpMwEORcDFVib9SuGOrb2OBu8mQ39YIYQpQrIZwCmHokjzRqfIBzvoeVDeQFAGabSeQVH/JDNP5N7O8/zOMUiGll8UjnZTEEyIuHtugfwep+IMD5cDNukVwq8eMFvKIy+gY/U7VywiYyWDwyrQi2zDDzOLEUmiO/Ml9SFITl2udtMxf4g4l6PpYLpgaucq8o7xszWFMjnzoTI/9HOZbZ8MsY2x2TdtPIPqB3T7gF1imrempUe8SaHvy0Df4Acpx57x+VUPDOqTJQHqwbIapJHdx1hLwYrXjWum1VP65HNLK47fgqgClqldSr3zFuWb1RnIRIxhMlXkFpAtVKSL/0nFDAZygzbhqdUp/BVFlYIl2sTBDgKJZRbqy5AIE90mLYOLrXl/tXDWJqD14Hs9SKuZpmNbddJtySWlRrz79sOMyeW89Ut2gcaT4UO6q1Lx/ZOLp3Wv3I4ZC9ZU2JxaIbhWqS1WQ1mQEls1Wrm+Vwwx7sJQfuU0bHC+6rJaI3fYM6tVnMzLWcPb9y8JKC669+zz9lbXD2wJ3ou51kD1A1JOopvANTODliFOpUGGfBZr4kRE5xPaVugA5sKUZzz5gbg7T8OgAKpjGMu+EokJ3YQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMzE5NDQ0MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85N2IxNTA3NWNkYTE1YTliYTM0ZjUyYzEyZGY0ZGFmMWViNTc1MGE0MmIwMDRmNzMwNTk1NzI3YWVhNDUyMmNkIgogICAgfQogIH0KfQ==" + }, + "3": { + "signature": "G/oqrCPw/4khVRhX5ak8uG46NxKutNnv+Yi9GeeSr7fkDZ2MAk9d6LXJ0moUraC2o6p5B0VoSzUccc1mtXEhsf2uyrkJUa61XW5aU9900YHekEl0NIEmifpHv5SaM+7NjHhVTHXws9p4/QozLUFctkVIRzb/pJiRurUedWb/u0TA89FZdHQ/kPa2JCAiMereKCPNpKJS6015lu42034tnqO6gr/67a3LUVzMSvVvKigPpYgU05+IZ1Wc7VxJQXcvb/oJ9gMM55lLHx3ZKF7oQrkLhMdrpGXOh7+IM3wzhKjJJnm/qWzbRRfdEnxVvfAbsiX1Er7teusEpZSwobbTRIHmVlWsMj108dJ7ztYUE99f6RPBk21ErvksaZ4Jaiz1/5NXcooPbNNRhknt6Z/gvVjBcm0y6exL7St8CiYykw8QgdTnK7Fk/s9rgJ1jxXoyE8gKWNsaEBzAc3N5uuoa/PDF6IjfSebf42nkUTIjHI9V1UKno+Ia1qMz1wEZYcsvPML4lfq0ZxJBw7U/NpUyt4cxsL9OkfQgXVlf1AKFZJ566Wcpo9YuLyaP9xbuDcQP/ToVp0zMWfGhtQwVFOKzBwfGBpaeRpwTIfrwu+W3ZGeXG+NPmOdGVgWoAT3z5W4WsBdfZgK63X0hqwM6M6Nk8YdCL9ijgmt/5WbYVjjL+Bk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMjQyMzY4NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lYTQwZWI1OTQwMmZiYWU3ZjhkNmI3NGYwODZkMTM2OTg4ZjI4ZGQ2NmM1YjkxMTA5ZGQ3NGQ4NGE5MjA4OThkIgogICAgfQogIH0KfQ==" + }, + "4": { + "signature": "jrcwP+gf6F6wMC0XGIgT+pc26U+PokPAW25y6tpaTd5MHrcdWJoKuIpx2nFHuMs7nop5AQwl0rn19nwTZl+lnMeNj8cdoPkHyJcVxYiefwJQ9j2CysuG81ufNzIpWJq+Fx8jfXtabjb1SkELer4buxHW06zfoqy3zwj46w2tD5h3EqM96YTNmW0ll/YdhPsYRj+sAqPmLHlEx8Xzz8eHe/O5/hUlfG/tTqUziN5KMEKnbmTRwETuzbP6X6Wgofdk+6flLCQZYlVdd3KJE/xLljnfdy5O8GJ+xQ6nnFIth/JprwAaGEYadXpuWfYnmx4p1byAjVOoeesdpR/S8/9tw1CYLHUh+fIbc1lrp3SLsXj27azpoAfkP3yU+pEiv8p2Eo2t/+xm8AJ9M8I9YdvhGzc4p8NwgmCECUN7K8/0yliL8FAWDlta9iMq11GNzsjFjQvJ9x56cd9TkZhJKEVSRvUrKh8ACh4etv+J088kEg/QA5R2O6WQPz/xlI77mMP5HuzHyj5pI6I2nXjkjPS2grSwdCSXf0cAE6eq/svro2Bwyd6Al8X7D8t94Gn6NGS1lN9uEDJXS5Zj/xixqWECJvF6dYnp/Ju2A8Yog0euxXFCQe58kTsy9aAq74C1VurLRn+4wSng428cCcq7qmVuaBRUopE0hr/fYjDgZXAKdpA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMzc0NDgyNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yMjgyOTJmOGI5ODJhZTkwYzk4OGE5NWY1OTlhZDkyMWZkODM3Y2U3ZDcxMDlhYTExNDVhZGMzYmVjY2E3NTUxIgogICAgfQogIH0KfQ==" + }, + "5": { + "signature": "dAniCzOTD0cjRMGM7GEVcLN1pxSZXn73BFCrp89IeBAqBCoxgSJSSv3XUsv0r4RHabSUpZoEEf7DMI6X793XJ/0Pa227XGv7v13G6+4aa5eSYHLWzLgPAaUCXyREB7OTgSmyzNIby3/qw/9/XVVtvcI5xGM8jm7NIm3SicWZxBiaqkZzAVZEjKJ1uhfNZ/R4EZRitpaRVXoTrdJiZigM7uCd7zE04ONzKp+sR2aOcTCTo0yoCl6p+n3JCudNCpm1q3I5EtqdfMofZbuRnJt0YdMylwhoEyrtHjI8zUaaggpdqAHXE4Sf8VuvkYCxGYLFrWXLwNKk4JxoiwEPUK9bWGXNN2NHvJTq0KwdGM9yNOi+XoQ6y+TKxPcunUTPRQ12Mk7FqIV7+YU+2DD26Gob7gXYn3fUEDe9LoUR+L17Yzip7FZiBdQ8oiPALywGPt+lyhkh5iU9EDGGzdWQJ2x7cnq5+zjoYbYp8mWUnHDNSzIATm4iZ93m4urzx0m5ZhpqSLne6Oys2c8EwFOEVk41x5x/c0vS7e5lHsUZ+grGQ4O3EzacguUKB3GCJ6nyivPaT7VzpJ5NrvsnF2TnrsYlFvQKvMd5QgoNGwAqCfvmEKNMJ+UH0FwBmV9my6T8rHDBmNQakzjdP2ZH/pkZ9iMX5C+aUZQ+vM3JnMUT8gcQLuM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMzE1ODExMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83YmRjYWI5ZDE3MjRiZjUzM2I4OTFmNDgwMWY2NDE1Yjg3YmQ5OGJmNDc0ZTk5MTA1MmZmYzVmMzYyMmZiNjJmIgogICAgfQogIH0KfQ==" + }, + "6": { + "signature": "s7QniBpkVKsHMSaFOHpO5xPni4d5NVBQ7WpWR/mjwZEVEDRn+ZrDdqMwadXWzJ92hW4ni/FuadSewFYlVtt9UQp9pfeR0XX2cvMzlC8icOHohdThNZbfPtDaqFg+zxyewF4pBd3p07lpREn5G5PyeMbR+DngofLLtTCnx5BIXmDiyQZqtfUfvHjqCr0aP45ZZUfxS4mslHrfidqYEwLZYxb4bB3G7rm7JmltUQDW/WWjaKyp17I5A74qrpxcPJ4gduvdmWjpgj/VzCRiv1SHjDKRyLvVOqpJL2AZs5YZPsmUtpA3WvxcVZoHJ45rYSxYqA3D7XNsTK8q5ZC0p9oA0r1miutApnUewSK0QNJ3PZPGJ5XjwGAicb+Y28mod2AuCd4TkNkwpsqmCEFJYUzNy0h4onrIzjE2wyRq/bhi+1IjCiG/V1SdCByhPIJZYacHf1HdC/fBQgfQUcxFPBYkE4tEX3pAPcfGXN/0XanduQAqi4JTZGNqxrI3+fZxslGIEKLxpQzWEjQweTUsCschIqTtG+DkioaJH1Pyav/0kuevBgGccM2owJK0jU+YCd6ncBvcr5K2Exln59xktFpMwEIWvt/vx/2Q7esNE+6Cmiko8l7jgqa02SaC7/56iHYLIvg77GfDh/Q6LgePcqVcPUI470s9ctJQJFrgnwKeqL4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMzc4MTE0OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83ZmJhY2NkOWU2MWM0YTY5NjIwMDk2OTY4Zjc3MTFmMTg5ZDk4ZTY4NGMxNGFhODkzMjM0ZTAwODQ4MTAyNWJmIgogICAgfQogIH0KfQ==" + }, + "7": { + "signature": "G+cMrTbRspCQFAvAPSwSbI0uadVOGZSusMnWSPmey68gBTU44lNRX3Seqrx02SXNbVW/b17f0IRh7erlVfPHW/lJEi0lAjstcNpl157RHILOscrYW3ZPnq9kRTziDO5E6AXfFaB4Vb9ut2HCHeU+oLEFcAV8t7IvQW6iWDiawDF9HOT+KbsvunnYeEW+fLat/t7SpPI/RCW3KI4yyYjYqnePJldJIWJhaL5INdoy8Z1PdKu6JSclRbajr+bFPYYn3MHu6lEuoIQ4cUeWwHCW9KJK7BIkSBpDO+OnH6PmajgpFVVaGXnjmrJ+sBj3NFSwoLyP4PxKCSchqqFOLqwv4G4DOP+Nq552KBea8bUi4hKcTK8uM0V9GCUFXAbxvsYyJvXbVdDQ5Ae1vFiaWZTQ4ShhCbAMDAgaAL53ejckimNqUsBzOJQa9gX3rRyz4rP3FcIa2qh9NIUE2LTDUeeZFEihRQh9HG8+fu7+K/sQhN4uK/kSqhCm3B66EQOW2dhkt6zwkNV8hQCYv+nSm6V//q9LQT8tqvmIo/Nhf9amSJk9E/Y3QTzG1EaJj0Lzt7ICWPIc6KIoNatO3TpFn3Y5iC9abA/qTN3IqMciyvJPztzNkSPW/Ghd2atRsC0buQ2lnK7bdR5JuhL5M0SNCdaDj5NmgZ55Ai6l7N1tmX9G4Is=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMjMxMzYyNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kM2IzNGEzN2M2YjQ5NzFmZmRhMDk3NGQyNTliNmRmMmM3ODUwM2RkY2RhODU4NzFjOTVkMDg0ZTRlY2UxNDc0IgogICAgfQogIH0KfQ==" + }, + "8": { + "signature": "KD/xAG7a0c/d0jqeeTI4Kp2Db1C5D6mfHrULVJdpSrCAkUWmV48tYkbwT29dJi710O21gZpbBXgoUdaAfmAw0xOhzssxntrx9x3Yi5D1sQmCcpk8StTCWqnPpiYmajIjEgTyJxf5JwehxoAwU6tD8Pa0khVPFj1dx3ZvvXQTC2tq5PGQU0bjEqMj/pOIjrvOm/PRU/vx3s4l8Ek8XeWO4E7HNF5TSgErgv/l0sFlk5AOVZtYR+ndVYm4PzgbOzLpwn1bTNQcVUweCZI1ETYvaRlT07KRhNnsYgfxZmJvGUeHqNc3SgXu12vNGIiSk8ms9KCO7AJ/gWEnjxaNQ0v9kKzdtSJgDnHrcdzbs6IWEZY73XL0IAXQEjoe6z1KjiiOJ8CzmHXPBpCBN6y/U6Ia17kR2A9L7r4dnLOj+U/mr4JMlMmA4Nh6TVSXxenokpz/RoinXWM9OixSxy2999GwoQ4Y0Ajl+BqZ7iOcdyyvPmPZMtzPQcYkGz8UM9r8APVWQblho+84DZmTpMFAaSQuOVqRiB5+/FGqrcIVxgXytaD13W4v6JSHE8hGShjBdBcsilc5lAkf1iJBu94wP55M20/CbTuzC6PnfTSgxNn4REZQhAR4Qp/IkKUV4QIURis/+wUSkOiTseAH6r51kZlwkpOPOCCq+Me6t4M1tz9Qy7Y=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMjY4MDI3MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iY2I3MDIyMmZjNmQ3MmE5ZTUyOTZmN2NhNzJmYzE4YTk2OWQzY2I1MTUzZGZmNjAzMDVhOWRiZWE4ODJkMzg0IgogICAgfQogIH0KfQ==" + }, + "9": { + "signature": "MNl4I2oK7a/KIyQiWrejMiA5gmshU+T59pzfqfNZ/GyL2vRH+6bpIe2KT0w77d24krzZ/R/KJ6KGsV9N60XtUCWk0YU27G6QoDqRIaOJKCXQCvbTGgWjxtbKeJu6izMtaFPHO9uQoGtJD7G31fOsMV9EcZE4k5OIyjkbcz4v50JNbbKA1ElLqxFX9Zf/IYvl1zQiGGyYpoyaTrmhTEYnj+f3T40T9ctgbuudX3RbdKrSkDV84gdHLbVa9e8WeQ3rl+gudWBvHntlUbKI6yg5eNoigAlkNNHEDyuIntuFiws6QcLaW2Wi4PEgAXjDb8ZchR1IhPsuUDXfngXsQQcsp6NbrUlZb0WGyF5ZNEzIO3M1rFqFDj1ctbIT5wtiJCFBSc/GrlgqGKxAQkTPty+NDEQxVjU+VnZYO2ShWbEsFMdl0n9xA1IiDIntgovAFuJ85xQSmnAMpapgUzxZ4vEo982ai9uKAcKqt9w6Lyvxd011Y01dPSRaMtYQ9UYDpUktffdaZu6z7PhEURDg1BJ7cYNCYiC+bV2pxyr5A3sqjSeDzzqWIOp2Z9KwcBQKMxz5n4zSk9LoK3hsP6B1qV/5hp/ZG6eEaaeTSHkOcQhz83sxZ38IJXVUGkpSvNbKq8jWnJ0AnLGxY0m5miiQGpDEir2T80RMxekZIsMd2riWBV8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMzQxNTYwNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iYWYyYWYwYWEyYWU4MmM3MzE1YmQwNGJiMWJkYThmN2U3ZmQyZDgzMzExZGM5NDc0YjI1NzlhNThjNWI5Y2E4IgogICAgfQogIH0KfQ==" + }, + ";": { + "signature": "xiJcNQNalADJCoM0C73ihOYz/qhHTF7pHea0bTfvVGzE0Jhqsy3WpGHCTSWmuqHQhioU+YdbM+RryGT6+JGAwqNy0em4ZliKPQy4PnUkSraJDexx8Fg+ui16qPx3yO7RNkGFJZi44jdxIS2olcN1sGwETqJLwxys5P1f0egUY/51gb6f5axFyfV262YeKFUHt7Vg81rN2DSknggdY8Qq/L/9O6WVmvxHuHyETXn4kkq20X7BUR9wdvGXXyy59Dq7uJY4MUj1o2rQ70Ed6QYMO7g+T08bkIbQy3/XF6auEgXh9DqesaUisjuGTP2urIE9+6eImCj0pPSWuZpdprqjbLBU+cLdrINn9fdOK5jZ85o/6P5Kp9OKy6WX3Xg/kjg/we6ZlaK886pII6vFS2DFYICmlZ39xFMSxItYhaxgEIcn4WYFxgu2PbqQ8T2qIiH3abVwS32kgs8kWokMa8hUoQq0mTybxanO5yLKAZy1DTkZzt0Pq+YLOL76r3nq0dNQWfZO5cI0Dg9+tU8FMCUccsj/eJ4qQSzXEzMOJcuWj59NgXsQuelGhSVybdNTKci4cClFqM9pT/TsQfJaPdqx/Jra8gLw1bwzBDubRZhBnlo4Ocdal9SOZANw6KA++8a2GRsm6RzUYcm3yCcuZhQffjQx2rwkmxAPH3I7DLrCUas=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMzg5MTU2MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hYTQ0NjAxYjlkMmMyY2Q4OWRhOTkzYThhYWVjYTY0ZjQ2MDJmYzliZDk3OTMxYzNiMzhlMTg4ODJiNThmNzljIgogICAgfQogIH0KfQ==" + }, + "<": { + "signature": "h/cAqLL9UVgVb5amv9rKz9sJ2F/+v1iee3JL3jy4I5RJzYTH7G910DQ0hqwIK6pe1p0kv+Z9y5kYYG1b2GNXiyYHt1uMiBEeLYpq+0M9BxH6gbe7rsR1uhOBHLIW77OaPR6iTivDNGezi9ag6rUQIVOz+vFvfGkb+S+fWNiK35/YEf+Are6hK+Tst/Gw+dYZfhvEU7qOVEQiQo64NLxXwkvLEUSPnLzDgVQ5auVc4hzUKpvJItPpTPGVIAd/lM1Tthv6nd9PLdx+iBgOY1FXbyvGJXdO/O+93WmxOKPEXaG7AlgYS2SWJjQ03dgbx8bp2IKMdgmpNFEs7I735+3wWD032BVGeGZHOMCR0W4u7r25mvHoVHF4QhH6vLUPnx1c+97X26bsFO4njQzaVdkGZcPwxJPTPA1ppeIzW5i3OudhJMaC7z/K/bq59VdSX9la+VZtqp+nv35P04ZY508KFxBepGx4s2EGVYnkrtDvE74poeDXKDuZBNWXdEckKJ2dcr8+qkW/mcOK8XMH/ympdsLXC6k1A5Y51N5tJRvSAgQrBHiZEljOk+DAy1R5ecW3mN+AoLtxBoMOS2MPo4+poHm9LyiFLJySkqwT12n+9Q73/HIEu6E9wgEJTc4cojGO6kl4aqwip8Af/2SWsNbr4FHXdOGh2yaRRJzqY6fA+dE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMjIwMjczOCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kY2JjMWNlZWQxZWRjY2Y3ZTU5NDNhZmYxYTU2MWEwMzNhNzE1ZGFmNzQ2MTkyZDk5NzgxMWFmYTAxNTg2MjliIgogICAgfQogIH0KfQ==" + }, + "=": { + "signature": "u6JZ8YNU3kkP8ol0WmLJ8ljB/92/GUbxGGH2Ez2LBUeFTUEt/87c3pxqj6T608Cju+96pY0jgWXJvhHZeg9GPXel3aJcYDFKCXaNbo9KuM4aSkBJ2MYlo8cj0tGff2WDfodFZiK6q1kka2dU8MkE3yIxoBF6SW7uSvtlIjiMsrEgddCFE8gdwhebZdd1QR/yUtYoSRj88YanONvRHuN9i6k2o4pvknGlULwovMYABPI7M6sQxCcjTyMd9XudkSXc8iTqzkGWGvE8vYW19U9QK1U+DqysV17ui7lIZzbg8ZZE7WfKk33LoiQtv5ZwqzjiXNNjIavQTiiIkiV8eyXESZmliL94JdNdvuTlQovyfXj6xL2Ecw0QfHudGCoyTelw3U0kSrOzHY00XdNA0B/CzG7UJUv1ueqLL6VC1gGK2LBS3eudsmlXzA4yzx+hSr/4LA/qxpXg1SBxgeCwkVQcPfku5T2KVbkSoCorlfhpZ3xgiiejJRPK2t/z18L8PQ/j3GvZLC5yfle3hmmR1dEc9i9az2GkvM2X9adF4v9KsRZRmvb3WxEk8+AI6aLT3V1V/x6DPuos/jDf73sw/a2rVTSoORnxgATvMJ0ypWDVY+0Q5+ffZYik1wlzq5Jv4B9WvGLHWr8EPq2tXw8OhNwp3tpqdepxZvJjzsaAEIickPk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMjY0Mzk0OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mNDQ4MjRkYTMwOTM0OGE3YzJjOGJkNmI0YTZhMmYyN2MxZGEwYjU3MjUxNTQ1OWVkYjg1ZDkzMTRjOGUxNTcwIgogICAgfQogIH0KfQ==" + }, + ">": { + "signature": "vkhQSYGqX5d/aBNwwuIhtFThduAmtW3eaeWqIuZJTfDAOSUJ3XFS3ENyfIwFz1ZsxRczrHuLWcuLchTXtS9LTj3fxCzCypi6m2ab+WxtCblutsK+7QzZctrpPoEbmD/NyHI5JEvzrR7DdYzugkgAftI7LJ0BwAeVjwIVI0UQ8XGQdJXivhlx5Sr8hSeYJ21toRzwFmD8/Z6CEUhA5EsuLTRzMMGrBRU6LsG0h3pnowYludvWAaMX5fr6H1n2ijuVDJFXsQ2yFiVV9qsnbiSlQJ/GAKxmpUWzp1cEpctkJ6ZMDb6zmXDFAgbOBtt3gutzN7KjSvQbiQV5ifja2lpx/hjdNbaspN9rSwCE2IalEf6ZFjGSvui6OooPo7lJ+lDB2UXwZeYyrKGo+KS3TYZd2kebIGTT7x7u8ZDAUiMg1VwbFdGYl5Dc+KCZf6btgXpZuUsm5eD0F9Li4Toyloc1JReRXbgQwYEuUiJG2Coq602j852UlQ4+YdMWvc89mSlXzi5Xq2LA41gMR24A9K+mD4kjAv/ogpidv6pzvFBrHN+KQY1lg0DoL37CyZ2DxCTEx5SNfqeHeA0vGr1Tchr9fTlcCMfN53Pl9lzGS7C56EJSnebPdC+mL2FRaKjnHrfA11cUqcX8fsknPEjJe+nDJVKfsbAWeQEavM1mgX2avEc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMzAxMjM0NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mZTFhOTY0ZjUxYzEyYTU2ODM5YmE4ODM4MzNmYjY1ODg1MmUzNTBjYjBhYmQwMzZjNWZiZmIxOTZmNjVhYTQ3IgogICAgfQogIH0KfQ==" + }, + "?": { + "signature": "F1EL8YsEq/2LWGDavTEhX2IgyXQJHkS1Gx9zwMSrbmDsl7v6luQxnyrMa0+WsavcUbDefv6noxaXszQnf++4MuSeSXrHDUZc6gjVaLxyybt0iFp/rvmDKhKdttT8dMb0+OMUvSYhUNJN/BVYHr99GbS93lFY8C1GW2gOB9hrJlp7iLZ0zBEvfZewmBZO81YyBgIgLIAWi/Tp7JfTqITL/kiBuvXyDkVFxLPnScRTA/cgJx9wktj2l886tcnEahRw5nVR3z0aJxp9RYRsZ8gCiHwG0nhA6P90Hi7IFrNYwpUtlHLwWmaY0iOw455okgfkKrAzS9Bau3qWLWtumpnjz3PQ54xX2hI9d5MsoW6EQFnh0fa1JHQfoDf1HjUIbLJiHJsHL2Y0PzqAYd0ojHF3g3I7Mh65O4cysOwAxrfVGv3Vpi2bF/0nYacNlbjU3KWmH4y78PUidAT6oxzkWIbmS3hGIZBnXC8lk3WOxGSGag9Fni7B9EkhjyVawgj3rmmAXcx86PXxvQ4xGAiUP3Fj32e168Z/+hzi23bTU7+1Zhru2zSCLWHeWYZcc5uPYOO2zohf9pO1BHjczwO77yUSg8QTgVNed/+YglUSh1O1516eO237Bfrfb3g91hpTo1mfVeCBi6m7MtHLYYbAun4lGq+7bGz7Sbr8QDzwuwEJtLo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMzU5ODI4MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iY2IzMDI1YWY4YzY2ZmMwMzA4YmIyZWNmMjk3ZDI1OTcwMzhhNmE2Nzg5MGYzNGYwN2M5MzA5ODVkZGMyNWUxIgogICAgfQogIH0KfQ==" + }, + "@": { + "signature": "V2jiX0R1RRS2TJd67BvjEKz515I+7T3ahUjXcbxsoPkL0/yUYpM9U+Lf86UFaRo7uEf7nq8EgcmVFnHk3PeFjD7W6m/EAGSghTeSDcSslJFEE69rHcT0zMI94HatQ+tXuIH9yj1EtsMShmKjrS2OwHyEZ64DY0X2Z6tze9CRhfSQrdMyoRuI7b52Tr86U6KK+CtHddF3WfHZ4cxdFFdnWp8+0bTWK1bfIuCbcfpeb4yWAD15dmWJGA9WFPX/ZL48f0DTF46BULTSeKRXp5ZCQIPWD8/qRR8nMdbjRMmhhNnui2/BRsAAtv2HBh56h7xHYkOTGDxKtYDkG9e4k7giy+ggj3BJJGZ6LqNx4UJWYEXEm0Pd0p1tKe75ixUEWOXaqmrxoK9JXiM0nwfk6yhnciEoWsaVq9xVO6Bdyq3wNBXOgaicuPtS9Cu/wFIZ2E0QwSQLC8vkvexj4S9l1takJQHSjIhWFVCQ3novucRpMkHqdW0bRw2e8ozOAYBjDpdCJrz9nrL+YfV8jjmdGKfTb+9LTE3oHWrgFoR1nO5iXWvcjYl0mTOeOLBBusYWcE267vlQwEncukS8cICsEyUGPctUvfQOfJllq6eRbUzzy3BU6DnrBU9nUFSu0YzEzt01c4nQ1NdRpHPmXhvePdd+vu+eAN9Z7AxWJVurmFR9IS4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMjM4NjcwMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yOWFmZGQ2MThlZTRmOWQ0MjY0MjE5MTk4YmVlMzNmNjQ5MGIwZDUwMTRkOTBjNTkzZWY0YmZjNjk3ODBmYWZjIgogICAgfQogIH0KfQ==" + }, + "A": { + "signature": "pIDi7p21TmLbRHLqGct7zSS04i1HEfRK2bfM/gA/pnXjId8U95d57YUHx+CObTz3cET9PoD61IPxNyKSpSpo6jKvGcHGCVq9Arfqv1iepq+eTpGoLYX2We/5+ZdsZHFR3xLB5fDwcGG35VtGB43u8EXIMbvqYfZdFibqzudhuawHjNPSYzjct0Lg94k18leKvuQLRJmQgGVCZXefEkKjJ+GwtVvZzI4XEvNQAmCDmWMy317sYRks54D1CFzIoXrikd1pSFDPwZYeNfRitxn1SGQm3fgyuDJgw5ye9QJEMQvKNw49IX4gWvIr18y2dMJGLumrgwUctE3C4ws4RF7G5LJsABCpTTd+nGVAvEyFDHNdN30SiQ1oyhV9zmIGz3bKW9S5YDksyBP2ecmSjvGJOUrkdsCLTW1VZjbMwZImPWKu6lugrNyAvfQ4zT/Gx9hFAUkWeSetddpQqL+QSChaaAnAvLLaRRNk3w+uZ2lixyr9UfLe1hrDr5BstfIKrSurGn8C4ac32+dudX/kr44ehWOMT8UptQ1dJmn7b4Q2pFrYO7R50hYDnQFMWtE/Zv1egssIpULAsPnjqgQUdtwQndvzsNwoEmm5ODZ/q1rv0oVM4E9raxuCVGgZ4zVFDaGHpK9LjJaWVW3gCHVEk2ZTRwH38VqJju5UrD80Ojuqef4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMTY1MDE2MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85YTc3YzE3MDdhMWJlMzMxODg2M2JkNDc4YWY3NzM3ZWNkMWM2ZDZkNjA5MWM2MTEyODY0ZjVjOGQyYjUzMDM2IgogICAgfQogIH0KfQ==" + }, + "B": { + "signature": "RuAXDJPxtGV1VQloQDHXYnAIraqx5hncN7CI0B+1M5RMJiLbmX4gaaWkzNK56McWY20Lm4oOGouHZk2BK1L8V53qpct7YafDA74X/J3Yq7uUlNhOjVbSQDDxicyLVUmupnI85/0sMecW8Axc+UvZk/g3umgjcKz/Ydbr8Kl4orwvE5NxHMURuxhZ8QT1/FBGsyiric0DMHn3tT0gLCLR1lgsFns9ljZTnK90Btcs57cpwr1qhdkv71FAaXIeEm1CIDF6507qey2zZAIhast+cnlAFt9PYsK3ReybIa2Az6QqAGgJ2PTWL0jQDDlQzyjed6vXXYS5B80TteHyWMAxp0Am0MlqDQY9z9BrtUWJrD3UaOPsFqRpAVqIpOh6TOyRiK5bZS/z1/hN8LqHFbHj4wGB17oinZlDnPJwj4aHDcNARzxFtju+dxrpIosGcPA5z684YFfNeVhEkMOW1vJKVRoysmI+zkp/VUUrAFeguT/bnL8fj9B02J4PpccJAswGuEMKLtdg3E/2UAshxhrhGPN3+4DwcHtTL2BUrfq2MD1x2u4DLatPap5wA5fT548ENAQpCYbiKwn0X+66V9uORj1oquiBELGh3B92nGUj7jrc9m12NL+Tf4YN+kFtYOGKcVB6wPYYH8/4A1rSVM94+CMvkHIJa4G2+UVRJfFyTf4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMTU3NTg3NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zMTRlZWY3MGY0YjIyNGVmNjg1Y2Q5NGExZGVhODdiMDA2OTY0N2YzOWQwYzMwNjQ5NDMxN2ZjMjcwNzI4MGQyIgogICAgfQogIH0KfQ==" + }, + "C": { + "signature": "LDmhP3D0jvsGkiyVFVsZqLy4emSYUT7x4punllGbFQOoWDrLHcQ2M1amQ4RT6NKP8O45Y2tP6tpXZdYS3YhWRxHo5PGSm8NFYvAUQNPYkuT+7FIW+WDmiDutzpu71HRWtxFwwHN2CKz8yiIuz3k/QezTJjk1MxwDHNC7BB+T6dKVb90KrGM2QKjlfSZqSXM5zHnr5HKK4cMCzAdNxPkcA0synqcZ82Q5V5Kznq81RkzXU7DPPHBxcH09+tXHG00imv7citbMcP30zcV6YpLzSqFzgYIjREtdp2ZSP39REgRBm50xdMPQZTGRb+tx97t0kFuozDFpFPO+JpJZdLk36uv0k25YyfiaoZRi9VcnlpqVI4qEA9mya954Edmo8+O9OPyyDogLRdLsJOuCIVBFlxqACMDDwG0xd50viFWnCI7jFYvxxHcLbA7eSuS3Qe1/fqQe0o4Y1QkwiWlPYqlVKPU9awEcLuxfJxy785jIg4CdJo86dQlOMCiRZA3aK0g7r4mDs7o/qkjHkNbNUfpByFOpRNXDSs4mfGoWNbi7rAPsEOdg4oyOnYSWpyAc+OF537lmXp2lCG2AKU8onsw1WAf9gTxgE2/yncLTYnXSvw6P03a5SBelw1YLNUyzkHS6itK1ii/+JYl7KF5uOB8R1AmkBuGtKN7jc4DmruQS1xI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMzM3ODg1MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lZDZiN2U5MDE3YzYyMmJhYjQyMzA4NTE0NDZkMTczMWFhNmJhZjU5OTRlMjQ0YjYzZDViYzJlOGM1NmQxZmUxIgogICAgfQogIH0KfQ==" + }, + "D": { + "signature": "G2X+mEoY5h7t3gxB2LkS/fySrbRu+FjPSDF5OD2b3rvjdw9RYgCXICiD1AVu8yMbhrnRTuYjfrwxUv0tFPqk43w774oQoSlWwv7CC1upWQz5XtQWK4wMOhYKdqqrz4IbiM/UN2TBYf3M7wUG0P1ol5TfEen4aDnPpGJdrlUWY9b7pe6J7bVBKn3cu/qQ6Xm28r7sGck9ddd0Ld3BpmL1evKue8JBBhcsbvfMH3EKmJpmXFQSHVYk2k1x4p0G75wqHzd3MhqScK6+mmjcfSIiXDOqm3G93QeDEw3r2Ik2XKIIqrYsg0u9CuVroufJn54XzqTKR7wCb2EAdxmMyppSKLiUKsZOXDus96WFyIhNCmzB2phDSXNwJas6uWnGb6TrRmmvgqhVI0+8nBrawU3hO1Cn7WrdTcNHqE+NGq11q/RSqhhdJGgt8ofpsDWzy8m/sygUXVm8k+wQTlRsIS7ZzoagEZdtfutDMJ1K3byo+k3wWtmodlbsr27LyQ/zpNRcwjSVsk2lVS6N8r3Oustek6bm+WxKxveTQJMYAehSp3KqD/yiGa3gIzJGHNWH8Zl8YVCDlXCaj140B/lUrwWyrF9dxcnLkfJKxG9rvDKWynSQmNXmBKbcYQTXwlH509y4Z/Ro6sY28y4XnXzKB5idamF+2a//LbLemPglXYkMBGY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMjkzODM3OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84YTQxOGJmOGQ1OWE3ZTVhMmJhYWU4OTljMzVmMzY1ZDVkMDU0NDExOWM5ODM2NzBiMmU2NWEwNTMzMGRiYjM5IgogICAgfQogIH0KfQ==" + }, + "E": { + "signature": "dtwuhobA9Kzu76OFb8PNeVVODCMhh57ecZhFwHMG1sOw/3abwR0kUyfl5QAPkuxS2HdOGBDdxwZiNGB1jhSXzh8iRwG/YC+mf+dsNSY51jjEXrtUct77A5BFKu7fO7hX/m60mSZNP4KgsLvkVF32AOgNa5O86XFIVUX4C+Srk5u9MJ6HvPNWTmom7J+0s5ZZYBUZYMtNaerGchhvLsFq5TsoBVoZIKhO9ji8OSt3JtvpNsJqOYEDWl0boS7aCEl1rPYLoNMqajfbLgUzwI+yIsjNgdP2anIxuNl7QeWI/lXYhM5ZTV9SfUQ78F0xd96MDZr5m+pkxtFaR5EQTQB7HeA3yYMbdPCtodqm2T44zOZ/Q9gPWP6LU/zdOih9lp8CfcyHxEyx1KC3vTz1lm2VhX+f0CrvGii9AkYntpZAlZSR8V/7xfOL5wBwWhS7YF9DSHLB3TVG7Ixv1+RvwwIn4JZLmQLNtzW4D67Fmw0ovD0w6lSX6BaFexPKxuM1pHzAJf/Q3jjsfPQeyjl0ElCqmV33zAO/+aaL9KDXYycbtkaK9l116NsKkemhl2fFO2vb98vVSsX0GelW8cVHxow7HNZnxAlMNVHOXXhO/9UxwL2gdWG79vNj2D9eqzio9BQKjeXvEl1Nc2K2pEVEKRgQLagKMETpOaANg5ydz+t8ED8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMzg1NDgzMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yYzNkMjg4ZTlkMTVlYzJiZjIzZjA0ZDgwYjg0MjBmNGUxZjAyYjU2MmJhNGY3YzUxOGYzNmExZjM0OGVlZDUiCiAgICB9CiAgfQp9" + }, + "F": { + "signature": "UyMCCuC4O+yRs635u0QqQZnihoDvhiF/+1nUqlHFO3WdTfnta8PM05YysKryy0LFIzheAWV8hBgQi1+KxLlaJ/sKYQM87Uos4DzdVH2YSx+93QoQ9DrfN4kJ53zYI9/aVOK+72ixNmQIBtkXPVktgAVbV81muA9iwhdmbjzl/Tcf7NSkxnzUNaueO+LJa6+A6IdqtNt90YaTs8gJLkYr6vlgFkETSLa8XGETCP7sEt/K2sCvowijGB1UlX0zpIvc5mfVxOD87Sg1vZ7eaqiPQp0cQjcGw9fF4dA+D2xXhGPWsjWhSQ9dECs97joB2VmOiM4RtFkVsazs+giBGO5WaIQxH41MZjzQmTwdJxcr6HSka08s6MRUvkn6geE0ZQyetn9NSWzkSoLMkKomoJCCZlqDZikPOngftbKoCELCr0h+OXDJra05TCM4uk3JBf/AdsdkJys5IFVMFZnFX9yL5s9LocT3IAh2Ran62WSlrZa5IkU1pAInCwnaUBt+qEScV1EsFII8zuMr3xVXvVm676Qua1XRHlDlh0Z/fkK10nQkU3PLXzkj3a5mi7FwpUsmBv6oysVR3G0wnrYnA5UoXVmjuifK/8UB5FBsxK18fVzo2ZHcJ478te0psQRiwcwFwpL7HmEC2g7uLuUp0DFPowMwCHmwNMXiBjGdoW2CqqE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMTkwNzU3NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kOTI1OGJlYmUwMDliMmEyNjE2OGI0NmU5ZDcxYjY2ZTcwOWI2Y2ExZGZiZjRhZGIxMWViNWFlMTQ5Y2JhNzUwIgogICAgfQogIH0KfQ==" + }, + "G": { + "signature": "RavDVkk5DiIM35onViC1lsgSNhfe9+a5M6o+UJjenfmEP3wKWJXw5aDEhPaCj0ZVW84Q/Qn1KEgLs/N5kKe3Fjw/dtalT3T4CGR/NeAJ/EMufjdsCEjb4g/vKocbeQscKQcpBHI/gpNjjDEOxb0t44/Az9UIflfk+pK2FB2BLz2V0ytIAMUaVE5E+FIPjJkigyd7yOhYHfBa/6Ii/UjuVJ9IeMh3xL6t77iEr6gqqPe6g8tc/8hhFrZGk3RX3rvTR7sSyEl/XbRkN6gE5qE+kRAzIaP9DCM2EOxC6iRSMbxIEZQXEWmu7PStcD3iSor0MQR0rswASk9q2pPdOtYwAkz+TnWYRE9DxJO3Rj9doFA8j/75I+GJ9d6DUTTkhAzKR+jmrcPG/3ikzQRC2/4jjjbj7GkoAq1tMqIkC2jwy3V7JEYrakAaBRb4LbN8U11GZeNm9pmWBA2mpyQDx8e52EFoTx4bQYI5BKDeC6UcQBsJ49pePAopmKPl/B9lF068Lf6R5FaySnr8yXL+Y8h7V6EqiO0bWPBWAGDbXA73nNZi3SZkD3IokknEv1s4CniTS2MizRsOWlgG41pRv1rjn0Nyk6GnuqIQwW06fPxKvLFTR7qu8y70EnPIc1BtZLPLOrvOO25JyYcO0hGSw9MpHPo1RA8J/X+SshmWU6smLB8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMjEyOTUzMCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lMDBjMmZkYTRhYzE1NmQ5NTUwNjg5YWQ3NDU1MWVkOWZkNjY4NzQ0MDdmNjdiODVmNDE5NjkyN2E5ZGRjMDcwIgogICAgfQogIH0KfQ==" + }, + "H": { + "signature": "LNOpV0ZM04b+0DjVPXikUKCDvTOJajcvR0S/02wsh0p5oPwGtuMlkk4uNWK9l+pLiFESpTOrNVtapKb8ouVF9tPo4bHLT+jOeObJPT3qOq5UZfmPtMPRKFsyXvhtSnpd89pN3ARcpxFvPyRbTWJD7kTVwpMYNNJLQXCjHdnHFfeM6PYEvee9E79uqtaT8MUdFPQ9gbIEXYd6pxaDAhVH0+a7jCIWSzPLYIETCrOWi4xYOmnIXkplCMV0gmzhQ7GEOHTjQvnkxVME3H0o8x+lnoDylIaNOwdEBjNq4oU2vNHA8JyJ9ppUStGVpMyOJ8cgGSPh08ce8x7FmQhoJm5CLd8TL0D6yzQWdKGW+EEmd2WP0kSgnemnXwgSRzddimvouKUcqpURc9rPEIFbNlRXfkXlM2l4bgksbHAcsUbWmud63DP2e/kcMbskE1U1QdojaQR7nyM+pw3qDYX7S3Gf71IVEu/S8kniCPP3+tZ74kbd+p6F1MHeWOZ24qx56KCAcFwF+PRd3Adp3tT89TIcLEKTjEGWNCkp/YKhw7L9A4MfxoZiUrrmLKaCKI+4coANchu3LP9zVfjUvxOv4J8SQHqXz500Y+o38rogCs8SBLzauLOY+lFOuYh+BqqAumpxncqbfuJJmtykI9QX2lz6UwpknkFDNbt36Aywr9zL70s=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMzA4NTAyNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85OWE2NTlmYzY3MjY0NmE2OTU1MjZmNTUxNDViZDg3MTVmYjFmMzYxNWUzODNhZWE5N2UyOTU2M2U3YzgwZDk2IgogICAgfQogIH0KfQ==" + }, + "I": { + "signature": "ut+ksA/mzrC75TUSDzsIknYEHBaqN67BVC81lHE8XjN9VsDI2nigNouLZeLrThDGMJxivTksUJcq0MXZPcGh8DNemlKV0fVfTYN8nHTZA/LK2EyJmzkrDmLbTU+0YW1YOhX8Dy4b3rLwVVI1p7LzzxDV6uX/U157d2fq9iH+IncZ/yiOxZJyIWoB5KqwvfTS3A8tcUrjl+Q9yiNv1CwuOJxm3TSkkhA4+3RUQEEWGmOeSSxlxJPtUbwWi2vMc2sfVb6mi8AEOrWfwe+t/HsN+bJnWouu9xjYJbsojS1uIJbBSpx3rnpUlsVCk34oB3BdZXwPXvYTuf85t1rHcpmVRHo/2HQcipUjIhq0kr087SVUoure44xg6+VCXvvW9vYswHsnuxNmxBzQbmB02BsWlxR7XAXCG6LNZ9AYbGGoMZhu+EnNvmidqD4dAryZ63DadFPi/+RLr73kvFu9rKNP0M0BY/pTpi5iD6Hdi7957xIK/+xxBWqcDlccx76FUn5+HZjSMWVKN1ufu4E7WZoCxWRJJBypsVZxbewm/YrYc5xtmAV8FMOaHuwRuRqd8Dg43rl/7JXomviBY4DbmHNDDZNeOQlRODYB5mIZ2NOadFyM1F2WNpQpMHqxcG2EJlHuIlPVnJ6Dc1ANzfnXfqdSTdxvXt5cTodu+AjVRwn6ziA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMjc1MzM1NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xNzM0YjEyZmQ0YThlMmZjMGY1ODg5YmMzNTEyNzBiNDcyZTU0MTBhODY0MGE0YmY4NDdiZjM5NDI5Zjg4ZWI0IgogICAgfQogIH0KfQ==" + }, + "J": { + "signature": "DBG6zdX7q6rRK4pY4ggZjfFtHN24TLmOum5zjkbMxfvB2A88S75L0a+dI+31odQjJTS/D1U3JzHkIfRLDWBqQ5TVdJh//AKpXcCygdkucTJxNFjYAIHw6JAH1kAoOoJw9+TofaoBwPhHF1sAsUqTFfLbPJF3V+WbO2UK4x+WnPkPwR3AkvGvrduR4DLyMcDsEiX3jDX6TnQghpvBfp83AdG07GlZiP5ppUhcQ2EMV3stsT1ygB6CHHM/xVbkijBqESlutER3K2QSjkq/5uOGtdPzdwXDEKzV3aVMWBQk80dc+ShYpQsZxa0HLL5Z2sx5ZPPNY0k6kiaAzN6oYbj+JL+ILntU+92oWlkOabAQZBaurlDboChftFkvi/56M7bn/LoXXLOXyCo3kYuqjKnkHO762h6MDlEjKCyb5dD4CD4voWBifBx7aUQsXxOy4pSrdQiMzs1RIewxsnWlxtsGSsc1bJfAqc3dO9cmTPcw7M2unkTZbIe32TrhAb00vNS2iItNsIHT4VD7YmoKaalQ5QHDSSVbAO26QOEOVgRhfTAkM4tJ6ElGaaC0qVGnhVM3hOWZIgFR4qREDFFBdKkcm4SCBQRFp+sKv+x1yh1Yf5yh/EtQPXIJwkOsYxq1vQumtulRtXtpQJl9MPWd+dA2XeGR5mZyrjwEKszRSFLT94I=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMzUyNTE5NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lMjU5NjBkMTY3MjZiNjA1MmQyMTc4ODZlYzdiZWUwZWM5YjcwMzJkMWJiMjcxYmNiMDgwYTdhM2VmY2FiODE2IgogICAgfQogIH0KfQ==" + }, + "K": { + "signature": "BoF1wEt409XQVRuX/0m4bSsJR63Vt+gLIF9JDhMkEYe3XXiGb1cYCDbrX6FH22GRKGiJ/Fo1s5pZLdhNC2vQ+KDH6ujhT5cub6gc/91c4ck4OvFobSe0ZrHs0hAWGGKh8oVFz3Bymk5f+JyKR0gaGOXAYcx4RnOeRWXf1hJOO7ITPWXNu+nmtOBUMIqxGT+ZZnhFKbq0KwCUIOdKcD/MGnBi0M1GNXsdPSybfohgcH381WaK1WIV08VnSspgPjAuhLFjpqBbLmvqQIOhcQWsSW9/YlrIdsQDtFb6GhJgBYxCe5daJQ0PxmjgSwcUhpnX1WuI/a+LwZ3GGlWF664W2D7gPQCybuRaN5oSn3zwY1Zsl8mADKX8nAxdMfKgx3qHa25Z+mYKG8PcAx1LQ7hN2ap876Y40dqb4M9JMdLsjw49fIvCFP9g5qJ4zSE92YXiJutCS61xquoaN+By1pMU2iwMFL0nCL0rbehPNS9xmv6nLRDOs6W89kYAeRZVDQkP7r8yGZvEaxTOpz8lWy5ETXh+SgeoGM4CdKT7zCNcH8qdp9M4aKk+LUhh/S1fS9pQYx53d7AAdMrnmpb8Tmi43HNuVriLOW9gl74w0jwNMfQ6yCR0NOlph/gN5baNH2ggFPb873BD20f820k8jd4W855BhOtI5oE6UO+c5R5Jn8A=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMjU3MTA2NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xODE0MDEyY2I3ZWFjZTYxOGVkMzBiNTRjOTJkYTA5ZjY4YmY2NTM5MDM1NzgxZDViODRjODBjNjVmMDZiMTMiCiAgICB9CiAgfQp9" + }, + "L": { + "signature": "b6EK3Bg27O+VVLFPalYrMZu7v3lWlXQWbhA+NfTt1OfYwWIkBg0uYAsrBkcEKeGjl7su1bZRSh9F4t3/3DmR+Mv7FLz6V7vhN2nE3BR/y1lkHN77YiIxeE+jpcQ3uIj+QT4AJg4M6vphlD5+yKjo3YcfF/DFKEv65cyWY5YhnD595qntOFOYH8YmONlrA2zVxM5gbbds1xtF+IQDMRzz4xKPAsHe9ptOGJ5sULsc7f/2fCznS4f+vRy4NJGQURskWZm7z7Ms8BwtcLQqS8QUS2Ji4rJaoMB/tbLjVs0JXUX4eLki4vSJ2xpYfd9c0g/8p+PuF2PoRLE/ijs9pDuiUYplWkBQFD0AMeVWGQpdUz1MXGB8GaYbi6cOgXehO+dEGBJ9PduS0xJaniyu0kBoUa8Wxa6BmNgf+CdfY6acrGjAY6NoYY3kyqxfVK91w5oqU1tM0G7a+ieIqz9fLJkyjbUjJajUkviDyAW2Hmnl1iQkmzbm3/BTaReAqka0c+xb2sJK6gxPFZk+aHp222haLMY8CMcnCBHBbzip25pvQOJad5qMojzJNSX8C0OTHVVdQWypG6R0nEOKTAzhlRx2JC/nuPwCYdaQfTCZt9Rq6z+sIw+Ptswx7OCaEzSv+NXUWcvyQyUIjvotYjzi4ydlI82RDR7NcKS074OX+7nQAbU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMjkwMTI4MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kMjYyMTI3ZmM5YjQ0NmJkNTZhNzYyZTI3ODA0OWVlNjliODA2YWRmY2E0N2VhNjIxMzkwZTc0ZWEwYzBkODM0IgogICAgfQogIH0KfQ==" + }, + "M": { + "signature": "MBLBc/PLQoSyumvrRKGPaPuay6qbrgNIM2jJwi+nBOQG2aFZzavuffJ7kxHsjy1zuKdSqM88tEalXPgWpfsem95p//hOXF92bbvY2Wu2vFMPnw7D75ASP2TKfTIz+7oJczFhZ39B4YXsVdh+BOhwWgqOmJ9dQ/o31+7Lu/0NHmrBPq8XTLi9PovTJ530KvzMIZ2dmTILaOy6v/p2pqagp9OWsM+tza0Ouy6dPr4vPJhLrGBBi1EzjR87eRAvEIst6VY4KOyLTqn2t5nVk/D+LGpm1QtzEE5/rl8uSmuRvU1j7jseACWagnnG2aDFtFexUOCEOeQ9EG9LvamC5U5NcTYMRU11MRXh5Hzm9lbSvn2xe1AR4RWWFBSA+erL7SgvVf7mCTlhlrSLVlHEpUF/gcsoZ8xOgxjFjTbilOc6JRli71s2ILzFZmt+y1zcY5WfIIkkVfWtLxUTTgljgKbUAgypieqM8FanRIIIPZ9AJY3K05rrJvD+EB7BNkOaoTMUTsUUkGSDYQwrVVL+aGD7nFcEpW8WAHq/0KA9QABNqi3BcQ9D8nxx/Aq4TilYW/uzV/MwizgyCoaKoitPK7gMyl5W0fummzmwqGwkJuSnyMEXRSaHVRGsS/TtLErKtJ17TW5UZNvZl8rTy2dh9l08XypEoW4OSpHW8MxwBFY85nk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMzcwODM1NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81YzYwODIyY2VkMjE2ODY1YWE3OTE1Y2FmNmJmNmNiOWZiMGYzNGJmOWVkYzdlODg5MDExMjFkZGFiZmZmYzMyIgogICAgfQogIH0KfQ==" + }, + "N": { + "signature": "LTsCUvypaC0oOlhamlTfNuj9scw19v8OjLA4wd9E5zkmbWBciuuzMgHg3czj77f937KdQSqe6+dQdqzR69aEZx/9Eks9Lnke1o+mHRkIwT5FM00ays7111hVMrU0NfwTpeL870qoT0bTDAQfhumgC26i/DAwPZnyZp7xfowV51YViphVv9sDGQ7pznpR0S29ulYKales+yZOmrxrcpDBBp/412Uu0qt+0ArMbDY8xDRe6nGhPUamuvrzweQcoWQqKm1KQmgUN/49i1wkoELfJCLfxuDE9jmLqEYw197zF+bfIi5qa8Vuj74AGJZ2Yl2/b61EqvkY9GdS/Fx1bw9xxRT3S+7l8frgtF5xFRkLC/+QaAI+T29uVCH/MXrwVDDWkuOObwRiE+4cXim1VFu+Mo9zFjNXibsHyv8OH1R2rlcGHdA0xWvID2frAaRmzB4IlzFJjz8eUK+5Xga+jqNFMjT1njvjfbQZtugWo1BwkpFTWwXiiFSdIqlNPOH/yOWK9BL3rjVgzqNshiIZHAncyDBGHX/AnYzkvTNAv8OnqiQU6BEs1QlDPfSx1MH7l/KkxTZkj94LMwkSM5Ns8zd/roeDjTr3bmaTo8kY3ZfOjFHwA4kXUcyO5nAlJvm8mcN0lP2FvCMYKk3ObXkrRWrFvosdTt8+fE77ho4DlHD4GyA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMzIzMTMxNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iZmY5N2VhZjU3MjMwNTU4NGQ5YWZkYjRmMzgzYWIwMjI0YmNmNWU2ZjI5M2FhODlkOGRlZTczZGNlMzIzMDliIgogICAgfQogIH0KfQ==" + }, + "O": { + "signature": "fYaLcaoJMFyfP5avaB1FkG56K1q/zLQ/441sC/Cviv29nIxorWQ+8cvldjtwjMG3f8I1YgDuvLp3dC0V6EBurBQK+ZLFIjjYmGhMcqfBmQO0nBRQdMXfO5p5/x4IcRaqbVFf7cxZ7O4eXw7lqwPPymU00HMxRFsxgBx025LHQpRYX1Rhxmm7kNL5wA1n4D9PMQqj8PRxALvFcG3d9426PmNWv80HbyCdxUbeUl4+1ObMQL6PzSP5vdcLR3XwuyWrtaFMoBI44QMLfpy3jce+44WRqIJNufrA5GzzkeZLmtoxmASvgeSA94g1ZAtC/t742hYeYIRtthzMz5aIZ7OT9dpy3i7VxuPPJQNuRp8LOsA4QlJU8f5g7ofrjv5snkbAWfw7tTc0Cn25ib0FEI6X0+D4PcbQxxsBUVHzKttM6unwAMNFF7jvKy9/NxiTxzpOvAh61KEcDUenAv/ISAIRer08EPjn13D9I4PXbkPRrMbclPELUV2xvI0SGpgH9Emx6GrImNb/PMrshzmn4RryfeuLeYGL7oR5XktSVCNsbrhzBTZftqDBw9Q8ta3sff7K/Pl1uIOiKdbWrpBgYEjAcNjLbnWJ4UMTfzX9aXXxC59PrJc1KW+zLzC5kY03NAD1gUjZzM//jZLNQUfeQ5f4F2JcyMv8b+FkanE+FQMG52Y=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMjIzOTQ1NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iMDFlZDYxYzgzMmUyZTU3ODhkZmRkMTU0YmM5YmQ4N2FhYzNlM2RhODcxMGM3OGJlOTZiZjQ3YmE2MDhmYWYwIgogICAgfQogIH0KfQ==" + }, + "P": { + "signature": "jRWx/GXtpTKSLaIvywaT79RsZChWc/bx7czLC3ajO6v4QR+R7/gsUOWliTb8u/UHWxP7lK4psS+nR0lFf/PgSg1ed7KIlTGHwvhTHIZuzOrMlFuXyDP2R0zY0Xm6W6QFZZkQ2Fuzgj+9tE2YlcKeXclTox6CYa6pC7AaJkRhGtlhH90oN20T1mT1RZrg4Oo7KjTxt5ovJWeZ+Wq0nVwAX43+1TH1U4eeWZn2z78MymMpLBJ22Vl3pSQNS9JzmEJBrCOfzFMnL4tXMwanhhwMxfiCRP8Ct96A4ClKnQRy7mbtD5a1fDxgs8RWYuaHwAdrgHJ6gVqYTEANJtWyDGjFAv5ovFBdwEVeWjBCTwXiFL3csHeY7zUzABPcUq3sMNZDe3iraLtOx0E6czRDEO5V1p3uALB3+SeNC+1oJJwxFC5jSat3kHW/Jejwhrn4wllIIwcZrwwoQEM1FJr+JDXSGmT26YFgfysBRoMhMc/xpZLqjzWmei0bRrHMW1Wh5Vq1TzGL5OnIKoa76WIn33fqgzo42XTxCvo6WCr4kfmsI4xerxUneCO2BACa5pLqiukgzciknEdmRiV091P1+mJxALmuX7wRJ6/bqgYgljpUGb0NdMKq4KKkXPm4gDPfYi6y2EpgWqSPx+sF1kDv84MPjsuw57rmyJxiwPKTk9UnmD8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMzQ1MjUxMiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zM2Q0MWJhY2Y1NTk3NWQ2MWUzYjhmNWM1M2RiN2ViNmY2MzlmZWZmZjk2MjIyNzRmZDAyMmI0MTJhNjM5NGJhIgogICAgfQogIH0KfQ==" + }, + "Q": { + "signature": "R0Fay5PcaT6n5rIBCiRu+vBqQeletQeFyJqQcw3P+WFTrwZ2J/o/YMsptF9eat9SaZyIH7fI8GmCM4pM1ODzj96XwJ7kU9IpkYC3FfpaHvuHxHU8BLM4S1jvAY4pYoV7u0Nz82i6lb9EW7TcL+C4uYz33lqi9xbb64V8F7eSQmvsqP/MlrlK/81I+eZLq3G3A9QZPKmQb/S+X7r5/w6p90PljqhPmO7PmjPABVOR7GLZ3PBHspBotDHqSdVs4XPqqzycRHNJN2LPGpUmfql7qJGNLZTz0sEiyW8XAspNbV7eYe5nmT4GYeIqwK0x2kZszCfWBJsNoSv7YhAgfdp0tyb0UrDROyj8/TfqFrLSkjAU5FbLOJKElzC6NoGRtMX+rYXrtbOP1ta5JBHG8hIuxX4Oxo78hqyn46qUml6SbSyk1r6Sa50iDvVDWOHamIbt+x8Tc8asZ0tUxhfBIA2EcbJvMHIvO/AlCQA7J9MptJLtQvc6rHf1OC1wbf6Ed+9HV2ZBmQUtEgCDuNPZ8LHz1ZZ0uzVIBvsL3izXVa6OzeNZaAuRmApyoWRLEWzm2zfYz+V15qZcrQfb5qrHRzwpRfDMFDCa//OmQVzzsp/RjX46kyi/D+3yOTRieSGfHsPC0dfQq/+3pgzYVI9BUjtifxY+6hgmUz6RFdzPGVUBE9s=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMzMwNDc5MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xNzY5MzNlMDk0MjNlNzJjMDAwM2UxMjY1YjM5NTBlYzVmNDM2ZTVhNzNlZjY1ZjYwNGY1ODk4ZWYxMGNhMjQ3IgogICAgfQogIH0KfQ==" + }, + "R": { + "signature": "wylfDq0mASlWo3a8Xgjny/FaMbueFHYs75ynlkPzdlgkh8E6cmMDRMYvLWuDW1gAprgKNjKm8z2Y9WT4CUws5pdMYhS/LdVUk9BaEfwImvgbzE9bp+313I3yMDwrGrAAhc82GYTZ+qrXujvIzoysEwBfUKXjZrPEZrKpif1757u77gybyxiRqyDZ2PyzFRsHtcOmr5xNPgr9p1DovnecQ82RkqE5xpkjrJ0PagTsDYxv6N9hxbCmB6BUiqZyDMYpKmg5AA8mRjuOff1ULrPyjpMc7iy0EzPH/e/yY3MYF/JkpWiJAunlq8sd9W45Yj81rN/q18cB4CFt+/xj06wIPpEdR5qq7hTOS5Hv1q8e3qaungdVQ7JhZLYFHGSfJQU9g0a5pwlQvi8h5kny2hq1+u7z/DJiyxa1M4aFoDANxQb8LRFlBaxyUAO+7rqH6HWs9IUvP+T3dCPjlwdRraKcxJ3hQ9Zyhk33B810kblvzzlEw8MoPUTt/+O2yT3wOAjUDNYCLuVW4Pw1Ya7DqQ/WCDksNGgTaVCEMtaNLnAlVIb7pj3Xjboj0sFrhBDJtiuTrJeg+ZIU64z3vspGRii9yCrDRCGhK/c89wh4Ve0fsGrpeadCZMeuv3CTrUjbnck9ba6rQ+PwmaAp03P9j1WhrkkkAaSEHItfyuVGlSvpPsY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMTg3MDcyNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yMjRjMjdiMjIwNWI3ZGRiZWViYzc4NmIzNDA0NWQ5MGQ1ZWU1ZmExNmJiYjI1N2QzN2VlYjBhMmEyYTkxYWM4IgogICAgfQogIH0KfQ==" + }, + "S": { + "signature": "HLfvzm+c8q8kUjqEZEvPO4AOckkO+ltNutLaSH2Os33CfB7be/tuMA+r5+AvYJHOyr+2elHRL/s5x2byw9k9plOdCbu5N0gBx5ChAa4WMwOrbzQsvSELThNJa/2KRSQ0D7iEgu7fHFUHLSfKIvW56/LPY7tQdWjAZUKLpvx2g6z9rsi1ljtHvemIjaFegv8cLdHYA43oTc8oeDO3EithmCY0LTHptwn9Eea5ihnzOQxflUoHwIh+7X4OXbg/A8SPnJGgmMPouVdRDY3Mc2SfLfpg4g5rBNd/qv/EZ6SYpEFxXV1FRidhT15OSIGaomcioXUyWLEhUa5zd/WsgCz8AfGoLMvM+a7ItXoxWpib8t257OJXjYuZtbr9LDTsAgZzZgTYfVT1r8cpTuEpy0Pb7+yw5ZfKzS2jKS6NvpEUmyFcEtfLgb+FMlBi724qYoo6SliNlYjWTYR+R+GmmIXccgxCLDG0gLqxkthFMm33PQJz6jI3/8bCSpSN2NqEMXWGEacx4fqFbcYDkmy31H8tIoipA1GBFlNQCyIGCfILypaLffW8Qild31F2CI9vqkOUvfpgRR0uy785G8cNU195ZU+AycZKQqUoW/c6ZJnzLf1i0wj5IQQHBHjCwGBdJI+qNfn48l0WSPMGM5VCdHJcrc9NpU65cDZqIcv79+ADddc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMjI3NjkwNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hMjAzZGIxOGNhZDIxZWNjYjA1NzI2MmUwMmFlZWNlMGNlMDlhNGNhMWY2Njg5YWFiMWNiYTk3ZTZiYjljMDg1IgogICAgfQogIH0KfQ==" + }, + "T": { + "signature": "NixZWA2nwkC4Yih04+MKKODFLFlKBPx4Txz01/OK2k+OHN5g7Yzwd8pNcsoSlKIri/7CfKzm5ShSGaemPSRlF3SyY9+MFHQC78VPjQGH59hhv9sdP3VrYN79A9mm/Z5ggHMzwwheT82EOGgyAtN4VjM2tqv3Ei4XkNIxeMbMjU9Xmze57+RRJOmB/ZRDQv+ODN7IVr3RFHezAfWbin4hrK57r5Z5fpUgKktZqgzdSwrK8OeP1WOhJAEfQVVKq+EOgNliQ2bPdjwhDYpy9ouIQD25UCCr6ZPK/4vsFBNwoyFvdieWaSPGn8b7HZJc451DYcsH7khRvzXbxqEXFwUBpIgPBq6VraCZGjEfNpugIGkQkPwOtCRyAR6kUyX9n6ODEScCu5ccPc+txw/CSmyIdazSqd/vUr7n3onL6Hqa4LU+OmSihF+s33VXozzodL9s93BlzQDBcAqMtYQ9a/0vgE4Og5MkF0FU+jBzm9O0G2Im1Vp19gB/keoxP9tkrT22mPlrIrSlACL2DaByiKZryVqeOPFwxjXZ8FJcLNQsjIw2LGY5WU7QtVo4a7apoEny0HhjgvtoustiReXGo5+6CNIL0BJxeXsW7uZiWCOpSxpoSkcRotHytgLx36zyf9CVc5kTbqepohHS2YiPmpkcXuVXOsmSRL5Atmw3wTd5UFI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMzYzNDY4NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zMzQxNWE0MmRmZDIwODg5NDBmZjkyOGQ1NTFkZmI5ZmEwYWFjMmFmZjc0NTZiMWQ4YWM1NjIxMGFmOTliNzA0IgogICAgfQogIH0KfQ==" + }, + "U": { + "signature": "IVpw3CyCMVhCUY/cPdJ18lmXNYTTG06S44Cc/R0ujM5iUPyBoc35rgjl0xnAfavC4GaLdGN9d0wh7uSksrqfEC9oRMeeG/9zKOyqLmhdw0PZ0bOrKM4tx4V7EwAZzC0+1FBdhFl7UXf76uBWlOr/7PJz9sgTLPFmAteK0FwCUKaZnsEAnA23ditcbGCyfoVXveERlrS6G4L6rPCaVNS4c6YJ8vIgV2QsgrDKYuqLkBRkt95Sp0x0K0eZwZgQ5gy56Fh07Py1BwvSzsn5iqZE1Y9RklcC6Xw3SO5FVd/kHAMLHFKfs+F4yV8tIZUKlS4Q4E88wC7Y2uvFEphQNf0hzisaCp4oDYTGp5pZSOtdY3WNLuV8YXxrzFchXLGc8zi60ROAmQ5RON2cH0g9q5pHODzbrxMyMTvfv0P59CGOtbjYmx23TjnsZwJlv6f39YNCgCMzj703eD18NDWFzR4U+XvIW5cPNrSUOdwnTjw39oCr6zWK34RTgUOqR+xtrE+OMZQpP0kw9A8yKmi4+0bKV5CsHACiowlV6L9QTZ5T2+0CwR1LOQc84caayPxmiUcrZ1DRE631oSQ5Y7F1KC36iYSBowR0eTiNAOxidd4wVXHeiILvfnQhDu1hh3+VKvU/UDh0oG2Exirxb6uUdwDFRqpcc1DufQ75uoRqdDowGmU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMzM0MjEwMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83OTM4MzFkZjgyZjk1YWY5OGRmNmFkNmNkZjM1ZGYyMmMzZDU0NDc1ZGY5NmU0ZmJmYzE1NjdiNzIyYThjZWVhIgogICAgfQogIH0KfQ==" + }, + "V": { + "signature": "ZicD+Js5zNU0Kwmr6y0LwLlEuiokLYZJobi6BS/7d44FuoiHwyZqhrOQ1rY5Sox2oE7wCaijH9ApCpbbx60Zg+2hr/sQMI0qKixPoBNv9/3YZVvF+KZvhnnImPzl+qRRW6yN0nFA+2Ca3ILZmJyfpeXiV3ivPaKyMk3sT7rC0eRanE0b0Vqtfy8lQvkl6qpPTl1XLWuz1/divIuhVUTalnmtpcIh7QCTro8aMGcnlqEIrovS+XsYsQZSDHKc6dcC+yyzqFng9NBVYIN7GPvGKUabAGn7MaPkVPie2kNVrnlum1sBZy2qaj039gYPBodBMML/Q9jeRPhd3sNr9hjP9/G3UwqnTI2HTBAa4Y+pzDQ7YQN/ShhMOEV/jrw1/wVZropr3+Ae8oZd3vgp1lcfXffyQ8UTJKmkAYQyhHjJaRye1Q5+rtaxo6YOFuJ2gBH4RRJJbRWnPW7LFpmAt93cQcQJiOd5JpMc8eIo+JAsp5HLKTZFJjz/mm3sjyiugJWD4P4tozjrTwo6FniWKXx8MO2IzHF1vZ0lP15w5ankccYiWN+P/O1fMjC6Q7pERI92bwWu5/V/k3rce8LtuZ+yMJLRbVVzvnDOmJMq1HR/b/Dt1v4COMSr048BqB1tdduJQSgatJBmUXixecweURjUTyzCNby2sy6NqwrK1RWX4mE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMjgyODA0NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80MjU0NDA4NzEzODlhYTE1NDdjMmMzMTBjNjM1MGY4ZjQ0YjBhMDgxZmNiMTRiMzBjNzg3Nzk4YTIzOTdhODI0IgogICAgfQogIH0KfQ==" + }, + "W": { + "signature": "DpxUTU+KpW5+dTD2EVA2Zi9MgVsXyBveWvoHQtqLVBU+U0lPpU5Bf7Yp3GYN/KAn3mve0bkQV/mjzjgw/mrX6nkLSEyvR+Ms3Smarut4jU9D74V8q6yQkL9GmLZVEzCE5HjBtEmZjZYJ5U0U5CLEyyhTtSRBYvaUXFX7ZaYw8THgFurjxhkGGXsSe1mD1ciuYtxRheU8sRHU09fLmooKfV7D1HxCmurVCSHKczlSFU3fqVsK0IGw8xQbM7jvJwxh0OqKjssfBubxA5Yx/v4jd2ESJVxgbb0i2iWxSlTdB+Z+n4LPdKaaqy8XhyqYQBqK49eCRTVUvPjPXfpYg0DcIwYzYAWJSBgvwzAUv1kavpt+IpOkfieZeAq9l1gLyI6JasZVc3l4a9czBuUD5yGNbZjOwqLvgSEYp7N8Rz+OHz8TZ6o0Hfjpurqg0g0yq5zHPHtikxHBotRgn0QraUOGN+IbgC7cIIy5JU9ewtmk9YDa4igEs7YxisO7aJsY8C78skEPkKmS33MVKn06x7fojWsIchDvHMWwf1MuDX2+4GCpekEGrom3MvnDdyWORPxX77xmP1erJD72gt/BAblKLfkovIYgUBzhVcJkDK6ZT1SLZ6WNkLnNOK4A6OLCcuEgT78Uj2OYBXH8KaljfN6Ujfo8RHyExZfpQGLygsIiTA4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMzA0ODY3OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84MjhjMTAzOTJlYjRjNDZjODMzMDEwZGZlNTIzOGM1YjlhMmFkNDQ4MTdlNzVhMDc4ZGJhYzBmNDhmNjZmODBlIgogICAgfQogIH0KfQ==" + }, + "X": { + "signature": "OSkG0g3QFeHjJ+b4O5greJRPweTQBSYVz3t7GWGb/b1+FpsqumJmIqFiCpfA5Q+UsTZ8lbJ87BTWUGB2u087vZb+cYCIUgeMBNv1DKeTry6YinOdQxWCDqNsVMoTBe1/chkVg5JNFSiqZiYWln7rLa+8tnljG6xrxfQo90W0p0ivX4XfWhhJlaY34uh+SE+teXeO2nKvMsWm5r3LRx/U/pdLVH7OBMgiAtWNXJ7H8RnwU6ZO4lhJNXtwLkPn+6eNy30k2NRhNOGKu89kcJ3eqvvAcvK92XLvHKKfXRUsKFlcC5YmCdpLjPVS8F1NPS68CxxPaVOErPRnueA23tuMnRUmPl82CJJpM7ZwA2cruHbtLT2njqRNS/mO6+VOkqJ93qsKejDOAJPtuHwg906xATlk4y8yBreTDWOlAIEM3eXs5duJfa1ewW5gYDm9smzIqw6flpRQ6azaB8KDaKahPlwU3PfjzLoCZSk0rwE9lCXDBrQoIa1IBS5J7u49GNrrXyCRWkfzOPpkeuzKgXoA1q/EEiJ3+GemlwgQOftMB6M7/9LFcWgE5+5SeOUaa/MxpG2Nhzvde4ADMVA5MJxkdVttc3ZHJ4oSI9tV+Hy+JwcKm5h7QGGx9m5MK0pS/hmLoGUBNtEAKDA94TNXVGwNApPrcWrnNmhkdkoS1mSoQtQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMjM1MDM2NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xOWY1YjA4MTMyNWVjYTUzOGUxODIxMDU3Mjg5YmFlMTNhN2VmZjYzYjU3MzUyZDBlZDg4NDQzNTI2ZTc4YTkzIgogICAgfQogIH0KfQ==" + }, + "Y": { + "signature": "Yi78KKyvGTiU9xqAYXN1zKGTG6bosr1K35308guXJqK3tMGmBl0fDp8IIQkZAZmFjR8gJT4Z95dPvFWzimLIifV0Mm+c0wS/hB0qBHu8kANwJZIvAN0iHj8YJe2qXHP+l8taQEF5LLbXKIkwfUHPkTRYVlu2fvyoeBn5YvMZhJLe6EYGAlrlPiX+Ei/uT7HeOs5YhIT3o2kAtMo9GquIZZNHiNiAPn26rrht6J1tYz0pPF4laReORU36V8m+vXSccmyn9jIcRTsYDOPACQIItOkaOr8imBHmwockFwiHOEiPBKPK4JKt/nO3lJUpw53rl+4Pq9Y6u5eQYNJQ2vYMidPHJwQXZYVraBKxPMDeZ61qYzk8pSOxWxhQNC/lO4bWTvUNAgCtwR0TwVV1WfuXF0qXI8mm048xCbygtFgosvul0cSjkgfUjw40WzVIixrDYJxlsv8ATM92Jn5cgT2QBUhYlqbJA2KHwGbL5JjcJPdBblhD6z9uCllLXBg2aaY4bmw0cQf/8mhRlSakex2RgFgPWY/jC2csdGEK19NXipm5EBFTm2YZvJivYwpgBrjT0l1nwSRWXFB0X6JcSblgZsq5Tt4IZGAXwlJyWUau4LtTp4GGrEGWtGsChiZnYaH0qHERyEjxgycy1l5pUST+bTdB62UVop1XsKpz7f+ooGk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMjcxNjYxMCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80MGEwZTJkYTJkOTFkZjc2NGNjMmYyNTY0MjIxZDQ3MjBlZDBjOGQ3MmM4YzQ2NzI2NWNjZDIwYjczODZjYjliIgogICAgfQogIH0KfQ==" + }, + "Z": { + "signature": "Wr89HRBwpv5zSkVE8CG7QjnsOP3KiBNpmbzQOeQIpkzNfG8WJqIMQ+mIB5mZ1nytxNvkGWH0+gMH1cUZ6KVF4W6eHoaBA/Zf+ZfEYmzdHYAOHZ1hFv6zgos3ArzSQllUlV5de3JN8eyvfxL9AmfFBusXZzFfxVZQfPpLrVRu42t2S+SFZHYE7jjtXmcqjYZpHC6JD7h07kguIBfG1bVNMdiWWiBeRXGDQ+kXLb7jUD2EqEulT/QcY36dC8nOcgxq3pXj9/fxTEoYVzistBYMZJbf9wgXh3t0nVEM0krTAtSZBabSD+bey2e/5PgbqWGccPMr2LwzSS2E8u2/flqxj+mEIB0EIftveXMIyWn2kzdlGk/P2ucIYwXxXH/P4lIcDV43w5kNbBQj/sW8hqHSzWFOWtwZpdWeV2pY2Kp8BTs5C+vNB94r1QzcYlOizTjjqIsyyXQ4xNs2KO9lJj1jMKAyrypDg/jlzoCi0+H63yj0CMnpkzzyxn3bBuo5RK8rvR5UtlBzvanlLTVHrPu+YN+Q7MxuNqu6diSrfRkkWRciJDPeRRgzqtf0feRjCNxOcVMWlUBVrg4Od8lwmqgzVajrFB9FZMSH6SwipXAOfuY4UZ3WMbRqscUtqJExcY6JuIolJTbuwXt7QCrdM3w0MX6wLM8ZH2yhUHhDIMI4WtI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMjg2NDU0MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83M2E4MmMxMzc0ZTU0MjI0MmY2MTA2ODYwYzk1NDhiZTI1NzU2MDk1YjMyZDBmNzNiMzA0ODVmNTFkMGM1MjFjIgogICAgfQogIH0KfQ==" + }, + "[": { + "signature": "f+jdYa1yFWBrU+Ei/vhZKSF/x0YpiZCh+ZqzVvqcTbCaixGEnVvTN/yW5L8ucTqO4tBXVMGDQu5H7kox6stW7CMc8whFGXUdZpsUDKK4E+xogI2gszBo6WwAxvxh0UCJuaXCdvyDq9bX9Jqekv/gOsI/WdximV449O89vqkWZ+Zh2NbSg3BjsuUL3McSi/HrRznA2LvJ491SGpBw0UyZsE1tDZ06fWnrRvpcksq3Qfb+Xc6NpBtKk1PcrfkoZz+WF0WJVuMHMjJru6TtR+z83FQk72jtoa5b1P1t+ia1f41kgSVL5RjaL7Ysc6k5mNe4+yWci/qmg/97KhNfIRzycW7vhcBhp9WOMzkJRLt3kHL4onOl5rGkUKx83Q6C85/TgIvI4qJHn8e4Cz9vnRYdmZ1bN5NXM+DWt8lL0gD6Z2nELAsaIhMwUODGSoftJbi0VQD68lzDkXqjYaUjGLH32ZNpp1lC3kU/wyX7lziiq0NS1YZOzTmKz+22XQPTysa0iD9gbeWqtk2Bc9GqSu7+eYBjB+qD5bqnlDXk1xbuxJ39RE8WODSjD4D9mN1vUKFEVo0VD3EbX5TpSSSt0s9w11QXGcG5QtZ6k4u4kxGsgoejIH6S4FheOzZRAaU3Qade82pj3lHaj49MJ9sHgOV4qxKMSGWLVvBe/gXE8NE7NMU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMjQ2MTEzNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yZDRlZjhlMDZmNmY2Yzg4ODRkODU0Yzg4ZjM3ZWEwNjNlNmRjNTNiODY2MzgwZDUxYWU4NTQzZjkyNDIyMjQyIgogICAgfQogIH0KfQ==" + }, + "]": { + "signature": "PPM997wlE+6mdensf6y6FPbJI3bUc9xgcEcctvuELtYkXAD+DGKxa2ZVfsHC/l1I9xhZlxSbNC/2iwfHCDc0li+QVMtXTlHC9a1WzX77O8kF/Pu34mtukz8lPBRiRUyZsbiLPD98zquZT/rQ8Ko4BxfsGwp4M2Iwg2CKuG6X9J0gzjeLtbIhyKBy78UKKZCY0HDppag4BiaGeUPqCMdShWXCBLzajPyMoLbZZFNj3I9Qrxb2ZhvQoQmarOpep+d2amzd883UQUQMcql33AbBgMSCfFqTVW+Dlw8KdzQxZ132BHbZc0yZYk2+AIUC1nnKOXlU/iUMPah9Kppx/5pQvqn6P2oZkqI7+J3CyDYg7xgLnLWH7I/B6T9dD7RxlC5t+mIUtjGNY7AmcNghyLHaMTsvd/q/seK3ZmmrF4DSNnHIlcXxEmLeNPFuA/oT8/4YgSNiYmuxxq/a9+K5y43CFLo3nvCat8NrtHcctq1VaAmy7XYau4nCk17M6fLkmyqu5HDU2HE3Owmiv2vTDgcUida9dOAgWJwyeduGBWDovJI71THVIHgsg0L00prY9K0LEkMpYJPIXC+atFvkTOWV0i+ZVdHY5DfAZ0tKXP9XFm24p0VXtqvzwjBdyY41YQHHxla8kEh3umFpMeYnclOgl1qeGx+JrWqKSGBXoUTKSAg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMTgzNDM3OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80ZjQxOTMwNzRiYWUxNzMzNWNhYTE4ZDI2MzAwYTc4ODIyYmFjZTA5MTg4NzRmY2M5ZDllZDBhZWQzNDZkMTBjIgogICAgfQogIH0KfQ==" + }, + "^": { + "signature": "CxwgWPCMyTh0GI/D11chBag21mHpjcLXbcPdhKAUSzE2BVV8/3j1Eyym9Fh5DP7F1jAf2HNi4u9m3TIONqsAy6uiqj7Xj7PzKiCAKEPM9fO3azRyViIqcRNRM/A/HBVD5OWbaix80XgcvZW6wuptRt1SuHTP4Ttrt9v7fI0v5Gyu3ay9amIbY0n1aqExWVxJGAFOZtS6ef5lh/RAa9Cj6vJvWey81/l78e4kZvMJvcdKG2IyP+rM1cZd+gwLh9SZjgwOt5dcRJBgkNkaarCFkDf8Fb/3c44LlOm1XYS7JsuF0nvQpadE92FMzyI/+9W9cVy/BiOJ1UympawNBK7PUxMLF/5DOW3RNKf7IrkZnjnk6c8hteT8MRUp2WhHSGaVEmO8w1ASSA1EWJV1r0rtbpj7lUX3xgwyKEwI6vRRRh7ZlFoYCyYSAsKaRSS3c6zupa3m/6J/4XvBnqcXzcnW9OWV40k0BKhM0x1Rbpz+TpVzqgAoI6Jh+ycqZlxdUhT9uydVNCelW/Qy4S/q3s38F4ls4OBBQysL1uDKkktqMhna5heGqxbq8+DboKExNgch2m2tLA8sxasSsJCXTWDu0iZJ0pDavmg58dkps527SCSD0x1BLbDHpl+Px3ZoVnmgC3cHHwLolUcj7VzMBO58grMdmj8sHU7jmGgOlrDSxQ0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMjYwNzM4OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82NDljMjE0ZTM3NzlkNjlkNWNmMzU3MzMwZjEyODNjNDU1MmMwNmI5NTk1NzA5NmNiNGM3NjQ2NjJjYjU4NjMxIgogICAgfQogIH0KfQ==" + }, + "_": { + "signature": "nW2rdHcHShGtbAg7tr32nVvPpEtR3J/0vQEK1OmrEjALO7BrU5695lP0sxuVBcpQfQpj//L7ciGLRkpDETA2vSARbOdg3A7qcMQPF6V5gwzxOyayo86L9b5tiPCUUf2myuU3+wvnsth/wt+xzARS2nMXAAkV4+GeXqkWGHfinRycNPnL4Dn5PgpEUy20abO8arhYqpMSeabA/cllo1cCorcbotBoNpDtQhXHR5sOdFslxk5wE3AiEdi0NTBLsW3zxamoeFv7Curg+WxjUkhvANdweMDUF+LxNJpdUy5O/WCWk3b78sjrV/YEvJb1qHBhBqXL11AYr3iLVSoHWaJyUVG7LyB1Kt+iAwOEeDhjFxgiDCAAwjVuJ++CtXdVHEAt9VxAOX+1H1AT2lALTk4JFSpcqOt1bU8kqvIjC/ltI4HF+EeKYiLZi8FwPo7b0icNqEnwUAJv96oPzEedjD54Whqj9FDoaV/yDqXkBlmPpK47RaveXO2TQjKXAwQt07u4ou/DJl0AADbkObuq61gUEGmMp46oMqkgS5uaTtqODralkGpr6YOmJVcsJe+7WQu7IA+C3y0kFt8nRgX96/uMAlgPwtfbQEGC1ZxhFOFSKhRQHXJU8sv84c6Xa0hFwVmEmvkt1hlf+gxoHL5uGPQiHUNw2F3w1uWI92eAmfBWzDI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMzgxODA3MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zNTU3MzgyZDU4MWE5NzZkYTc2NmI0NGNiNDEwNWMzNjQzMWEzNjIwYzE3MjUwMTczYjI2YzY2MmNlNGY4M2NmIgogICAgfQogIH0KfQ==" + }, + "`": { + "signature": "TtjR55Ho9BH5NjlTmArfA91MLHVg+ZjcxC3DzU85jm07hShQwxXMGAr1dA0/h7QeUO+UJkprXkwKgLC0BgLJX5urC/nMenWcF9se62tJlvSbxkZJ/dLTJoj10W0kAB6g4P5s286rmRqU/wM0ksxxpu8OfOy+J7r9HgdOwDTrBHOcV6ZzZ9/OmmLqW8QBgecKuSxvyIzubAOobBUJJFREByfxLYkUeeOD6XkUm9Gy22W1YSQpzwA1bK7Mvb+XXnB5Pew9hkk8JFLs21tUcnHHxrlvE4sl7ATuP+WX2UI5TIKLI9Ar8JUSN9tlGHaXPn/TB2lIq5K9jHhlW8EMjBR0sS1O25J0FihwXSxC9MNQt3Z/xnA9Dgv0bBsCqz+GWMbsQzUVQT4gilzA5jYR6dUTmEvfFS15hulClNWeg2lkAgcl1gTm8NVCdb59BAlCeeqTVSaXPspLOnW99prJgFJ+Kl67jAdLfR0jMu2nB59mpSJrb1oV/kZWKrvDIC9+L5qYTpaoKGmG7o7+TtTUNDAqUa8GfSgDqMo9ylVoD4RC6grjxM2W7vV4/meNPpdP7pthzO1AzSqSViPo5bPFgbge83QOh4MI7NO5KIPLD4SLCNVDhVWJDBjEAc1Bv6ZAwQdQl4u1VkhXQ8S+Latgab2wrMWGnl2gcjYB/DRKFzBDHGY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMTYxMzIyNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xOWNlNzcwNjliZDYwMmMzZTllNmZjM2I3NjVlODZiODRjOWNmM2NmOTc3MTk1NTQ4YmQ5MGJmY2YwZWZjZGQ0IgogICAgfQogIH0KfQ==" + }, + "{": { + "signature": "MbbiR6IJFtGSTyZUi+yTaWkFQoktvdLEAuGZVueRy25ldbHo+cOxFKTATb1u2bekxzwFcRTIC3KyFqpdI5vD+IfCNqGMifujIlCcpB/4b5ISjPQFZhjDbhFdXIfg1yBhaxDDB/PqWcW7McVTRrRKh2Ap5XbEBU9MZ6/4g+6cgn60UePFKYEzZSuNKUco6a4wuZOWeGjBDRR5fksMFxa6379oCyxnN/T9qsu/WOChvnR+lw61nW37qYkrhAxPSAvTfT6dRPNtkCQEpjleYALMCd67efoE508cBb4bpAFBDiQZEwrqbmTRSQE6drBjyYkJ29YR6wHRamYCB7UZJMBNPobIcnAcOav9KMgRDtcJZyCzwbKWfHSWZ4VJAdi1Qb7LtHpNsUApyzWHUAhpEvs+s9UBP4g0Q2G6xpvW16L/LrVsyWocpZf12yeWj83tMeXUzGFR4QmnsHmYwNlTCj2gdbd6wkFi1hYj5cVDU8TSos7VlPQ9PJEmmpaUxeodhSool0FncnnqL2CEiRueDTKrPswlreGIHn6srpRqnzoPw3O9ev8JbAblAieXP5VXVSxJ2m7EctCpReUYqYBUYtCKMG5dqxcqM6cW4gBJvFokqpAr+z+aFXMJntnxxgBujFH4P234HciZML89Bvcjy+3UqEBhW/hTBhQwf2XRw1vKZiA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMjc5MDcxNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80ZDJlZmYxNjFlYzUxZmVmYWRjM2RmZTExNDhjZmM4ZGU1ODNkMTRkZjNiMWFkNDNiM2RkNzZiYTBlYzlkMzFkIgogICAgfQogIH0KfQ==" + }, + "}": { + "signature": "xsUvxHcFSPRVypHKYMOcfaRtd2TfLa8r46Vnx6ry2PGWV1WVleomn1+uGHf0sAzj0FdprME3nQqY/Q9ZUVveYqGG5+pqv1T3LStxib2iaM3+Yr9R76DqeS8qWHsOoUQRsGvkpZqUV0K5tJD8VNS0g9s6Cr/RE+tDvBpC/E1xx4/7SsioMu631CylyWwCBAW1pAQshfRuV7rzUxFXw9sABlD7ltovDWMLNnVNZFbu7EguvDk6jhcIguLYBuH/c1cbNzs0Gt+5Be6m6yWqafMM6b4dBOnw176xp8wiIv+RqkqpA5FMGVz8W8qEibgBGqCEm2fzpOz+8Brd2PYUD8KKaG3sFQQ3j9GcanPY1mzhtY+mi+DAXVo+cIs+3UcE5fTiHcac2a43/Fcl6HmKbASXYO5Ti5CdtwD0uflIdhpzyb3zKdWQbkgdwAB+qglVmCrk8tPOWOk6dYaoyH3wdpGwvnlb2aP72zcwXOJLAPEoSBLe80VrUgTkpjworSDtcvkJUglrB74xvCYUJjfhWXXqcuC7/hXEUG/JAsRLM+eGlIZUZw9E01Eve4QZpyvME/9HRS3Ss+8G5Jg53farom53ySOr3mExgWSkFY+v8H8AYH1PKxLs26rsO3SPCLTvfnn1VhIQeEzrckI4tCQcPlurTPInskfWP46OKucfAi4ZGqI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMTc2MTY5MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82NTViMmU3NjNhZThiOGIyMDE4MGUyNTIzYzFkYjkxOGFkYzMzODYzM2M2NDc5ZjI5Njc4YjgxY2I4MjA5YjNhIgogICAgfQogIH0KfQ==" + }, + "~": { + "signature": "iBLXCoXhgE1eCDTtTCJdXhU0y8XD4TacrGh6O5Z9jT1CfXVww2P/PuweHkU2J8qSWstQiBQsnU++uZBpTSLEcB9bUxKp0993AILpfkqZa2N+7N4qyfDVd20DPdqkAmE7Ysj2SeiC4V9glapL+H0xNLMMt1VCr8tWixqFm5Qx9VPAeZYUgVRsRfZTmzlmNYheSpOUIGmNm033vd/IdRQ+xxMucyKsm0b0epJt0RcBreII7eZuSj7SGYpZ04PF/8aDVP51+a+55SLJCK3TcSDluJEv3woti+5gX/G9K0TQccqPYKcPEsi5Pr8l3ILcnc2lrjjDKA3r6AxV8VKeY28YyQFlTOfnu51eccXZlJGHuRdqLb9hqK43Kb5JwLuz2SRzdm5n0ADGYAHvTTSmW6yGJxs3Oa4W7U5zRawDZPzBT3fXV24qWtq7c1NLxGHjJZAgusweBfFZV0/V5npbF3p/ABzyRGtXks9/n7HU+WDL1GuTl8o5jyhF2D7ZsIucBcY98L6jwo9q1TrRmK4kTQ7HR9+TGf3rifsfWtZiQZeIHpIIL3qusK3cfv+HRkb7stnwsd2N22V6UUonH6C95OKNnFqR1tsSHty7FUYY0xMD3ayjUTMHw4E99Za+ybpMyP0RWGrriPF2jX94NUb8Mm28N2VhG9Px/F7XplnFm1yKgEA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExMTcyNDM1MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hZjEwNjg3YmQzZTIwOTM1ZDdmZWQ1OWZmYWUwY2RiNjE1YTdhYzIxMTAyMDJlYmU2Y2Q5MDljNTJjMmY5ZTk1IgogICAgfQogIH0KfQ==" + } +} \ No newline at end of file diff --git a/Network/eLib/src/main/resources/skins/chars/orange.json b/Network/eLib/src/main/resources/skins/chars/orange.json new file mode 100644 index 0000000..061c613 --- /dev/null +++ b/Network/eLib/src/main/resources/skins/chars/orange.json @@ -0,0 +1,266 @@ +{ + " ": { + "signature": "DCZEHI0wf/v9l2Krio2tRaGR/rwn51IeQ3AZ9kmkC4M9Pp9oIp6ZVAvNwizquY1ckfpA7uyZVmRTBRNwi2eH9BMqENKP7Es8u+Uix1rpta9kp4/e2Z8FUxVy8lO2aql7Tu/x9mP84vRZW9y94nGiOeV80wvfO01bPMmAYgMcm7QracJflzPHnava+zQWQ09vwksurt1BOuPxePtVQIFOZ/VmRAgeYVhqaQVNKW7Li4D83l/TvCiWPQrOuLiAuBh8E04FjRZJffZlRnUgqaZG+zHXYAGtpQb6YNLjGVm2psuskvitXPm9g1BeJRUfcl1hkCqZCpiOk3Z2xg8iO8V4r4tFCAri+LVuBHrTYCkPRocXWMAPhpMsWfGCGeJn3do3ati/a82Xd00dr1GAZzHI8F9yHNZZqZq2keB9ceX1MlaHEWLeiQfYcEGRv67mk1RSAZS4GtQodewH2WBFXeq+FkGEsQYJ+MtCxT5yOrH41RGGIlRjd8X4YWmuV8YxFXWxnI+bcu3vAnaTAGBJvaT4zd/ld3XiYN7Q6ixd8eQJVEfdIrNrc+eOjUOk3zf7CVDOagoEC2OCtRbvByL2GOm+8ceOlGi1M6oV8kxCUAIPbH7Yj1NKteo+tJ4N0LBOGV0hnCm5JEwb0519wjAYcia33B+uOvbt5puKMTT+79GcAkU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwODkyMDU3MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85NTIxN2ZhMzAxYjA3OGZhNmY2ZTEzNDc5NjU4YmIxYjYxYWM5NjM4ZmYyYTk3ZWM2ZGZkMGQ0MzdhYjIwOTBkIgogICAgfQogIH0KfQ==" + }, + "!": { + "signature": "Dd59Z7Jnrg9cOsRy90d4YkMWM526JD1MCy+QxS6BKjbNgA23CuNNzutMqZ2W+q2nkwK+wQht2poyF117vPMoCwP0+ROPLvcbsZUmLAttbAs43kNuAvI46qwuMm/4cZnTBGIOnAEOktbx62NxHfJUHzrue6xX8HHRfcPrVVBPFCQNq5GD1JmuLsl4XJ1C8XKMW14vBLWF49GUoCk50gH8stct2ist4LdMd6BQDQGJzbNjuhUwZgKkIipNkrVNuzjeZsdHVeEDnTswBVHKUw+udbqcnpf7XxJfQjyjGcJ9p0j7AWnjLV0vRo7hvhLY6/bUvmmFiez52TaP8uWUkz8ZbuaBN1Nxf8lcWtZnQJ8F6OvfmdPkCOiGLOl0fI4jHUY2VHXOHlSRHDGrH1jTR7SxhMo4z0hU05nZmJ3uIjp/merPCNfbTqbFbXtmtBYPtszsvWFlAJ5YJIAwhy0ortLdI+vq3vaFiMivBgOekiJZOflRHO3tLKbASCFURwsU3BLjY6d94hs2tk2z8MZ1RAlD1gbvS/d3R+NPIs4hSWkdbQBB7GwijnRSwBUqM7mzloH0oFVHP8j40evUrmjVOh6R+geOXL8FkszxGKb9MiMX8dzV7XjrkLzpUK0UDr0HIRYuO/UZHxkW6pn7lcY6WhDqxB3binTQR5nS+Fs2jmqWG/Q=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwODI1Njk2MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iODIyMGFiYjliNmJjOGE2ZGNmM2M1NmQyMTA3MWVjZjFkMDhlNzgyYjAxOGJkM2U5YWY5ODA4MmU1MDQ0Nzg4IgogICAgfQogIH0KfQ==" + }, + "\"": { + "signature": "GyRg3vYmpyW62iW6tHFC7BzHD+UZofJYtjdc5T7Ww+BfGSScF6oB8jvuKIh7Uq6r13L+bv/fnWcnQqd22AFm+QQs2XR+kpd4u0Kpmwa8rmFZ7PGr8bxpWgIEHoUmnubG5Bvsl5KhqPojmJrELkgdIXXtHtUXU15t++sZiapP2rn3nHse5mPBd1oncICzl6PTWPM+ljrnrNwFHbmCsJMErM8q/Ss+odi9BE4zT4gvkfF8Zy77JneKk+a7syLNJlV7McD+Vb41QlDTHtLyX2Zo7MVf7hLtaW1357b4g647aRrxXVDDbUjFNav0b8uirRRwx2FEL568syPiL8uyE6UBRrR7NxXUROGeldp/E7WtCYAVwHPMtbwcGJaFVKqbndmQ6a/th2b2b/G/P4SJulkSgbpzGV0FsZr2hIvNzXPO4bv5WBsIiVJRj3bq0ic0FJbabsKPcNF5RiRcdN5m2Wc+iG/l+aCwsUwoqyG9PSq1K2Yq5ZwzjBKkAJjvKDAguOaDOvB36T9DSQYF7XkCIqfuYRGUWW+WbqFZV+n9LrDRu89ouP3Yi35QXvYkS1w9flcTa8qMMZ3LvzVpL3XFz/OMqIwx0eONBGF5RdjXSIsOv1q9cSCJinAlYOlWS0iwWwLjcvz/iF6qMSpmNjpJXYXe1GHIRKogD7YxsoRYrG7QNck=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwODY2MjI5OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84OTZmODlmNTg4OGYyZmI2MzNhMGFlNGEyYzEzNzcxMmVjYjY4OWY0YmFlMjk1ZTFlZTdkMjIzZTcxNWNlOWJlIgogICAgfQogIH0KfQ==" + }, + "#": { + "signature": "Yl/lNx4H7XFKH5nKqQ0knf+QN8pL6F4ast46FJvdxA7xOuSCFYK+txHrpyTETWkvrcbWReW+N6He2Vsz7PwPTaEy1zMnk/AMuyW2lxQ3CAwf/NiLxEqrWuf5n6EoaMRfRDbewXiaTqayZWkW+Np7GD1p0W0eZrfAfwhBRR+QKFtRB1Eede4K1+huETeOQ3eNhSWPvxGK8ELHYRfhIjv/NgSwaY5rHbaAaEzdgxQMV05zFlxLhibvdoZtrurUiL0vpt44O1WuwhFVC4R/WJLjpEvECV8UUZu1DTyn7HF9xdqaS4XfWPhFYndbT47AoFtVKFNkvQKlULWDnpFAddSOwjW3z6yLOr/m+ZMJwAjbsIoSsWwD/fOtbcRmWVohjCphE51a0KMOWTR5Es2sNU+nub3sLkfDPXZA2ykQ5QqzV2d2ePPTqX8Lrdww8cznvkTCi1bY6ry0ll44WZ5IAV6NsFPGVkea3ubGDwY+6SODpWA3FC2stoyvZ18hnABy2Rg4hohRTr7eyBp4zd2pQAVum2aKnuFlGlvXIO8se2Q4ikiFQ6lINIlD0y/hNWVQH+gLeCoQUCPIivy+wTulpmYiQk1l7FDy65nN88V/4JMHsBplrd2EUkIBON7W7Ve5JWcKVOcFBUOtN5tFecI64XdnmMiZhv/svRXLSN9tif9M7lc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwODU1MjA2OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84Y2U5ZThjMzUwZGUyN2E2ZjAyYTE0ZTQ0MjkyYTM5YWY1Y2JiYmNlNWJlNDk0ODg4ZDgxOTgxZDZiOWY1YWE4IgogICAgfQogIH0KfQ==" + }, + "$": { + "signature": "Ud/NHJzBEa8jGQ/yE7ujQdmRS3W9lTR53KwijdXR20kpYEr0tVXcXf52FFiHcRTnM4BtwClWGy3SEj8cE1sxQbkstgZi9cT85I5ZiqiEeOgUMqlm7OWhbr32E1uQk9TkyhBnM4geUHphqw9aTZayHIyRwQwvaoP2eqj3vA2KdUHAwI0VkoJ+LwGLu/57AYf6am1Zneer9seY84nmZwEpPDav7soRGaVGXA0oA7P9CxZWBZJUlVxzPcI20IGkBn51Hf0vfswU0QFOHfae2DkQDdfkX6b7bzBQUPHdl/1hH9uWSq8hmNzS9F+1eExFq8XLgcz4oq/HGSx7GmP/MSzsi52fO9GuaXnmwqIoiMXINfCt0oDd6FFBYVXyf1IXr450HCSpdoqCYWyLabtJgjSWHuRTflTViBi5jbi0i1pmvoIXsGSC5fo/fPRkJTXZmzZTq5OO7ou57hJWyInEV1L7+I/ly7yTZ5RSROobYEGC96uEClrP63c0L/PDrtIjxo+8Ad0TGuGyGrklGTkydQrqXUBYMy1Creem5YzMTZlZuIFBQxU1Gf/Ns+7Jz2XTEfbPAePhfmvSESVQnEAa1jjEpZVL86axPW2MyDT+V0FUDxDllJMOLxyspLmwOJ2jElyFcnYR5fJMiIDHfEAuxa45z1kHe3NtFAfcSLdhhybQgLE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzAwMjQ0MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jODUwYmJlZWIzNDEzZWQ0NDBkMGYwMzg1MzJjZWFiMmI3NmEzNmI4NzBkYWNlNGI5MDIyZWJhMTJkMzliYTkiCiAgICB9CiAgfQp9" + }, + "%": { + "signature": "ZVvfAQOuQgwBGrEvy0cF1m4hYT/OLaOfhfzg2YEmMLjNv7ZoLUwsrVSUAnja/iGTq7RH+B5E2VALB+UjMzIpT5cEAdzlfGZDACJodJursOBNnhtYlgjQwAos4kciiR5ncoBmGNyeB0z1Sl99GTguNFWcePNT+q3evd8NKl6fmdvTap57Ok8deXLm4GAeyIkOXNW6YiupSHH6YoHy0S8gcp556UjF+DloEb2vPoKoV+IKNDy5I2YUJ1enJ+o0NBjMVA+ZLzlGsjcYzBCPZ0fNfJTthRR4BCdZiT8lqW1fBAI04RwjWryDVnd/74HkBQ8Mis86RdHSwrSsC0UxsHcAKIebG3LqUL4cO4gVDOxrfbla62kIPtoLFP/Rp+20+VjP+92oU735D4yLWJcrKlnL0wdGmYceZ0IAdhlnA6HdLLG1A1/KNW0p/kkgOWg3+Qe3J99Jb+0zIKI1k8EtvP8M9mFgMyLvngEyaVxDo1EhUK5Vo6R4uEB5rNMKpwttsqsQLmWG329N4E/COl1yLMA2L5pvUqJ1qAlwRkqsbg0PCUBYa5f/x+EqeUCXhhoDEgSnAzRdMPrcX9sntZ7j6Ikb1ESv9lMgob3JZjD5WfdbEbVwzyQieuvK6ZMKck4FvQ0d0zax6/+flB4m2Zz+7z1dsMmC4VV8p7o4oYdFXL35lVU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzAzOTAzMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83ZDQ5M2EyMjRlZDI3MGJiY2ZhNDNlOTViOGExZDEyZmJkZDY1M2EyYjQ5NmRiMTk0MDdmZGY5MzRjYjVmMjAxIgogICAgfQogIH0KfQ==" + }, + "&": { + "signature": "rgggLPcXFb2vRu1WY1IznLAbShlLwCYdeMzGT64SdjZrlgDg6JtaJriYQksUVnEneKJAtjdXJVRARU32EyxO53LmYSf3Yt/XLcGJbniNkDwdgPSPL6jDBwepc2nw4EeXxdjmEpyunx5+GSy7ql/XJ1GVcimrFzePq81jRxkq4bGGubj5easaCmqoEUCuZDtrQXsbfn8Kyq8V0pKXIM91QzYo9ECWl1h+bOhtEXJ5RWC1sdOiearubzS34zkh9SmeFhis9uGzGdLWOkWt6ZeGDAhDnFmECqC3ggygLvdZamCRprCk6OjLbvh2P1cCQU/Jmitn9cAcXfc6F1bSIWsgwFJT/zitw9viqeMS9j4dVKH3/yxHJaH0AwbnVfw3rEoCMzfNXZi+gvCXcEN4TkRtMoh2C7XuCBEI7ObfoFoGTn6hs5L/HZIGRqmTtROH6ozgr7p9kh2JTQleIEbJmoctmdBs4Fpr8Wpz10MjsC1Xpu9oa3CSMDGKOZuZJhn4rfSOBuFjXMfmcK6P9AJAD7UHCGSZhOu11CpWh2LTG6VEam2alxPveRB6RhmrgEqa0+kiiZU83bOgJW5107ScoHJF9CCtqMztJeqZ/oYKWBklh5+rgzzjhoIQMchEU3DVYbqK+K3gri3Vbs8wBzET8GKHFWtgXbavzwjgZ3eXQAeGbu8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwODQ3ODYzNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kYjc3YjA5NDQ2OTAwZTE5ZDgyY2I2N2E0ZWRmNWE3MDVhZjk3ZWZjNzUyMGIyNTA0YzVhNjFlNTlmZWM2NWVlIgogICAgfQogIH0KfQ==" + }, + "'": { + "signature": "UOJxgEfog2a2cpXeJ6pzCN0Ens/c5+TnruT2RpvYrSZBnNgeBB6uSiGR8nFgdIg4D3LKy11yVWxTTkDJFZlVsbGi81PwAXbZF8Y0v+oMyN81FPOfgSfwN2sWxthXLF8gajTR61q9mVlq5/XDuynD8qvZIDFlvImxqCa/d5i2JwRPYihXLvjxlfZl45plItgXtZbffKFpx7X/PQb/JLS1RmqYnLnKqfoYY5thuPCZXWj9BCr4QuByQHO+Idh3ArS2QGbEUUvucV6i2fiFc74jK98hwjnbnfQzbPPDKWNV37ZteJp7coSbfAiK4Qo11mZuNfmR+anhtiuq6fKRHEfcUXXyDFukRhNjua4nIvoHLfR7Esxvak+u37yBcfyKG3I/18Jxmgfixs1XqUhxLMcGQEtEF+JDGgZwROMU9IPSgP/KurLNpt0rVUgqxQKD+8rRdnLQoJwPY1k7jrA++oaaQGYUjljnsXosRUbiOTgMvdXYAyPI1yeLEE435dIgCARHbM0UkcLTEB99E1TBHKOGBjHNaMnABX03+QSOl/1/klgRhNpVPpAM3zbi8wclri/1l6VSpNm6dvTREkr1/UTqANM/UrETA9y3oVhbJAElms7nCNk6YL7d6qbLyBIKoKUjmq2zGFuMmp5LBDKc8m1K49H0fPwxFcJ/C44BZaynCUI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzA3NjQ1OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81YmRkYmQ1MzUxMjY2ZTliMGUwZmVhMDQ2MjNjYjQxNjllMzMwNDdjYzk4MjhkMzViYWFhOTEzZTEzOThiMzYwIgogICAgfQogIH0KfQ==" + }, + "(": { + "signature": "ekWW9YYM3RxhQj3AkPWn+LN1VvVvjoizeFvRiVXDGW55Y8AnRc42QIY9Vr6yXMe/NH/XQMyFxc671YeLnd/sh1xDC0LXicROoynhLw67lugj6i2lVn2vvRNKTTGcM262YbTvVvdqTTExZEtPnpvOh1UeS1TT4ry/c6MfC2vXWfGo1dkKmgBpdy0sN0lJiBhbM/GS6n9SA/r5hTHILwjzzveiKaJA//mWQqZITBtOjyjqzF9rR+2+d/gcQrqJP044m0BTCi4xyl8xJ5NFNIe6lsp4gHoXGSEcDUPYfKR6AwSQ1iKVvdWFyCgP45ojBSBSe8I3p7Mi2mEx4+Gz3WqaDSAxCvy3sa/UpL2GFtIh9Q5mpmCDN0ybHBJj0NP00nYREp/bi06tw+nXrOVQYwdI1lDhjXzwGD03BlgrqkS4+geCXSRDgXYSudSSRJc1zkUNCmo1tCidEmCMeehkbGlKquo89ptT7oyQXAVv+rj01joE9PbJuKRzZyEBewmLLF2w1ceswI/7m8Kty5vFyEmijoW0xkrWSI+tiCGDX6OomMlZrOpMFXW05OxfwEuMxOzBsUn2dxoNIdudnzT7eOPYQcB7lgFZaPCFvqm5AKnqQriLy3uLm+nT5OxeQyVTfsVc6W3SKrnKCDul/23ibrXRMxQf5RvLCsCGxfxMUOt5C1Y=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzUyMDA2NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83NTlkY2U0NjRmM2Y3NGI2NTNiYjM4NWEzZmU3MWIyZTc1NzljNTFlYjBiZmMyMWZjZWI5MjM2ZmU1ZGNkYTY2IgogICAgfQogIH0KfQ==" + }, + ")": { + "signature": "goNQ0jnO+Cq2TXkpEUutg/3VA+BRDTqn2ZhIOXoeqnRIDMSv3ghRXGSPu005xHPCtUVfFlAcHrRvBSG3vgWQjwGq8FH1kbk0eoxAnPlkOFN+7F6vfKTpXj29BDBO1bF49AEQG9fVglMVqQF5+SS3yAxtmiOgJ5Y6o4BMof4s+x60ID7E2IsQLNNe7dceB1EncWcsXOtJwm+Mu2b061qn/iN/ByWfBCeHOKfaoOlkX9AdQUe5Qfccx5T1fbx1fKl2oKp33ifUqwgvjaI6SiGQlvevBjeu5QNEZCHtzcYebsPzg64ju4KaK+9WWG14cg+lDspBgGRadFPULESq0IxBc1dRgBHVBxS4n/FW+yRYz7lfYLcjFN00SbdopmDgO4sjL7V1VI9sCrQC2ODN7Q+HcwXDIzHBoGLw9kRXRyAK5Yj4dyYFBvi4o2gwpuOd9fQlcBWXdBe5kf9lfSSgv2RCtzHVGIk2pur9Rnmr+pkpEQOoW1UZS6GMKYT3FivgwVE7E+xiSf8OxwiAEejhMFzh4myj599QRrA+/FUjfj2LzONEkovWBxggPW3evzGkMhP407C9WIxBnfa/r+BJgMOojHQGJ/wPq9h1RSU3uzmaR7jfNZhNFH0xSLGgdQ7bK34YsKxq3qLZ7Zvwz3o6bAnkhZfcvyvoalM6t+1FbgLCZwY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNjkyOTE2MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84ZTJjNGRkMTNjYjI2NzJlOWI2MmI4NTQwNTBkNjNjZTRmMzE1ZTMyYzMwMzliZDViMTMyNTkxMmFhNmYzNzI5IgogICAgfQogIH0KfQ==" + }, + "*": { + "signature": "m1dHteeLBbW2HdSmtdKkzoYTNVNJTARd8UsLE2/xuDchYFYjvBlx8XtbH5zyLke2tw3AP21+zSjt70/CfgPfVmdE/UC79CuQw/OMyaMF5ubIFPwAE1uSGaYf0Tb5qGOXRMsYKhBdJskiX+RFhT4uvgQ57AKfSUE/rba6LQXjSAgNus9R7tlFWcv/8oanmvVQxqKQdYk2EYXcdF+c2+ejVepe1/lk0kJHKbHs+bpE2Dh+FiXWxisp7qFB8nhd6GZ0obVhwR9hw9dtHy4TO4A46NHAmZlSYVWkm0TNx83pxwcNATAmrMJPnkC9t7ifrwaPns0LDp+I52DCXHFrGvzjpor1gWek0ZKVCO6/WxJuhwa1X+mhPnweKzlQYltMaeQLwMCTSUPHOzioHqrn9VJEEsR6ajqId4MLnyGEXdAAcXavI+UeveYgA4VStF2VpG4PfsCNP+Kd5LJUhrd2FBdakKFOWZ3z/qTTlgW3vdmxy3ZsR1yjU3lL36ZTev6ObCfApMwOsiZBtmzu3U6OPAMq1ZpuIRQwiIevmPh/ggpx91xURQ45+7GHxvHKkcYb/xaUDJBmlFHNkbL7zMVgxnfaP+95shzswgpg+IU44ezy0/VOLiNE8HFN2izdc6KBZKxlBuQfvNIkdUbD9OO5HQJGQ6vHMQTSHMn/KMjnJHZ6fLA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNjQ4NTE3MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80N2M5NmRkYzlhYjMyMzRkMzdmYTk0MjY2YmIyN2NkMjc2M2QxYjkwOGQ3MWFhMWY4YmQ0MDQ0NjRiMTNkMTRjIgogICAgfQogIH0KfQ==" + }, + "+": { + "signature": "CFPnJrfbZEmUkl6XcjdIZZzwKq4ONUCE0TFeum32MVV2ifz9THIV7l26EbfjPaGmPPtCllc54NmtQojqRYd2wDlGBbxUUYWKhYbcoRT6H/GtDzExjUA+7sBVQIF55C0Yd47z9hSWaCQLPrvjHxHiuIV9kZfwKXJJP+4rSqEK/vO3D4rfscPtdVvzxyNVv53TtbyVjF106695eixzd0U9onABY2E8oytsJD1Isa9PVvC2iEbvKOseQgWt+eYgzjETYDIGzgS2Qe8PBgRdHXfZ7As48zg0iBZY0o8fgdkMDm7QlpEwEz69vbF70+lgrpCAAdG27WVNqn8IVIqb83aRIOmSXqRG/t3IRXW3D8wJge+w1lAI3bITVXDGzQx+ievBBKCN54UagMd7P3Hhhb/xH6Ho9gtST3SL/sKQyu0kFddOnxQgQ26CX/o2p1rhQNn2z/0Myopsg5IYlg+/zmuybHpl8vT3yYfHXd/HGxwijqKdWrAR7LpvmW+Gp2eKG7/8Eb5DLQuKuXkE4cm2SuunOx/JDi/MvaaVf2b1DjGNTC9xq8j1IxFVeYo5xog68bJ4mtiAFB2KUDkEMyEhtA0OtXm69f8e04AkkAwnUfpSZoJb7TFXVv3rbh7N+TMHd6jJlNWiiUqwxmp8D9N3VshqX/X0dp/Rr3+62cnB8w1U5M0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwODEwOTU2MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82YzQ3ZThjM2U3NzA3ZThjYmQ1MmY1ZjU0MDRjOTJjNGM2YTIwYjNlZGZiOTE1NzllMmVkMzQ3NDI5YWE3ZjFlIgogICAgfQogIH0KfQ==" + }, + ",": { + "signature": "ckSHsENB/rW2DIbe6BvEHcL1U8I5lwNY8xXMOIsVxrf/TBKJmq5imffN0hKKYW96CPU+wfkGW0DvsNBtbdY45AYvVTjCRZpjOIEOctvtKVg6n579zolNViUhW/hoLme3zbvDQo0iQRn/bHFqL2GUNNNX3oPuPtE1/ws9SWXBu+27obThe8Wrgpq4atFUexUA/44uikquwQWVEZsVh6HL+cKfY6Y80QFAN/RxqLuA1yr3TVXBgCraWnLv6Lgs5BjpQDnIJncdkdrowakrD3t5YuQ46raSZyZ5JchE3Qshp32hs+SzJYsLQ/1GGBYCno83VvAHYnvdbxB5jQLyiQ/r589Ac0gLWtxI5Ccb6ZVpgdYvyjodDFunEdixmZTDpKxO8hNRiv7WSctXUx0s4SPMKeEit3ZfZEHEsW21pXmvdt4UOEcLPJkJHdWsFe31cYTWVaQJAPH9yEFBkZuZlkLhxSME8vc0Rz/XDvnKNL7il4wGNgYhEB/rFDIbXiZVm6cIn9stBy2e2CmgtqYai8t9qJtpwog9We+j5ndUWPM+sIjZkr41XZSnARSf4KJ0MqCAH4LmUsYHc/3qDjprWQZI0GQZG/k9/cGRlrJLobo9z+clGDQ+4Uj+lmec54PJCiGt0Lv2VvdXUiRnD5gAvVYZbj7iMa9BQKDtuyafodf8Qis=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzk2MTgyNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85NmQ1ZmU0NDdjODA4MGUyOTQ3OTE0MWVjYTI4ZmRjYTkyY2QyZTNiN2U2ODM1OTllZTk1MDIyYmI0YTliYWQwIgogICAgfQogIH0KfQ==" + }, + "-": { + "signature": "ADw9pIqnLir/ugPD2+hmD2zX1VYG80IQJmNwbrTxjYK+ySpeLwnh4oofuh3TLct64Co5E8hibaTpVWHnatj8MMPE7tlpFGZzY6CuTOv5cPDvnpOBtKN//urXTlhwPHPhQl9HD2so2FIAZwiUbhP8XiLUbg6VL0YaWAh99fh/EDWpGO9J/r+1CJCLagCIkmJD/eq7Z5hfrD6yU3hkfczQASyHEtIl2I3/NgMOISdxkFk9h/6an1O3ww1w8TcqGK4vqyg5HB5Qv6t8Cf4peYQ8UaU8PUtp2o5udPQWlMggLSE10BYSmWXqXRgXvSvV9xrD7kZsik1tgD3YXH4qi948BD0UlzneMGmjKpflDeLEa+zf/XNbZk4sMmHUZ70TepdNlSXqe2lqqz3d0tEw+X3V0nJBXjFCOE/dZnamTh7o4wr+fVriKVkd5Gx3RFp6OiTTg7hEb7yXk7XukqCijVo3q+tH5bdT79mObR5IGaqObuzSr/PWMlnxfax93dSHhIlNvHAfEwz+jzlB2nSMuJ3DKKt0APtk/SD9Jijnv3aL0HQyNb7IpQLUL8Bedkfbly/wzzBlOTRG7eQhN5QxoG9DRfcuhX2E7pKq4A5A7nGRYpcG95IKdvnokyBZwvJBxask7DfVNJ3TOQ7MsTI+ayDp4LEfMVPU1E07Y5kReyzRrZ0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzE1MDAzNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jYjYxZTkzZDI3YmUzMzcxMjNiNGEyZWJhNWJiNTA1ZmNhZDUyNjAxNjk4YjIxNDcxNWViMGJiY2ZmZjZlZWIzIgogICAgfQogIH0KfQ==" + }, + ".": { + "signature": "GNqr5XreNUXbCTLrPlK4I240g/wxOSgjpwL0be56kwxivodRyIx8LfZe9PQ0D3zz/7BpnhyVJIck/ilmKMXYQysZmcyJTV0w4h6cvVKjQ8RSRr26g9EbGTzWXAH8ihxmC65kNFGUSCu0ZPX/Hsl9/k3Ski6AQZXGfV581uzMPFmXCjiM3lt2ALBja0H7wjJRGY8HO4QmFpTgo84g3hxr0Z0ty3ntn42dwFW3oY7FzZe8/dH4aLK3bt3nP4Rqu0aLakZMAhGek41MEjnjLUmmFm9IDPDF8QWFmUVvOfAeJNS8MZdwXsweq93Kc/iXLkWQXTwjgmy0bKdK7EJvCtotQnL4w5rMfFVe3QAX6CVb/GrPxG+Caygeg54a714r/RufdaRNTnAflbOaIG6W9y4svuoyg698ma2PbGMQvL4oozs8ItuE/AnZ47oBM3/azadqjghkHvoC/ZNkgAnDmflnAtD+VKQycSdT+1sqd+vUvlMkRfY0eIkRia8adNCYVTiQz4LIzTGctFc0KJG+8feX+Mfqt8O2qDlojIn+A46r17yNhRjMX4OojWSz7G3tpsLLeIOJPUE+Ci54vrKzP6D6HkxMhEA0VTuRbjfl12+sVQf+m+YyosOzdt9s8cazWMghhzn1ZIVQ6wjoTHCDpNmcwsQnlrKsgnba3b9Ee1A++u4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzQ4MzA1MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85NmQ1ZmU0NDdjODA4MGUyOTQ3OTE0MWVjYTI4ZmRjYTkyY2QyZTNiN2U2ODM1OTllZTk1MDIyYmI0YTliYWQwIgogICAgfQogIH0KfQ==" + }, + "/": { + "signature": "ciOobpUKBcCR+taIO5DXA46HIwEbgLufOans4rNaBAzJnJJMb1fqcmtnd5LXb8Ifb/odlqizHb3W2cWRIxhTWnOBP99kWeJXEghoBUsGkmgpWx5S32DdBG5SbIREu9b9PZQ5Bcg5o5udQYfGtwYhL2Nxg+e19kI4+mtkvWZ1BbXVhMYG+OyfC2g4bI8CIiwTlYvUVWDEX8D52D7HllD/f/36O77oSi5AdFYZJwiq38aV8H6N2TbVKPxlCYjGB5r9PgoP+i5M0HZsQLAY7vtY/KURdtsXNwuOodnatN0XvzM6P3Jd00L/s2sVna2WmG4KxKgAGfLcKJGZw1VbRL5bxle2tP0SOxHUi+woSQCHEUE7PnOfElJ/xrhkVzB51oGSNNxq41rvjmnSjhcrqp1P0njRCYMXT2LppywkR14t1+FYzcGMmd9OiwMq+Rqy0WWRMVDz0GsqewITQKP/RGm4tl/SKXNyRRiWrXJi1EYK1GKZwqqHawz9W0SwG2gZuSNX1br08u/P7ohpUuFFMvN5PYfzpnxw0KFTpDRILAZRehKgq+YmOZRvuSSUo0ZX7lh7FDSGp1VWV320fzZ8qq1u5W4yOsC8m4fHkAdtWhlMowrDg7IFUnB7Rc87DxKIXkI5yqVX558TwqEGb/4GWS/Mg1Pz1a6jPZplqpVzvKqR1fk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNjc4MTIxMiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84NzVmMTA3ZDFhNmM2OWRkYTNmMGJlNzRjMTUwOTBiOTRhZGQ5OTljNWI5ZjI5Y2ExOTMwYWUwMmM0NWEyM2QwIgogICAgfQogIH0KfQ==" + }, + "0": { + "signature": "vWoClTvFAn9zzs7rsNclXgXxrRaHp58ELBNkdsJ2X9STAQcWkXJovcZehv0O9J9B2EJZkTygSXhjGtxIJ+anT960BlA+yFIFfa7Sd4oDlamedvTaN+1Y5hLtEUQ1xs4n1Hyil94MzAUJMd9XMcozXS6HbwtBJEn+6HDGBdlYf5LAxZvzuyrsVDmJCq3jtWvj0K1oMw3sDu1BPFrc4jXM7iVXD1jw39LpvmzOKG42w4vXqs+xGuMBXUNFialkTkoWwJaUqOuoKasN7/FStDrJHfR7sEhtzPQwnFVC/5NuV6nbCe1ujbdNiZlMpjPIosQiy6PmPv8PyYJ8zslXq3YR1wiNOtZjz4ngfRzeVGFTJK/nv+mV6eMdKRu70ZQ5ICUr5GCrjh//pIuGqZEUG1imC7N40/bOaT7SvHk8f3oUQOhS9zeinOnfr/H9xtkc5UNuN+OvjSKaRejcfmvlJ39hXj+1Aic//s7uQEbLxxypKGGlP7EuMYTuILOdxwBN0CqFbIzzHTzoAzS7rNIKWJE9uhaBN/vipET1BzXGbiPiC6MP3zbbIbznTIaQ5n9dszrLUngWjTLmZbmNvpUqfgfe+BE+c6GaZ+yeHtsx36erL3g8wVhmakJDMrpva/ZXM08fX2WfVVdcu86TskEwD/4UMZAIvfKL2YDkwrkSZWzgSgI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNjUyMjIyNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85NTZlYjUyMDg1NTQyYjY3ZmNhZjM0ZTVjNjA5ZWM2Y2U5MDkyOGIyM2NiMzc0YzI5ZDdlYTAxNTg0NTVhODA2IgogICAgfQogIH0KfQ==" + }, + "1": { + "signature": "KG2CHQ5HWHIVL214gJ5106/W1AtQGLvEnaDJ0daoI526xMHWFcmfqXavtXxE9yuNwDOCTczg9WjruTlNu8h4aCN6hgV4qJPG+osYn8UIV5T5i4GQdjt8GILs+cjq3jZDb+xmN9G6fm4lD4a4kSxDiGQ+Vw6bqPu9g58mW8zHQgdVcA2s8TItoNsyw0tpvj8ZbcI1eN7wIv/aIU1PJVXdmW83BCKMChuDXa8k8/Ok1n3Wx+NUk21RGTw6wtvl3RzJMpShA8qvqOcMLlAXgo6aodvNv9vqntSMYQ476rb6EgZKb8Rj1EanXPO0+V67bdxxiilvfYChzFwWBnPd9MSG/PHeKQklxEYAGQD+tSFPLVEPlxV/2phZNR+M7v7gzBOCxzZV7LxYjSk/uidDL+xD5NGMyAaGHfwp2egOZT0O1kdwLmFs41N62H6uQIWDXYYf04lDcKLKGDBbLVl9vYjMyF72gVbgf7tui9+5YWZrhChWURVoJAGfobvdi7O5ZuEMGaRkXXaxGw+neBJg4TfdB+SlFVmt/PoiTE73JeOEOPJ5kaTbIB6MUokshmR2POdOyGRv8rA39hofQzWjNJQXBGSha9fR9Y6g+rABFz+mxY1eCS8MYKcZQAMhIj18YhxwJpdYg2KvXJydZYbxcuQZps85BuIBjaK/bqHvs3ARhr8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwODk1Nzc2MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xMDFmMzdlMGQ0ZDJmMTBmZTg5ZjM5ZWY3ZDQ2YmJhOWQ0ZGNmMzFiNmFiMGIxMmZjNTA3ZjhhODM4ZWZlN2VjIgogICAgfQogIH0KfQ==" + }, + "2": { + "signature": "c6M2ozgo1QntTfuVXT8eTc4QR98FYKBf/3u06fkLCdIhbRsVp79w9XtspjXV3EL+tWsHsYJwN/NcHo+PeUf+RcUPTwyp/+ka83vy2YHjpeykHZ4xLFlGh1WD2dVKrE/ztFVK/5op9l1Hbn2kx+g9uH4gcJWaSyPQmGmS+447LsN/b+KLbpsOrg6/XvhiwoNmNykeDD5OyOjgTa12rdG3d4f6JaM2yHKnMPjbx8LdM44/eo+skYBDGtZRHnE3jtjYyCdmEF8s6tX3Fh328fOvJekcbcNwANUqWvOAn+sE5wlWiV9JmbVgMYzeQhk6x9i2+kKdVCwCWJyZ/wMt0FKxzLFZyLh4oQwMdmMrmMG8FNWOzOH9oM7hdgYT71+CJBlYVgmkGCaVdAeFKka5Dw718y0ye5GdnLZhLNHiioVdQgLyjOcHXvvslgdtlf8wqU+xfY212sCcudmSixrj5rmQatvJoNZiKsx24nwaO+X0An0IMTW9UwKBbibMFwnoVRAQOmBB45OrysQfXRak7Z21tctKTwNFs2Nc3pSxiyeb4cli/x/7yF2TKyhnC/d2fhrq7o4fP0bA9glWT9/2HAMam/B6me5Dy0Gp+nL6/hcGwvOjJMq4dbweSIatH4MVI0EHPRhZvVX3bT68MFdhnaOqwn6hImVHnoHRAIs2Uh5nI0s=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwODE4Mjc0MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85YTA0ZWU2ODJmYWQzNGE3YzM2MjU1MDliMDJlZGQ2ODVmODUwYjYzMzBmZWVjOWU3ZTAwZTVlMzJiMzY4Y2RhIgogICAgfQogIH0KfQ==" + }, + "3": { + "signature": "Iw9cdhyYnIuTeTalEQ2/lYn9MkxneTEcB6FgzwNjpBga4V765vQ91ibx1fmW0zsq+KrRuZv5j3UnOUTo4IEaOd7NkjlF/JgHc7thHiodR7OC6dift45w6o857KHNsmWO7h0ihoXvXCOO2hzzoL5W4aWAzFj5NKY6hw1ovJIljYuYOBbulSPcM+Coh/avoH1BKQtd/CaLF9u4ksH+ILasFszHZk17yp5Cw3U39jMk57RM6GwjBGldS3o2ALERXaZye7a3Bwxw5MrorQbrNUhe3x8GvRwJhEHX0rDKRWRpBeUcmStg2G7Fh1wJHVIt2mo/ZIr/OUGGY0vgX5bNuEseUcxZQRyqD8djH+cCgwbtNONFoFjDopPGO3dAF9NbImnTiCQ0njIy3eXvJjRQPp76mmEw0jBgTJjYgm7ekxNlLkn3e7kFfbMIJ4U7ys7nQJfflILUPO4U8n9W1enfTSWsRna98jHUSuneCoTFI+rsHvhhoK5pmgpRtSVQNX2f4nfNvP8cUD5SWaVB1Bz8fbFaNIyeAuPqkTt6j1zmy+SC9bLxwtwC8qCNsCaAf7up34RjaP0Gx6ZHnA83nC953VaI4IC1bwGIHeDY/X/5MovN2UqUXj8hvaKxqz2VRoebxpJ6uhiSm4WgYBg99bsVSc2lTdmAUHA7GH64MYd/+dXTXQY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzQwOTE2OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80YjI3OGU5NzBmNzhjNGZkMjNmY2NjZWVhYjcyNWRjOTg1MjRmOGEyZmQwYjQ4NjEzN2M0N2ZjOGIwYTM5NjIwIgogICAgfQogIH0KfQ==" + }, + "4": { + "signature": "YsQFYRkAaZL/FO0emMDyOvVicbf+eA3ovc+l1bvD2kfzE0iNdgj7Ki7Q5ZUYUK3pQX+DG7VkJlQTVdpuDT9r1aFKrXhQEKGo97hUccXTZWJ6QcFtXyqKtaSnOl5k4zzsmoTfFNLuw3zTQLS5NDXI7mXExbheBUualR70z43AmLrXQ30cxzZtU4nFU3bVz7eKahkSFY+WCd7QhspGHtOdL8ziJXaIDlvtzqU9JPoDFFgQsbXkKch8M2QggWDE3q7GOtiD0x2lIRhbwdTyB2a1ZxcC/N9VRvwWeZmAE0K9mfYAqla+QF7RtEBLn9YlDjEm3yDnPGdn90+MaOGDuXD1cTmJUCCkVoeH/yWNAXDHb2qFk6X+sNgWyGK2idGyQd4DH+9+EBUSZwdgUhJJdVrAb6SD4blbtj8k3s7/K1/pdcuo1/cnEKk5iHCOXFhEXcMul0tMvzxBdvA6Q3xtjn2VhGdb1423IITDQaOlWnzQ5YUJphBlbe21noQ5pHTrLLURa64wq4SKzaPh2OsBsuwk7wRAU2XDBxu3xJBbSm0eHBOjOy0SrjZON1fiwk7QYFf/CvyHwDEWLsHzwgYSANUxky5BK/jA2H0W2xU0YaOm1OQQdZD2O5KnRYUzJMSM+lEReax/CyPxlxnbxe5GkLJcKrbB2UXnaJw0EkSTJtCIv78=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwODczNjE3MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zNDg5NTBhYTRiYzk5ZGE4YmU0YmY2ODg0ODBjMmE0NDEzZWE1Mjk0ZTkzYjdhN2VkNDM3ZmMyMzQ2Yjg1YzI5IgogICAgfQogIH0KfQ==" + }, + "5": { + "signature": "ZvQkbpNdEFFN3ggtAft2tuVVnE/1shYvfGVUu3ABbjyCxvCPimT3jJHe3/Du4Lp7Ce5lmwAQvRWIRmoK8jj7b3LL29+STPje0Okl+l2lZE9WpZZ1ur5eHgciQNgbGwkjtXo9eJ+/zRwMtuyrX1q4LgL97qLYwuFlXkQ0hBecy7tdcTKzEE4Ju59DtRGrNgEz3O3H9a0D4eP0ckKGnzeWbDY21QojT9vR45LmoYZKPQ2Ln0DbVHWGsDFSMD+4lBRCbXWYdxMMln5rdg71pYN7R8iJtxNDW55OiJc0COeFfVMZreOgBc7+a7W+Bf6IgC47E6IoEEekaMrh1BHTVt5BZAW46+i4ke7zWl94x2CglvGjPUbuUjTBnyD9NvIkzchvvyhR4DUWAn073ZUo5zm0VhQU9D2U32HnYXTc921gj76Z9CgXsZqjf/2fBaG3O/BZPZ+GvL/YwBtORjwHgfutuBPfM4bxh59pvZq5FDASh2tt8GE6k8EnQUHuPtWsd//8NUOift9XZtQxDG/Axe3+RCA2HxRf+IYIv5J938iHSCahzRoInIWKgDhi3hLze94EvThbmqfeTN4hrBgxuXnXn08nhq1lTazEP4+kf4xZeh34luMqKRP5RDh8HIUNaFdE53JCofXRXYmmE0YvwRFie2G/REAFqDKOkdL2dwxM8Fo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwODE0NTkzNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yNTYyZmEzODM3OGUxZGJjNzU0YmY2NWYwODZhN2Y3ZjRmOTAwYzU0YTE0YmU1YzMxNjMzNmVmZjhlYWRkMjVlIgogICAgfQogIH0KfQ==" + }, + "6": { + "signature": "vdiJ+rXd131Bw08wxZcUsQLdv2SAZxWNvE5JmwGmIV7XPbSW3I8rKN0r60DyBTdc+BQxg2h1sDHmtawb5HoEYGLStUnLz9GmrJNR4G2+x+3u1yqSefBpt+u0OeFotDbyRp/2T3Kv1ytKUOFrk4NZDp+6J55aYP1shBvXiuCjKyxtEad0Ztw4NCsfv83NSob5agYiyJXZSHMT7DKHDg9qb10Dc7hfSoQCfAR3jj1Q112l/DRZozFlrXGVBQut/uPCFRDDTTbkirTobaYpL9EOJ2Q0Bq61OvkEaYT/sgmCaccktvMVqEijKWkI3yx7LKi5tP+Vn7aCv4lB0Of8CPvttB3RR/gAerFW4XFnhc6U+YgCqg4Cq3+n6mHER4LLMYjqRfaHjh3Bbu8kiCeaCj8Z9kIp2khKh5gX+Vr1Mb8BmZaRJAOovrUoDrmxDOaCweAWZyqG4kYbhILZKfVAsrgwttnjpdMtUAVZSfdc9dMzH54OO8arNw/AzHSOQD/j97cVkuV5pWMfHROaahp4Q2zw35Eznn9VSExul8sFoYMLbFs4Gim++2VFjIwwJasADoJq3yNfqN4zsz0VaOOYM28aaoEzlVK1OaY/g+rMegPgEsoWdETwCqKFmb57NDor3tLHagWNBB17jsbZGNbxzM199BUU5Lc2O8RB7Qwi+aC0wLM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwODc3MjYwOSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xNWNiZGU0YWY0ZDZkMjI1MzUxYTM2N2NlNzg3ZjY0MDIxNGNlZjMyMzYxM2QwODcxMjg2ODI1NzM5MDg4OGZlIgogICAgfQogIH0KfQ==" + }, + "7": { + "signature": "OfAlv5mZ4ox4yh7Ff5dxfL731E6U9GgQUIgIH4pL4NTNpxW6FVEvHayFSvl77bqINhT3sCQTxa2qRCe15/Zs3yE/clIraOBf1WaZd+WtrYDknaWN7CkurWFavfYUHADKOs4bTNrWExq3TLRLHRb3jW/6T6xo9A6kCG3g3MrNNsEQX0X8Meh9WgAT25ARhlJyqUPy15XSfCOfrzYsXqHign5zbrVujjcrxD8FHuNhpREIXf762btMTh0i4MYS9B8MvuJYzA581SNcaMO7G5dTufc6+uzfNzC48wV/Cob6FRLlRp5phOpO1aJlFzylmwIBT/Xfp2v6cM0X1g56D8wvYLd8IIdE3S1eW1eoUiiswgHACLpbqS7aqcad5t4dBbW+D19vz9Lup0Gi8t9CSDIvLnnlnFmA4z7EXnw5sTBMBaiI4fhtepkWYSSMCtBNsP3fARLzgi2MdFGdfRd+6/mXHRALjsan8MkmBDsb1+up07ulmb39PJB2pk6nYP+tWHdCSBuTrPx/+w+yIypdzxpjM2KlXNkqAhugH0Wvg2s45O4VDLCHQLSJ52dpATXa4uoBE/VIUSFbJgkBtpFLU/wFpf6+Jf/LFpXV98jyFiZphCojQA9s9AtbZR7BcgSIdPeg02OvdzDCKuDBaHldBs2fmU3JLiWLi3slp7K1KSCSjAU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzI5ODEyMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iNjEwNjA4ZjVkYjAzMGZlZGU2MjUzMWZhMTZiN2FmZjA2YzhkOWI5MDA5MGMxZDFiYzBmYTE4OGQ4YWMyOTc4IgogICAgfQogIH0KfQ==" + }, + "8": { + "signature": "nNi92fY2gTn3qt7CfeS3ZGTv5zNBXRL3HFzqw+JS7cWSa67tyg1Qvmk+JvEh2fKdt3ODnRzzG+IGpxCT4UDBCg0AxsN9SW1aiFX963kO3gcsbRJ5dTVUWJ9ZAHhN32sscfCwvzvQmrhmtuw8Jdxm+X5Z6/duFqMLfgQa5DHdOYo4Zu/oieJkW1KmxRxF3S21HO9roDnCdWszJIiYjF3CDX/fDSdtc71X/aU0gr0MwfX+9BFnIm5g9fcOZIzDISh9Nf9DZk4sD0DcI7hPQG+WJqRXephemH673nHdpJPQlivzrlNJ8BnGDgS1zqNJggtroBVSRaFg1gh1EEUfsgnURN16jxBkTiEzkFP/LO7cc4S6056TinJa1uQb9FgrLEY9dU2TH7hhb2ejH23SLbUVy038qPOfYB4tI3tbGUOGnn7jctSmVPYJCHR8/2rFN93YV/ps8/zF8vdR6mq3qoc45GVfnYvKhkaa988/EbiUM56BODWFpIm8dVfvVganwHA6BmuxfOeOQgGOrMD+3GsntzaRTQ5/nCExXr/yNZPPNpEerhT3mUUsc8EGX2fly6oZhOBwifOUW7wGH/C1iLEhD6pdl/d1Sq5W4JS9koaXboaHK+AaALFLGcQhppi1mLjl5u1LLdNsaeyeJDOOE/yjzuZ75THI73cM+hid0SNr6ww=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzY2NjkyMiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84OGQ4YTRhYTIwYmUzNWYwYzFiN2I1YjVhZDIzNzU1YjZmNTdjMmQyY2IwNTI3M2MwMmU5YjExZWIyNjdkYzJjIgogICAgfQogIH0KfQ==" + }, + "9": { + "signature": "Lwu6AvkBjubL2NwoGOIYHtr8y+WBtLsxq580DogPGYe48+qJyUnOOpaTj08Q1O6Rm1qW5D6v0ZdqcraVUnawMNbjRXQBOW0FYYcOJSQ1SIbA3FsgIiUeDrOG1Dw+hLBu+ph+AZrPZLAm6ekffDB1XntZ3UaJIhDglldYYh7iPgSVtSwkSMNbVaDXIfjvzWjjm8yz+hV7a8R7WXVvn6ND975QSyWFeBzJWV2Ic7I+rlkp8VTWNJEB8liCq/H9a2DwL4NWu0iYRn4jxSM14OpWU9EIkr5apZikBOiTJqMrSBeES6qeOIV+3MaaCZg6mnY/vCexz16BNyIDSk5YKULzCDjZrSD/uweGDaGZ8y091pcfyULbvAUi8YoBK8pV5LkfBYYX6YY+mJk4CwoKwSe6XQQme7THk9I0ud/B8es0ke3h51b3TqwTFM+1PGlpCxAcQYgLnOab3jYy1eody6+R6p8tpPz/AxbTAyqSJxalW7Jv4myjbbE7cD64m8Uh/QoCKlOmTCbeBkpsiVvkqrieF9biBHWKzrFTXXhma3irWCx//PDuxNwDx9WdlGo7lws5IufOBRtFbyC2kE/yRlVRXBnQbiG9MNI8vjhhvvqxbxOyHUum43cgWanyWNVAXsPhv1dGgEIQKFPBgmPH5GzI5BSsWvUj3MMncwgoQxVJLzM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwODQwNDc5NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yYmNiYzc2MTU2MWJiNGI0ZWMxNzk5NzBlNjY3YjgwNzI5NzRkNjdlOWEzZjY2M2JkODRiY2EwMzU0MGFkYzhiIgogICAgfQogIH0KfQ==" + }, + ";": { + "signature": "mGKvDsmnorIiXDtJYwAWY4BSvH2qrU3Ds4ZPPy9WYLEW2knffD8OcpSE4EGf1r7LUengtGi32o6LjxhUGojnIF6YKK0v5F5cSHnQHiMn8KGka+t0gUiY/KXvkeniGmieRdVdxTGf1ktz3UKM2qkxa65Or26qwHqNNU6kPVLJ6yFMl02lwL4uOcHeKVyPaq9ctQLkI7d2jmoEFQEtySW35Dvjhm4cgs1vumPQIlQKD18pszb/d678gcU0MJla9Iu9I6ZI2vqe2KkxGC9cfCKvhFHilE6b07KIZizbLi22XqB2AuSCu6ON4VIL7zlgSD9mXw/69g4MPi7CuLNfgOOPCNGGiQWmjg2NJuEARKxzE80a4+v2TNBCW/NDXnnAwXa00o0dq5g5Q0n1t4hKSg5Alkweu88Mwvto3+7fEF6eRk/eng81OaXG4TK/q2UG+ldqGlsi2kev9U+aNdzsLHRPOi5ZstkHblotzYbc/GXjaHzsooZvBwr0gZ4p3dNQCgtf5KkrEosIGU6UHo0LI6TrPUQQct+zaUNlxLmLxWAx/kHBv1SJwZEZr8Wsk+22bWNRIvGLqLF57d+j3aLJh2YgwAeZkV/52ddPJgHrEP/90K8dJ+bjNMB8BQu17smqSCXX5ukB/3abZ6/iAA13dGbxkMqTFLhj3cVN5JGPzNUfIhM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwODg4MzcwOSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hODJjNDcxNDFiMDdhZjBjNzUxZmVjYzRiZDA2NmE1YTBhYTVkNjE1M2M4NTllNDU0OWFhYjc1NWE4ZGFhYjE3IgogICAgfQogIH0KfQ==" + }, + "<": { + "signature": "p8vwqDlz6UIZExhXfiEjrB6dPSnIfLDrhJu2m88odlnm9xhPn3jv/OLs5GK6+QglgsBTrkvQ4eCn7iEZtRaOq6wFyCIuKBpe1oH5aAA8xey5pZwknoKJiztb2nAwKS1Ma0VQ7lZ/oSl11/idh4YusmHXTJJA6LiU4GS+uF/pbbP4/MrGxvQnjwWoZrXlHZjxluiKz4rbW79JFWP5xUoJoRtpjd/AMKFrq1ym8fTmkYSHH6aC4E68TLvr9dFA2yG0WUDAkrYLIZkPF2R8UgVMRU3Sg+aL2jDqWqvBuBobai+aLUa5I5jU+0niKgMXwM5pnzBtKsP+BVcyHP/eJDMV85nde+HMiEOpArjvNzs8gTMvr85kUelat+Urss5S6wEVxlTrej8Et3PTTAncQTQXgMfJBIsD4YAx6j6O8IJ1iZUhnqRRQBCD6ZDAVNJrIzCnBRaikqBZJ6q4LkG4tFPcd744T5DB26DDDT4IfFBPmkgH84fUFpi1UX1zPK1/IaKAOGcDP1fOgT6UpAHDIuJGddzL1hTVpNVIhHVJ1wqQx+br2YGMCGBKbWuGAxFBllA35CBSoz3ICK9+R7PEaZy6djrPa/afRtxlmYinIRJeL2zPEy5NEeV9YNRi0hwnVJUa9m08vCHi9DnPvrnN33IxaMa4ylXekvDXH60Qm11GpSg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzE4NzQzNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hNzIxYTc5NmI3YmQwYjc2NmJjMWNiYzUwZGNiZDkxYjA4MGMxYTRlZTE1NGFmNWNkMDk2ZWFhYTg3ODQwNDIiCiAgICB9CiAgfQp9" + }, + "=": { + "signature": "MIhpULY7CErPoiJ234elDsm8/XfD+tsp4RYiL5SRfI/WAglu6SlVD1rEF0msCbdcV/q08UY2bJCIfX8FXDKdOpDEvI9CKMRAOED2p03tlac2o3Ardomsw4U5D3qqCc1vZajY6YN1GBSrQXAZR/be7nVz7aPRh3vDGSVPxRl2HeiGEKh79ehbd2pd6VZugmHHYCaa8UJQwtk88mmaB9z6YrnVztjLRRjG/qMaBI4B0nZhzXj829a37DCfHjYgjvtXAbr22BjGXmxkAVKAgccYnbKV/2hL8XomV/aKLJZiUOlwRUjsIdwhy4s+GrgoC096CUGaS8SmmLzaKUrmvkvZPjFBqMOGQ4MbjWZQsg9V4idEHgGaSfoPtDt6mIijNznl/0EYrmdS/yD4fsbDEBFWx3a4FZ8vZLjvWJBgM1AII7rmQ3PvbRVl+RSBIr8lJCJrrzYDIpziv2b0F5aF3CTAio3E9qKDBd10UiqHdwefKkjYyJEITws9MXr4FxPEfIwF7oK1xWHZfrrCTzfL1CB54CQadzPV+Ok/A1VAVfin7uKnhd/OAcUJxRg+XfVvZzg5NSBJNjLQOI/tCzl2bvZrg/5Bk7R9ha3oMB8jTGjk4ltEWqU2L/ommJsras8QW4TqksZzPxNKX47ZoB73d3zGgTd11uiMiqiCzYmy6vVCY0w=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzYzMDQ4NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hMmM1ZTRhZTZjYjBjZjc0M2JhMjg5YjkyYWZiYjI4YmFjOTc1YWNmMWM1ZTc2ZDU0ZmNlZGIyZDZiODEwYjlmIgogICAgfQogIH0KfQ==" + }, + ">": { + "signature": "CNKQwsVHkCoAbh3Qpxz8rFcJNVqkvodtnXl6Mvm9t8zX1KZeYpdYYZyn8d6ZANBs7OSaMwoAGJXrqM5a66GvveY0/E6O+LaCBFkfeLuEk/yn1VMcr37BFqPXN4QBitCrC0ruWLY3W3cogmeH4QSxSBbKG+DNw/eCyKhY4d3drGYMLyDnUazwqTeAC8D8ZDdMegvTav4A9re/TMh1opLLRsDxFbPIJACCigqW/EK9qsbS8lRV5Rgcl/shRpSlBAJwgrlAwDsqdURQNEv+26HccHw4QYKXRh3kSA9+6mUq8j5Lmq42o0icvqbRIg2zY0JcbM9/gV1xi8l5IiaWXmTNAjSRi8mRICPMIi1txWgGOGzQXOHUYSDP2/H+31jgLifZdiNguqJeBsPfePbu6BFg31w2DIq3AOvCcCN4Ge4XWizPbxRzP1iCJkNoGLKImOqIWDcgIHG+ZBuy8IawEEB5eHLxgl6dHa/i2+N+pSoMLR34Ufevpje+iDvuMbNGACaQQ7jc3eIuASijh51lQ6IuKdNe92Swd/29yg9DVf4xhybxRSjGFO/cjrn+GQeqm+rjexbata2mc15+T14jl9fH/r6Mc+8WSaX3gryhEszYxfpSA4aXXaHKiSNr57X+fHN1mOPcsxtOgiNnlVOh6Er1qcaTxpb8OOzXekEO3fvshKE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzk5ODI1NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85NmRmODMxMTc2YzExZjYyMzlmOTAwODMwNDg1NGM1NGVjOGExOTQ0NGUwN2E1OTg3ZGI5NzNlMDE4ZjkxZDAiCiAgICB9CiAgfQp9" + }, + "?": { + "signature": "w2SEl5HaSkXyTNbfU2tSr2VE5EBRqbbKkG9HHpcVJW5k8/2cyuIgMD9JFMrVdKbPlmS+g6/BzrVJGVtqgR7RLclt59HXQGwDsUQe4FYCCXryg1J5Xh1gNxDqM0aeur1+ZUQ3trbuuMuhWE8TcvDoImxtIY88f4p2kx7PHSb16taFhD1YtiCc3QDR1/Glk87Bh1QAcmMrHyihRNH7+z9qjjUF6MXzdEGYTOQwBIgjKt15NSiwWjcpet7e0xVkffMBFhFkDFHCZ97p9/NYYvj8oNTHCCY6sSrIMyW8JHf9vASuamhHFPorVt6cHJfT9QwHp0Posx+uTSQ+BueUZdFDDxGZtXgX0PlJyBRa/4QpC4t96tUm0sIDywDqjK4qU6qHVzwCGrLgKyHP+oJCVum+aQvXfOuPYQHUfQvkOpkd1VcL5YRjLtuiABneMeQCTFAyDUxph8rxJ2dPh2qtyy9ZBTJBrDcFUFOlvBwtfItPeUeAZwWBpX/PWfR0ToPSOFKOxdfJ8dO/oilAJs1WitmiDk6HSbx8UwHiXrzOvTVVwRp3gNBcPB0xFVr0kYV3IVqYUiZ/IELn7KZmHwYThuFKImxE7gxl6dVPZ6e9fdeZFopQqDE5ivAdvM1QgczqQ4BMR+ba5jSSVF3rRaPfapMUQdPEM2hgT46E5nqxNHdtiAU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwODU4ODUyOSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xNzE3MjIwZGE1NzEyMDEyZWM0ZGM0NTRhZjdlNzE0ZWU3MzFmOGMwOTVhOTAwOGM1Yzk4MDNmMDRlMThkY2U0IgogICAgfQogIH0KfQ==" + }, + "@": { + "signature": "yjhM3WZB87iLl/OpWHeKk4n6WJkfWlM2JP0KuCfYOKYhvmkL0j+bhJimrpziLZ15HKTIzkGdBJlnVGVdFYSZr3v39S3sxOhXyDrxgAxA7ZsJGJvG+sQbuj/OJB+otif+aMGuzTx7Qrz+HiB3kpVj1J+wIpqTYvWtlvjCAzhSVJouJSgXYf6MOECCqhspqYDTikCs/a8nNv11AtsNZdSjKajezA59FxClWV82iavhh6TL1kRMmuoasALg4LHJkFRV1pfdxZpdnZwKb/1XJpFjfVGLoOiA0OnxUs95VsgsXi3zALMLRoOg5oo5SrecFvkwBfUulf8nvINC7XS22mNU7dK+x72YDf17QvuEFIIvan3OYq0CkkbvRmGcoZAR1P6lem3zBzJEMKKo5FyK6cMhevLEEymPrkqGRsdfJeVF7vihrpukBlpbftKgNnADPdA0o8csTutOqLhSzeFekinueQxd7uFQRwIVPuglv2YjfMl0I3z5Edp4EeGPFctl+mGiOO6XpuQqGob9rDNP57mUGxpLnZN6wt734wiVG+n5NUpRJ73lJCVb6H5qFRwyG6uBdt9eaeogPaFbWTbwzOKd4szn+yFU0D2inCNgLWyi41yq4vyTBT5tu9+JcHtmXcIwBPxOO8X5taqRlOVaUZdK7/gg7yg2VdQEEeu5tuIPntg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzM3MjEyNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85ZTZlZGM4MDA0ZDU5YmMwODdlMzdlMTAyYTliNzQ3NmViZTU2OTAzYTc2Zjg1OWNlMjU0NzQ4YmUzMWRmODYyIgogICAgfQogIH0KfQ==" + }, + "A": { + "signature": "uJT+BHVScmIT9iwYDumVtVeaGPK9XFxKkEMe56nevu674iRQpXOTCtCfVs2VqAeX38SOEUlUpjbS3eZlva5f0IIgPK0DXDThE+hcUdtCNKUYvthXdAvGtFHZY2sCJia/2b7tElhzgX2/0VKi06QHOPT89NJc0P9C9+YCdV30myT6Qmp/WDHzG5GaRzacWHFQ6GXkZZEo5gDWdpn/k9WfImYKs/uqnVFolH1V9B5pOUJ7HjKqvO5N2fw9fAOOhC+Mx1F4IMkMT/5RBeQRE6SXpBpt8Q2jxWDeFV4mqJey9qsxPBYPDGf2nhMesB5En9jlrhyU2GO5DwjCr8IJoJZKP26Ih+iaCpYSXa2xjvkXIa/eJ/Ay8mQ0olm5ssZp6vIwvyR5560nQ6Xpfaowd8FMTSgcN1huizagr+ByUKx3rab0sHhwvYcbOpMAQr4xGPAUFwu0V5UuyZm+b9g5MPAUnME5UZGvIRNk82SpvAsY4DCXSX9uLBYL2IyyrJNk3Jt74ScI80j7cjqb8WluOnGt9qzBJ5WopfmbUwR7nSzt4P0KUSvo0rT7XHfFWp+MJAfYXBxXgIgMv3kndnk6IisrdHTXF1fa3k5dIIHhmT9xWS2AFpxyJNPvsmcEBAR/u7pQ1VmpSRACMKeXBB9sBENdkQo97ndt5dkL/XDVKdIUL6c=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNjYzMzM3NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lM2E1Zjc3ZmQwMDJiNGFmYTIyMTU5MWM4MjcyOTI0M2MyNzRmNWJhNGZmY2VjNDRhMzgzZGRmYzk1NDJhZmRhIgogICAgfQogIH0KfQ==" + }, + "B": { + "signature": "eyWpnwrr8ZWysbi7v8j+PpVdIZ6CTaPkBwEyJfnYJq8CPOTRc2dVpdoJC6QoC0p1Pgv+MY4/0slB3HM+c3NGxpNqmzMzfEcdJjaBFMDVyZG/UPhCNKy8eeGpnp66G+OxJrb6a7l5oIm/B57zxiDiAAQHJfqqr2p42/A2u8THLmXo4igv203oG3Q9t2Y1jEYAr5s/h/1NqrMXal3yNU69i1J8Zwd8TgwVkmW1AlTPbIfEPtsR2IChzQqpxD14VKSVAuUlmeIyRNT4GRhOArsMKYHqimAY0x5Nxn/eExXTf8fi6iEJRSrsjZimXvDL41OV8buYILZx3kN/Th3bVq+Nc1NwblgX56SrC1OXE5didEo9a+h1jJ6cINCQpFL+vj9aVKwRyK9wxjCvB5odCIc6VQ9c4EdMTsyVgy1yP9nZd1Y/rTPFye9XX9OttHSSo+iQauqx0BjUVrORM2EwWqnZMyFcDtar06yYkHpCAr/3i6jZ7GWWG+yTOL9cJrn69CyNuCWqLg1MN0un5E3UUGrCLKdhB8CyRGvpPf2PCDC9SPD5iMlMev0SJ5t9xlwP6OgjLRrLXhKJ319bLUm0FkitP4C6caxouhD+1sdPaOQ4lB44J8C8deuICCd9UWkJKw6Hfv3fIoreTkq2afPQUfwNB/Cs/76r1wVxsIRsdoDCifQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNjU1OTA1NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85NTkyOGNkYjE3MzhmNjZhMmQ1YWM3NDE3NTMwYmZmZjhiYTM1ZmMwM2E3ODk1Y2QxYmRhZmM1Yjc2ZTM2ZjYzIgogICAgfQogIH0KfQ==" + }, + "C": { + "signature": "MWLbC+AB5VmsFnl8Kf9vjZXq45iuniErW/SG+AXKFKD007/r1wvGo8sIWIJFCh473wtabuxge3HaIuktfFzJwjWiw5c3+M7P2tX4IVlF315YlKUXZ2YieRGuyN527oWFGznbIuAFhy3r/W5GGWymfkw3TfNXtuZbkNxqvrk2tke02q0mFWEzI6ggGjvUzAvK1Gyorp5eFQuzNB18ZKb2xtGnna8tL6XSxymvK2uUkgtPDD4VaEE8FSWBJ7lBoylMWMudJaJJ9yvSu21S6vPhhn+RVi0nIUh2krfAn45jZSbFyzP7ZIuu19SO4Z/uyswuU2LKcob3+YCgXqZLuwnYQ43EqYSKGC3+3G9FlPxq4yHi+DUOwZ57kAx8oS3n5nx1KbrOOX6/2bcoiRQZ+3BS4O99qybvBFJGtR7shChoE3LVRnqM9y4BczRIEOCBuGpH+oBC3vGpz2jWt8dxwnHjBgKKpQgqIVb6XiqBVjWLdvbNuUh14tF38zw2bw5czZBI+37Vu+HynClr7LOuI4Nbfs0jTjIWJKnIVXjsisffZisNOj1/BIpg2rsxmnbhfoG2DmTlNqi/QmugJIE7Kh87NbmuAxn0B6x3GRSWgYkxShcsNZGnWDK3H5IrcZLkV4C+0Onxxnpgg3AlJzuU5MVnUxt36bRjWHeAIfyfGpRCbjA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwODM2Nzk5MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84ZGE2YTFhNmNjOGE2ZTYxMmViODAzZmRmYzZiNjQyYTE2ZTE1MmQxN2I3MzIzNTQxOTJjYTUwYTY5MWNmMDM1IgogICAgfQogIH0KfQ==" + }, + "D": { + "signature": "x4yYcjVnQK25FMQZk8Cb4rwl/TjPS5X0IoPmIEL1rXWrcIxvi+HlgGJIjSSAQspbuey852D/YiHSU6CYjZ5XzZEuf5TUWqEVmmWJS6CJJHSZhMbdap9tHCQA0dKt++4U4E52NKBW5WZhhk/EEnScRsXo1Mw+eVhnALiS8jPEkxzbDzv67UlqR0R+mq5XjZ0EbgXNJ7AGG9hpGR+Gxfg6ObRrhMtXqv2BcmWGNVRckoPynPps3a/AurJT6+9xVzuXe/SvJoZPcWzQY0t8+n24i/tQf8cpssZ+HQTBJAweDq28zU3JmiADzzOEBUqaXC+jaQJ1EtC4S4w+6fGNIJ6pkECB37JnvuJ/gpwLLq5UCkvreMn416V+u7FlhvpaOePd3vAmSPalAa7d5SGTzl9ozQIY9Nl64EmYNdqgT+1lsQlMzGGG8FIHDWyrCnQYJ68vdveFj1rIDYXojYsHNqjsNe7xW8TGfNepZrf17LdpIqY4NvvhiUdVjS94YaQ1RjFFjeRRWKr/YiyJ1AY//a2KQ+jot6jKINbaNQ+7GH8Zb6FE1zthb1DtpsPkMnnNNLlqiX1/4B+N6/LhzypIKwZT8/ZcNNQc8yYMmsoGycN11322g/P/GXJEjLg65wLLnmigr1u+wrwK6tP4ZPvjFFREjNYs8FNAUS3DvF+CdJ4F5mU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzkyNTQ3NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mZWQ1NDA1OTNjYWQ3MDkxNDJjZWNjN2Q2YTZjMDhiMzkwMzA4YmFmZWI3Nzg2ZjEwYjA0N2VmY2NkOGMwZDk2IgogICAgfQogIH0KfQ==" + }, + "E": { + "signature": "FQwHfn89ptgRv8pqnc8nKdZAYFE1Mgh3eau65VNn122a0sPas4NS04cuv1e5P0mwiNyLTl5ApxuQrLtwAmAZq7cQMozOdwDJrN9HMgkYRUGkvBCOhXrArmhWiOhg+E91pCexta/Nmenbf8YXhhvqyqTZufYEHOScmtKcVn0ONifImQIk4DVQ/ErzEhinitde2mJ09TFV70G9iHiVZWWpyH9AVL/v6gMnFsubmYXlwxzs5Ud64Utg/rK791tfPU3dnXTeHYrrlbbn34NBN1NzMWtJxPHXoT0i+KukOMCnICzzKpPnG7reOd6hDwlW1C+JCDYFBaek7FASY5bj2vWbDj3TLBfwmEhY85PXB93gjsIAtP41WQ6vVBRGzrJTQ2E9B0Gt5Gm9Akktanng6i87p0xXm1qAt0he5kav+7Q5qfV2LtW5+AJaH2kWttNwr7jZWz2eoBHdvZfPXa5WZZTbDT4GQuv6VvtJhd/2e7BGan614m+zkJHsiKIClU6KfW6ZX2rDy3QDbOhiCRnh+c85eGasxF95wWz5YLE4Pr1LFLIGvvirSU3WZxxWxL5hZdPHgeHIU80OYhxYlRylTVA7MLnPI4hYVBTQWjWxUwZ4ar6BK5RqQmIX6qc4RZkw5aA89yG7Yzyav4uVmCmb6O1Y/EATZapBdRmZm3nuS89tNho=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwODg0Njc1OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mYjdlMTM3MjE2NThlYzlmOGY2OGM1MzE0YmQyOTE4NmUyZDI2ZWJlNGQyYTJhMmM4NTJhNmFmNmE3NjIzNTI0IgogICAgfQogIH0KfQ==" + }, + "F": { + "signature": "Pl1K25rxaBslJAlgHTCjjqawmq7CzoWLYDRx8agIStiPdmpcrScZCgMEBbtOYTYaF5zejBIa6M94+1WGueEXvRSTr9Hzq6ANdGtSgvpmgyIVhqrwDKI+qrtWYYFKAo3/O7MF/t3sNzxdU3efpylT0FUJUkkAwiKFoZPYrmbbM/nyBM68VWSVTwJEuTQFIO1HF5/G+FfHOb82RjdHyfimo/Ko5i4dOgUyeSzYlkn1syoGmb8OfFRq4XhAfhuu4RLLf675RIzFV85PRpww5kuDlIx6xadbE7C506w2ubDAoLUFggW9tpm3+148w9K9u6BkjZGyrmg1YI+2xYLygHMtMuyH3+ftpaUgE7/ZdtE/DZ8+6KCXW/C7wsldTn1LLbk6Mt+C5mvV5AEgos2CcF8VdR8qfgsNrwIu8lrdZcGSTYEslVbYlfrDIHfQhKciBvqt6nSbuxNn7DtUUnTOyQN5up3v66fVyBu6dJk8tAVcmxnGwYwNFj3L6XmTCZb2OIiVlhq8exLDf59evC8085Pw8G2xqE9GYiwHb/yBrtxWjVlS5EJAM1TcDOp4KDGUzlcEMXa3JieCuvxVJZdRWpx6csHpKJuqObvHZ3E63fqsmvliJMmzxbs3WC7QcXuCbjC/g5m6AwIkCTTgQcLtDB2T4us+R6KY30+NkLCk3JQSAS0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNjg5MTU1OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xNjliNzE3YjY0M2ZjYWQ2ZWU3NmI3MWM0YTc5ZWFiMTk2ZDFjN2JjYjBhODU5NTNjM2VkMTRmZjUyZTE0YzVjIgogICAgfQogIH0KfQ==" + }, + "G": { + "signature": "crUh6XZDT4KmgXTYPDen6Asn3wTUCStGEVxt8QKHqngk5swBRRI5urrX/KV/EC7CyUgkvUxDf9auo53TDTXmJ5QHg76yPgF5K+lxjt/dM7NuPRyANM7bOYc9INvOyMn8OEpWWuhloP4n2wM4myc+DzlbnSRCRT4pFSId5QN6i0VACDJchyt/t0EotymvJWFvohriB4ZZm8xvedtW3EVjLykcZ3AXYui2WDdsIs9poq8n60XuwV9wVe+y0fKE0a/VMHYzGDo2g+7Y3AM5/8FKdn3FaOfHVZiXr2/LGX4/AA2DUPcPU6lw6PkArWorQ4s46U+G46p+8FD62XByTcrM0PkPaehW+m7P0F2Q0xvtkfmbRRN3KTc7jGSHpxf40cvN5LVDNw5gg6oD+8l4TgjE6W7b6B8qLqc4N0XXXHFwWzG99z02Jlc59UbqBW4G2vEO+7w/WSd8jmUhXcKRjAubpTqcZBv4nJpsJiSnjG+WLrG2cqhLIMPmMuIJBOmznFAluxfXJCsYZMpLbbMECfinmJwIEb65a9hwc02SizOiOmFSeN9jcM6CE13PHWxnDVncLYwgzN4GcTMayXsDuuNDqakHikPHlFTl3ksL69pYY5zFRr67Z/xYvXCWXepEXcPWkTgdr7fc4YdlnyYleojz/3rFNa7Boa1WIGQCUQtlRE4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzExMzYwNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xYmVhMTVhMmZiZTdhYjEwZjNkNzlkNmQzYTBmOGExMGMxOTdhMjNkMTYwNDUzMmE0YjBiYzNmYTI1MzQ5ZDk3IgogICAgfQogIH0KfQ==" + }, + "H": { + "signature": "rO3/yxgnhyCF77rKkD50F2J0Yk0kNHWKfCLNi1JarzSy9A7bjJWV4bLAJLHkrTJk7EVQKOicEhQ/aQcoFAQe08RQXzqkeanUgf1EcSrxy5t9efzg9q9MOV/RYfNjTRDiecqQz+9C1cwvYK0SPievX5E8QybivjQhlMnC7BPzCRnGNlh8vtmBRqdm/njKSsV0wYp0w3x+EG9BPt9xg2teoWDZZN4LhxrhLZayq/z32sqAa2dB6QUYALIoHxD7rGD73th9ozy3/B5YT5GHBhItxM4OsPwjadHnOye4bKijHVZ5QfdeQ037K775rPNZr7IK1wlPxNMvOaGJSXOVb6j0hUfUhn7wHYACHQBh+jm9BGMjnvrqlSDU2RregkfW1SOfQUuwSFL3PpCp2e2E31qNkDPdf5240SKyj65Z7jHUD3xFYaBd1Fdo7zEKhD+yuPX4uDY/Ayj/yA3DxpRYp9k7BrXaAmYwsQRQmhmrXcb7pwxxFty4J3fEyih2WsZN2vFKoGsTUj0OJMqWLOIrr5S+69v8deGEI0PbYdoF43px8U4n+z3sdrX36jEHevvO3qGJbn/IzCKvZoJsWdBBjBFIFF+eivueS0Wk7WE89fqUPFqUvDUwwyKf5kWLgkwRyBaNKXDIXYLrWqMgxmWY4hM1Vbl83FqbOP6wsGoP/F/FBWQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwODA3Mjc2MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84MzljMWRiNDA5ZDRlY2U3YTY4ZjNkOGQwODdjYmQzZjdiNjk0OTRlMzQ5ODM2NjYyNjcxNWU1MTA2MmQ0NmMzIgogICAgfQogIH0KfQ==" + }, + "I": { + "signature": "JMZING1faLFE8vW41hVrhOGP5/ck0DPteZNo3mszdAlEtjymDtfbuiW2wLc8e7v1LfsCqc5zW3G4+ZcPb+uLmzm+uxNFemlysRx+cFEXnGYLMBU0ULwnQHs7k5c9Xxw0VhKenr6V/T7YQ20iKHWdV54pm+JhwMWHmbCJdbybjSUlJ0jcycGWn14SSzoOXtr+zvimWEUE7+t3TxERghFi+W1p3QagO87jNHayRnjhsFO/cIsc4p/tdGKY0cZaKlkW9BxVIXLnJ1Gh7A9b3k86XKbigde7NRjOLe9Bwr/0MeA6riiVyXlJ36RaY5LqkJLxmekDC6EQpjDeDUTiIMPYLqmmXyf9oKUME5Uq2tjlPFUZ6e+UxMN50C9liiH+X1T67aQdpUfAlhU1zME2m/b8ApK1Dl010Mc+X8QUhyhHuOZIbKDkUANL2KCjJ/DXUL9FkA197WAr5rC7mlXDhDuMv6198tJ1AU9vy73fVBRrtME1cinLkny9ozvfDJC56G1r40w+5tD5naMzfo0Imjjm9q9uRLWfjI7eGG6IWen0WoPjLJAdKYJacBn9rMxCqXNYmDkuI3Zaj9u2ZGjtRDrriv+x8KsG2A444NlPpVcyom8fTvj/7gLIycWDFA7Lk4JPDXYI1JlXnyLbFec/yYAzY/66QwpCrhCtdt4Pnz8uCqI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzc0MDg4NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85ZDZiOTI2YTY3YzM2YTg5MjMzZTA2ZjQ5MzQ2NThkNDM2ODQxNmE0MTM1ZWU3YmNlMDgyMDJkMTkwYTJlZjJjIgogICAgfQogIH0KfQ==" + }, + "J": { + "signature": "YyIcsXCq/kQHss0HAOlo6iKsutEOgx8DNel+eCCqBCKf7vZ4Urm7+M4pzVpJgzBKbtZFmm1XELf60usviY7KjrYtBPSe+jMx/8y/FIaYOXc+NgOnp1yL8T7/a7I4EU6RmisXe7uyyjdpapFm8loEh7/6OZR2lW0gWfiGtrICcR7vRp0UkDvyTrWSsehYJnNSadX6wrzNweao3rX7Y/ynzbrCfLmw+IfsfJLh09QwpYuz8y5Y2bMI1W8EHBm8eM9q/29LJ43BOKhwLnDnF98d8YnkFxuOmJBvnAtpoMR11chGWBUEVw6fiBQiP5O7pR6Mxk7oEhpjIXxDkCRd+q5cQaGOPb6DJsiTA5vtvEEToIZjyvomz/gsGUTY+G8UzQ4SOcZG4KIFmhzdjc0g32PrrReBGIfqJj2lgrLYLuWuiQjJKcU6JpSAtUdrtNDzXkA3oxtKdKJ92QUebsJ9qoCJMqtsHRcx4QKrXRy1Sp4RNqDrw1ZKOQaFBj5NKM+2riQQRKXsiDlBJQhwfqEGR7GXyEyqg5b2MDYQE6nH5s0K7sdOHa3ubLv5KfBuxY1ncCD6nYl0yjAPu2mUWPSe+KEilLvBy3iFZPsib0fJ7SVRB/B6tEmMc1OgGo7gl95FDErURHEDB9+OLaeETZROWQ82bs3t9YWCBwkYeNusmAMS8fM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwODUxNTI5NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iZWM3NGJjY2ZjNmNkMDJiZTIxZDEwMTg0MTg5NTYzYWU0MDQ1ZDdjNzkyYWI0NTBiOGNjZjNjY2E4MTBiZTIzIgogICAgfQogIH0KfQ==" + }, + "K": { + "signature": "nbeOqoWwMW6ui6HZb5iu68Cw63bqD704FzJzjC9g9YERBh1KPt3c/I4t53ItGcQt6FUHBXXGCYP/ElNctYIa2RQll5xx9JcnhC/D2y20Re9GVb51FrHD+C9sE5ZCubBBp/4cRKSqE6MvZnQ6tp5R/wSuUKNAL0oOt2r/H82SJAnSrvTx3SNcb7D/OmEAYMhZSUUIxTMBdCkCTwybxU+E9XZJiGNn4W+9KJcgReCGSrZ4vpqVF03rvk7sYL/dCnU28BeUsVYxYceRVeWiBhW4kx8GR57d6ApJ+prUroWprl7iUlpN2gF5pvChc5cj5yhKJbtoXYFK2PP0xvJ0FHzEZ7v/JlBIphAD5WogiqppfK1tf+gdYu0HiHYGiInXvK+B+PP3Iu3BjJ6D0HJkNh29P27r91O7BYdEsWxke0vhiebsycI3EWVNiiawFkdyC6ToodQ+xDT9SOAwO8sTNXryx5Rt3FTgerdeZNC+yrZayGxVitI24ebUzipbd++cHHf3Fm7e2T5SNqYPDxNg785/79Eij13abY3vUWahUOljE1zNR4SafX+iDHolA6MminSraxdfprtd24c4qY6l5vme+kbvc2iPOQ5PPdnVvevVeM4z9v/djBuUq4Dqb5/9I2qE6oYFWMHeZf0udbhBmtPgWB1fi18rC61v3lVVZvgwNdo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzU1NzI1MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lZTU3NDc0YTgxMzNlNDU2OWQ1ZWQ3ZjNlNzFhMTZkOGJkYmVjNzMxYzc0ODQ0NGRjYTJjM2YzMjg1ZjY0OTA2IgogICAgfQogIH0KfQ==" + }, + "L": { + "signature": "W+G5PmZq1q2avv1x5r+gIjOufds/YuKf2LdJpeAae0tzYnNbCg0Ek7qNzsWF7wxjr553rikKKL9WxZFhUBmjg21q6fRTgj3wgAYh0SZn1L/AvTHyemoBGF+Fh2iIwafwgHT8AdI7dMTSuaqTDHmhnkId7YvliCn3UO36hkYnaK1geJWkRf+ZQSTneFolaNy7ZS91ii1yeGZUwC3Vkyd397OasFFe3Yv260vEkm3L2YNy4evzH5YZRGY1KXxLwGRHMtNQyGBumfKwjW76Pch0ACc/Zl7vg70cunhxhmqDomz/gezS3EHOKh+iIV7zm4nm8YM1Le4YSqkrS20bzex9goNPSiID6RhdkgXzF0H3SySuPesuDvtKMksOLcviXjZlEvRiKjWOv1NkksjUl9L70cx5H94nYbvbaVOuBOEVcsH6GUE+Epe4f/vP2MIcy1an6AeSlzc2xtK03wIez5phG8CtcbPn200PhsSPDdBePMUwboRIQrDm1UeYP5QgNDdM9snSf1RbusyOHcAOfj0NJghWLwvcQbQEb4dIsZdvzhbbrJ5uDetmXTV0gAVNZXysEALmGIyHg7tlazYMjgh5pbl7kDgNZOELIiPGPbViSU4XbbE/JH/Cw9KHKrSsCRwHBHrQB8oDlNPIwSrZ1eYRGnu1lKCsxkSDC7DQ3b/LLcc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzg4ODMxMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iNzY0NWM4YTAxZDI0Y2QwODlkMGExNmJiNWYxNDE2MjVjYjllMTQxYTEzZGE1ZGU1YTc2OTllNzdmMTM1MmJmIgogICAgfQogIH0KfQ==" + }, + "M": { + "signature": "uNyrtGweQ4Su8ChoJ8cHlXKYR8Svk/TrUXty4QRixfnEk8BItM52aVGyKxX3I3ReMI2e/aw8V/lQXSdqsBXmfwbOAsFGlaR+E4PoeuuNoBfs8Vc6u75gT0/mRvM3+bQMYhINxeShtadOZTMmmyVZlx7FjoXnAMB4zBEAIeZofbl14yHwEz9+hSQfv/n3h+QmK7zWlPiNlM967kyaXoX8KqSmRtKAClWW1/sBVq9k0OAX5tBVm/cg+IxzB8DkPnwQwSdF0Vm4QDLsnf0qyMqhVs/EcDLmXrJwKVeNK4H0hUDd+ohKfe8JEkzUSA0aHymNdr5C/LDEGFwfHz/OOYCKIbOpBImAwW0FdVSLdWL60wFTxN5JPyy/aX49Rk402NjNmTO/8JLi7kW8VHdYTDitmS12+Qw/dixvsEtsZz2TM0fcCLfi/7FWKz1I93ZE+86WRXKPRc343M/glV76C9vel5H2XsKn3rJNMplksWrXjvPRfIJmbweUv8VM/B1b9D4LCIrBgUPAasLV23Mn9VJes4yhtvTn3/3yG80MvFQFuCjN9sYm6QivKavMlJ33KgLU/Ov7IB0N5LyjinOyA2z4M43PLh74cY6Fzp+e6VCD6rdzRfFvA02ZoieCgx9enUw8rmBgEmzCHCD5s+MJSS4ojEDxP0Ne1Y0WJAlBb/KtS2U=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwODY5OTI5OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zZDM4ZTZlYjk4OWM1OTQ1OGUwNzMxYjI1ZTBiMjAyM2E2Zjk1NGIwYmJlYTlmZmE4NmZiYWM1ZDc4MzdlMDUzIgogICAgfQogIH0KfQ==" + }, + "N": { + "signature": "xg3HeNPHrPJBJHdFjvR2La1jr7twbwsZloN3XjxM69vCIoNAyjoy7vtgfF7oX7AfD/grfMVJLN8NjX97NmZ2kes5omLXZPQDenue/3xKPN6nvNFqwZk8p36IlvItQ6f9Hr1ptqcSBxMSHomcWZ5Ri1rMxCaxHtnEx3S2XNTVw2DIxJPdI7Gf3GozvfhPVjrw8tPCuPrKnNgbBy3urdHZQFM65ujQe22pPktwoFfuoY5LPzPd1egkqPhpn9SlBfburaHfDlr28WuPsqzCHiC6a/sW9tPVxkWsFY+YdeJ6VyQuL0ILjkZPfFiqJgTUhoN8kKd5mqTFQeiX5ljweGaUN5OLIpfHTP1vGrLAmE/08TN/P638ttQA5ppNm2lFHMMi3rqe8U4B7HxLalfghZ81T5WH7ybQzORJxiIOE9O5B7yENLTnQ/+Qf/+3DdVJ7r3NzpebJhmsI3O8Jj0iHgJFfNx6Jrlo0uZV2LOlwWl/2bRjU9LrOed0kSVWk8C/4/u61J2hiOEaxxojXExEDxcSKSfJpuetOow7/kiA6jg7FwR8AmqUbygCWk/JRYh+vp14Uf68ffFsKLCEZssypoKVs0FMs4NEB4pfgoh14S9Cq4CUQef7h0/bIha3/qblGEk/8uub0n/meYSdLCng3juhzT0yubo5Oj1WQDQCIZ7xoF0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwODIxOTU0MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lYzJhODNmMTM2MjY0NmQzZTRmZjQ0OWIwYTJjYjMwYjk2ZWRkNTFlMzgyNGZhYzdhMzBkYzRhMGY4YmI3MDQ4IgogICAgfQogIH0KfQ==" + }, + "O": { + "signature": "nqymLeLm9EvpJfIRbMI9R4xywwDLMwUP7p5Vv8n0eML+WMIkH/DJZA5VcwPZ62OerfsTuy+m0dhjCqWDoL59cCR90dGLIwdGlhLxhDCIb5ojgy6xKQAHrT09f6CPYo9FLXr4HV+01/ATv67W87cn4RQ4mntoV0ZSp0Dc0Hy1aCh1FoUWynP4j1ofa62DasJHuz7Dp8TyRBpu1IzRMl3a/f/B8Lhb0gwaMKMsIvn/VbTE0qJoDU13cB11jnFwcqNlgOzLEifLD8nuc6BNAoSBjIp/k34zBnU4dwIhrFVjX9aPLmEnRy/pqi0nNgfMKK5di0xJDmuoi09W9gVUIgbily3vUfTiHUlzYF4VZejp37feRuFhws2ba1qOMuKvPmLPlaLG8da+LUVOng6VK2bGtbBoBYS70mAqtCkGmPWH5ms6ouFHb4B7/mpzC2NpbXflW60DRZS4OjmYbJ27orDDJUSSGpwD96PaN8DOTReuGqXf57B01GrQ4sfT69vD5baKPlbfkCzEe1WklBPmyb5vNDyPh4EuaBF7SZ8exHyw9OyUZcS043SJrNCHTGe4aDnbmN/c6I0KipUO+Qryt+mhjqNWB3XTmo1wxYcz4wTMaVYxH0Y7Fw1LL5GCA+jGAM92xgzqEZeNSlJTj9G1M3uCB4yx4gibIBwtUVeHnQXZSt0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzIyNDgzNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yODkzNWM5M2Y3NTYxNTI5NzYyNzA0MjcxZGZiY2MwYjgwMzZkNmYzNTdhYmEyYmZlYjdkNjkzNmJmZWIyZGU5IgogICAgfQogIH0KfQ==" + }, + "P": { + "signature": "gOz7u+2kGomra0b2HsR6DEAelcy3Z6TJs9U5nQG3DTZKkgopLUt00yYHoMKsyXOm4lHjPWLOQ9CLqtQNVVrA/sBIl3rIGG07kKjkWqbmKu6J78H0SrruYZOQUdwqI1l3FQh2VFfoGwroX0DQy8wTfHKQLoV7viwbNmKTU3e4870uFKoDXigT0VDOhO3igdmNZdfJWE+/iumRXDjoO+1qSTQl4a/B+yk4J7nJe3KYsgvUrvHaYSkis4/Z1YA2WBCdR7HkzwUZwU2kS1EcBdR40L8VGmLVnipeAAFPWs+PXWULeTFa8GAmkeu37Hn5LmXBSTBrOU2btDYOyKIUE1oUI5JAtfXAtf7EZQWHXofIwtoQeXTXuc+WiuMn2aRg1mnKIOWEY46WFReR77KqFauMVx0k4CqeyVupWvrH7kwxc/T7QxeYVTDfVJdjtie0sXY9a2rMhtg6UlJigjcC7uO6rMr96MXEDxn1zxD1gXTAgh5DR8/jwKIWSDJ6an3voGPboyWwXkUu/esy9c/+tcjFYdnw0NRgxX2PvDU7OhqWM1ezGK95dzpZoIwV2O54xPF0gQ1qQueMnQZCkVaxn85lDLq4gWnnAOjGSJdiq3hSDhV27t2yXnxMoT0DSd7add03CRFXd0Xj791tMlm1G7mohWLhtcxv4rbNcD1nVnDqSDI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwODQ0MTIzOCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84YzBkNjVkYmU4MTJiNzE5ODE1ZTUzZjFiMGZmYWQxZGRlZTg5YjJhMWUwMjY4NjQ5ZTQzZjBkYWVlMmJmMDQwIgogICAgfQogIH0KfQ==" + }, + "Q": { + "signature": "s7KBdMzGU0io2t0LHucrRZiQk5xtPxdWFurEsYUOosgCe766OeHmZfhIpmubduAo71SQhtiCVsWrUCfZdC3fPIQN+wPphUPNyjgK+89Hl9mrXF7ioEvxmfsMbZ58gyeIfBOAoO81mn/dcy+FrmoakmqOMC4u8woMaSMbA0Nyqhv0cBAJ2hmsc7TTwXVMk5nyoUA8BETN8IcNv0VjGpwA72kBZOTvTqv1un4hZbt/8vzemk0/2InGQVe29Lr5vkW5RdbfxEND5xadNAoxOvLzHIFVxgQm/RlLXp1fxwJQo0b854RSusNXeiiaNhVqZfRdZ1x4GBoFaOr5zjU7RTyhUCR7fwBRQpldPkoA65SV0eTjbByiNwnXvNsRummhiSwdoa9bsyaG+9vyRQGcJSJIUbLkMeQAA7Myj0HX7ARhX5Fe/cTJUu45CSb5k2+LY3T7qEoxFrJkotskSQDnP75rcAHx3eoYxmEsVO0jdtXYGOFXc2Z0U58EgcYlV7KBJBEnpD1luSaKOfpIEVSwwOVzjFXjkQgklUPADWZqOsmVrkN0443YEkds2UTMk2oCu6QTzW3lnH/Wx3vZmLzQPiiWv/6qBWBfhtPohKjyJf0Sw+Ymy1yuaJ8VghNb6wPCeIZYuSlkpY8ncqdMBfWzqSme3DVYM32UkUVV1cKokGDnBio=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwODI5Mzg3NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xZTNjMzJiNTM2NTAxN2E2MTc2OGRmYWE3NjhkMTBmYzk2NzQ5ZTM4Y2E0ZjM0ZjA3N2RjZDYxY2VlYTIxMWZiIgogICAgfQogIH0KfQ==" + }, + "R": { + "signature": "eVjUvB4gD5kdadAmSvcAwERNsWTgMxj99y6NSEWCkQn0eghjy2wOV7QSY5GWczN99v2aRlmREtvABx6ZJiXsTystNdrNY+OiJeyJ9V+j5/0O1jK/3emzKZh4Cg+qkI1raXGU2W6jE/UKT/xo0mO9vS/H34V6O+dMD487CzahLgG4pJs9iLB08bfxMa36+XE1T7Z0sUmfUVi+yFeU53rw6EYfgx/6RXMXGQczW5xnApAgYrx0U3HXW6QtTnwsSiDQH/+C7quGb8tg0PWS3jTZCxCl2YsvQOpeFDW7EE3TAVgs3+XY9lWmSrcyls7DsvwmYOisVou6bmLlVJ05Ej+2nmeQnbxwGPQeT47F2cgkxsKTYoczWcmozzGTABVjDxIS+guUkVb6O4H2Yj75H9ck1Vdu3pYVt68X1RqHOKeU8iGtNKLWvVq2lTaTRkD0UAoKfZzSFwdBP1TSNyE5t4wZ1KyxLVgqbb3tCL0JO14uhUpSAB/XVewYNHutKVOIZf15E6lNWHDre7xoXx7Z7KA3j8KT/o80xlKfHe8HSKcHBShe13oLpivoV0Oq1kZ1gFIMv84G4HUiw2ngHNxPLH56kn0gvX7RFjCwd3hau0XJ4/ROrG9NYYjCgHblBPZYjYIqzAiNDST8/QJQV5Rvl9MJzLk2ZGS7B4mGqr6lVMTZkTY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNjg1NDUwNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83MTgyN2U3YzM4NGNmOWRiODZiOGU0MjAwZmUwZTM5MTMxNmNjY2MxZTM1ZWJjNTUxNDVmOTEzYmNhMDNlN2JkIgogICAgfQogIH0KfQ==" + }, + "S": { + "signature": "Ub7/pMkx1p1sNspfhffAL3ZzlONs7g/dR9jOukCoQSsRd/4RItprQ7Oc0l+Vbo2JniEmp4bTfSMXJnqL5T+FvWAjYtZh3p/X3vtDiyueSte9U1C/8uEBJBa4jXaE6hSqqfqD/Q7L/2OFrQWyChFtFjjIxHlelse7yY8nbl2QbRSbfkWrF2s+TrGMIKP0iYXrgloMdOwwUHuh3+cRfA+o/q2TEV/W2jvoUeu5uMVmmkOhJ+wsDmtCfwLxkOW4BVH3Y81ihtAIFxuaLGJe6W3yaHGvwlzmvhSGiyrYLldzH2jNq8r0tXqwTYLJ+yDwTUjotDiQxz57gx9z4KL7M1zryOFEJWewxmwvmzhaH8NrQMlZHkv7MkDeaMloj0DnZlIyKYtMqEazXiQFgw7AGpPB6zs/6b6cM04eYG8LA2CUtCwhSvdBepTrMFy3w/aEciTJXkEDKQG6Ybsf/WbngJgTKTtQYAlbuABUPNvBK5IbmsVLSZJ9y1t3U28nD55YhOJ76ngnWAXX/tTBy9wF/S2UVq9WrOebzWPobjqNwSnPY96ULPmxZoEEcDfDiwqkG+gak2ldC1AgcnRPKyM+FGqpqkukZt0vIU90Bn1r6u1WUF9WLaaVQV2xhjCmNYWFf5Rkl90q93a9ImX5HhqxlJ1YdkppKZGcEm49RtBjCrfIoHI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzI2MTc0NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iZTkyNzMyNzRkMmM4MDE5OGJmZDAwMjEzMDAxZGYxMDdmZjQ5Y2RjYzkzODkyYzk3OTQ3MjZiODJlYjAxYjkwIgogICAgfQogIH0KfQ==" + }, + "T": { + "signature": "Qg4LKAPptoigElKWNGE/i/L/fIpLsNSLGHbaj8OHkQRP3pWXLJouNdcbREmulrLXKqO8fy8PCuGLUn/He7XYdxrU1daYTiH2E0vfJNXnDGTkEx3OIx0QlHbZgrlu4K3wff3N9w6uFYKmNtBov6aJ7kjP9bAoqu9XyMEHmLhzycFMaqYZ9JoBUq560vUWqy8t6A+qX94OV1wQHp9WsmiaamXm6Rr/kOYfO1fJ+HP8KjKGtFD6BBbj02ybej1IgYwyLV8xvsfI0Uwj1tK1VYjMPVuUf+Z2kWecMllVqN4FtTodTwnUvqUJrEyhDUPZNlqy0DA/KQJwHvgmhSMyemNO+V3bUHtOBLQ2oY6J/2e70jFs0KQt69kkEX4pdYbpehPfL9dmciGEhxYKkTuG1aSpvZAu4xuSP+i6xwr/GEQAzoDF+mZVRU8poX5fkWyyd6faFVmvQWvy3IzwoiUz99raV0z7rSbv6zFNd2jsWlCRZ4WCj43+g0ws4mWmdeF/p3J38ACcG7w3jdfX9YoFC9pLPq9Zu6Szsp+sfNemqpUzpJfnNV/K36mWjYtmiErCjL/BQEWwvh+uE3j4yc6QBFoOkHq6hXwUkAR5byzmkokuDXyqZkTlIxnyp0TBYSk9q8QOVxf1A6UjozWPfKNcIkw3TcXYQ23vy8SDNgsdkJSHzX8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwODYyNTg4MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jNmNjNWE1YjBmNmU2NmMzZjRkYmI0ZDA0YTFlYzA4ZjE4YzVlOGFiMWU0ODFlMjBmZDIxMTgzMmE2ZDg4YzZiIgogICAgfQogIH0KfQ==" + }, + "U": { + "signature": "YejkjQXFfWsjrWGPJ+eLhVYzUCH80MApBJ5t1pyovnO/nfkmgw23riOhFT62HC8eawNvEDGi2kFWcu6545RsqR0Mf0X0meVZoMXC3EJrhAWTQ/QgmVz0lu+B8/0uoJGrHXm3Su3PIe1L0vFpe6AN8fEWlLbf3liLc39+5Cw3+ObWIzzlp2rdYDg2ohQe7vh/rVjWjLwd8fz4sjBmSV/vKf9/PdEVq0WemqUp6kjEIVk4HzQo1B2cmmNhVl4y3W342wU/P7ULgT0aII9W+Bkmnjxe6pP5qFpJMQrOv2eGh/YgGeFPi0WwvIns/picbd16AvwbJC8IeGqS56HDdygdYqORZdfbksQlqUV+w+hVhBrha89/G+bhNu//DR14LyHcFHJEAMCPHaxrXKEbq9XxkyjTQO0V6ij909p1quY67Bd+WtwcYHXtHtLP6WP8j+ffrSGlGUjtF1cQUsdnfwFYHlmnda6d1osBh7JNyfS7fLNcGZpPpMm/smgIMhpBRqAQhV2/sUZWtpen6883ZYysFWZcNpbxrSSzJk5Jpt7VCHRpyyXuokoeSjwSrTfms2drpPsQnaF4eT3Gls8gyxUXKYvGysS7tBjV0OaYhtyzYx/95xx+NVZpCMjynFWEqdLA6RBYi8My+qtrsSYnuMTSlXj5dM4vly+LfbgBdzaQPQA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwODMzMDI4NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lMTNiMzc0NGU2NTQ2MmQ0ZWY2YjQ1ZTNiYTM0MWIxYTc1YzAyYzFmZjg1ODIwZmI0NDg3NDU0MTI2OTRkZGZhIgogICAgfQogIH0KfQ==" + }, + "V": { + "signature": "F9+pOKWXIm5hbS3guA7LJs2XEfRwOSFjK90xEPCB7LgR3qOUWqjJy1knatCXrP1HmHGhexq/CvaPZZo1NsNW5BhFVE7bjJGktiMRUOgbkUs4dP7cmo0qSwOQwInlLZegD62/8S+TleEfnFKb83WUTVqsd4UhhZCkW2LZElx/II2mxvxrbruM21ca6hLP+I+YIZvfo9W3yGIigqyxmeM/Gs7R5LfQwcUUpqeuzKjoU4zmYbXh2vu6ci1Fvqp8v2dpbxrtIe4KJEdbS5qMPX27EdhkWff0XKBmlMffgHdn1KM07wyR9dmDMr6S+RVAtwMCYtlU4G9S0y2GRumNnI6inmNwRy7ukfTgtJV7gotX3GVdY68Ivse477rmzdd7ywSkkrBoDxtljVnzsNAXAb9ngZH1Z+RUIcdKLm6mSsJLZExdUIRVHFq81Gzc2N+R1olHLTgJEo0vQvxozLM/WFdhbyTuUBW+T9L9hFDAxQRE9gJVIMcnicjnPKpKpBnSmpsU+of+C0YGqIaPlUxXONgBqUk0V/LwowqBiduZ4kdHCUivMnw89zTGZEAgwWo4FtedddoCBklH7QguWbBy2REXbtuES5B6dcXc87DffC55oxKBcP94ir/7W3feT0noe9WJJPLXO+tGG6OB4fVI969fuK6n2X2gBC3bItZr5icktD8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzgxMzkyMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zNzU0YjYyNzEyNGFiYzM4NzA0OWYzNjQ5YTNlMmNiYzQ5M2FhOTcyMmJlNGY0NWI4OGI5YmI3MjdhNGY5ODQ2IgogICAgfQogIH0KfQ==" + }, + "W": { + "signature": "j08AQgvuH+BOeVQNx9qweg9S4KIi6wBz/AsQcfqkqR6mgEB/3bYPJapZIS4Gl3HUT/RKzb1J2xqBW61Mea7fzaZaJtKvAupokWX6t9m9Vm8xTU2kYgvUIrlxeuk5yx6bDGzvS8V2B7QazcG2oZwgXwBUIzOXIGR7NasJ2yr81eUZa62cX3eYMKPjRfnd+BRsqHQVju1ZojBbGd3xT72AxYrc/8VXt21oZ9gq0KrwmTvo45vFzeKOhksmFoO4ij99gbccdAPdidgGIHSO9qhiGlDxYDjm+VnlFX6kgo4m558cuv44EkNSHV6ERfoWVJzd9ENKO1nw3XXG2ezhnX4BQslYO0OkTccb8QJWeUDsMl5tKFdC8nLS+Pe08tkdDGrP1GvEyohvbVo7J4I1HZFWOKaK5LpIDDYc2hS7SBND8e1rkhk9PMZ+r4/+WZnya3Ngp8/RW1ty1qWdZkcOQyBEcMIe+uvVwlMJEKIfeLonvzHmdgUdcTWOwpvLXTmz4G/CdHwE9JHiRSwHg7Pb5gv49NXuzrMOC3XwJ3G5N52n8l5TIWGeykoWglvZ6s2OYCjyyGxrv/5S+L0w9na5POHv1ew7Q9wno4WM3i7EM9UGg0kz2oCa/QgXmV717YJi2h4Zf1I8qFb6ZMxKrFIun1qZmeWEMdR1XGpfJK3evtzH+S4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwODAzNTY0MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lZDE5ZWM4MGQxNGMyYTZlN2I4ODhjYzQwNjk1N2YzMzc5NzllYWUyODBlYmRlMzVkNjBiOGJlN2I3ZjA2MGI0IgogICAgfQogIH0KfQ==" + }, + "X": { + "signature": "Dvur+JhXoKnT/wi+/84tlq+S73xQl0RjvikUjOsutK2ElIOK4wKXndIZiRuKhINrzTLQQlTid5aniSnofpvHR0YJt3hDCasQrylQiHvpP1BJpEKynVSoUTPO0v6QIJoJHtlH9P+URm3cEQhUzVyeJE9nzSCuqHvAneGh/aAQohLaBn+ZRPbMO9ExRwuBYEqFMV2CfCVBNCmFnCi7TuVeuy6jqtnGp/5VF1Uf7JT60r3M9YwuMUY8IJJzXX4Gt2XnPQElKWGc90CIM4xVD3b8WxejX1yI6kJuSgfemE8yd3S1hPFLEIlKPce5GV6zU545MAfass6OFQ+5M7GO7BzJqUW3+5ihRLFXsBQk7SUQRuDr6uVrPBfVH4PnlgtPyHZmE1o32hbrwE6Q50ON/eKE9v+zmE+JEi3A4qmRs6wLlgohM4r2wDVh458KRJUzVCoRJk49lwrHtMw8GbxpXRJKbKR0Uhyi3U94zKsUkIxmzXGOslkClzj1IwVqUtnSgRYLBNFpN6qzUDQBDLoab9qdljyO6PEpGsxfFHfKAJfUuM+YWUbdgSjkZR9+6yv3xtBqHdIPg2v53mYdLycOsh2bmZm9rTBo8jPeev7LqiHihktfTN9oyJ/pYsU8TXSnOl8fs+BiEmCT2WI7YlvIIQmsYM1PjxuQUWcdrfsQUtLSIR4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzMzNDcxMCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hMTAzNjdkYzNlNzg5ZTA0ZDMzNjhlMTM5YTIwYjcyYTNmMWM4ZDk4YTRlYzNlMDhiNTY4ZmJiYzI0YzZmMmVhIgogICAgfQogIH0KfQ==" + }, + "Y": { + "signature": "USwegH2Ptz75L7bPMUniu3g2K44DqZHXfCGFYBdkVEp9dnxmmrcogkq2pb9a5cBDHvvtq7nwM4uqDp2xOL6FbxsJbfagPmcoQhd+Z1NHMgl6p/CwsiiYiXCl/XWdSBMMZeziEbbFfg0mPVnEsKIJK7dNuR2UIja16etzj9pHhiYZUFaQIrjgXMlmBoc7FgvVvcyZjt487/9ZJ5So06V5E1zGz7dZqdOe10kqBtuBPkrtcksjYrWC7lV0lncvJAeq910n9n0BCBNAHcb6g0fuad8vKYRu9Gu/bvPWTkh+doQXTVsG0B9UqPHg7/8Qyk7vxoUTr49klZ4SuoqgtGl6kyIO37VrnH0V754J/B2t+1zGmOM4g1pNl8hRpBfvkQNDUrigjBNOlIgj5SYo3I+iYn910QCoySnoeFpo3o6NqGM2qc937fjvzCjlRxAfbPr9ouqMEY4iEEPCZSZYNKbSgXAqfqv6owlg8y2uTTNpRgGuW1AiHEbwVeSSV1nu3fgy9xyeMtg4D1iXqmH/xJacP4HdLSnqNjX3UuOGWA4RTf5hk1bWx7yHCInHlD5UpztqH5w1VSgkRofn4DwvyfIMlxz7l85Oe+5lKdqUdRmmQru2uxhqGYexkh23kuEdVI2SQnGhamfcsGO255IyDXOD4KQS+ApyV7vtHJdFlUaDqKk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzcwMzUxNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lZWJmMWEwMTNiMzdkNzlmM2I5NzE3YTczZmE2ZGEzYmYyMzgxNDUzZGQ4ODk3NDk5NDI1M2RlMDZiODk5YjVmIgogICAgfQogIH0KfQ==" + }, + "Z": { + "signature": "Kculxp5G+IobHp5ul66BMIdxZ2y000BgLGZMmZ3Qc3NJMQpfMXtzf/c+gl7/kHD64pBOO+i2anK7xBJlxTY+wWvPkozocMk8y2uWbSIal6L1VG4s0QMdTKUzbRw3j9CV1EPwoOXduJJLczA9QpQZzBactQpVXsCBFbXhxpsFK0O6hrPzwnMquz26WX1TdZaWqYIeK7gqBSm2PD1ol7MYnRV7lCNOqI2bAktrhbZxPFtAY7SroxCV2GsB2X3us85beimQkHl1ILSUnkoFXsakbs9zsoTniuIB9Et8xOvKZ0TxjyBW88xMLw89I2DKpqMzp6VCFtrDcXSvaTHLib9EUwcyPyiA3XFUgwc16dXCGMf9+4a7cYPOBRQSWj6lvJuhd+87eZz+SEo+glvejFhu3EiAbgp4QIfGGTofpWbQkDK0L/OlSJnzJIou80j/mnHiczBhOy8KXzzjqZgTUZvewqnsXeFYFJCNblLwF2NDIuN+7KAkwemuRaqeQbFPQAi7HekzfJtVfsM3UaOZ/mSjfMLSec5WtMnBQYR/buTSmJ8JQmtZyQcuwBwxHuZqqU0tGXk3ddHQEJY/TPLosnIYY04ORKpnVdd42WDx3nBB6Ftm4NFNNPyq5Q+A2h+9G8EY1valVoSLQiToaaHGbKxJ00GLep+1jbA7eRANG/qpY/Q=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzg1MDkxNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kODAxOGM3NzZhMjY3NTU1NGI0YzI0NzhjNGZhMzEyNTkzMmRiY2YzMDY0NzUyNDkxZjViODYyMTg3OGRlNWI3IgogICAgfQogIH0KfQ==" + }, + "[": { + "signature": "IBxfockTgeEHKiZRj36Ca0ZxBzKF5MvopDM9/GPInirgvsX95ygM3qPSDFg5K8S11l/Bk/7RnLjevUFYGtwtA8HAdq+abLqOhJvM00nHzwJK1Wgy5OcG0nj5AoPJpFZNfqZB94KHl0aEUBmiM370n264BlHkzLt7xZmYwKcUDce+sUNsuz/9H8pVLhzXGctvdk5IsGJ8vbtsB3lVQZFErnzcNyh/JUgQ9tmX7hnaa59ANIXBkUKn9nx3ue6/RP1KfefcM2BzEg4KcOifT3KSUTZR35M654mQ4l+tdUnIZmg6ZkMsGLJtvcYHbLUHoYDOZ7POUoP5snYD+rH/POPIKGcrdua4MUPsj1joD9NEFg7bd9xJz7jwSBW/sbPEZs3Jxj8jbcSDalKbpO0cFN+HNBtUpykIcKb9TD1VNJ+KYdZWP6MWnZmvICKsu0dHLhlCi/4vahzRJFp8NMw7twrDPBkwN3EJRnVzwuhA8Vcc0okRX4OK3hPmHUOjatprqUqJBjncdwtz2Zp+wsBaJtO9Kymromd3CYRALR3l7FlrNy/jBjrQBN0isGKDntEZhmGFMc8S/EqjEfXF9jTyjuTWjiiktiRG6Qikr2V1fGw3XAE685K3ewxTEWYLZO5588CfLhQ8IgenwfYIUHOHnoH5sGbzG2l9TQnb3SMzBI5+RMw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzQ0NjUyNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83ZTgwZjQ3ZDExZjc4NjhiYzZkMTFhZjljMWU0Mjg0YzhmYTBhZTliOWIyN2MxYzk1NTU3NzQ3ZGYxOTA2YWEiCiAgICB9CiAgfQp9" + }, + "]": { + "signature": "azfjwlGpvF2bdXvHqIRU/wbbsVhqdl9IwkimxMfKpjujE7kAbAnGVEBfcsA1Qf8zUXg29qjjEDVGHkrx56+LIpyPqsD90z7vSAhh7oQlP26MgFJKKdmK1SrsJxRSlHnhlGvj2M4NA/vh5aNbi3nYWraVhDUiuHm/pL9DIg/da62TRSDDtkUw9VOzMlbytFyl3MuhfFG70jdH/C+HWqemGHL3eo5zPQGkApGqNo94pnmQ0gWWVt88qMv3hpsxJVHXjV0uJXxq1FDR3jVJnjfXeHLIilfy9X//4Ox7fKKBCyIC3LFk0tPJMNBUjUDiiNBAtXvxZc+a57ynf53SrlkCx++9rRd560wRAhgg52n6lalWOxsbK3YffM9tLr7p14nbTOa/i2emVzOBtFWggZoveoBQoZxeTreuX3w4bz1rwr74lTkKCgSW1Eyd1uv7eOxLIzObnj1eFyEL9d2Zvqg3/VY1WSNXN7JTtFDSFtZgh5rH0y14TfqjmS25apUwBf0mcj8eZxbUWt5Q57JBe7vtIJHIsE9cYuWMdEnUbwrUJVRmLkfEdkqXCOlE4udRYZNLjlkUf0g+zneuOM/Ur7J2fZ8p/eC0YRfRC+JY2Y/p8g5cJGW789VYgKkBvNdNAUJ4chNF8kV8McoqxwJMfwTJbzlvhRruWPlViqE8jFpSSAQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNjgxNzYyNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yMDdlZDEzMzY5NmExM2Y1YjBiNmZjYzZjNzI5OTM3OWUyZGIzMDVjMDg4MGJhOWU1MmU3ZTdhOTVlMWFjNGJkIgogICAgfQogIH0KfQ==" + }, + "^": { + "signature": "jhte6oqNRtikjFxwqcHHSZjdGO+8amtOqoXXnjvjybdYxbJiYWVN3yur3DOtyWY5zZX98zxkGQwr5MwOoKmYVNwGtuvEXfBW26837jdZ9mepWiKBD2T7bVjrATQPAX67DGJOFwRKKjYNYcYDR31MGDa1W5q18R+HccDP6s/vak8WqW9DyrtoHsWVctIVioWCiuwRk58E83SkWHlpYJQ5AcitpCZEcbaQpYOTaWhzLitrerllwNM2X399GCOUXNrhckBtnB1cY0Lnq9L2duU2Okurj7QN5S7eHISBQL6vctTP9+SLFRrowX5Idhw7TR6sN4oHbMmrYog8pw6QXecLio96xwWEQEx+/ZRRDdl2jKk0W5O6Drp+eFleWHxeKz+3yuCrqZtWv6yW5/piKteLDh0Z7d5XsfY30suSLeIGIPBLxV92cNtXlQhq8FTrvzrhFjkos0cXbAz1HJOSmgS/yzbnCUF89sKekzL6Os7zGH0bmHTsVe62S8A78Leh9Vfrz+pkaKsvM4DsaFp5GCotJUDaFe1643pSiKr5M1Y6LeptD48FpljJVm6yD871dhS6LuUeDKXyDgOkWPgwLdQKIzuYozz2AHDXF0GUcB+oZgMBLgeTxQyiv3X0KsJZQA5t/Wr22Eg8kw1Z5HlvSwJqlMU5b6HbpI3mRwH+8x3zEWc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzU5MzY2NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hOTE0NjBjMjQ2Y2M2YTkzMmNjNTUyMTFhNjIwMzdkMzAwYmY3NTFiNjQyOWVhMDc2NDE3MzMyYTFhZTFjNmM4IgogICAgfQogIH0KfQ==" + }, + "_": { + "signature": "FmaConYRcE4JUq/X5qUKNrYqSelBII0tP+UGOdd3jnddh1rGtmn+ade7EDB3JfRo5yNPCCCG4CzPPV2fcFateh4qzhXKY1DHGo+0F/KpFgRaOeHmPifjCBaCFo2JRMucucKTiEw4rnjHGWUepOP6BcTlX43BY+wRsNxD6hPaWKo1foUSVawgSoSNweNKQWEvTb9t6qDzpDBZGAOpGx5uYhOjMuuWdJsGuh3Ocms5NDYutw5Mi07RKcoiXlTjBUbo3fdKcXSU19ks7OaEIzl0cQgLWV5xJMatCJuK6WHxLrCDNui0EqBqBQsy84wIW7b2RbCgZeza39pP2mVi2XkV2G+OWZfbGZTVrtup0CFNVj7KIw5B0cdEEKgtrsrYSfntIy+M3X+li4nxjkweRFJ404v6O3DxHiAR05UqaF+Sgg0biZ0epDTAmd+Q67r3UhBwrLqNsHGBEIUDTp30lS3uqjdHErDFuTHxccHw217Rm++2u+HXxpiegtNYOiSzRifpS2XxPVnT/wzNfs2Dze3KOEtV1qq1D2g8z5s6Gj0TkOy1wyMfYml5V5HX3nq7l3aaAXvBkeYCzM5Cbbx1wN7mrS1oXq9fPpDfLvjanXSF0qdobtXS+sv26TvuNh9eViosHNtWrYSgR03kAUj/kEEN6lZzXRtU07e1Gr+XteMmt+Y=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwODgwOTk0NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zNTU3MzgyZDU4MWE5NzZkYTc2NmI0NGNiNDEwNWMzNjQzMWEzNjIwYzE3MjUwMTczYjI2YzY2MmNlNGY4M2NmIgogICAgfQogIH0KfQ==" + }, + "`": { + "signature": "AwpUyiBe2khzhBg1G2NOw/pUNiPGNMNNAaVcYNn562djO50GrZoAJdVXAwskhYwtAcvrqCoWXORemWyviSHXvl18AK9ou7uHfuLFrsTcL3GK5nwcVrFcTY7BL9hN85kYmuYTX8yGADZYxLQVJY5izPFcwA7GjlW7mKiLLwTNPziIJVk4KLc3Fl6q7VboY92fejGebM2+64BKDMMtA+a5rBeDk8cYjEBC+3m5KNbgrgdOwZKRPYWeFbN9eNgJ7dxLVdU1g320YhPlld1bgtb8LGRKCHHhY8lZUFPY3zWhUc+8dvFj4oosZif7ym/OkYHHLfBOGbeae7dcT1b4Gz2yD6truBbddT4w0emyvoJnrMUqvDEseTA7lv4zvTpM4wgX1yTrt0X8R98A7xKZgnphBEeGC5SRuYjnET7WTeF+zIY619NsxcNJ8L1pXmqa1+9+GdpoTReQvYN25WzCt2mmXQhVvAdZtfdWzSOsiGwqvVbAWbSNCiENRbMWTNYTsclgnMxz7JW/Ix/wgtW57XYO5nMvCA5qmu4sYsTqheF/cpEBsezULpXdA56PBgJ7d3VTsT5LuHXRebs9xlWI9vUPm8ASJ0Vd55M3gKtSp3EKcsjh6yjKT8eXmhKr7pf/SxteDWgl1OyclIGTxmyzJ2GtnEeP+5M/H+WWaqB0WAuNfRc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNjU5NTkwNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83YzU3NWUwYjE5MTc2ZjE1YTg0ODVlNjA4YjliNzkzMGQ1ZWM4ZGQ0ZmQyMGMzMjlkZjRlZWM5N2RkZDhhZGUwIgogICAgfQogIH0KfQ==" + }, + "{": { + "signature": "JcZi/IB2tVZBxpoBAdM90VlMbs8tLum22pGuDKSDzi/EmPJk6R6qrpLwmHq4Bgqr4tJZfWnBFuqAKm25R9X07a1uiKFf8hTnurCUJrktWm+p0mpIYwRG6Jq7vu4X2FL9v80bTm4WA4xBYTrSb4QY2SWEUzRnw8u+ixxda9RXAdUr+XytRckqFa7yE6Galm+zyNy+Yey2fnTi21usU0BXFJYanW3TRChGNoRiBO1ZKZ2guYJS0R9WpMD9PTu0vUNZfJI2UQaKa40DUXAhSLZCz2uEw2HIK0Dt2xZHXP7/DpDAMI5vRzN+Y6ccnlgteyRdeBeqs7x6KWwujLSWfDZ6xPUrSW7jixO0EZ2TAjoyCDFNpJ2FqfDxo/p00G0HOEKuSs4MyLjD6V2tBwm5lAaVJHNemUqSLKkhq+kgNx7Q2T98PNNunMPZq2K4tqLM/fGNzA2zCSZr9+i8kdrU19t0fF6Oj40p7QKy6EXsThrPF6QnYK42ikxrG3SSky9bxnRD2curkTno1fUQ0MiPm/SFMbuHFdIaPgI1AHmC7HvegpSXIrZVeUcqqI3IixcUirChNbfjx5+iqnZ+FJU+j8hPW5iE+bOwehEspZwv+I/5EySXcrSh+eAtB9Lxypyzo0iPHbWA+dCZhwZ/TVnS/BNYVZcTHGN2KCG9i0Qicf77BEg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNzc3NzQ2NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jMWI4ZTYwOGI4YjZiYmVmMjAzNjhkZDIyMmYxZWRhNzQ5NTM1YTgzODFmM2Y2MDM1NzRjMGExOWJlMzYyNGU1IgogICAgfQogIH0KfQ==" + }, + "}": { + "signature": "YtSvTr0mqOeuxHtHZlpmlSlqgZ+4kCSdvxHNkqyueDwbiYDQqGG6XqoHTnbOXqj29VOtIRHxTlR6qt6o+5Ai+OJ3K560SbQ8mHVNcX+ND10tGbeZpws/9tCTt6mkTHpbO/DRjFmPlMRUtsD/ZRD1G15HMmmc2nBATYKMkwDsz9cpAvzRdkvALovwUnRQG8bTxmXRNAdom5B98yoXCT94+wvylP+03u84BhtI7KjAm/mwzJUKsvcJGZtGNu2J3Mb/n4dVdL57RGCyHTYQ2TKd7s2J73XMbVE9eMU7sKtXBqIzWj+SNP24ce+4CUfBBnWpMIFNABywYP/h6DUUVFaie3M+CnIcRb18X+N97nngP1maks1WXGJDLctsRGGEpjzPjFiEa87D9VmHqjYHEHQq8RoC+lEPdHlYhRxfGYzyzR6LM9Bs7GViJ7b6DzliYURbIQKLIXnloXaQQ8HWi3s9imRKA+Z5GvqaLcQGJFP65fO+1XLJt22U5YEpy61Q9glhdaUY/DRz/rzHRHy9xGlbWgru0BR+6crkrWSCEQcN+XX/jHRHo3JDb8ainvRui1HSJwdVUh0TTaJZ53PVJ9CjktbT05d0iHL+95p4pmeD2OB4jWVgajsnHlRP2Ufvs/TwLos/dFoaA7h2xkQ3DqnOvs5N0FLctvn9xJvjgzz6xoM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNjc0NDI0MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84NjhhNjEzNDlmZTU2ODgwZGNmNGExNWJmMWE2ODE3YWRkMzZhMTI5NzNiNTliNjhlM2U2NTJjNjY1YzAwOGFiIgogICAgfQogIH0KfQ==" + }, + "~": { + "signature": "NpWaKRjfi+VEX4swkthC3UzYzJrjMgDqs985WC10ff457xpwQqE6uGJmam5KA2fZxsr80GhDywQExOMznbAfRPON49b4JPEwCveqgUDbDP/iAptgte4/GIpuKNgegt9gHCKLUTkP3XFJGnFI85h/RYbLEHjlXZ1rvlaRppxC0vVgZAxjkebxltLjWNumY0INXYbmL7pNg/kAeq6WQ10TO3zS3Ntrhn2ps6ncPfSUUYVVpgZNYFgQZzxchx+NkpBdfkdgRnOGQ5mQYTgztRyQF7n0u7WHKl8i6hh1RTAxWDCJT5mGFJq43fVIMd9IfBa1OPFD16tJRTy/oYMIBXGKw9JVxaqqAqhP6PjniRTFhCK7ay3v0jrFQSm6MJX6QGkundrsfUVcpEd8iA299lIUveZ/ruT9R1ETyGZVbRJarpsSJqvPB2p92soUsz8O9C7RHzo9TEj+1nOJBqORjdJoRAh85tEzz1Bcs+phWwThiJVb+UKUiZsgtCuAqiHrG92BrNVqPHBV6hw/MpEvhppDiFoEMZt97xotxi+/hkFA615uqOQJdmeBxVG25g5sqVyjtQTPcFvagh8P0K0DpUA7vgMz9Y4aq6/+YIDSi2GAolJ+z7gyGUXI02QmP+ohuRc0C1TGrxm+xIbDBkNRl4Brvma0WbG8rT1Rd8C+d9MXRks=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjEwNjcwNzc3NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iNDNjMTc1OTY4MWZiYmJiY2QwNDljMzVkZjZkMjE3OTdlNjEyOWU3YTlkNWM4NzA1MmZmY2M1MWFjYzJmMWE3IgogICAgfQogIH0KfQ==" + } +} \ No newline at end of file diff --git a/Network/eLib/src/main/resources/skins/chars/purple.json b/Network/eLib/src/main/resources/skins/chars/purple.json new file mode 100644 index 0000000..d1ed23b --- /dev/null +++ b/Network/eLib/src/main/resources/skins/chars/purple.json @@ -0,0 +1,266 @@ +{ + " ": { + "signature": "A7ILT9Cw/EmRcl5WrUZ3zYou5UFyaZBGxYWN4jjdHg4jDelY+cEGAXZUbkvZGVe4KL5b5Su4YOCEFKNhig49bk6mmZSCOUF1x436WOSBITEu906+ZtUL8Yt383MDyUcLXfMGQu7YGi3R5niXP1BSvjFdvE0wovhanGppCD5ILMowjqx5da3fbbqo6mPWaey40D1ZOL19K8T/CZCpUegd+WwLzAqTDYdF5E2SygmQ2zvUXe281/wN9W7e3vVuRlOJYrCzNKJYZgVL/MF/ShOU19BjUgRv+pL7LZKsz69hwKuWe3k2Afr/FwS+LsEw349GJ5FprtybEWTTxfAC4/aQVs/HQdbIcjmh5RzoDWd4hvlPDisnoblEDx8xgyO6zGr4IAAPaNcjpCSPh9Dp5y1t7MAGx7lPpLinAguqEthyxsMzULCk5BA+cCZgVXyDgK8SIrTnDetclr/FC7sou0kjgBDOm1/rwMoHnipEM8tRrtXuoBN2b9JDUwT/6kN5q+58H2Y6Qzp3qfajX31G531b6VEJgj7O6gcLyQeMjTEEcwYpEyfnGnGjZkTMmi73OL8NTJR8lclc8v/MJ1jGHQqge//J0PnKgYovcvxd5uWhb8O9NHZgcu+kfdCasflqFcafGDtaudcZY6EFNBBoLzqQHiwl3t8OyHpL9JyMkeSL6fs=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAzMDI2MDEwOSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yNmMyMjU1ZjE3NGVlMjlhZDNjYmU1NjU2ODcwOGE1MTQ0MDkwZjhkODlhYzVlZDY4MWVkZmI0Y2I2NDViYjQwIgogICAgfQogIH0KfQ==" + }, + "!": { + "signature": "R8cjS6eH/oaP/I0f1FN2vfUyDiq0kPWEGc96tRVLvijNZoiDh7LQqaf/VSX4XwS2UkoAjdnNWHWIHcu2CTIw/TDhIHlNrFOgBcmaD8OEk0n6IcWvm1hIR2jfmUWczOMAXhOtsS/YsG860PaewxtnFxyIDnl+ltkPNM6YcmvF19ZK1WiMB6A3znS3QlKpdNMfTRhBtmRw0U/lX0n3LPE6GM1ejSZRY6JLTUvwZIJBZImPQxptSvFq1n5mjlonvyMfJPv1KPIXdJ58vaSWgXXFlhHBb/66LGw8Jbn7OtQnOnspLkdadoahPmqL28ygyjYfoYT/tLq576UFeFqLPSknKcauNZ8JsRcBYu3kG6ZbkiLWDz0o6A9LdEmyk/yGCve/f6esIA1JxSnWfxYS3xmY6wNvu5oONOwM1n7YyLNkcN7b/ymbflfuY4qYNQYlW5N8Xp1WMRk+aDq1bZkaS20hjvnvEaJ9nBwwP+m1fB0cs9yXr4KfMtEVHloP0gIZwDgS0AoYzD4b0KSf0i5naki+7DBy30AHUhxuUBd9S16/cKTV2pi5w0tStw2BaaQclSv0/nudwJIkWnZW5ZVxKecCZzAAtGU4E4/NJ2VhBo9q0wtHYEIGN+9UriHWfEWKQGqP9go1zJU+52U85rqCs4uJUOUtoZ6vOQ4535A9orIAASM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyOTU5NzQ3NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83YzZmZTBiMzVmMTVlYmY2NGYyZDMyNjVmZWQ4YzgzNmMxOWU1NWEyMzNiMjUwNjk5OTdkMjA4YTU5OWI5NzlkIgogICAgfQogIH0KfQ==" + }, + "\"": { + "signature": "l0JVmDTTCsAhf9JLQhXBMsbPG1NPXWpU7prCt8QwZIYFZkPA7flhpOQdm38WkOtC/lvcDX0DpEeiQWFB8PineChg7rPzi07+mw2yMuG7siKAJv54sC0Ph7sHQMDhn+DkwCADp3WLoGvxgr2+ronkYA9zFp5FnEiKXFxE3TwevHjeIiEkSfOjSG1Cey7cfHRWHgr+3InHiSlKdRmBQsJ2qT5/IKjM4BP4IZShSOnEqCrdgGQ2jSV+Smr3fGrlpK+T8lEmYZhxFf75PKsErPR17cQf1wja9y9IMCb9Z/SVGPZXrFiPJay3mThHcEng1rv0cUOT/UWu4FmLRvAZTSua2xbQOvurhiXhnkUyUlJ49QKDc0GReep5rZ/UAPyEwidrfwUmRsYWtEsK7QaKYXMfrhqCyJzP2i5RR0Ysq+LChUi2WcOcuteazRAOPtOVRo9kJJ1UqnwP8r/fRTlFfGdH8ymvqiIA7tcToKjIIfFMMgi7RliWpAYbcAcko1wa9+kSZPKUgcnbiP149nLUB/GqqL/AOW0qP/ZJVnG+UsO8+w3w9u67GNpO93FTV+K8VrM0iF6f+hjdkzY3pokHvs1UWpWAmWYhfweEiEIjBQc8S7pRJHNS3wauqmddsR1s+68ExelRxYQ1cJzG0YBvibqyxCVGbFvVMwfn1CAMUNSIQYs=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAzMDAwMjg0NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mZjA2YjUzOWE4NDY4MDJiODViNmE3YzY0OTdkZTBmNGU5N2Q2MGM3ZTI3OGEzODg4ZTQ4NzBlNmY2YmFiMjk0IgogICAgfQogIH0KfQ==" + }, + "#": { + "signature": "CTjJd7v6+4ev4Z130GDQBVD6veHSKhYHdc++KY3NISsqsLqpyD3CtlQ/dC+lXRpoMHwFUIfouS80r9eaCyLFAJKG+T0V/r/uuNd4nDfgMcmc5yofvEIhwi4yPqeuQOYyr08afiZLiIISzceuUjXKZfEgc6ZOmThgSnelCA/CJiC/UpqiDaSk6AWplHTERiSS3yfBsEFXiNpWcUJkdGaiczpJYwp7SBZ1ODzMV8bHO5zPRNqVslat/xC61BFrArx9WgZNiKIy0LkUenoTZgnKxifQw7YLniGQfC80q1pRv2+1YXel+FCxxCwPLi8ldENoqJKVQAc/d9G+W+r9FAG3SyTYJvpNzlkOG+J6jWjByT/lHilZjNDmHTXqhgpiVxuaCO05O5SYA0HisbH1+SHdyp3KLhFP1zmeldGnXy1o+NMDjjDkdSyKWXCHQ0QmKjYRsvOJEhgP8oKWlGDlF5mVPOxxdla3TPkV64JjQ9tUFnaJ5MzQT6Q5/2FUjSZyLpD+ZUhnYfyTXs/D15XePLgS/CTrFoBDEdrbgK4ORB026oJfUwYyRYBiJERHrgpRY5HsumSAA9d9mjnTWlH47WcvdVaQewQ8nEXsYQcRLx9c+dz+tO3g8QXanZwaYx5w3llkxI3VVqASJPB+Er1GwBZrn0DeYLIC9XgnNWpoEt0PN4U=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyOTg5MjI1MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jYjlkY2E3MzdkZjJiMjU4Y2FhYmNkNTBmZTUzMWJhNmMzYmZiNTMyOWQ0YWM2MWU0OWQ3NDhjNDdiNTRiNDNjIgogICAgfQogIH0KfQ==" + }, + "$": { + "signature": "OLxgP9ORoZfSHqMjCiiXS1XIk970+/keIGQhF41JJ/figN72gNfB24paZ1l812G6JIfWT89/VSZTZDI+ewvBUj6lcadzcQz0pUCG0qjolWzh4j06KPqyPNfWmxSLKXCUeP/L2lwIPpwqldxRthI9+5SU1Endt/mbUD0SFiyqJcWWVHOsx7Iq+ZKVU4vJRCrfTrMba42x/gRoJ8NGO7AeEEEVCWDwp/ihCgYSLTY8u/Cst9aSVQ/yZPJm73vRvjI5KC9KSPGvuSKcAiMUgWXKecOvJDdxG2wJCfJdz9/Ln8ut1Gfsgz69w88vpJU/4DvT9Dg0uEYeaTc8ZqkZFkjkLq+2dPzGdYjqjsIVjSQsH554Fo9lXuTbfsDrjV1/kLzxlfoUD8xN/6DpTBtMsxpSpdY/4brC4Pq/AELpLSWJ+e2bONuPfSccHNI0u4no2OowfAmzT6rR4rm9i3YiHn8BqQgVJbJ4D1Rs+B6grqt9CrYKOJXFS1Gah8iir+vJT0+s8LFofJ5fejoaKJ+djurF0UN4fSP+wSepceV6sH9XUQI+OPBdCzZ4MxC14f65W48X+aeohdyDD40prq4ZdkyzdYRzd38gmCQR/nkDybMrD93gUW6/FGmvZsF6x/MipWz6PUaBm8qkioeX8gEdjjyI5HF1Nl0KP1tP9HApYYXEuD8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyODM0OTA3NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85NmRmNTVjZjg3MWM1ZDY4YzEyYmQ2ZjJjMmY0NTM3YjdlZDRmOGYxN2EwYTgyNmNlM2Q4M2I0OTJlNWFkMjhhIgogICAgfQogIH0KfQ==" + }, + "%": { + "signature": "qUZjWZx66LehYj9oDcbX1hdn4Qx0it/0LWf/dVb8WN6jCTiXK2/eNfw7iB6nvloLh0D9BMKMleA8NN9FyfGBAs//MtPrt0SE8VW0dDGQazqloB/Mqsb352TGYLBixznT3nc22LsDfLcCZLcdpDHHZ0TcURpMwFPNvW4o+I5nXALqElkkL4+xdzTk2CxU3r3hucLCK3V4IKpkPbLOO3pBRmFUHQ13AjO+Oqow9+wmeEcKhYmOXEK+qD+zP4WVjaae3NNPAseCWvaT6PvBYtlGgcXOzneMifcJtO9OocmfnoMw/Luf1B0VPFYj7fzavrQ7uRPZKIC5zuEQdHIG0mzoPouIVc2kY2Z+f16Ug16QkIOLbLq8ntR/KPZu6vFQ31VW9tcjPHD/R7zuNZqlJJF3Ppzf3Zqgg1oDKSqZAvNPguPug53r/rsmplT2trg28Rofw3bfsHe44h9iHKT+pmZit9thWd2A+fL5D7woX0hAcCOx3RGa+034/YGFQ01goroKc8fXcoulkPNhVbg3fcOrjgR4gSxQIXWlcUBwl4dEKt+o/oEjvrdbhceny7Z2JRUCfTHLrgaGzapYzP13bUJRSvY8dqd6ztckUR0rzCTsOo/ISy1CAYJJYNbSy+krqm4Qa3GSxvVaYeUlagQtzM8GFqEiw6sqyJK/pKsfudOfx8w=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyODM4NTQyMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82ZWJkNTNhMjc3MGFkYjA1MjEzZTNmYjgxYjU2ZjgwMWNjMTY3ZDBhZjljOWExNDBlMTgwYWU3ZGJmYTdmNDQ0IgogICAgfQogIH0KfQ==" + }, + "&": { + "signature": "boC3XuBiZ+mTsN42cC8MSHj0s4rULrf5g8m9hqqogmNJtrxqK29S+8jy/fn3S+SyeF1mdkTmFMmMXV60e9tHv9ftZlC8cnnX5Xy1M2BkMw6zjL4v7HXOGO+hQbSFpgVO0LfdMxkXxjKksOIAeEV/l6DJoQkoeLotHfLG7X/sVUWdAFjbnw5PwvIm7DilAEoPpv5mEcnfSCeo4uE9xjzCIXh1bBq7Oabt5MtpAUBU8C0fvViRdTj1ZYlmRMIoId5gcj6apxveEv8aNg+FKkuJSFqVWfVW1nQ9ZiBZt70oZaaUpCauz6Bf9J/CS1akpNTB9ym4P6/71JvkE4gauI6TH0j8DDyrZumzQXnlEjV2Xy82ZN9TxHegCoBkO6IbFhIN6FJ/vktrP3HvZa66qwU0ZFwyscmxEPlj+z1gO9X2MqBQkZ4IkUpSTulWJjwzNlpP7GcauAuFIqAU4HoLbTqmDOANkJCZbpHD9umE7/971SyTs1net08l7XDWOu/GBZzPcXaCf5FU0huXT+TD38+9+aTeCv0KFnMpFbN9f8WDh9fbX7cgboOjF/1GyUeJvRcvEZfu+uUygJCUrBG05xtyDQ7je3P4fKS+QR/C9PbCq09PlVohE5mueNo6fjD2HSuwcwaR3vFFwONTWUzy8hqccLWdHwIlY05lrZM9Jb0XWK0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyOTgxODAxNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zMmUyMmQzYTBhNDU1ODgzYjU3ODRmNWY3YzNhNWNlMzZkYjlhZGU2NGFjOWQ3NDU0NzBhZDI0ZmRjZDk1OTdhIgogICAgfQogIH0KfQ==" + }, + "'": { + "signature": "jILu+461MOee1TBO5XVU3ccNXWCOJ93XUe/2jyMz864FSQUh0BcY7Ql2uX9LoDjM+sxAAtpEzNmInHnCEvNFd7766hfMTprzxCGDANN+azKnoM+Q4OB+yjtWXI3WsVKJKIibjmV3x4XE0rtISq9V91Dozj7oyWFSm2ajk3UTlKkSCKPHmolDF4VuCyoFcn6mdL1B3/LlDARIOsxrQpCBtuvS2w+sMofYCj0NLXZabAdjig5JxWjorG951yuAZeppHfEaN+4RuFid8JgT0M8v9wgk7ab9iTXic67ihriASpLR+YxPh6Iusms/MFJYmArZUDIuoiZISk1Vmulgw6ZP+p0M/IgCMRSBBjp2z9WUjRwiMhSg12kUoH9je6H7HijiXxmuebIHRGlPdR6TE9fbmD16q3JRcEIqX9yzVgBORU3HW0wXRrgfGvIJ/dilSu7Vp5a/mkGXWwqHj3x9uRDXYH//EqJgN2WibnKv/coxViHmMX4UF61iPey7YYyweBmZtOesSH/MZgav4QS28qyCMxmsaSYE4vTrVnXPTTMETefGVX+7+KIaocesWwUXmcla/31ZS1lYbfumRpwAyla1CLexOxZkSi5KeSa8T0+Vd2IYnYYGeNO0KCgrc4ERvQqWKsaHwfaCo+TCdLJmkYo39yxDhbIhY+lMtMA6lquIpxo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyODQyMjc4MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81ZGFlYjc3YWRmYWZhMTI2MThlZDM1OTVlNWUzNmZhYjFlNjlmMTYxYzNjYmRiZGY4YmE2ZmNlYWFkNjJlZTcyIgogICAgfQogIH0KfQ==" + }, + "(": { + "signature": "xNhHY27ZogMWsmdEbI6D7qzPmupq8xgbXOx5XAnAoAXmpVhiNx2GMOtVwxy/gDprxPp8p8PfQM7rN1+R7CPgY1ki36kef33hxtVE7sziT8TCh72TX4EZpl0n65F6pBXfMOQRQW+XHyfGi2Jbc/rxgyPjWG1xpg9IVbQ8Y1SzMyjZiA15JUUfnH+yN2N86SZF/uvX6T7wjv274v802ndAcVcwNx5y33kGX7CpUgmm8/Jm9QOvdRkrScih+eX08emUge6Z94wF44rkWYtay9J7OkvnSP7lr58d5dCh8ENctwhSbJ5BMVqOLnlbTpIgKLYXW2P7LKVrd8jFw7xCBJRAOzQhFtl/uFu5o65sdm+n0U9TQ97NcriLOwhcI5S23Jv1JZawCNjY3HJFbSqVkuz+Q/pB4jy68FWXzqzZiYYRu//n2bzZZcPiTnhW6OUVVzGr+L/9vvWNglZPV7nJXw+b2owaMOuQ/2I03+OBjBHC/PiZFOpx1dljTcvHXLhsvPw3nwhfD9W2CbKeDEn9+p7BgYX4w4scTuz85fiCJlJY8hR2AMp+wkgN13dOSBIHtUkZqxXKOvznVkGw7M/HiX95iCkuP+xBzEJb7Nl/hz6voAJAbHv+ZyZvh8iN9ohaj7PXzmBmgdKhsOFnWZPpgnDCyUsYodE7PLIMjdWafJ0cgm4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyODg2MjI1OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84YWZmYWNkYzFhMzc4MmI2MzllMDhmYmIwNGIwYzFhMjJmYThiODAyOTQ2NGU4NjQ5NzdmZmU0Y2Y2YTJkY2NjIgogICAgfQogIH0KfQ==" + }, + ")": { + "signature": "u1fixtcYq4G+2eupA/q3piilgaCEWrAslgh1T4GIIvlFFc1AgV9yqJzEzY6qbkq68S18XcKlkYAzrhgbeoRU8FkwmHwnoiTHl5v5oT25ota66ntGYkmM/sDO30UQZa3uYCPGNMg14uyoU8HM4szkhy4ZeaL+2TgKyA63uS1pv3JQLwb4U8u5yFarK/XsPKrg2hFiJqZpow/yzg0Q+gtyo9VmHOJqJ9JXky+2BP9tIntc3/h56gVlVQ4AHFk1hsOfwii0SvK6lrbRukMFoul0GNaN8Km5+GPDJSmk4EK03z1Ax34fFYFE/AvVtSrobpJ/w0+r/arljA0Z6AcIOKLVzfjh9bgQgicZ95aIgvtubwpDAo+/88Myzs4WFDQXTzPiJPupUQkPZZmOmI4KZVMn+9bgLJmDL5pxHhRzZUiyAyD086C+uCswL6/HidWMICtICSL5PVN1Di05Lpz03Sel41EESrTGJVJqYpf49FdNEBXjSwMa+h4gwC1Ohmw08pTqiS+PHEQQ8xuKu9eSrJheCSVne+4KB553z3eS8sw6fMbATN9Tajbpe+IsRJNOGOjwZ3BDuId6UlIrK2nZ18bGNNHqVdL44S6o8BnKY37MBg+c4uNEQtJ/Brc7BhVBgwCui9PhxvSdaF3EXQ8ZuPpeiurVbLOOAYDoBx+FMkTm1Y0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyODI3NTYzOCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iMWIwZjk3NmRjZTU3YjU3Njc1OTAzZTJiOTA1NDNlMTM0OTBlM2NhNTkzNmY1MjYzOGVkY2RjODkxZWY1ODE2IgogICAgfQogIH0KfQ==" + }, + "*": { + "signature": "g5jFijeHgrEjUwHx6pa4ThTCONBlMVlGw0NxoKI8JNA+0Qx9OV4K4mYqUNhZihMLusMu7bKZpr4Tfq2ub+CFlG+xFvqusvmkhSemkrxBoo5W16JKil2uTaPx4OX6uOXvy5y1F33xEkdVfuOLl1Z/obo/EW/Gj5/qidHddpd+fOpjS1xcvQ0JNG1CbO4jFNLmt9MjBeQKNTJw5ADyVneCPESyo8AKpk/WBb+yOloWZRzuuXrOXqEubzXo1gZVZidcbXNZmixVr3GSMJbn8bAa/E0ZBxlpGX1Zq1r9//OziArZbvSWrghfzqUoUaLtixxhetz3zLvqa2S7g1C/sjvKxvG4PgXqP2TNE/hJna7ndI+SXW551tkNtTXZiKaOD8rt5INMkOIAaOnlVnDTWnqxjVxJIA9Idy2UkhH8lVo1dJydEsIBb4TN4BQ2wn6EqUnLiTsCfP5jK18ey9K6YxCRm3wmlzkI4zMQbRw5FvzCr1QyzxDB8CTT5YTAR/NIVN4yatAwKhK1joTNcuTkGoAS8ziVxiQptBpsrnde5XgdNXJ62Jt9bdyTszpsgl7W9SV74DVkGCP8zsDBtvEHQvVbnLDFuVyADrvADpk8OzaamvbkvawVLXqVo2Z4lbn+5PL0hraeFUpwHe7wIKbjjvLXJWgtwMQCqs4TZEcsgHYfDWo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNzgzMzU3OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80MzMwYjYxMDhjNWU0ZGFhMTRlZWMyZDEzYzI5NTFhMzhjOTg0MDhjM2RiZTZlZGQyZDQ1MmM2ZjVhYzk3ZjYwIgogICAgfQogIH0KfQ==" + }, + "+": { + "signature": "hV8NsviyYQ1ssYpNo/mhiv3Jn0lRhMWwjsC2cHLZkI8bR1bprI6acAwYMBsa3EJMJA52dxF1aHsllpxGN86/fmF8BatdkArB3IQIUuEQSECrg2vjqaWRip7IZFL4aPvy7zJnvA1TIdAZttwsC9zk1bonvNLOgxao+XvipIG8xNlOwpctmbKF059KMa9sGmZL67SE8DbrccinBmFovbBwvfpB2DzNHpHhujyWshiMNU9aDVJFdXCmtbN5dW3A5tUbX0adeE7Yd+TDZ8sWgA4x/13S594XjhAzKZ3et33Stt0xdUvXwaLU308J+dECx0R/B7h/KqktWUP+HMerUVJPnmO13vjkAmeDl4RUFAwOnBiIIEkUFO+sap3zwiXTLQS76fvWaIAesJ/XPz9zWxLZbIa/ZOD7QFAvuDlDlfXtPyh3KHmqkjUHGBx6A6nab6uJbNv5YP8YLaWFrXvaUZSVCihiAqNPD4bPsWKAamO+jCs6QQbOHjhh+pYYhXVcvmp37PbBZaggntQOG0smDN3UeRU9QFrG+DJ76s9vKFO9DWi7uvkDt+HXuxNzz8Rj0Qqa3bModBKm9CznfqjOIDd8rROfkHMhAY7kpAXJjPLjWrbH5UNoQOgNXDAR/FoR7/ur1+U+hPubKpAELo3AMxtCC3fLUgqepkNefGcWpS25aMI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyOTQ0Nzk5NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82YTdjODcyYTg2NjQ5MWRlZDJmMTk4NTY0NGU1NmI5ZmU5ZTAzMTgxNjUwODU5YjA5MTczYzI1YWY4Mzk2YTYwIgogICAgfQogIH0KfQ==" + }, + ",": { + "signature": "XX7qSb1TOmYdri4884i8+7p9DL/0Ww63swhFFGiVZGaxbg7/gkEdeLxlttjdNDYYCNq85YkBV2tP/04h3QsOti5naWJy8FGxAIR8Z+IhsHiWK1s4nv6GJxy5kMMCt2sDWgLD0D5lhCW83X1embJvRn4wmwZJEBv6uipc2YWmXh53MLu++L3f+khklc5bCSFwJwGvsdMpuXY1GEAYsbr7PqNaGvylke7Ci850wkBsvrsnEYm5u/VNw/gPOzbZkHeB0X1hokIbAu9lu8f7vCOha5C1RBQ/WIWwj31RxgyKzzuoPyfz/TLmaOt92rffAzMfneTSftLillLGd8aFMUYF83l0cyr1rfSqI2XQJ9k1/7PbbrU1t7lrJJPKcyhwTlco27XQ04SjkbpnGAO+RTYB/XddMdmuHfnwS1lAV7+Bw2nwcs4XDDRcKfRH48CGN0gW6U7A++ewXZzPhl7z5Mzefh29r5HVXWAkYzHKh4yXTxcoguXc/kTDkPmcsalaRB8+nr64wrtqJrrKKOHk12hM1GZ9z8JhofTxcwI1jsJKIO42LSpu/+rvxp8j779/SWDqdse3GS0mSPP4nqhvf0VU+5Gge9a5BiR7DQA70OZsBPLi8yFQW8G8UioVy8rrwjS627qlD1fDp7e77VwiMOojbxCGxdN2WG04KKkyr2amUu0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyOTMwMTM3MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lYWY5YzZjYjJjN2Q0ZmY1MDgxMTIzYTViY2YzY2U4Mjg4Mzg4NTg1YWYzM2MwYWIxNTQ3N2UxMTFhNDY1OWIxIgogICAgfQogIH0KfQ==" + }, + "-": { + "signature": "UMtyPX+Nnjk8ofrP6LjpgWoAd1fYv5xBxZanqcIk7ErIrLwXQdw1uZSPsKzdvkmkxn79jA3BfN530vMHpYxDsrpTb1tySf2u7LMtUxpei2nvVvh2J8nUX3fREkRVE8dE/3ElCY4S+nid1mRnAE3UF/YbxbdbN7PPVYT+e+sAcjY035zCdQvRqd7ybRrg+/okgBp0jHAHphhdjbb6D11VQ/1gv35TkSnmkwEC3u4/ut4z4HOrpFeMQ8Yeg7SZDgkJF8iSi8eT9sLtT+Iu+rMFnWnFe4ZuSxNawQhf7QO7nZsNMQJYvgzlhsJqUYpM4Y08mOOeQfZL1m5oEHpD2VWrYo4xJDduKfe4IA6VfQ/FB66m/pshORfpU9gV8Um5WKmFGJ0g/XLco8vfzeyimRvDJySOb01jmB+fWnn8fCFnokAS2G6CKL/g1Rj2MurhvaojG1ImC3mecbcxL6vj/MB4LvfwjkSZjpcEp6+lLjA0mXUIs0nPwFeszHCVxWLFeEPEvqwg7fhhg8uTPEXCYfjEF2bhEoLGwIrYjwaFZ1UUz98jwRu/pCCELTQpAIN7CSSRcu4fKX0GlccDiN8TRBBD8TD+EF6RCkPs6kUFy6Tb5IljybryGRABlDX46FwLuwmkR67SH6R7WTZNr/tMHPJ8bZRUEPEi3gVIgH4UqfxbNlc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyODQ5NjI5OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hYjNkMTI4YTVlYzdiZTI3MDhkOWE0YTVmZTA0ODM2NDU4ZjVmMmI5YmE4MmI4NmZiMTFkOGY3OTg0ZDdjZTk5IgogICAgfQogIH0KfQ==" + }, + ".": { + "signature": "A/NdvWDrIqdtKqgiI+gPEWNFE+Lvd7b0ipWhMOSc3St5TwVHgsEaE2A88GVjeFflym0CLUzpWfT+huUK3WRfSdqaHHWQvIkl58xT+DjpfUNUTkqZURwIiRLAYowRjJzDvlMeLBBu5XF16riFzgk/VILGMv/yJm0nrwdcDHL6dRn75PK5wxurA1NGT7L24BpouoFlrJGlGHyPrO4sJLLFWiMAyuFWYaqfwsDNdBcmMji7LTqgofVwGA3kD4/XipxpFOCSgIZFk+2WI3/Ng9WGOVXVg8OtlbX+ATcBmAVz1YXFSpLsH669vKUIQO1uijwqKDRgmwcLWYU7vo5awMkH9zfmhH2tFYUbuIHlNafE7tWV7aKrzbUanPyqVoMBiBWfUACvHxlFnzG30Svw+T6mOEILOI+lLmvhUw2cWX6GV4o0Bb69Qlt1B130+upS35GJLBTau42oCemnsgx2EVkk6180VJWV0HrBKVk3fG2gWbTt/1I2xjuQzqRTnoYhjqPAnZDCufxNU/AGYuYdPWYbDXoDYUrvYI+l4YJ/YRmJdRU01zoTUgBX2kMUvttclCbDAhKUTp7KGNn7l9EtmmA8GMZuQfTlwC6kA5g7r/NXnZVqqwvq+G/rP52p6NIv9fMiYAFcuZe/iOccZDOlSGr95UsL5rbb7L7j7LvZ4ukjr30=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyODgyNTQ5NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lYWY5YzZjYjJjN2Q0ZmY1MDgxMTIzYTViY2YzY2U4Mjg4Mzg4NTg1YWYzM2MwYWIxNTQ3N2UxMTFhNDY1OWIxIgogICAgfQogIH0KfQ==" + }, + "/": { + "signature": "wWBhtBK7cw3mW+b6ot6DTqIKTfMy0Zyyl0JZbBjfBsOPDoZ6coAVjWB94AqSNBJjDe4T6ZlDB0Oi+xvxx8m5w19D9EPBUrKXp6zrMbJc+AFgZPXgotzoocp4JFaxkgj3PI2tX3UJ/AxQuuqx6VYDdfCy/Wh553qUPCzS3Eo7m52wXMzFh4hxrBTuf/7lCQkRmySvAt3OlxdeJXNJHYWW4uyiJv7D4/RBKAYQhUjRNrpqAWq+4qSIO+tF0jMORSsoyLwVoOzrI3ZkaVLGQKkPbBRIVbsEn5+/OQn6EdSZR4Sm2Zc+rzyoLeFTiRM0O8p3rKvgTanA7JUie07z6iYLDm3XirW2EYuv8X0fFBqqTSKYbqHGtloktpcXfUEDKNrJDiNEXDubIEYDFVeMmnChKgPXpOFaHsgDwntDsbKuCN4MiKDeEf5Wq1FsFaASbS9DT7PyHPpMfpCRFGHnusxsEK8GCGht26smBTkXNms+673PDCWm3OfbzJQFGiBigiqOx9NOAFT+YydZBi1h88QnoiRviXvVekLz2j+s6YHycZ6Fkg1GNwO8kzES2sPiMaVxvGtddFHtFo+zwEmFDFjmEaMioLMlp4kwNeOgLPqL5Yywsd94ursoSDd0WvmilmlBcXNLPGobzA1kCrXu/PStZFtao24hIUqb4W4Lr7UGiFU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyODEyODkyMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lZDM4OTBmYTNhMzZkNTg0ZGRiZWJiN2VkMmM5ZWJlYzFkZGEzMjc4YjA4OTg1NTAzYzI1MDY0NGY3YTYxMDdhIgogICAgfQogIH0KfQ==" + }, + "0": { + "signature": "H8yk28fqyV9YvFYUBA7M5bWgmqfUxrm59QjjpOs1k3V6lZ1Jd3NwCN3XoIHnB4MaBwGQyxcr3E2vcdlqgGDxhO1VTv1UcUPC3H5KDrviwpeJEzEi2tPsY3dZFWVcJNFwTmZ8okfdH3HhXpBGP5u4DEr310pEcSjEz/LqIyznFYQcQ4vVua191EbIdLa6WW16j6p7iVDpatf4HJ3ykfm7xsdR8vgjCw4bec3dnkDw6qgx1baA36u2utI9rpEhI6x7nN3UuFH96cejeh2TZRinRkUYhb0UYQJbnhMBEpfUtkuzIK8BkpedBfTmGjRmeo2kDnwJbwf1xJ3ZG5XTCAxMpk/IlKoxmMax+0JaluiK0WSen4AkfKljqWzLNzvkJr6p/OhGslFdxxbCJa/68mFcjR5jmtsEKxaD2u7VkTJF9T2Bqh0DJGwTqdv6sJLrCiEWYru2m2Zit2EArpQRR2uvA2rcohAqyNGsJyl6vyyql4VRZcy9K6ni9CBJEs3XpQDbvzo91HJUJ7/6WHKTMstYs1gLCcTcFPaLAkRRW7l/O3AKRp3x8q8H/bN/BJ6D/CpyQKF5lPFlESnZPoXLhSsgsOWjef1e/mm7hTDL2czZ5W7EnF73B3DkM/xFYaUDdOiN/fpevHeXdEjemy9U7cik6iW1CmAsjwVHxzY+DGvbDmQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNzg2OTkzNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mZGY4MGJmZTZlNGM4YzhlYzY2NDk5NGFhNGQ2MmQ2YTMwOTExNGQzMWRkNjA3NzNmZmE4NDZkZmFlOWEwZjI1IgogICAgfQogIH0KfQ==" + }, + "1": { + "signature": "tGfvcXEmibCORBq3eE3gKISKCkisUzr8/wqQ8ZFfShm6mis2vWML+/B7/lP2e9UHqyB1S3VPcgplhfocVzlEy+IOWbYL3rsjofuCJaQt7hw5Bmbza73WlfkmLPIbtoIHZO+qkRr54JPS/UDc3dfPxZy4uMOsFspf/OB+BynpnXjQii6/biOWddjOqkdGL0Cddhq749WD3QcIlOI0NQQ3BOHRNLOOBk+1MmPlJS75g961sJvWNe9jCOu1hgYTP1VI1Op2q/3K92vPi99UM1kp4JgHx0BdZh/w4pgT+oVYKMeocA2uxuLgtyi9ALLwcOSupLt8DFUCHgIv0WGvylhj7wNAt7xOcB8cO4PvL1KAhZaxfFmdldIcpn+bcYGcOl2x1CwobkxUWptuDrs0PmR36vyNRCssl8dvfzXiBnnAXDK+y4WpkeGOL7xiLmpHoCtWeH1KfayNX/dw+sCwcQrjvwIj2L08+MjzZwN4/JEnvwjHl7qfW82jHUjtobBrBYLi6nMkLwla91RMpMtD/GVmc0JnE1fUNAH3gF46IPbQXgVG1jjfJvsqxStYUDstML5ClDBVqhnHhPL7KWiWdHfuCSMfqV6xqsa5rxCqtQiBzLVUEPKIpHdIcv2K/dccd+m8TSBHatRFhO9fiWa7hxUFfMPOQ0JTMnxUOM/dRT6KrNU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAzMDI5NzQzNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85OThhMDU2OTY3NTM3Mzg5Yzg3NmM0ZDU1NWEyM2U0OGQxOGM0YjE5MjQxYjljYmNjMGUwYTZkYmIwYzA2NmFhIgogICAgfQogIH0KfQ==" + }, + "2": { + "signature": "KUaVzGmcBlR3Ja9/w2cr8/WWxbO0QyZ1/xat25ZM+EJQiWGFP4+CeeFKYNtc1/kXd6S8qTszCKDoYw68xWt+dtU1zf6aSTBhduh5eVt0o2YVcDiW0p9ux2SeNSKMKfcTPFp3dACUduhw+XpsoMQMY8P0SIoNyJMkSlilcshffdPLnWgmHE1Qib8pDx7hqN1JfLgS5NlUh2QkhZocv9910XOBhWUNLArkCunv9JmMpuMvoqhDDcbK7eWbCKYDtrMPiVERMpwtT017IR3qWcrTL87q2lzrp6TdMJwWi1IJE2gd2vSXgMm5mrqHl5d7UQvGa1LjoJ4EzSkYUsF5GPRtuMmELBvrWsgPstapGEvTySDnRRjs+Gi9sILfNDx9vGxM8xQSe2chJ573CFYrD22KeulFBP48IrtrrdraPIjmrvkew6h/IfDY/YubGWDybDvzQcIWJfRndzj/Cm74AUh9IrH9kJWVpbdvskhxLfQhD8FKdlysmCMZztN9yX8lzf4WVazpJSgkafeRSCK4TYQZTq7PE4SrCTTJbOYTcWbQSbRrcjCsivpJRg8qabTiaaM+aloE4z3GTuUHpTTfq0PQFMjb1CI1UiP9zf8hkht8tmS/NZTctxxjD1d295MO/teW0MZ9A/cKjDm+h4cpMCwLXWXd6EMho8J96CLdNfAycPk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyOTUyMjY4MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84OWRlOTEwMjdhZDVkM2EyNzZlMTA5MmUwYjZiNjg5OTI2ZjkyN2M3ZTk1MWE2YTVlMjE2MmY4MjQ1NTc0NzljIgogICAgfQogIH0KfQ==" + }, + "3": { + "signature": "U2tJp4EioU7axRZE0nOtFERsoCq+od7qOoiyVILNklZ2AxGDtJa48sHbJpUqtBhuGFunOiGXXsXUUZf/uCVnXlc4A+vzWqJlsF7T5qDrnYqEEjO/+o7CFSB9HdeOXE5HuY5UefWcLktzVhwB41AU9YaPU+mA/nNUfcIa/AnxuU6JguM78unBu61K6G/qX8vyjX8R4gGTlLR9DtyvkzqYoPUGOWIpyZmr4KEisx82gh+cWsyv56A9LgEJXVwDJwIU0/MqjF8viVdStY42HAxPXZr/6KLI1m5fIX/vqC3SoY7Q+C7cGKvCGXgS1e0I4L4M6Ha5smWsgExUWSLfW40Elyq1Cqg91pE3Z8GpCzKZWlzzVpsB0G6MNaIDz8ERfb5MJx5eOEud9n/kGNJXLK/R3ynRjvD9yFgT/L3JdFIEs7rjwuMQ6FG+sgzkBLPnhlXx4p1uNjjgjqwPogE69KeHISuVWLrq25/oXN4ash3WaRaehe13r9uUwBEYU0qZegEuJm52Cl8OL5ZjjZ9OcLsDKRwPUjuXbghltWaK12/pyTVFSqEq6LJggpo1AaFMIZYarMazWmbs9JWFO3PUmahUt7BFKx+HYTz+Mo1LPELFyoeHWTTnTyW+oKBdPUs+oyUUQJzzg9ILBM6YphKZr6Z02E6HqC8IuvbnFJ5ONr36YGA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyODc1Mjc2NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yZWQ5OTgyYTgwZDJmY2VkNzAwYjM4M2Y1ZmYwZmE1N2I0M2Y0ODE3NjhmZjVkMjk4ZDYyMzBkNDRhYTdiZWRhIgogICAgfQogIH0KfQ==" + }, + "4": { + "signature": "X5hgltYRKHYmLMS+ruf2fsFhNwDJPAY3FvcH79fhiXRRTAawrxYKVGkw/d7Xx1v2velEdEi5rGdx3vej0kNWjIPL1WJiixaHzbBBe29Yzi/YYH8QtCkWHNZf9gbcS6nnRvtDkWL/oP+fxiSvwWMu+vu40fjoge1LBRr6j02KVZV3ArlmoWJ4gWKxDz/82he1kRf0NElfnCoPdrL9SShJoUayMa5tMf2gqYF8hPOI/8eFteEE83GK3zbTdy7TjHzcIRfcgm6VC65P7+c7Pn740/8w5n6mru3Q/5N4cs/h2Ct0HwFA/mRIqolNrPhfat/evOBpdNCfVWLb4r85iG3H28bcFkiVT7Pfev2npL6cNpRVTrhCSyajEwffThOs7IiYVZJZTHZezINyjmeQZMHQFp8VXWjamhKz3I9SOVVUoPR7uwuCL+UXv1YbNSE4fsRIrkJECYJ79/FKYp/7y1BPVJuKzwmEQap5VClp3cBW8DDK1RZnJ3utcSYIv56G8y58G1WRtwN2ek8+JiUTKVRjnxy6lhp+fug6ZidyYQt5cIryR183uPPUxN3R0mZJwdijFJF4n57eupCl2jjOYFIwj8jzZDhVf7AqcDLT3js7VdZ9wljKBMq+lBPIQ1W4PcQgRGVd9tD2rDEG2Yo7X1hubgvTsAu0NzfIO2jDOMrVLw4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAzMDA3NjMyMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xYjQxMjZlMzZjNDA3YzJhOTcxZTQ5MWE2ZTgxNDRiMGI1YThiZWE2OTY1MmI5MjhmYzU2M2U0NTk1ODIwZGEzIgogICAgfQogIH0KfQ==" + }, + "5": { + "signature": "Lst0m/px2w6XTVwjPQ9djGdJh3PkmB4tfVlGS5gK5mSbJ9GXTTVjHDnnumcWLZtqevdK+ubUaZUkDwxJwXfKj63R6kcPnGprxDWFxld/bNSCrpg2qnQgZEsJSpVe+M/befx9LElpdV1F9ih768m1My68KbdMr+FlHN4N97oIn7SrygD0UJzgQ9KmwUdt7j92WPyAAOOfsCw/Cv1dsmC3dzZXCX41xw8dJgdyfgcVISFfDIA5DQ/q1TfAVW3P2uZUjOjscwZZnh30LTuz4XkgFBfcwu8A8F3Irt4tK5TnObSFywFWv7pTt7zxb62tZFg2mLNLLbcu7A6BLqvsI7dSg+albZtn0B0FJstFqi+HTGJJvuGHR1RsyIWglMcHpSylNOQOe2q6rP9x4KnNlNBOqF+2vUaiLmnGxgTo/jhk6T/ZHGtpDe+kQUtPoGOrC9p6nV6+/CcHQ9M5jZq3p1ZSZMUo1OvGKZbpTNZLeUgiXYgcXqtpolQjXh/E3lS0uzxa5lzEIxD52n+aY1K5eqUyS2O/Pw47OMH6Tk1Mkud0HUUX9pb8ArIMaxLeQTyQ1Ajw9ho9LZUvfzOLDMicisndsQIj/TnH0kBs8xEJc+KzG3PVq1RJbanbWYytUuAE1MSuMkwF2P8IoDcU1dzHVLgySFoAYCnFzz2Zoz1eVZRda5w=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyOTQ4NTM0MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hNGUzM2NmOWJmZTY1YzJlNWFhOTNhYzBiNjA5MDIxYjEzYTkzZjZhYmU5ZjY2YzZmMWU2MzdhMDg4NWUzNzY5IgogICAgfQogIH0KfQ==" + }, + "6": { + "signature": "n13WVjjoHcDahkkW3cOku4DbWpmze/wEkqPI5lFUmi4FxavQIzWyiZcIX2EwY3w/nP8LCxn9mPJrkeHBm4xE9ug2uGTp52d6tny9mLiEaZFcutg18NIYx+21qVsWBdMhc+dqQO2ahSaMkG/1+PUCB3iR44rNF4a2rjcve465bwujztnVKjf7dl3AKHEp84GiEqf9TGj9eJoZBEMIVtItuNaR38CbHrIzHAzVdPLNLxl9254gbpLkw5UbJG4T0MvRiZkiUnMpR1xnKxEDUATlI/iLq0gnQQBbc1KXe7ttfOIB8zX1Jz3ovYNUIhjY6+XF1cUbzOJtAsL3b5GmAbi0XM1vN6FWYOJ5TI+5k5ZuRBi/G9TgEIkt/Ih15NGAKVmwuuL9e4+pVtYePIGCQhVETXs2sDEh5j5WGwUQHau/StOfCIAV4IiqfNYauBNVO1rjmzPVWZLmO93VZ4dzuAGbblA21/ds38SfoSAwLDSzu81zRepyvAcceTt8bw6DKe5JrzbRiKyYMyIifjOi3XNdg8o3sNIvYjfnCIRLLKy4JFQaM6ZsAFI5rujbkSjxleGWqerGJvikImCD+JlUo1FRc55OSCWZt2ybvnweGwfrt5bS+DevkUrdzdwYxZPX0BRaFYfBLvd5yrHnPgm97KZpQTi7N7bR7H0ZzyOyk/0BHP4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAzMDExMzc5NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85NzFmNWRiNmIxYjQ0ODdkMDIxMmJhNWY0ODFmOTdjNGE1N2Y4NGM0MzhmZmI4NTdjNDE1ZWYzODE2YTBiYWU5IgogICAgfQogIH0KfQ==" + }, + "7": { + "signature": "eC6eqAJyrduDTZwyC88MZrfom+Eqlrbx/wGbZSUBolhONcYQMv91JeHJ7Nec1e/ij0us3cKEyPtJu/M6WPz79cQAqgwJq6BN9VBO6wZlRoqidVIZ0bS3RrSle/Gv3lbbuVjwwDm4OLrXSeaSqkoEY7ariJTM6cU3cN+tPXiInjhylef4QVcwBZY9VzthFLeetezu9HLKfQPwsJCh264Cf4alaTl/iOkhkmVQ9P7oeFNaAXpYeCHA6ztOiTsv4Mbmug8sNbKeF2KxQSvZLsFSkexEjAuSpVi7YCvg05AQYP2KZjEzBBthHSNn2j5rlfJjKTId7A7MK8kQtiFtbgDcBWkSxyoZIeOiGizcPZBRQN9eOPoyCT4ofLOF/9dPIQgVm7sJcI3jV+7jmmZP0uXuzNmSk65MzniSvTDz7WuDbjUpxNUTMP4LAuusIcuvDl0MhXq9Eqm8IeXgAWozhEDJgl7/VC+QlADlt15/C7MTdAPltuE9gbfI9KfCPYz111xRIPoFxatGZJ0T0//xRcMrPBxLe11nbeB/LdMqP0/FWm2qjU4wFEB/2O7zFgXeXmtJkRxcfpvKJxlb8wSVQknFsSr1MfpiNQYHCMiwxq390ZYsZdah1kCZEZH4oRMseNq+ppTNPB02/7kfXpka8lLtnIDg5Auvy4FYWU+8QiqISYs=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyODY0Mzc2MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mZWQ2ZjdkOGZmMjA4YTI0Zjk1ODJmMDM0YWY0NThiYjk4YmI4YWVhOTFmNjU1MWQyMzIyZjNiMDkyOTVmZThiIgogICAgfQogIH0KfQ==" + }, + "8": { + "signature": "K7Ab1P4dZdvztXpqaid7LJHurnRFhrS1Wdsyk4/D+Hqd2wYFiDlw34hIj5Lhh+r1y0nHI2reO6KQ/BKIofhYSJT28a8DSaGJFnTzInvMJsjYAHsKg3jDxzpTQ+gDKakkbD85E1MbE5OK675Y2RYTTYJgeGtZ1QaUjkmY9ah1MM8qpOAVbWFAQG7nGnh5+N8zOf0loc5W7ze+uWXF7ptKVkNSqKWzZ9xZ6n0HWA7+WQY+TNVJg7SUxsXKgJ7VelK9yLJ3Kt8zeKwlc903jZfzbT1g1yF14RiVOGlitkvaLG9RlCVYZAsdlQQ0XZHTD5dB548uECVKR8cJfDpZqSV0zp8M10Aen2kda7LZZpTsJXRErrIJRPG2ZBktYmM0hvzKj8406uwvqnwNplwxpwYoiGpJLIIxjhJ38j2MiyDxdGs52wTrt6PjyFKZnDA71FIqJpjUTEj/ebMSIVtaOHVrPOOVllGCYvs8rIGgsCTjtSEMd1ZwHdIX/yOwrNG9NC9XFEH87xYbVbUWA2PaDfSwkaHVCyHjvvqBcL1ocLHYKCOWVmlalYgrgTGmQR8V48LqmP2v8tyvAsurmG6e3/dh03yFdtUrczWSH2bjrH5CNZ9/tNZslz+YBpdDbE2jvSDHrGo6XIJmz+/mXm50zDjmEtP8UJgBYx/2UEQQ5tWJCY8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyOTAwNzkxOCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83ZTAyYWNhZmRjNzI4YmU1ZDI4MGUyZDlkNmM5YzI4YmYzYmYwMWFjNDU0NjlhMjFiNTFkZjI2ZjAwNWFjYjAxIgogICAgfQogIH0KfQ==" + }, + "9": { + "signature": "A7kGk9GC5HjuYRiqvvtgXBQVXTFqwh4WUjV0NuM9dmq5S8LV6t1e/gZ5vQrZH2M1/0Rfnw3MxDKOrmumItVMCkhxVfgUeSpg6hyDRWsMMxxBEM4bY45jgyaon6xGn2dV3TDlyooaOSpV1OdmPJb05bYHaXSlRv8OBX9KZqfJDZSA6web3EONYT+wuNWXePECNk4ulwzBjpNsEw2k24ubRvMlrjz6EZgkWewc/n/BTYqMQwIi1OUByNhNcOcaFGRiHbB1PqUPHh6yVWgii094SdY3Vv7VG42MqMrCdLAUNgdCqCZ+B1YOrtmZkmAZAwRL7nywzU2C/wOkuLP+x723sZhYj7WHdU7LvmGqswj30UXHbI7/NGiqZDSPvAgVO18zQOEy3WGUj6JhjSQ4merl0CtewowlAOE4xSemfBvWVYsoAZLJWKmXUuHGXFy4lihwk7FQVEtt5dt/1ruwLXW1aOFDS0J0OfgM7wDJ7TZ/0cvtIDR69NtmLlFUYRE18CezOU3l+PjdhiWNIyC5SlMFRutsp4DZjfrfM4jaZwzvY8kEUUUHQvQirgAqOeaklWyffxSNN8SRdLR/M4OxSTWr4A4U+e+2b6OrPnuPSlXOTribb2egmFJtWewUu7OSl6k19yLKEzXgw/7844UOPZi0VZqpAy+6dkTXdVpoupUVl44=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyOTc0NDc4NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xOWUwN2YyYTJmNDM5MWMxNGZhZDViY2RkOTI5MGIzZWYxZWE4ZjQyM2IzMmJjY2QzNmMyZmQ2Nzg5YjVkM2E3IgogICAgfQogIH0KfQ==" + }, + ";": { + "signature": "UXLGoAaoJZBnkyZHO9GTSK1ZQVrGg6v9bwXOVhYJO6qJi+jso0oTxDR+sibrp0HkKyMgptP5Gv1l1cj1rzkmOOsIiV14NlNBV1RNqcRElHRekqudGmyHEXhGIobl2a2lJGlRRA8Vil4p8wvHu+qd5UkTFKf52dH1wpjnCFHLY45XeRIh2ACuxUrzIUjRwUvM4qt7gE25CDWFjCR+UZni5XNbYQIxy32WSbtKDV/xiXshqrtYEHBW7lNUPAJ2Qd5FtKfr0np09pOgXRzzzn9iEZuM4eUEL05dO5U7A7qZ66pgHmfRuxf9gGbX4b9UQdWoLwoOAyMWXUIXki/7n5UYutPPrZnxgAxdIWVhNiiWXTh3jo956QTZ8LbVqGED9nduU6bbTv/aFmsKJ6NWKwL4oHp8chUTYpPpkWrRfBhhH1lCAS9oXFQLGq4w9/RZPtoxQ9Eg+yxO230RkLyzS0Ia7EQkvkrVBvnyw0x22addJThWaSHasgk0wlIf7KKWQ2P2+QkPdjSa0LJb470WMASx4U0athBAAwit3BajJV+JMPXW55WN7FvZwN2fiRioW6UzRDjfC1cOHdBvqvq2tUwK0H7UR3gMUQVV63tFqGp9hfvkNN2aPaYF7ykmLNE6b7MiAOKWf3PJz9ORcQSCsosajeoGP0iA+kllwvVESQYA5rA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAzMDIyMzc2OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82ZmQ5MmYxZDNiOWZiZjExZmUyZDk4MTQzZDViOTEwOTViMmRmYzE2ZDgxMjVlMWJhNDhjYzllYjZlMjY1MGU2IgogICAgfQogIH0KfQ==" + }, + "<": { + "signature": "KsGIav7kBT8cfugENSIMeWwK1FsWHiJnqs9qWdiRuLenUJ1BANlS62mQzMBG7mJHLs6yXM+/0+mJGN+oiVIgd7nRjF3Je1IIPqb2cGogq2n45zOSCCXimTdY8qVMgritlI/n86k3TlQLJt1+I463ClqSEppWyDPerd8z5zrdlAoLWnhweLSlndlKB0Z30DK/XUjnfemOVWprsi3sWGBOlurqfKJHVTPacZCsjEAjVnkfmyyqMPc+px5LrxPTFSsWcFcvEAMkEjrDVgm8mraG1gZhvC/YoBoHJJvkgtc3qrNNgEyMQeJdOx3ngLKDeoUmUzvZBKNbLBB2q9sG00EEnsRbhBg/n/GW0BG6Js3/VKOcpreUbEVwMDuEmAlQY1BS+19QzFFxXATA0SDwlJk+S1vJ2dA6ydIcx9OHqZf8trcXLChw35kwfMGooJjbPB50eCIqUiSv1/zk/gKK4NKJZ1lreyCkau/6vcmfb+REhGcevW43h6+UOf6drY9pfd1XgtFGma0G7t4FSU2H8CHFHy2nGimKB8BXQLNPOd4EM/JJyhILO2UggwvdFen1lADV4T+AC9j4AXXTyjsV8iRRogPXE410RklPioqMCaufgl/HPwXy16MpGgFa5FFi9kY/vbEHPcsyGjpij8OG3jJjyoVLwSu2u8nJ7DTL82EUdtw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyODUzMzY0NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jODU2NDI2ZmZlNjFmODk5MzA5ZWY3N2U0NTQwYzU1Y2FkZDE3NmUwZjI1YTM1ZjhiYThhNTI0MjVjYzE4OTc3IgogICAgfQogIH0KfQ==" + }, + "=": { + "signature": "JaMdyNaSjQV2jy/z2eBnFyuckuKhEo74E/GtGDmk/cgps/lUlwXrFGGumHQLJ7x1cW7P3nt3C4ELUQS22wnP9UFqlwxwx0EtLTCrzQLT6eIuHzYSDXHEjORLmea2dnYzd7NX1knFnYLw7TRZFNhFpehNTQbNc4qXpj9qFDZ3KDQKUhWXELxPa0LvL4ti5cWvZaQUebuHUq/NOFmMs//aSm35rOBzRr1EcB5fPQcJOHPBx3Iy54SjRaBThaxzuc1zXfZiIW+pHPo/+8pwTfa2ru6go3/LGH+iL2sNV/scrg0rCKXBeJSlHGDTeKOCU05UaAod+48uis0Zd2msUYrN2ji3AE5mZ882obBZB0DfXBcfQ9fVFX/wjb8fpiVZB8guHF7W7lXM6Au+5PBwLVqB5yeER8E2LwKaRZLuSfUPNeSKL4jl5DCgwu38JOUY2suuNYtetNgMB+kdi7JOL4K+bnjNMnUxr9k3VVXLerF0b1eYP6A4yZRlWuCssEbT+HhFJ6Xz1Ww4v5CqN8a5GHw5nxL6QS6ii7zuCQU61OAbBLoNINQEwWDoBYj9EJ6jxQRxBJwb2WbDHGkltrgoHmQCtphGdKBrQPpamUCBlTwJmJwcXQfGwEymPACoqnLF5PkhxOyYiqMpopxdQf1GqvJqQ1N3oFL9kNTOQIs6wZ3rjqI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyODk3MTQ2MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81NWM3NjZkNGRkOGRmOGFlNGYxMDU0ZjBjMmE2M2RmMDlhMDEzZTBjMjQ2OWM4NTRjNWIxODlmOTk0ZmZlYTc1IgogICAgfQogIH0KfQ==" + }, + ">": { + "signature": "ddmndoFF1ZtwYBFCZMhn7T5qImApXojPhpuzzHPeceqFBAtbDSXOvu7AyAzmS1sXtXLOU2BfUdMKp9C2NeQ62oBNL8zpi72nIuvPxVAp7KWhwXvobAGc5T8HoXiQ/3pgeUT4csOZXCe2XUz7SVO8CgLHXrg8H2KS5rTor4+5NoFSdnyJuVkkR/uapPh03gO8gZZZub6KCW9HqsQRYByu6PLw0kTep2oBFsgLVgjjHBinL/Wr5+SZEUazssHScW3tce/VM+3YYPWNOE6fE1nWilIZeHiCmqK3tPFBh5Z/lgnpmh3THqE084bY8HMWfVSwbrsO4HBLh5/A0oEvo8jg+CcSP1jOmqhNbQg4lGlKQa/4bQA8ARpmkAz1y8il+e5kG49QzuLiyfnjqxK2Qjp0LL7jiRfkYJH16lWlIoUp+72Hvoq/eXSVQ0XhhKUJvbk8yN9i2mxbcYb3CbSWpmYpzfj4ZeOm8RdIvQzPtvfffcyelqOhJwh/3ZdC2bFm3L4fJuVHNnA9ItiRHy+PGlZYEgB3RHgv8z2lT2qk+aDhmGVJOuTiiLb2h2ld+PmOqYBljuulM0ajcwywJD/0NGueukM8+vr1WOLo002PNdF1QcH4kh9CZC3JvrX3wyxrhiaSHnQK+hamEdOwLEugGLlSf3Jwkb5qjEpZqMRE/6lXvIw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyOTMzNzcwOSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yMDNhYTYwZDdmZjU5NjYwOTEzZmEwNzQyZmVkNGZmNmU3YzU3MDFkNGNhNGNiNDg0NjRkZDhiYjkwZTQ2NDQzIgogICAgfQogIH0KfQ==" + }, + "?": { + "signature": "owJM4MIOEZp1ZT07qfTs/VzsMspMRP6g4SQlMpvg0PapClibSgT/HchyRCPjQet2NnoDlBPE7u/pl0rjfrlRQHXn+zS0d7l38W6EawWgkIqhrZwzMYgbciqTWloPt3CW7Lz3/iv2x4f9tt5Sr77Mdth8N09f4nsHTdFqpkFZCtZ1OSzFTYEaJlhI3oH8sQqrKqS63pFQV+zzP3f9dKSn1YcMcgV6/OBOQzfut4VpVPQSDfB/T4jvKisPCtyzj7F3rrP3XmeFrrtFC4wdgqnXqvxWG7excRtfemkFqDmViauPGGTziMwUxCoeKIo9LjHRU7ERvuQwDrU2rAF9RUA8lMo8CwYsfY67IWgmfpxaoGgBYaTzfgdZ40lnep4s6ZhbMeTO611KIDOHOha0Y5Pl3R2Sfg5c65Xso8FyPvj2u69m6F/G+HF6De8oTvwhLZy4a6DBmWFR/9kS1sOaz6lvNo/4+trnD6Vn5t/0xz+nARNSUmQdeM1ARheqictfk80dFBg4rQyL5flPAGhPpem0okpcNAQQntIJ8VCa8gPvZav/EJJzWQtxrp9cjqAdBpphFJXNkvPzHlQol7VGI9SudqiKYmjljnZIKHI+xCiJritXhmPhwyE5Wk8PzWct1F62D+EiF3ru3JDvBVXWTEDTUrgeJDI2R1wF5E+Y4Q0UudA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyOTkyOTU2MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81ZWMwOGRmZjRjZmQxZDQzNjhkZDM4OGE1YzlhZTZjZTcxNjgzNTNiNjMwZjM1ZGI3YWU3NTQ5ZGFjMGNhZWE0IgogICAgfQogIH0KfQ==" + }, + "@": { + "signature": "C/aEBsCLA6HkFjB+H04FAlqwqu9ti3bo9MzD4oMM93NXNbxGBt8j6HBMsSBF8XJISaltpZMuGsL8ppYGtM6tOidNCycNxZiZ6wbCAlzoq2/To2F86No7kQTE9LKJ1jxGX0KhnvdN2wSfqZUu9rXygBzd7Old9KlLXuM/MPriuvQdGWX8Cw0R+nev8jLY40+HK1IZb5lZN7293c/3B+yrZF0oK38dqABCWVLYZ4PS+EpB8z6TgYMexIDtgrzmtEWLoWTXq/5ioR2roSDldun8dxG0Gv/i0+HWnY+4EhxAXEDV6tt11XrYTSLyVUciqS5mteLwJWeYFDmTrdT+ltwT4WyahptovNyIVc49hMakRvPrXXSFGQw1SdH8t2p/kipIUBYzMA35FhG5H8HuosBX4fE0GcEMI7N/ldOeGs7KQ7IpSU1kYmag5ifWWIsJE3fuFQIXbZSsSIDyi6gItMWJj5JwsujxJE+azlkeu2aOJDDMZ7fDDODKcT6q00ngSx+2aOThO/iKvBBrV7pIymu+42E5QIfU57Zs0jB4R3nH79RSJwF2wlwkULxxEeP2OcWpdEXxXojodDApPF2SOOQT/lPGNFAD1hGdmTGXyzBWMwTwWcdfhlavJKJmPiIbXR9AlYt3C/T/ap0cJw4Tt3/bGnrj6EdfoSfEjbB1anHtmMA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyODcxNjQ0MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84YmQ1MzZkMGFiMGVmMmI5Njg3NGYzMzRlYmIxZjgwYTIzMTUyMjI4ODM1M2IxMDE1MTQ0NGUwMTFjNTcyNmI0IgogICAgfQogIH0KfQ==" + }, + "A": { + "signature": "ddFmHmN0jRVtacb+Qs8AaQkeHdZMRvcckWS3ADN7DKJmZ9AeAalERd1+89zdMZfrAIdYum3JukDTzmZVujNLYOuUeOMOP2BFP41vNi0t4xOwIxXgaSAm/4UKZVIfTdNlwbsHQRAXVA4YTN/N2mYP8hEBrQp3L4zAYWfZJ/A96LDQl6o/fSaZOFb+EgvUZ/OZYhxrxEUcIclFPkP6C585TH+kpFXOxSSEus8inCBHshdO/sXdDOQHDYItkt070GVPuAmYx7r6rn/wTshKuv8ZFJx9UUWOQ53HNDaeIWpfRaIkjSq+W2Ktzu+TkO6i4tTBrYoE9PVyMzei2+azq6PNDH2zau/1v2oeZ/qc66WxU6W7hDi7Pk70ENEnxN5XCj6bnH2X98dSvzxXcpqSkK+gGOnqUiGF0NknXZG5T88tWdaCbu94EYPLttiIKBsCBgLJKHmDH69+kmi3W+uHKdPDUmEj5QX+ulZirqy7bqsoa8hr4bnVyUrCc9ke9ipV12lTuPniKXdXE4UuNqpXJWVcLkaFpWnBJBhG25dmXNv4MSz2glWXHKK+XKYIj0oFfTFmABG19iMh7IUatpSYb745T/o5GXPHHwAFIeyNdCCZBx2wkVPDgbNrT71XEOgUiMi3F2MHCZ1YYj2Y3VYCQL/OlqyMlmlWEbiCB+jrmNUA4Sk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNzk4MDk4NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82Y2Q2OGViMzAzYjVjY2ZhOTlkZTFjYTRjZDJmNjY2M2U5ODdlMzk1ZTBkMDRhMmU0Y2NiMWI3NDcyNzQ4NWUiCiAgICB9CiAgfQp9" + }, + "B": { + "signature": "PS4y0lGHWKLag2e51TlMevIzgoSiZOLn9WzQca9heswn2RL5ZW1g1X9hHFTCMpARg+C9SqRl9Z0DEmrQlrHJpDH4u6aMQ18kzEdQI9O95VONjD4s5cRWbpW+LioC3k1Zhc+iBf7NvNOuvTKu9k6luHlu+rAyHpqFOo4QXabNP1gRm+awoPARoCKuGXVznz7HNWEeokxa1IvCbfS+aObaaALiKr9GsWzKJZVzzryqBcN3VJF5a0GAnb9VCV5XprgG2lU0cWpKS5ABGkRHfi/VvCNU8z4rsjmdrtSyf5NxomKQwfB52KzCfI4VB7EzwnqOx2vF3SX4YfDztQbEVIHyvcbwdxrO4Kigri3kMEHklnwyELjy91aoVR3nXTPMz00RHQ5W6Q380uAvR0EV4WSmZ2rF/4Km0tGNp/DwpdbE87yjcgKOeoQthlYY4hFUopRzvmx4ZhDTkEjIdgy/q4AQRS2Uisd1he7uiiX7anq3A32P8Mi0T6cSxtrpHe9VQem6NCj61WvzV1LYrbTYf+2Q/9Q0ZI7CJuHldNq4x8Ktb/enQLn1iTTAioVNtHE19XawuCo5hJwwp5xSj9w5skYs9Ta1frY9uAlLScl8WYTeUQSJvPVvLMWPk4x0zuKhGskSvelll2sdz/od/T1uWbhjA6e/3v83IM1m8Fnq1xubiZc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNzkwNjY4NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80ODlmZGFjMGQ4YjQxNzA2MzI2ZWQwZTI2OTYzNDQ3YzU5ODU0MDEwOTkzNTJjZjU1MTA0NDEwZDI2MjFjMjI2IgogICAgfQogIH0KfQ==" + }, + "C": { + "signature": "x9QZFvJUSVh8gVv+tdX1EgULNC19pzCqrireVDKLiE60vvBz23EPJHAMx9nIqqWH0dhnSgpBRPEFRAerNFYH40gWJYqNMd8GFRmD7SlC4p6FTswZnynhdE7fREYVii/uXMthLgZP5YTcYkhFlTTeMzeF4cfbAMsxFxGj2msvcSQlLT2rU8XXgFX6K6klYGaD1RLlqEl71ezigfoMPVN+ZSSxjLV9jYFJu+2oATtvjy0H9iY39j+Qr/+2GVgWmpOLuHbCXnu7bgeVEmMxEwpgBZIWCuWeezJLzDOUI8OmL0FGU/P4cdBbavSAe1dtXPL9cT6f0krd+XmynDoO0OL5cC4uK2xF1aXGYfAW/rUF37ARGqwXz4IqkJrqsf1henwZEyjAK6ClSqYCjBaeOOsTLpTeXWGZD4YxOsgLtiLsg/rdGjIPYbw5YDzey2KXBx17cE2gaKdbv/dwifSvGdEiyjj9CmwX8URxzLTi01lB3Ma1TI976ADfai+cKbVi29Gnqm3JDkv0+0M6JaGf/LXBnW4JqM2oV7ERO3ajaiWf0yQsz2Ldkc6pcQpk/O7deqbZdM7G4+eM8EKug9qaO/6cYOXFS7QHdkQq8lCRdu8SrIqVgSID7u3vAp4yb1T1GLPnEDpYnAT38ydQG2NuXC6ul+F4+VNy8FQFyPpdq6BiU3A=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyOTcwODMxNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yZTMzNGJmZTgzNmMyNjU5YmJjZTQ2NDFiNjBkMDk1YjBiMTBkNjVhMGVmNzIwYmE1YzY4MjlkMjlmYzEwY2I2IgogICAgfQogIH0KfQ==" + }, + "D": { + "signature": "Nu370jj/8cgyx5vpOIoRXHAAc/nin181CB3tMc5Sd56Jo4CTzXljWQzdKWI/6HGynJyzn+Z71ULzFufxxPFaSFGIZui+C0yT2i5CsiynLtmUu4AoxWnDDDmI2J+eW3LA7jpdSsGwSkouZ+3bjxtR6jjWEYASfVFSGG1tZRwQMZB0SMYDhjLLg/agktYefvhc3slDuZFdZh4uyGVlb1o8vUbEJ32xLg9rNDmGUnP188cBq81afk+MqoeIktyG8yFk6lbHQ6ageRTHgELm2cIxw7ezMrf9NgFwuID5x5/oHi6cbjD43bCjbhM4dQwBauKKXclDfzlmApDAFmsTCH2CcYizTwbkZFvtl9XLbnjaA770eviETVInmq35etrvdc4Ygy7z5YONUv66qFRF/MYjnidzVPBtAV/R1o8V38wBc2nM93UPlaa/fV5vUq55aj9QUP7veusJ2IwO2+31z1Oe0dXGoNGeityYxh5BNPgfn4qhV3hDAQEHGONTIUz2FrUvuRY2Egl6RgsLwH3eUNzjKL19gzCp81iv00xabPH8vJ42rxgNPDbDtF/Xdj4buEW1GN8ZK//tl+FWyfjl/mPzP7ax7yv+8kB4TrO1MTKO/ea9emNYTXhkdhznIS9iyaRmkTdKcioeoCAuNxwCATrq9cUfCS0kc+QQA32OtsK5wpE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyOTI2NTAxMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82NmUxNGVmOTkwOTE3MjllMDE1NmM1ZTc4MWJmMGNmZTdkMWVkNDdkN2QzNTQ1YzE4MTE5ZjBkYWM4ZmY2ZDA3IgogICAgfQogIH0KfQ==" + }, + "E": { + "signature": "ZiMOuIynngxlGVGHaVd/xaZltAmu7nXTdwGBbpuo6TcQfQ2hBHdTLmBtZN5xHxFlnO1xRxtWG8u1aubJfEjosIEqzRYp00cLCVsq7uTNEq0AfHxIwGZtEPei9zqXw41BQ5QRTjlV383M2yECNqrD+o+VFH9Dx22pWUt9yB39ki0bgDo973Q9ghTRg5nVuLt2IevPpOv93dhk7arRs64xGSGmumMNqWI8aMljSfims5PL+AtzRv0ByFcbEo+VjYpmUEIh4lCclp0sWjMaJwRFU7+o7AzUAWEWG5y0UmIstd3Dss2ZkZrtBrF5w4n2u6XXIO1oMz7/f64yt+7DmBSCvbcCYvhU7cirzxq3TGlmIzxV8SI0DUN1yhoF4maO39XTpMZuG2y1Cc8mfshbKPJ07yq3ykkGgKhwuVE1TuHV8P6ipKbPoUesXHU1tjCcP59p47zFaulBaH3Y+zVurrWw57GvZMg440kBHEWy6H6Rp47NvnEiN/jEK2jbqNKQRE+7DHysnIeKqjFlK+87wVbUB8Jz1z4eP5bthw8GpzPWTu72nXr6jh4DDEah1EVSm2IYV/9HYcKpC9aGjwnfkRiW6VSRK3ZrBcIS3Gds8VlbFAtu7mmfSlYaUx2e2L+LUvsV9TdYOPnIbrIfhduKkfh0PyKkX2IKrwIuSAQukI9KjP4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAzMDE4NzQzOSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jNGZlMzAwNDk5NDIzOTQwMjk0YjQ1MzhiNWM1ZDZjMmFkOGVmMDU2ODA5ZGNkMDg1NDJkNjAzMzFiNDE2Yjg1IgogICAgfQogIH0KfQ==" + }, + "F": { + "signature": "QMzwDJchrOYBdcd57xrgvY23+08gAlZsWdj6VsWtdK1+zfOWloI/WWvDd5GEZd4ugnUNy+be0tolf4NbMFKKi/W5UxARY95wi4Jk+Xl6ha4jsTATGWbjQNr7pC1CqWII7mfiwQ0ECuKzmwzRho4EXzHXbNgl33aKg6eorQlrfS3U9xSme7RwJkh4vJ1FJBdfTYzIzxe1DtWbYG+ROVD3TPtTvQOZtE6afbokmoapMSR/ffI6SYSABjD7CuVr2dkZ/yVlLnsG7aNgwo+jTn1EdljJw8RrLUJTvWQ99xhmpTMjXjsfJIipeMGRND0rMB1I6w1/jtaKrFQuoPuWsuAstjNDCPW3zLdJ76JQw90IbDyefWJO9UTr3ITwGmPMUPuXigtz823M7hN6dddc1yPN72I2VpkOFxW+aHXKWl7OZi+ysntRKFuPWpAAXDnxFtc9TH1FOPjp7Ioj+mhPlGTIcwWB/056LX0PiaOo6zpoGGzs/2sEYyvCSROixa0O3wWuCzH9T+4X79GgPK2ND1i12eTfsboymQAFIJR89pfs201E9fQLaazrLnLGgq8cDzagvWt06R1S+iEuzdxoDnIGVEKUVB7isOWvGB/2hEPLmPzRosP0ziu5l+4L7Eq5Dp7eUrGrgsP+N5vIDLDOZl1ZLtyU74/q6aScosrauNQDafQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyODIzODkwNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lYjZjOTNhZGMzYTFhNWU3MjQ5YjBiNGI0M2M1ZWU0OTdmNTU1MDgyODRhNjRjOWFmMWNkZDkxYWUyZTRlZDgyIgogICAgfQogIH0KfQ==" + }, + "G": { + "signature": "c/1/i57+k/BmEiuFGtdulhKOxRC9hOTwmdXWEBXcKWmITgOK/8lkLFSE3VLvL8yOK28LEY8faAD21vgO/qcpovjbhkZpLstxJyJacMNF1jhLVdbCUNZ9/Tzk7dBZTD0nEfb9kgNDSXMlmazMfqaUixza4WyCMEnxcd7ews2q3z+ENbqS/R5dxcGPbIxvuu9TmCe1dMXhdWEanngVV4AIgz+E7/4Iz4pEtKMIZ7PfQ+8VIP1zX0TuMCGVu38TPFOfT2vGRsPI+8xx0+4zbnbBkYAlY1p5XQP+DPa5vPiVVVVMBJYRaZEBtqLTFy++ov75Xs6bntpBlTYpIKSBXdOiJ9iJ/RfTfH9HFRAd0Grz+JRnycwhTEiF4hwrRAQYl3ZvIHXMGVfGkE6IE2TeFAMqAnOLfvTn2MOOG0r3Tkb2Nkvy5hCIhVPNGBdv90aLYYsHptwC6Cs8wBxMFhsfYsFzklumgR4+D7zuZxce/SjWJbf5YpBxxXvnJDVu1d5jCpcEqEQcLd84rX2YU9zbLDB8NBjc1RhpDvzW/W3EV5Y31J+1fp12lDXCL68MAU/5j84M5JOmo3AdMi6I9WgPJOlFx6EwVFEu2H6WfU2RWx3t02yRX9HD4obi88xyeypNdBXXYTAaOcvHfG0KmcM4Kq5r9/9VsUcieiRC+mCybpK5Dh8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyODQ1OTUzMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mY2NjYTdkNTQ3YzI1ZmFiNDMyMmYwODE3YzNmZmYxM2UxZmQyYzhlZjNkM2E0ODU0NTdmMTJjZmVmNzk1Y2YiCiAgICB9CiAgfQp9" + }, + "H": { + "signature": "NDlPfoaHoX7G3DRIKceI96DHEtjbFvKhT07bbHwVHvF32icuSvkB9Av8FNiu2pXxX7l3e8c8eCMPq514U6rwZZIjG0QYHmyivdR6Cp2vS5qXh8qimcRpvu6i2Vyp6KdbltKltL6VDWv6XuMgCpxQDE5qzKHaYTE64bAwkxyDVuDV4+GyEtjgLXA/PJcgopngx8Y7Tiqe7vPcvIhZARB4Wwen0rFwqLGgjphg30u8Up/yqmYApQp9YLkX06dd9KyomfsT/zU2WRBdSnrhhvRFQrsTiAG90P+A6gjmXV2PNSidsKA8hte6n4Z5WrPg2iy11ZpDIASWkmfLfVeHyA+9+E8edBr9jeIO/+/5lsbXhPRz3hDeTLwHO970tfIRJwUHjEYaxRVszKTGvBrJWx3Tm4O9jiyvSIRy22uB+qaZWnhi7z6YxV850kNJcJzT2A6t6+50/WbBIOL3/pnX5P086XEBHI71Bht0+vBxcg4d7uUe0dmZouKKUSp7qX5jsA8NC6JFzCeppdqTfLwOR1d2/WzT4Y/t6ep+dp0YcDFAgP5EHH1fucfR8YaBToEz84qb4BBzv4FbAzKtf1tc+BwmdyC3weDDadKmqJEZlWhw5mljooud6zD823vtwv6k1Ky3ywVLDrPAGRQzvAm5eZDQaINXgif9fRaRZuLnVbpcwow=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyOTQxMTI2NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81MjkyYTEyYTBlZjc3N2QyMTM1MzI0YjQ1MDk0MDI3Y2RjOTg3MDQyZjYzMDVhZjE0ZTNjNDg5ZjNlMTgyZDhkIgogICAgfQogIH0KfQ==" + }, + "I": { + "signature": "sAMk/0Rd+LIm7HJo9p6DMn24Ot1Nl3QsvJPvV/o8fSaZY0i6bCdMgCeOrJuRfzw1W05rc2X+Gn3iOp6Q/mxGUrN+Rf2yP80Yo5wRWp+xgmyfErO7O8sSmqI2lLOrt3L4t4DcKO8zeboH+U9qxS6UpeOGYTALyDOkOrp3CoyGLn0NKP6+jRps0exMrzjpwZlnwAQaRDKZxKH+BqH+8q+qmxmAdcO3L9nafNe0Eox+imjJV7+tR4STArepi51khpwCs0K0lFGBmX1oRlaBhtUx2oFhv7pDEb+j4EW2bNl7qPYv5cPuB5Bmf0x1A17ifJMWiBsPCkxx2s2iJkF7kxtuvVmGQna1m089hb3dFckUDQgG+sfHYF05XG5V8WCbkJ4DGh+ty+F8knIDeHqsOhKjBpTirevN5LCgfg3ooAZ6Rwq8pCYIwSCpPQxkM5dkmU3VhX2SgEPwkcfd+OtkZwlLaptEHy3XAF5GfDWes8FxwTMWgZ6zqq2TkFIxVJ/3+hTUxgLte0czN8zLzfsodyrA+gYFYfMXA/lHReXN3/D8bCPHxevLYU40MI67OATE7uQ/2a/fj8ku9AuVTgsbW5xstjpVJa/FceSl9hsmDEmIk5kgQ6bCV/wVxBRWXPJDtLk4mhrvmcZgFLgcjGNsIJA9PcCdqhWkN9I7G+xkOSdz3Mw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyOTA4MDk5OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lNzAxMzU2NDgxOTM5OGE5YzcxNTlkZTI3OTFhMDIxODYyMWM1MGU1OTRjNDgwZjEwYTY5Yzc4OWQwMjhhNjg0IgogICAgfQogIH0KfQ==" + }, + "J": { + "signature": "hb86rpez/g9bzLKE6o7ViBjrJRwzvl1fk7RRBtdAyy71Y6IXF++dKoTS9qCYUOp0GhlMweaTgb9rXAnjdqEX7noObHZBg9LvcKY1HylOFa45szXb/WzlHGURxAu4k3cjU1Mmh7UwX28uaU1lq5jt2Axn5hCxptv8h2i/HC/NQgJXseSDra1xyVlqKdEZZ1KCbHCY7WtM1PR2XHTdzbA2sAYC76Lj4glYpNFe8Kd0xdkBKjXmnQoC+7gitCCjesh1nyECNRVvGqqYtI8p0UBuFdShu2HdUOeqWzfZLM9ynV1Mz8MziXQrBWbpaTN4NbFaJP4IS/LsK0pqBzSqu1t+SzleaHX7TvvwanGZM8lQMosDYzuLNCJVGFiI/0xOmg5/dWnoT21l3RPmkGJFphA9m5IEHfO0cmZ6gTHY91GhFlI6b4Xz0cACeVwA/I4jKiNz652As71+UWcqzqKmwMmJiLc3CCNzzk1Ch/j9egMzUPqRdXhYAn/ClD9koGy0MHqG0ieTyq7Ok2l/kV+5MMhe63o3+3rBDs3qvSjFr6UmyPVr9mrsuLtkWCJcKW3jQ9vKXdIXpsqH3cz0MPKdvffoHWz1EVcFMkXaoxOl2I48dD/2Kx5HwL4cQugiDKvBqXKc3raQKHl/EM20QHL2qI3vP6P1HDVuuEY6ToM9WAsA2tc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyOTg1NDkyNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xNGJlZTMzZTRlMjc4NWRhZGIyNzk0ZTM5MTg0NDNhMDBmNzgwYTM2Y2VkN2M0N2FkOWM3MjU5NmUwZGVlNjM3IgogICAgfQogIH0KfQ==" + }, + "K": { + "signature": "o7TNj+9kX9miYtlXNuK/v08J5bCnQuEyx68hPC3K7DSyuz9fq5ifbDXcOdiW/SB16XqJjh1Q+sRptfqxzfeZJknpIoW2DEAwCe+JMmQXK7pMuI4oS4T8/KZiFy29PAwmo7Og6t4Xt8KAjUqBVrpA6ISQO8AKBUNepI6ch06p0Rtfbv5n6CZGU+u4JL8o4viAh+uuVAprXe22zAJLEGljmvrXY3cjiY9dXQlJs65RwgbYJ/6Pasfb7ItkLxhOKR+93xs6ApT3fN9uAxFf56KycKJyAJWP8ASPD3aYJAa/INTorW2A99n5ZlNujfn9VD/oumZ+Hy/gmy3EgcHfub89DJO5AZLK3TAsRkG18jimAcXC9ShWvsrX2niiKD5CccWhZZQM4G2io9+egZGM+GTLoRjGJ1l/yzF1wkwErS7UtRRc4UJldxfTPQNCgBbUoweIbPeCcIkPNHnIRli1g9BWc+kjN1LAAhE7kW8hyuvqIuXwf4Vk1RJ8QzMBbacT/tCqlTI/KdA+uIdS5Y/vG/O8l9vrEeaVQ4LWACksHAfXv1Y9fA4qFtj08Fe5mIYxSDx81L6Rp7tt/3+7yfdofQQDMFQz6U3HKQnRuteZRAxzTJiMfEKz7pz6Q0FoBcEhAy9de2lgtgc6/Mip7jnmL0tmimODqtBCInEQmX2+c6shA7Q=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyODg5ODU2NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zNTlkNzY4NjQwODEwZDkyNDE2NWFhMzg5NmVkOTdhZTUxMDZhOTZhOTlmZGUzYTlhMjk4OGMxMmNjZGU0YTMyIgogICAgfQogIH0KfQ==" + }, + "L": { + "signature": "iOVFVI/YgblB4e3Zkxnmd3hl1S3WS+OWivGHGU8wWaIVw0CY/ZQeGWkk43tRzs7YHCOQ2KuMNxLYNi2v9JAQsng+HDTyH5GsqZ5q5RxAWNgehOh+roc0Z9o9G1CbxTIDkVHw2759eoUJiZblVTGlvk41s84NX5R3HyysTOG6B7bNDIfm19fQTUEb6UCFrMGU2lNQRQH95JliR4PWl+s0I/IG7FghuvoDzBXUsRASWYMdAGTFptGFDC7kwTnBOkurJHJwYHZtF0Up3kzqcy9uu86XvCuuD5aklSd+2R6wEeHN2Kw50E3gFH8/79AoOQK6n7JY1WWUkkdmFtcuwBrEKH1E6AcasWpMyJdNk853XksoBdUlALdKu2fywIivGIOyKm2PsMhgyCBl/81lTYEZ+bCicm/nnADVWGKNU9LzixJRqO1SMP7lbSfa1sVnSUDZrqGHljfx3vvw7KHvpHRT04+DD/fV/IjFhjw8KkH5XlKNxhFjUwEp4rPcyUIbC/oOzAeHhDSq2pN+Mv1hJO95OarMkDdf/NVWM1Ro2zk4JsXPlsgyGbvTrWshko24SyhNXCoprYmapLTljcUbwxB4cnHj50DruP2NDdZWKcpYLBRWifZnEHDtRLfvv57hIahbsEWwRBFOvS68OiZAll5Yvgyhc5RB9+Lluhe4jaY/B+8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyOTIyODI4MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iOTM2Nzk0MWIzMzEyMmYzOWMxODE1MGYzMTg3YTAxOWI0ZTRhZWQ3YTllMjI1OTQxNjQxMTUyYzUzNjMwYjQzIgogICAgfQogIH0KfQ==" + }, + "M": { + "signature": "dKblgmBi/IOcQZMWAwjlFuThIxnvhA7PFZQaC3DXsU/e6UZDr6+0nm/ZkM8yyXyM/6p1yXwXL8yyt8pBvBwUs+u4qk6HHlLkFyL+6r8bot6PJj6pKB5BbnfgpLUMqOBOd/V+C2sMtIusN6Zaigy7Esh50/aHRsMuUI8Pxj1F3nCb/qA5vW7+f/rMkay1HY6nK8yTCiW6JrXC9EgbvSXa5CEwf93iFRY3NwFq4xHQI4UFYaddLXXBl6eCHe3nI5uZIQYChzsfx59UL7TTs1w37zUV2mYMARTbf2tX05uAzOokWA67U1wnIY8OKLvG11Y2h37hfpmERm2FfbClGmbwh/fPIgOQdNH8M/32Eqnxnxt40ruFt/KteLdFndftsJRRNPHUHrOGPtAxpoispdK66MAeAHbZe2syq29soEyAWDG+ZxTf/bZ49gob2IkyGK3DKPtkHw5mrrAVkPzR75xZBFv3yprqcX6dZOGjlQLGM8vPSnjAnkJKxk07s4oXXgB125n/IZQg9JP2vrI/4uytbcD2vtESFrUYz2ksm7Xu82ntOC8G/J4xpuIFV182qT01MTok6u6EqqfLEuQC9eFLknn2GdlGIVKC593E7e8cGWPgz6UZsOpAPY1ZMIkqzs6ImP6W7LkM9W7aWQVibxfmAOXk0waYCH7CvhMjUZ8H94c=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAzMDAzOTU5NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82ZDEyN2JkYTcxMGU0N2MyMWQ5NmNlOTViYzM5ZTBhZmU0OWQ2ODhhZTIwMDY3NTYwN2VlMmFhNmMwNzU0MmVlIgogICAgfQogIH0KfQ==" + }, + "N": { + "signature": "jRxq6w5AT0Q8Lk9A7OIESGd4ZUgRy4tCuNjdrr+Kx402eRvOwhWnlsXWUGehzwNkZ1Ln+OS/Jx3LqpdMgPcks3S3TQ72fiJ1Q4pXwAxFBiZBt7eKH36OfVahHuyvJugNSVLkt4MA01xHpBiAIohsgyA/AFTQScOK9MoMOjNtELc11j5af0qnCzj6q0psSFm6US5RoVDcy/ANEiU8tBSxkQiQiFMOHZ1GaKw10e7mGLJcgvR+RpIRGJzSxeIlGpuLpWtNwTGXgP/YhkVby1qNNMMNprMipS/CK/Iafubxrx5nwYjBylrH5tO6K2lPrVTZb90v9YRVVSz8pBVO1yup2VF7Z7V4T+ybGzv65E+MpvAXhLOSPObX+gQFdpObgysKP6Xw4pzp6LpU5MxZ/A73McEm7OGgh6RbYJowPwpnXhNVYi25xCVY0J6E8dMZFhYSyD1DVTpFTUy9mJ3BrQzSWFq0RWBOR9y+0pC84dkj8JG2h2mcR/fS9Uojpk5taEFkUUwrnaBZCLnI5EDA2InsS77ONtyEHHJvw7v7TyS8ibprvs+t08i+WCkcekUtZsrq2ODYynLOeHfuOrZURrcLatMUh8TXi8ArhgFuhGOCsrEHjdBb7LAaK5rbS7oa3uC7BAEeLKlEfIyxGcw1yANvYv1izvobq27Zb3TMggqBSis=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyOTU2MDAwMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80YjhmMmMzZDliNWM1N2IzYjZjMjYzMWZlYWU4Zjg1MDUwZWIwNmMwMWUzNjBjNDRhOGI1M2JiMmM0MjdjNjIyIgogICAgfQogIH0KfQ==" + }, + "O": { + "signature": "wRIGUboMZo1JI1S762y21i0HNxgKraMGH8IfaDz0iB/bEoKy1lI7mowlgrY1wqt0sn9umjPNPgZgymOYD9iu2tpZSaCl/r9wYVRKNPEwxRodcc9DT6FRDf2Of61AvYXKy4ehKM7ucTbRnpvaEk526QWH/7/6C01Ym7mVAVskbec8xjpCeToiDUkZ+K1GXqIJzd2AktIoBjdfXx8/xKVwF4VNR7ws8i0jb3h/MEnou9wm0mqK5DpwizAjYh/dp6tkI8heR4dDVu0ypoiYBK2ZO4YPKPI1XPpIt+Bd1QRl+0cZr5Hh/6Lqe0w9+eUmzoQgMH6O0f78iBJPx+Sf7Ibk/KJ9L3isrl4lrfzTnfkT1K71ZL8yc4AGu3RDzcWrgMz5sxb/bIJeyJO4HDf85WgbcydKWnKan+ntekBzf123vX3G7bgPX+pJO2nsCnPFheHQcHH2DT83I8Hu98vpe0O2U2L4ERf73BP1E1S4v75jtc/PuCNaR575hp/q/FnoiIxbQMboDEGFDGQbhLSv0gMEjLRhJjiuziFifkQJBjQYFoH3RUg4f5PA4Kw+XvgbPSNPoyZcs19YeQ3xieLkjdo7VVdGVEw5KTEmtMT9H9OYtHqETWr6pxfzHr4ne207v+0Ewk6YWLcg1YKN7nTD24fQ4BxH1v/GGTFoOKzqM6f++CE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyODU3MDAzNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81NzUzNjU1ZWUyMTQ3OGIxY2RmMjJkNzg0YWNlZmFjYTZlYjM5ZTU4ZDU0ZmY2NjE5ZGNlNjI0NWNiZjE3ZWIzIgogICAgfQogIH0KfQ==" + }, + "P": { + "signature": "l0VkUZxXuzDGwW92ukUVXXzDdccVnLPURx1TJiwDXRz+bb1vQDWiNEC3n49qNYEU2xGAWqc4ck6IQc2EwsSQ1LSBjJZr3KN92GwjH/emyId6cR8M+BnDZfaMgXwkDebZurouc+vpl2hd1IF6jo7sXMK8n8fPexMV0MhGpdflQYClk5kG9OEMilFOs94c1DWZ49NZjARc/UODjrWP2vbRJzIifEv84TzYOIkh69ZH00uNPiFNFmkgrvziEYmOnuZcXZERoLrXG324uaEYbrGV9wLBBRHr9iItRrVn5q811cJ3p7jXhv+MY1UVAVpd3b5o9H1nSNoU6G+QRNn1H/fZGk8nxIIWGzK3BJvDUBhJKIYpdYBiror1NOo18h75fn4ULJTL2LvhOuGeaDItTh0nyIC+mK15CT+SNwZqXDkifUWZdUp0c4b19eUmyzyK/4ZVcsS+IdeNPudn1Tbzxzj+HXK3rq757CPkN2WVBgNMof7fE1+L+nAaL+EGE8fDrmxnyZlZKEicYu5pY5VcQ2XOxOI46lvimvvrWkLXG8C6RoddCKV0iCe3Z9K4+tikaxUHoRtehbNgfJeRWDM9PZt6HMNFF6y5mulhjK2pBGkqpPy99Encx4hcYk2YYcM9W2k4bMCDJbQ96RPO0h/sFPBY55T86BsyeY5lnhhfRemv9OE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyOTc4MTUxNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hN2I1MDM4NzIzN2M3MzBiMjAxMzgzY2U2M2MzZDRkOWQ2Zjc0ZTVlYmE4ZmI5NmY4OGVkNWU1MDk4MzNmZWRhIgogICAgfQogIH0KfQ==" + }, + "Q": { + "signature": "bMO63ey/R/9GZuX7hamHk6rnNbNuFI2sXK7Fw2y2pvpAVz1hfZmsrD0DZ09JYZ9/bpReuVJvjvOJgGoCfxr+YSGBmrVeiqeWolB2VzigwMas9IOB2NakQGeL/iyEVNMHanRzlD0H/zk9HWjyjUwJdJ04Xw2FhASIoEFUW84SRJ9TQ2ATbJZiyxpq/0XAZZAA0C0dRWE4eIKVhFQSWbDXewv/h7o8vmmBmE4dBnhe3h+LCbC1bp7l9AAT/NXGXMLyfIYg8kGXOo7TazY2hTW4FxdcxiMGjMswzvPYA2/t5k5qGNr8aFHHwUlQ7/00CveTTU9Blo3JEH0y/aTkFfg9oW9aiLoDCLgDErSN8dlsFPfRpxj3Umx4nCbTWFqOel2NU1fE/wxFBWe545wlzi/wxeAV23m26va720/YlbgLf7ec5J5zVn2fj8wF4L8QgKK7zTG3ZvP1kg0C6M7WMuhCCWv1NQBtM6d8swlJ2O8J3qqpSLuqZ0TzRVo6VYupX0O1Lr3BD4LCjrK32DDTR8zhE/YGfqj4FqDAdpykTrZ1VKXpq00nd5FQd9+e2QNTHy4zKrMm4aHBFQw993ZiZL4gEC7kqT78fBETNXX3jMmn/xxUqYA/3nWC/LUUrKpjtx3LPPuVQkcKyUDE408KGu7h9IdDwE4HOYmPruSlIvIQIVw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyOTYzNDE5NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hMGI3YjA5MDA4NGZkMjRmOWY5OTY2NGNkZjE4YTBjYWY3ZmI0ZWRmMzJiZjBjOGFlNGRlMTA0ZTljZmFkNWQiCiAgICB9CiAgfQp9" + }, + "R": { + "signature": "GpU0DPMRF+RUeSSqxrIncvpndBZ3+He0/NS2lNxFD36n6GnYKkwQwuWr64gmJR5hSWzHiXGvBUb/X0r5CZDQduRW9tEgjuuiGJPWOKaug0MvNyd5kqlE8Q/G9VebA1TLEBZU5B2li1VgorRUtwvHLzGfbyeJPkugANeq6/pVPgBoWPcilr73QdJf4Tsn5irTl3PBpwUSuyCbCDUZ5ny1aybqDbH7E/ez9cZWJQNtbqG5p7UY8SJyK4wbofA2xdwEyPIvVpVDVR6nFJwNlUUGc1NxqV5D6ZGnwnskCoPqMlXy1eEhXPfflhq07Unx5AMHa++RVC+xm2uleSWljPibx4tYYEnvQfnicQXxk7blYp0zkQwc7q4cV+qcyYlbtSQ9pE/aDbNlKRUckUzyjsldjREv/8k1EOMYjEBey7oibOJ1dzN9f8mCIT6UBuLqeFO4vkMxPA2ADaC36MjYFOBYiGfpOY1pWgu8mZhDuHdtyDJdox06dv3I+SrF2XcOXhtigrmDgPUV+CRVtAUKKVDNf+aeznOnRu5+km1Y8t8E29DgbXT/+rFhpMUe840tNgRxH3SYAqKC33246K9Urj2DITI3aldee5i36KNAfoD/uSfDM4QM+hTC5QRh6a34t46ujAxM4GX1IttfUBC7gWp5LYwjq1LjsW1Zw7kBAuUYOtM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyODIwMTU4OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iMmRlYmM5M2FkZjZiYjg1NWU1ZjBlYzJlNmVmM2Y0NGVmOTQwZjVmMWE0NGE3YjQ4MzUxNjkwNDU5ZDVhOWNjIgogICAgfQogIH0KfQ==" + }, + "S": { + "signature": "B96JZeCAMc7lbNAt0GmzF/WIMzgRZHMuH5DnnKz4FETf0i//YK44idnrvL84qxNWWiDlepujs7YWxVhAMEhYbLnZ5vYCx2sytRRuBFDsiPZXXwEcFI0j0xEL2D/tjeXpjjn5fnL7ZE06qP+bqysZelDSz3egPaY49HJwXxGLv5yBbxeWf00hxJcDou73nxzwQAyf6CzGfnySvdI4po72/XsAGVhB6BnH/yEJOFnKr+CPDyh8x1Fx2T4dvx47fjbk2uDSA9UoM+Udyu+qcd6gHfLfe9Y2o+ytkuxBSdfNrHTJu5iZkQBancy2B/qXRKA4rI47nFbJgO+11aunTeXNlvj18pHej1Zpc3NEE9RLh2O6PEXI9qJcoZKdiFkT/incVxefnUu0h7BY/YMskoaSw+dEaqAVpEliZEZef48ODdFRjX4Raudv8N61CAeDBqtF9dOc4smkzzwBmBnJv1m82PwMSP/RjthPO9fzDwZ40jjcxIeHc/pq91NS7mhU6uOPBy+nV8EPgvZDVEe58ZgNFZUSrsmoswEN4BCXTb+t/cMaGBqsIh7YA7ePUz1PtcU2dhvmVvXbSKuKI8M9Or5xhGrvE/Yg7Q9p4MMkspVtQOY5Gi1a2SyNeD0zh7fJ1IACsvgGTHOgCcsEl8zbAF/R1hwawUisrkVM6coNqH/5owI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyODYwNzM5MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80MjYyYmFiZDc2MjMyYjcxNzJkYzFiNDBhOTJiOGFiNTg0MjNhZDVhMTY3MzVjZDAwODhkM2JjODU5OTgzOWNhIgogICAgfQogIH0KfQ==" + }, + "T": { + "signature": "WvT8Vbpb8ci2oCS3vHfWOA1is8lqeMT//RlEOHcR+f/aT887lRwinlVzXkgPNZFPLLF+PWRWOsCDpjJs2OgJoGO+F5QuZ8JLxBmUD9RqZih6l4UkO2T9kQ7QOeQQT/IFSSDttCPP5j3tbwWbTspeb1YKmF0XIXoIyvrcolJ5sEjkFVrmxGSo4dPO/ZBUELMqi5p3IxUQEi4qrf8sjOkhJGRy47CIhv92bWEhXf/JzGsuPb3PIDIcjpqtbUYmMZjUFKgndPAaNAqc16wHgM63LjJqX4erHGefXSp1Ww4UpL04NoX6ZBSspPQTaLM3HU4Q651tTHXbg4w4W6mYKAndkT2DzZfv9+3iGVwJS1Vg1edH/Q3quni9aCJVkyqIlEy8xryYlDY3aW/NsVkPC+cOVEaBUgKQ6tUTH2ArQETwfGJnVkhapgYCRlZNxI5ZVqbFvJiz1HxUspfwVzIKWFZ4dXmUmj3m33z67G9rdxA0A+GJDYEcdWSV6z7PugtUXqvnvS15swUwU9q3sRVvkAu4byJeqQlkbAPGykPGtDaRsb5i77SKQZ3t20KbtKlmYKqggoRQ4XxR2YRTi/WP7JEaLvZQK+RfTZMLwEbu2/MCkjmd+97xUAIoED1SZSJ3Xp79+NnAoZbC3ZWARYw+2i+BswLK/iuPXaxa74DGbfFOWQ4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyOTk2NjUxNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mODA1YTJkY2FjNWE3NjQ5YmQ0MmMxMTNmMzNjNjBhNWFkZTFmNWM2NTI4ZDc4MmMzZDE4OWQ5Y2I2YmQ1NzkzIgogICAgfQogIH0KfQ==" + }, + "U": { + "signature": "nvWM3Bo7zaJY4xlsmJAUMSwfsVUwX71ajysnWNadggf4UVtBaQOmZT43mwaeCij1+Z6kOpgQ06NiFxdDIe1GvYwugfOOyRQC/iTUcF3a/S5jZSkcPXtobDrFD2odsp5kJQ0lnH37NzLJDFAQoC0MIgvNrtnPxIEtew3l1iv55Zg4W7CWAYQk5m9Ye7vrYOu/SzLW35rbr/q78o+A5BSlxuVIEZLJ4E4H57tF2EphErgFgGAouLbjDG87XYXRxGQNhfk2hu5LsJMPU+5qDqsmGtTBz03O/dQAiAsdkZ47Zj0kCSON7TCf3dB50Gvqc87n37JJSmdjn9ETq7LWVXrgjqWiU9ktfBbIDaog+3G8XqguCvTgpWio5mlGWSCYeKkd8pvwlM6X33908Ywr3zobY++nPIkKpE3OPeCVgJYRASHVRwVNz0vcXEHYWHVI0i09cZnrLB+sp7wQadWMMO7M7o4/H3nqxGBQCzmFEzUuH6iCVW/tW57b/13jWWgSQgVHRib6eQgU+GoYi5NvKlV0aME83fjeJ/ibFnu2pA57S+jaFpME9OtUL8hfdatNnd+nDEKKlBOyRVmUizVyiPqc5F+XrnkxQ0OlzzB1Cc7JgtYe94t20w8HYsi1ef9rBc7ICUiyzUIYTG7hrcfNELjvtkuXyqYoapOy9JVv7uj4S08=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyOTY3MTU1MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jMWQ1MzkxZGZlMzUxMTFmYzZiMDJiMjEyYzQwNWI4YWEzNzY0NjZmYmJmNDllOTEyM2MxZWJhMDBkMjVhYTQxIgogICAgfQogIH0KfQ==" + }, + "V": { + "signature": "mC6HHblpCEj1e+jRJe6GFCawi8aWOMgpcf9Z+PciMtNfOuX+81U48sJkJIncZW9whLD/wyhIVibLBxQAtcNw6mDj0afEf9hIKd6LaCaE/ytiIWwCzkPRkLlZiXzJ9nahyoDYODAVA/VAJzPP29JOtkBh+xdMgql8kKVHOZPFJu4VH3z+zHWXPpR5Q6JR8ISoc5zDD4Zw7xkJtFUwatBe26+wRUXoLuHB0I3uyqSqcDku/tXj1nLES1AmTodK6FtGFgAsN8ATAP5rKZSrb/++a0gW+Y2jsCubskvXUQ926BFv7RMJhzKplCBFU9gNHVAefypiPjL2srW2eTvOoNzA2O2gz/eKWj9Oc+17s1ykeIBNrFHJdinPEwdDqK7xXm3+BdUeGN8ebGNqQHnMQHcJLhPCpRsIR/2Y6l0EkMsSCAQO7zhCzw0KRplkvSRq+k278b9eEmLAO/xvhtEYDlXySg6fG4J42PLMFaDdAlln5tLd0CyzcsR0x5/kh+8vz+7tr9jeucBqa1gJHkz6jXtC+p69gKVABw24262w0tIkQ5lsUiQWumd6HZzW0ihbN1LxHPjc5ZLQEy2k9MQgpjB9YtIaYuoXAmEU+3DS7CWCBijtr4lzTLi5LfGgpN2HQlEffJWhEv9z5gKHnf0b1VkmMa25vN0gI6iI6EIzAAX2Ek0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyOTE1NDU5MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lYzI5MTk1NDRiZTI5MTNkZGVjMDBhM2EwOTYzYjc2OGE3ZmY2NjIyNmU1NDU5Y2I2MmFhYzA3MTU3MmNhOGUwIgogICAgfQogIH0KfQ==" + }, + "W": { + "signature": "QKKLeE7mfPu3yFtcnRdRfEu6nSMpk6O49MO3kojX7Akxg2tmCWe4so00SS/FJSOlRaTvbv7VY3xoh9ZnkPAsLlPIgWs8TE2FAvKTtxPWpEItSswkF6PuYoQXOxf/ZiSIQfc+jmjRtyuWncDNN+1OWcNFOa3M31AptVVMX6iimUuivFPfmE0mdgFjwRbjow11F8IWK/ktg0Z3J151GYTYnUdDnFsLQgtVtsjuNmcxLHek45aooIwjYDJcVqfe91NH2AsK+i5ONdtNx9T1OzaO9WSZZnzJeiQ9P3HLsfThcB160m+UjKAJplY81cHceOouf543q+jt8DSqNThijObSBAb9bOWIechy4NkcVFZq5DxTg5YVY2eiQ2qrl6tsAx2s3OkfSraCoh9YTWYrxwn3J5ZrZqYxCq/ingl64CBK04sT9Ciail7w+zDOLkYELRzX2C1vi1wt6LMFGbLLTizkkW+0vVZXS3AWxVb5nOPBK0rJIzDtI6KpP09JJeeTrfgHIHOudtbKYk7JI8mg4iIdTH1I0LJnhaoG6rpthn7oz7bEm0ooAHyLyA0+eWoOe6oDtcjyt1nHcdzoj5e70/5ruv+BcZkXboj59Ns2XRKGyv+i8ZBUdOEHl/k7ZAu10pOoNGjEveEPq2euW17Rm7eyl0fPXA9tuzgcX8otYYdfjME=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyOTM3NDU2NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82ZGQ5YTE2MTVmMzcwMzY3MTZmMzE4OTVlOGY3NzYwYWM4OTYzYTk4M2MxMWRlZGI4YzkxNjU2M2ViNDQyYzQ2IgogICAgfQogIH0KfQ==" + }, + "X": { + "signature": "LeyDZQWTjzQG/lq+qE8cDeyVrBKrfpN2V7l4/bwLmiOES64ZFWxVwGNceRNQyEbb1a0CUuavtagra0GQUyEFQaDYb/q6Zhhlulq/P4+Vi9xcjkZc+mhN7cSNIhUHxGx/SS6swgwcPhr+gFrw39GxZO6QQHNbbe8TFks0ezIe+4jc3zFCG4hrXVF0CRhEY4W2M6wWU6mvcooLr18W+n+AFLkWu1xhCCUM3OU3M3aCCvGmegxfE48IBask8b5Ctc4ILMC4m4UnldzCbD6MMz9TU9a1kM6cAwpc2CVEcfX1a11mmq/+85bFSoBULbftFzXcWduw0hjww4v+NW1TWYrQo6zpo/iTyYb65MxdV34gjuN+JERHLo03sE3g5p7DmFARq80nYsJopgA5ssN0ijDhRmnZ0UjWyNTYKF6ze0LmU4KtSfdd3BDD3nuaP9YZ0Wcr+4ol0jm54m0jwIWnH1y8XM/9/XiCIkbg++HQQ7p6zJJfabUW5AMIjuu1J9EV105u0nJ1zA4VYeqdOG/r8qop77hD+OChNY0Z0poNkKKm2I9By3b7erE6ndpZLscaKRvzuik3OABScOtE1qKrNeO8mjwovGjl1umBrzBck7by9UFyXyoYyG7DNpfBCCijV24WWa+fb6cC7EaH2iFrs6sXjxQRr4nKCdc31F03iWN3XGo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyODY4MDA4NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kYTgzOTBiYWNlNmUwYjIzZGE1MjRhN2FjZDEyYjNhM2I3NTk3MjQwYWJlYjMwNGU5Njc4MjFiMWFiNjI5ZDc2IgogICAgfQogIH0KfQ==" + }, + "Y": { + "signature": "r9+UbHCMwaHEdBiJZbiYBp7a7tbXCV3exfI0NDGw265UhAYiTGSPe/KSQv2D0FT5NA6KTQJg4umnQ/5kjSaiJ5uwSQ/ad5Lz7lZQdiJv8nvt9uqXLops3KNZ+nOSME/xgXntzTS2ujS3O+gi6vGjGM/SpRM9cyr+E2hOVMxVUq1DRt+k0zIkWNvqzqRszJ2yKgHZ57nDz2THT21J9zjYewzYAn7Jav58nLJmCv0NqoKQh9Vu8YdfXEl5Wz4aOiVr3sfJgRvm3wrBn5HwHRzXAfHGw0d2UdmOkoR5Gyqyn+dAlVBTX+TQhsZetth8bgEQCBoTY/3zQ5w692W/szaWYdLk4k72L1kIQDHPYdmSqaFSrkMKdL4Y6CZDwn550bLRgoaP4YcRutl5E19J0NOkteT8oY8bJ8EvdJyo2NaHI32rX00QQbU/cEQfmmRKN008OxVbSZ/GhC3dGzmfTMWja3SttvWWuThlwBfCRx8liCj8zNnq4R/XKmyLnYMsPjT+ZzZAlYcB4G1uEeV90FrnhjP2vmOZndBbx+L+XOWmS9bOPWRP8aQxPvQpwTaW10ROnrIq8qP0bfxADiowRsY2wUQ3aAvb9EcTYtOIHmedLDV3fylN5sC1SsfPKumSjueiHhio5Jly/Lnd88HkbpLLVokcVHrt8KGg/qyLJgEdie0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyOTA0NDI2NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iZjgwOWVhOWZjZTY2Yzk0ZTZiOGYxMWQ4MmFlNWFhYWUxZjkzNjJjYmYyMmY4MGRlZTMyYjUwMWU3ZGZlZjY5IgogICAgfQogIH0KfQ==" + }, + "Z": { + "signature": "tFCDTkP0r5tLEJYEt6VLmm/fQa6WtMPcaSf3ha+TBfyLjCqjDcsqfkftiW4V0O/FaxjeSB/49+8YuszxEM0rfEjCZfXGV49SdHL0vf8M/3FLBvL8bIG0+Rl3aJkAHDScZjMP9wuUMSA2qX3/33zxCK1Cet0W+fkSOyL899THdhqrufdswts7LDusKKfDISzv5SHxXX/+q4u0rcPjYkUKAkw5wk5J89tNO2DfokYgiM6IOJKvddh0u387X+DOHtNgRGGDLekd8UxBPAsYCDnWjbPm/YmwPrSZwiUJY9WHC8Kpi4f5nO47HODHkNeZingrWTa8cUT1c30mE04MyTm18Yp4SZBuugo8RkX80tCZ3EO5/odHXgEVrR4YUHN7hzefdru8Gjp4OaRB7ZLGYtMr0yXFZhhYWPX6UEw2LBPCWRCGVbI968/NhzgeiUB8RA+DF8lRCxe6VBbYmCFxIce5G4rmcBZXjmvO6DtMUyl0HFlM8fxnGCbd8PBmmKLsw8Hi26e9UdV3VoRViyPRXF0aWdt1E6+bGGAtn4H94wEIlOQeGPQKUWGNrZQdhtImeWJcNKA/8Ds2asP3NA55Xuu6xBwy7VAZbyTMtcq6ZSNK9HQ7/9REBn9stuZ4E8bvHdCNC6k/3RuhOv8rufCtC0Kx/9J+IJOEv4VmyGS1RLYymXw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyOTE5MDk1NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82ODE4Zjk1MDg4OGExNjg1ZDM4YjFlYzJkMjUxM2UyNmY2MzgzYWVjY2FiZTNhNjkwZGQ2NzRlYjY5NjU0MjJjIgogICAgfQogIH0KfQ==" + }, + "[": { + "signature": "IMGGoPuV48Tz+tFFugc+aiDwyX2AGRAOo7xn8fPvT0ruxiBbtaDMEmubY8K2325L2QYzs+2eZiAur/xy8Y/+01vI+mbOWqSvYhJwst5+QFGGXxd9gcRjOiMJAY20B0GaURmRW8eiaKTqe3R2chrjMO8MLd4rMunYKVUAWeuSCf9KFhjbnEwA+yJIDXyFb9uy/1jeh9a+tQ+btd20CWWeMQQ+4X3ZdxQZY5rbFJ5K0xyRlQd4gs2NNh5gHAUPKcaW+Tj8uBN5KZTlre3QUVqX1BM2Z26glHiPikPQ0xk3aFdS/clZGwGg9IShGk1+pLdiddbmyNJX5fdB0j8YBinD5ulSZSmiPrfWXS4LYctsL9QZ/SbTxub78jiIDrGRyejtT3OhHcS/PN5i+ALXO8qW3c5zo/nBXPVJyEcUSwPI7UcFoXXhcqYk8SoO2gbo1fXXfKMr3PIOwd3ntsqrjZAFkNzibNVk5jGo6Eohzd5XU15IZHoREva1fLufSPxw31QCG7KvmtOYsOyE89ZxOkqjTtBkblw+5iVfKpW48fSFpW3FeP+DtN63J8P2/vQmO4VIKkaEi8o5BK5E8iuV92ApRm08DbqtiyveovnNpY5ZE37IewaOSVpn4WIYAvLz5COmX4wD/+ajlllmqSNfbicgQIniP2V001roIQPnZASk49U=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyODc4OTEyNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84ODkyZmFiNjk3Y2UwOTk0MzRkZDZjOTEwNDRkMTA3MDY2Y2EyOGRmMzRhYjY2Y2ZmYTFmNTY5Yjk0MGY1NzE5IgogICAgfQogIH0KfQ==" + }, + "]": { + "signature": "f5aIZ7JpJJYrwH/bk/rhWPcOdDcF0ewPfz1D+vltprofFC2jxcHhinG3NGEMwSMF82sruOR5WN0xtIIOtXv8zoLwMG38Y8XFMhWVf7CQCvB6sVymqhv+xjAf5EFuQ4cVVDiZyhu4a62O6RJ0svdT58fuPOirLD9hzfl5cZupKMWShi//FzC558KfaO1ws/wEDVu+oHUpyBq0cjb68hRU/p70ZQMmQ/kakUqk+Hvet2ODzuEWHIQRJvHkrYqImk9fdMG+Yuyj+AOACma5bwhki7LQC6oVGdbKhXppxHbhDbq1rt0LpiDauHPRelNd7XtwThQRAPW/7ddH5eDQks4AegAhKAGzxdBOIcVsoWUIyRdNGKP0GJhx7p0M2vo0qNspPfPkedtPr+n77SY/Sw3N70cPEjLXuKvBKpB+j1smo1t88bDiFpmHUm+e/jkcD/h5O+YwyzPa8cr9eiwzrfebHsBlOy4kkrNxQUoZisGP+a+QgzYQmxAokzv9UnUmqGaLOF8nYfeBxWCX+c9OtZ8RaxIlbEPQmBDqOsiT9R2B6gQKQML3kl9BXTzUjwaf2roFJwWzXk0jGviTTWrJonp6RTQqyFiKRMnicJskixsUQoLY2mZRGmLqHD41qZ32rQSV99+SperQH+1yPd8culjnuiQAYZiVajlkPyZ8sccbVqs=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyODE2NTI0MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84MzUxNDhjOTllYWQzZWU2MmFiODYxZDIxZTcyYTI3N2Q2ZmZiOTU5ZDRiYmQxNzBiZTJhMGQ0MTUyYjM2MTUxIgogICAgfQogIH0KfQ==" + }, + "^": { + "signature": "n0p58hs9bpldvHylCxuS1aw7dmnV8z4TGV3UztWkHC4CLV20hWHNnWQb3xNU2KU14/pjj8Fgrlgv83/pWYslJo/MUidirUeoD6VntEIjMhLxyyeLoa8ZFO3ySbRcbxVpSol9DSZFRF7T6FwDdgkh56y88Hsxunlrqb48FI064d66pcCvIDj/h6UmVNMWXCSZyESBFmnRuQIemiguqAXxBPg4QaaVUEj530IlLaBx2Xtho51ChdN05Rq1JuVZbOFGIU6tv++oVg7MVSkBYUv/nm+PoPP86ijJZHyS+X3KKeYIvqnGHe0iYltyPdOV40aa0x77wsc+mZyvwg3C70+pENRsQdBN/rOwU7BB53vwtqO+TYD50MymjB1YGL995T7uqrTnj71ow68AGwY3XQr3yu59DQ3mgHTkknzcj5g7KP7g9lS/blbEVqhoezqnhx3YT62OQhUOv1tWFcvIkoy7fO+bCmNqLwbKqCCQd3fjG8o0TLizvVUpnq8U6sdDgp5+Fo9njJf+3hzNmDHrchK9wCd9PIw9KINZwgN0xlMpSCmhKoGOq/63NMN0gzBqUppzQQ8ySrw0AORVcqjBC+qeNcu9FDRSYKjNLew2+6IRuGLPwl3vv9Dpn4CDcY9GzCxG+cA+8atlwPmGSsOQO7b54o+JwaAmpQeIJjuHDIxB5Rk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyODkzNTExMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84MTg5MDJmZTU1NmI0NmIzMzUzMTAzYjdkZWMyMDkxZDZmYWNkY2MyZDVhZmRhNTU4ZjZjYTI2NWE0ZDU1Y2U2IgogICAgfQogIH0KfQ==" + }, + "_": { + "signature": "iOufJ6LjhEzrJmtP2JoFNatLWdXcL8HyewRupeC3gZm5kFxNZ9qxNFf103BJ4ifnbZ9d+wLXVWHWR0tiiY//uWhstbZ1XyMJU0YSjxZPJcuPlknyLswC66646rqtFQ5TYtpeNOxQO8+qx9vZlT254Fk1PSScOUQcdFVfinV1F6B3ClOa+umRgixBrYqbSnvS6x53VF9BV966q1B8hFO3bPRqeMdZHXgwJfCbzHWCeZTYnQWA9XklJ1Q50icgEes9ZuxANasnKbiaFhJd49tk6T/Tgxoidac/vOh0ts2ehRusYEChpNuZUD2FHFSNNYQXv9/4Goxt+v7sF66FEBcRG3+aagSWY8ebb1C46CDunt+vxr1HApYsFQi/T/ctUz/b0bv+5TJsP6wARcuRhFY19HmhZJKiubfcUOiOeAcWQxg6osgMBJTlpEGeOF3uKrL8FcMaw9s+5cqzxWxYnaZSZJdSoWmLutNfqf26parj+bM4K4xlSf1lbF2k5ou7as9se15T8OEhzeRyh7xRw/ubq85ehGU8upp3zguOYb03o1pkgM7CvEb2z+s1pg4QKbsxwslOSALuzBw+bZYomoqznficeSx9pHxxCQzAEJPhKJsKa8wZOVvMl7+bSbYDxEQxrTuE3cdrFzlSlPPb95moDqBVOokmRcQcmmfMA0RsA5A=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAzMDE1MDcwOSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zNTU3MzgyZDU4MWE5NzZkYTc2NmI0NGNiNDEwNWMzNjQzMWEzNjIwYzE3MjUwMTczYjI2YzY2MmNlNGY4M2NmIgogICAgfQogIH0KfQ==" + }, + "`": { + "signature": "EE7MYjggqibECodI3QI8cAQDz+BXwn/6SOmeKO1j0ZzxlrNy35kExjJIv0TKu+hor4l4dhODJiIL4L154lI7KzhT35331FQHgIsMqhifpvatrLjmuFKmqwCSvfAjz1tr+0NGUEVRMVAo6OuQM6btjfYrBbJ/re1J51JrWvhooJ9qqN/vi3T98wrAA/skoG57OfiD3lg1dUiebxi751bh9hf62Z71ZqjKr1bva2vjMtLdU838moJcPBQo/wsmfrhbuifrF7eZjohmau3mRBVnkGwKQMJZg1wXPuiNQGNNDaHqNJuurqCGQp6VPPdCRZdpKG8ubNY21m3XPECWgIcQeSUsBaxVNEG5BrFAfjVAcQ8dMsKmhbCMaOUZV+KRCHHYFyiDwvyhApdRuv5Rz9N4V5QmVxwVWDn9zRo4f8kYKCVDSRcKB+8Elr2Q+JfSXuU3Jf35IRtxWXmSBT3OSg/aaQftTvyKKCjXjis8T0schVgPUNpwUw6cfvS6+vbZMATYQ28p4O0gG6liMLzLqs/9qPfgYDgnR/tB0dkvwbX5IDxFrauWtl3yExrcC1U1F4bWeUDICffYigKA9kb0PitaQKp3TvP4AZntJNrUiwWe1C+PgHHXWgJ9DPe8kCq1L6YmHyC4YBdQQ6+lfOEF9WFedeng/e/ocU/Tq/p8+yDrQHA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyNzk0MzQyNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zMTI5MWFlODc0ZDFlODA1ZjdlZDBkMTBlYzY4OTU4NDE3MjZjZGE4MjAwMDY4ZWUwM2IwNjMzNjA2NGVhZTgyIgogICAgfQogIH0KfQ==" + }, + "{": { + "signature": "Wkofq+cXYla4F7Chm677mgDP5EoucjTXdAGjm/5ZoBDaNr3Zo5Dwer3v3HHhhlt7RwfegkYChruTDUFFWBEhoaixqRj/DWUtJFSWT+6AIrYzO0irPg9DfDDSGJV7LTuNjdylmdbIcxpOq3C7ewyHr9pRU5OEf48Gr6AtQ/lEFSNPLHaOovKolHflz3NmzD230HYjXL+ZbQ9pL3ytqcf0as17cgXWKJrznmBQ8gzTo+E6R8o08pVFEYPtDYXHQvJKxdvWo5CVFyKTZ/cDuXNAHeMB2ICPqmpwKwT7fWefLCyv+8fWiNgFUg/xz1jX6/7M1TuvKLqayF8lFyAgELmlOgShlFWodamQ7HYq8RxIp9AeBpduRbYIqiqlMxp6+zrp7Shkcn+7bfiDW1DrmKP2yKgyFpwwXp8/oc8/Os2UY+Gw201gmSYuHsrwFcHbhDaITY9HP7C0k4IluNFPA0wULLi0k1P7HB7Ub8b49cLLAo5DcFPhkpaioJU5C+eYSxk3Bd5g2wtBtkTsxXRjrLCQS/b3LczsosqnMDNGO1JVPuswnaUHdmCOJlsdmzlT6UwCPv9ny9lMq13CaxB+9Km678Pt7u9KaSFcZGbgG5BnuXUkGHL2OvMB7UrsX/UsQfRV/agi5sreVWj26dr3fjzC3O3T1SJdwzu8zvKbB29qiCc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyOTExNzczMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80YmI1MWNlN2I2ZmZjZWI5Y2I0YjJlZGJlZTlkY2Q4Y2VjOWE0YjRkYmNkZmE5NzY5ZGEzOTBiN2EyY2Q0MjVkIgogICAgfQogIH0KfQ==" + }, + "}": { + "signature": "Rv3pU5/eblhUGhSWLGnlw+ryJ5CppyqqzlMJTsTwXMiQvC7tuC4K19S7/fzINYfQsTlzt8q51CJkaDdkgwbs99tJihCJYX36M2coBUfe+8hYY7COzT+ffzUJhi5I1PD3KgA78krzzyzt11Mb5Pg4KMnhm7/DEKo6IBgjHtmKE708lGNUWbsAj1ufcByxvao3AM5pYPoFyje9/FrP7KPfEyMBWyRZJ2Ec/fz3560ExeugrER+/et8lDhHOoyD2jPjIC/J7a6IQjhoZAoo1jkj469BwFdrQMv9m7QRwCkiLbOD7fV6WYXQWnw1VUvda05ZGYl0pN4PeLd7j74l1VEMkGocrcpnLcHJ1Aitv1pBIo2QdakjMiW+Bs1bcdIifY0t4mkfTVkOfsavCP3hhQVC8OxW4f5ScnB0dcSiCQ0IT9KSvZ5sLTHEz2r9mb0Ec7kPW/PVDdhSPeSmqLgfFVbMEn/ThNM7Z2W46g5Ax9cXeEr2elfgBza1R8y1ecT84PAZBO8AAZNWoD5FhaSc3sTLPmD7s/xUMCwuQqZtusKh3+UJpb+djs0d3BlyR7fhNXUseA/TB5BYKJvdbgiMIWNqp4s00mviE1IGJH2C6pGGua5pZNVV76ri4EyIlTBr+mCf4dJNR6OYl2ap8t9xV/zKUbYBSjzwUJzYFYHr+htmQNw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyODA5MTk4MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83OTc0NGNjNTlhNjZlNTg2MDBjOWJlNjVhYmM5ZWZlYzZhMmJiZjQxZGNkYzFjYjQ2ZDA3NTUwYWZjNDhjMWVlIgogICAgfQogIH0KfQ==" + }, + "~": { + "signature": "T6Mwrzz2/uiEd00PYii9+YE/BB0jkC+EHXe0tovdx8Dbv2EOpS8wzWi56fRWFuM1eL6Ya2uGZB+vjWw5bcVj2iObFHkRa+gYrBNgJFFmoGeVPH7HpxRvjuWYUUji+2N1TvLj+SqTJSVKuiZka64BvfVllQICG1ly71PCbqnpc9liZYBCz+LsfagJKTYS9jaGQcd/2vDukyV3FqswBe2I0RKWU3bzZ1EXyefPMn3e3TQaNMT0zIfdkaDy3TYU1eJTXcPPZSXFBVILz49u8VsXZTjavdN4eY+tL171FNJ+6y9FEPuacSQ8oaiRrJiNZC3mHianZOgvbNhah0oTnWDMnDi/QUoB53CAC0KLpAIm5vXgWQ7RZdypEIR4VXzFqy8Hbta2AOK1WpT/UPSqLkAitioU7ooB/xHHw1bZze92mfVezqUlhiq26Y40dbPWpzEAf9sJDXA0Rsb5azcX1fsll/WhgQIW8P1wvIsFRK8wjBXcJMSWmrlCwQkSgDUszM5cVHCC9AQWOl5y+g7qMwYmUgiHOOLr4lIABZNxgb/HEwSD9gQAhuVFx45D2E0S/H/J/as7cyW+l1oKmvNB09pT3k9FEXDyMhifeBPv/bDz1wIoygRGkLMwP3Sp090RTlsmtveTXnj2o61tMH6PhBV67vIsek7BA1PW4OQTlHJ1qMw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyODA1NTI2NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jZTRmOWQxNTBjNzEzZjVkM2NlOWUyYjQ1MGE1Y2ZlNjNhOGMyMzViNTQ2ZTdmODcxNTEzMTViNTNlOTkyMmZiIgogICAgfQogIH0KfQ==" + } +} \ No newline at end of file diff --git a/Network/eLib/src/main/resources/skins/chars/red.json b/Network/eLib/src/main/resources/skins/chars/red.json new file mode 100644 index 0000000..a47f928 --- /dev/null +++ b/Network/eLib/src/main/resources/skins/chars/red.json @@ -0,0 +1,266 @@ +{ + " ": { + "signature": "mwKVDVw09lIjFyKG38Q2+tOpYjD1aPjyMTgsqTLsyi5Ee0HsL8y7sA6Idj3GCHTICU5XULLSmCAzzyaqGn8FOE/zfgkwSS7i0EW9FX2D1vd3StnvYoozRZ4jHNWJGXUvQCiZ/O0hltkHWXKbzGxNEWo5gKHDoY3LGak/l4mYdrHlRBAgQwGbb/yJlXiNS2AbqXDZMfPBumz4prH+WuXJ0r9s/K/lw7pXlKQVHbdg0d/V6zL0/6KQFu0EMVIMpSuybuxc0Q5VqmV9OBZR4GRDIzhceB3rcUFc0q1hRetGG2gY3woG3+8gkeo6U/ewLP3vYn3TGf+An36B6234Es7fSOd00ZU3N7xFMiy/mQcBBuv58Zsc+sQRdHP6Iezva0GzfrqxpAV9nGYzgfDcKnecZivDt18xn7Y0k4AeYj6nWNi+H4Qf5WpZooXn4dPQvQr7jhMcdhKSEqswGOqRW5TrfY+I8f6koF7TvyXi1qDaAyLc2ARvXqvfdeKzooZcYcr1HzbmIDXcSTN44z5yCq+SzDMDuQyIJSwnKCHrxYoO1tFES9x+QVCe35gSUQJWI2x/k6Ga7U0EC19Yth+rQETjC1jKB7rjPJJ/YDnA5BP1sDd/4+vXbOQS2u1YxWV+QVyS40AND5VgCqKM05yqOw4AVVvwPrsg0kvOKnLsAGbRc40=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMDI0OTY0NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xMTUzMzU2ZWMwNjJkZjJjN2ZjZjY4NGMzYmI0MmU3OWFhMjJmMWU4ODA0MjA0MGYzYTdkYjJmOTI5MDZjNjQ4IgogICAgfQogIH0KfQ==" + }, + "!": { + "signature": "pHH/lVH54GKKcrRbpWCqBBVC9CO59gVyA8PdAGC5vvGXfR/91shcpGIYDAyTBPcYko+kFQfaPukjgWpnS4e0fuI1YmUGRA0w+DPeItTPdMvc6sSo7uuYlKvvF+YkpTr03BY46NlamUSWrvMUt4U+63mxCR4ftdWqIJWuHrqDo7f/c8SKjc7Cd/cj0sH+sNTrwfVUV5KHUFpZ8wtqh2WbBw+rsPHwJAtN+aydN+9UWJsqhMQdGXCL4JGP+b9y+0MIleFmRKFCZpYb0rP+q+Ta/7d+0T3uqBucTaq4YUTYKKuKk75u59HDRIk+08x2OQjSb7Ha1fGT5l7RHUR4tkHeicWQs4JlCMjYQkAsGiK43XH0QxEtKlk8LU52DbeB5KgvtSaQ2RKSwSCQJCAjD2oGlg57aE3NoABO+zC/iZ/cbxI04mSZ97ssK9mgk4SoZhEV/7vJLqaf744d00Ll/zkdo84fPtKQ06/sZssF/6de0kKF6dnv7dJJvF/AMhhO2OUp2JiGTd51FvmaN3xl2mO8bxW8h/d4MzvNobEtCLljC8CC32krU6XNnnmO8pHKjWMJ1KiG2U1MYLhBWXcJS40KllkUUL5X7ei/EWuTrYDJXmon7LG56KfKRNNgqvzIljuVlA+zjqo9/Po05idwawGP3OA0ux5dI37GR7qlezKsq7s=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxOTU4NTMxNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80NTRlZTc2MTg0ZjdkZjk0NWI3MDY5ZWNkOGU2NjZhOGZjMzhiNjQwYWJiZTNhY2NiODc5NWMxYjhhYTY4NjU0IgogICAgfQogIH0KfQ==" + }, + "\"": { + "signature": "s26DpLuRn3fDhOvZZqhUBUIdIPgyN/E/symXX7yH8DBLnBHfMCaUIu+e7zEwGMYgSG6YObFBY7vD2iJfXy82GuU2XIJjFgqn5Tf1kSefAO6/cclxReSg9fqyD5ujW2aNFklAJF1iKQVCuyXdsNNJeEzjdwqBqAfO8j36HQyFQyE36/Mrg/rcyPOEbc4ndW82n6RjEg6+XY0j9cp1RsNjV7r12nqiRtjyrQUY+DHgb78OfAZ6vgkems27y1OAnZ/B0Cx1UXip2IS0/E3fFVLnjtD1puCMRa0SbT6cmW4ojMQLrozwECaB8ejIU6zZJI+Hi50zMcPHZ0f21GPLKRrrrXsCtTpB+ys7KS9SLh3SWb1y/3etipOfunnxilP/MzPEAMzU6FMjpeLmoTkf2TAlsuMz1stSyjufBvOt0ns31LdOB8J09Rw9eICyS7OO8Z8rOp33ePhMT2s834SYMDY9y8iMTmxkLc+ATNViheRMvs15YSSIIbpK2HLr14e5OE7vzCGsGl4mwAjqgGuiLi5CezmFNu80+35hrodKIjW+oJbsk0fWddl9A1Lw0nZupZX44YnNIjQXXXosuUQPXiLms50lUWbI4yBrl38gv6+l0JYgU6qaMo6zVimB0qS9m60H5GtpXreq249pOB+yaEtLKRPVmAk/WMBL2ueQlA8nNXs=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxOTk5MjM5OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xMzllMDFkZDkyNGQ2OGU1N2MxZGYyZmY1ZWFhYzVlZjAwY2YzYTMxYmJiYzdjODc2ZTAzNzg2ZjAyNTE1N2Q3IgogICAgfQogIH0KfQ==" + }, + "#": { + "signature": "ajF5ihfYsacZrFVZHY2dZOxTpntrJ4ZQMk+3yxDzll3yFo+McB/+ActhvjuDiEMNFr8rmpSjfhl/VnFB3Bt0Zc7+qB8GMo2FFNtaMdGjCCUnEixNQdxIWZaEnePOTLfYshhfFz7iz2+/I5eGGbyQfUXdNAsIhO+SyPGBj/UlM5EwRjeZq9GNx5FfRh5lmKh2A6eYG5wPBlJNBoBxB16kdgIiVkiPFm8a5hfSCTcZCcnXm22kK7elVxxZDrKCTEq/gwzw5/4XibB4ZdJkTWFcfd5bRqBuVmA5nsD1Ng0gbxZ1KPWmIv22OajcHPpnJRHk1LhobHXDUAFSTS8OjlTVVAE7fTjDIFXfXMBJZntKsPlIIzotuAgKOol+ydISN4+nIjZEzqCiz5TdDVxJA5RL9RfKAQnBG2CzOMpm8LVcq3s+Pnhmyyv6jTFtsRf6WeK9MdBYwTIh/Y/1YKiWDhRrAKKDoFqjio5NUY8YeCrA8Rv1Je8S6N4aVpsRSBpr9MvJSB7LBnSnpv2GDPtFQC2DBSaVDKNeLTxMO1lkChiLo+UGysplxXi0L5N8TuNd2s+8JkvxqnGVVQ3Y0ivPt01+QT305Qkwx4b6SSA5ck/bcuSSDlt0SyX/UpQTXvXCqadC8sHqqUlaHatGPPv/A+AXQNTMSUqc1GYKJ8uEEpkA7Jk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxOTg4MTI2MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mOTNmNGE4N2UzYzBkYmE4ZWZlMmMwNTYyMDcwOGRhM2Q4MjdhZDM2MjQ3YTQ2OWExNjgzYzkzYmUwZDhjODc3IgogICAgfQogIH0KfQ==" + }, + "$": { + "signature": "ZWXFl9ZBoda0CMcPgT0YwUZEbN8l3RVckQPdXRpLJ3URgAvjY9Bkw+nW53IcP1ikgT96M9KxZDnBRe5WEJJjMpLzPD1HxDqYEMImFa6YAfoz+s+LuodbnPcL170RjLFD2L4Q/DjXWg2Byj9tyiEs1Cu9vT/Ng+b7FayB1JanW7DGg/t9tzN5PSveJ3nYQ/k9j3/6lD473bvW5qOe1QAnipRaHwgW2FDG03i10oDpHb8YuVS0SlacU+t57A5ZtUBQrgAFiI9bLloKrvp26wPNwtKBKljG8ytdz+WHuqQrEEWLqCvdDRm6CDK3UjwKgomLxWsYpdffHPGbFMKyV50QQdQM74Ut55SIWjb9dLx6nPLOzzOejcZ/AAM7FnT8ErQwIdww+0IGFsNNM6H3O8mt/3G8Y0itB8Ys3yl0O9hOm/s1hqL4Z4fFLD0QcLpG21t+z4X6yTRCyPaESstp9BhEep2BOLggMWi6ErfYWfrP5pS3Tr4khImxQdkBVWCkc16hHV63DL1634HUvJyA8MRUClKxgFbMRoPUxrlr/2jv5dw2wPOcLyaIvAo6MGpPmc6kgEGpIvVKOBRRWIe8X25fbAH7JulF4Dv965kutUP+axZXq/uogXQG1u+XP28vx5DP+GCWE7A7nrzF0gAm7yWeE3I+xkpmETzsuE/WYmJSWyY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxODMyODE2OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mOGY4MDI3ZjVmOTMwOGI3YTI0ZmJkODgwZjA0MDgyNDg5ZTBlZTJmZjY2MGRiMTRhMzI4NWYwOTdlYzliNDQ0IgogICAgfQogIH0KfQ==" + }, + "%": { + "signature": "SgXcLzHFyJHnuQ9zqoMN+CA5JgVGjqtqPs6QJVQbjmLsA82SCZ+504ye7Of6FiRlOhPwNy/HwzEhcZ+8rphhvTwbENhdYn1Wz8TL0VZpLGO3eGqcXnzSR8/8w2XDsYiLJaFPyPxkm+B7PMRFAO1WBTQcIhYSypzfzaRi0Doc8XuG6th8b/1XqCwJpjFsE7RqpeoVD43N62aYlXqdmXmgufcpJyIbccxwZk51TKgl7ZgkBvdtHiHYYxLHwutv+SfCetQyGyqESwRazQQM/11+os2sZhgmg7hhm9oa6GDf+jvnf5lJhrxMRXa4H5MOtq3iMyYpeECcJkGNg90aQXDiPDdHcSkBY3ETrsp08saO5nEvzrFTHlgSzH0b80c3bdsaPqA/Jp45G7l6Yd+aLLURL4tcnJ492QGOQLalOYY2+4ZWqalY7zvDgeml4yD7czNoXbKPWnrn1LH2qaEbsRvtXpTh16zwUXCYgSpYG0f9KGzAcDNUwIfdJm+wx7Y80eKhF+SbhumBadMXd6RAVceEfeKXDMWpjz2chPpQuiO9yMAsKKNmKV7kQyZwgQAmeFO6o6O/GHeycUqLh/QJ5wqa4iJAoS778+5zSez5vYHX1E+yoYkpeCG//rXJyU5YBSiTFkXb/WiogIXlKuPKWICdFCSZSLhOnnl3Iiuy/5TV0nI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxODM2NDkwNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85NmQzN2I4NjY0M2QwMzc0ZmFiNzY3YzlkODU1YTcwMzE4ZDI1MzEzYjJlNzEzYWJhZGRkOTU0OGExMmI1ZTEiCiAgICB9CiAgfQp9" + }, + "&": { + "signature": "mmvZzo5KRA8YDdcn4YDhAZwUZhs7j6b2oM10lnq7ORP7lmiLUxnWT1ClSgq4S0o4NJwnVItxFPu94eduaeVeN0CDySY/itB1+daRu/tUFkcrR6ewOU8mlDggYJ48f1OJzaC1c/MPQNTO4Bw+LgCcN4XNPaSumhtj1C1/BcZE+HeYXFUcbPCV0oSTa+2hTpqllDeF9zYl9Te1dIdOasDQScuztkFI2i0a+HXBoYBJV+iHR816zBUajaGG7c5NDQHtFEDe80wwsxZ6iT7x0oC3kUNFgLDMdrYfFn8EVN3dam9JsTqSz2+2DpE181Ihn8R33+pBqg9LGbmIm2Mf1UpqWoPsNeDyPyiPsLBcsBXzj0Z+BDoFQ54frkZ/s67FOX918KjLPf74sufr+CuEO0ukAYjaN+7QfVVCqa3pOuJ2Tls4ifD8MXn/JWGZYPTHlHomxuizi+Z/QyPqvxatlmgL1AqwiQM2mNTCcueNK6n3N+X5FwoJUw06y/3S7elTq1PJa9RTDgQWFldEsrcXEIkWCPCX4nNOSyIWgnwGuaPmn5vTcvlu2YARyid3KyAA0cxfaGji5lGqWeyx4mJfs6s2OFKx/DFj2jAP+Z9U4hTCCY52fo5e7n7Ri53HM466VsRyDDQcc5Bzj+gAL9sRjTRnDqOvEfbJ9cEyMYsoH1ugXeI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxOTgwNzUwNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yOWM0MTVhN2I3NmJmMTIzZGU2YmE5ZjM5OTlmMjg3ODljZmM1ZjJmODZiMTBmMTc0ODJkMmU4OTM2ODc1NjI5IgogICAgfQogIH0KfQ==" + }, + "'": { + "signature": "Y+CygyaiNu0n6Gb0faXLpl18Ilj8XnoHN4Pbpl1Qa+W45hpi666GTNiWw0G99tNScoOGw7jWLHnGkxzn9XOtJNNl/GLHtLXx+hFFRFLHwdSrsh1qu8ZdDh2JNLSnC3/1EjKcW77OWi8LjMp7HmaGUU8sTpVBy2OLJfV358bbjhNN7FHvI0/o63F14i0UTyGLnJri0XRPqawWaPh3ShcsyFhL3TUGnIM+81veg5YcmFYgyFx3x71LfIakCeNd9WH+DI72i+/GFFapofm1LpnsPT+y3Y9PfSItsW0xHNB4F5yHXWdFZjB4H91U1BMyEUWcuGIGJSPeZBEs/gxNHz76Y8BRIjXlur65fdCCab1NcVWbQW1FcjgSnrT4QJY426Hg9opXrm3G/FIyoARIt7UQv0wf+RDJxhEnPAaWkE5SPaW23CNSRJgT0hnNDi14XagNoQ75XNgiCYGRJi4P/IdfYMid3kPITcDp+eWu/cPpNYJhliHA6R3Nz1iA0LmrlyDowlHx+FO2WVHgRhc5Dh/O8N72JcN/3R0ivQxQrd9uFQuK73UWpwVyjKPd48UV92PdVFE8aGyPNqaSm+VqqEElqkMNGAbBQnGDdFp+QzusnigYeU12NMa6hvinc0KVaYhLJM4FihhwJlR20SUwk5TMiAySZGWWikQC6zt+x6Cy5k0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxODQwMjQ2MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jNTAzZDc1ZDI2ODNmN2EzYjU2OTA0MzQyYmRkOGVhNTRlMzljYWQzNmRmOWIwNmQ5NTk4ZTRmNjQ5MzM1ZjciCiAgICB9CiAgfQp9" + }, + "(": { + "signature": "OQxl03vAN5R2w3NqwA/GeABdWwRwmAtoMwArAnMSjh7ky4BKGSGcupo/54cqcWkqLrd+imOnHDETc7MhvaHN4//fSmWDpP9tkKfaF5pz27pnvuMWO1yHGv6SqUB5dL/iXBr98zHUhA7Lr/a8gTW+H1eciPP8dtAv7fcC1Ab701wmkM15CGA2xWP68v2qh+5d9DqD2eB+T8f9YadFvX7fLdHlYxKtsWX2CHTermJv7VtjHqfj95uTGa1UlqNI7zwTYhsc3sumRbuOXSA6yyt/alai6XNkbLYeNUd/71+ITGlv3zNg44thioFNZaXfOfi4o+XGaMLmR92JdHZAvFcoakk8uKbwRO94vjqR+axTsY8Sy6BWiGIgt90umcFekqB4SXOn1uOHxjFxH+d515/NHyk0lLIlyAl1J568Fyl8clL6EKOpUUIug97lI3HRdZSO98qDcjCvB0FtzMysKKEAPHi7W7Jt7b+XQGDqqht1u2kUf7IkTafJYnpvCAdF6aKpznErSIA3jmRjT5IiCCYk+z/FZXtezSoRX6a5tEb0KwLIYEujA+lqNB+kR6B/Ll4LX5gc7KVarHM+xnYdvbAmK7jGy8Gybs4EYaq70y1gkPG4Z8CW0CeBJyijlBjm7FxBBsczf1jv0Lmh+2KyY0ndH3t5BFHd2xRYURYtjW2pq9E=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxODg0NjQ2NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84ODdiNmE5NGNkOWFjMDNlZWRkMTg0MDE1NTU2ZWIzNzYxZDRkYzQxOTg2YWVhOTRlMzNiMjRlY2E0MTgyNDg1IgogICAgfQogIH0KfQ==" + }, + ")": { + "signature": "HbkxVS38p59UoG4aNAxxZQPXUhTa8yUw2N8B0gGT2muzvvPJ9kfTx5dgMnKqAGJer/MT0HHc9aFs8pZHhpeHhFdAmi88myhLCGknW65LoAx3Umf3AFCjFZP4AItkcWGx7CyW6UThAeQ0Y0uP0c/9MCHqiPsAFr2bi2MD6BBuVzgihJaEZC6J/CIvEx9LvNVdlgOUkBWCJikZgkzZsuJdtGGK5XcxDvlgRoZ/tkFTSd8EKg6cABubxwiAbr2h0bFCgneQID2ILBZWSdYMSzQxCgXlvXONocRMq/d6c4qmAca3qqb602URSqbzB2ht/hWYFzsWUHR7KJ2qdTcx/DU80JmDI3e3DDHW3e5g5xXULLFQ/vCpRD7Iq9D+APTiI08DUXEG+/SOXBiOI9O1eX7LfONZXV9Vqgdsqf4GuKy4EqY234QNVFJgf0+mN40UrafltiV1LmgFkXtCNFCQDRFkB+F3bcdNwCcnwob24zy42cBVgxyU2Tr3zlZVhD5yh1AAjZLe/AK0hf0Y57iK1VGqbtE8eJYlpnLqPY+aW8yqmlVEXCxmkPqVRTFVJMq4LDqjuAFyEzOh+JwRWVtNoyMP869u/KCWsnnqGc9OmfxY8ndWE8BUL2vUPgqgicxEJ3d6lpeFejHtBR9UiF23DwvOcyCmfh45CtXiQNGyDfrIJ6E=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxODI1NDQ2MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83MGRlMGZhMmMwYmM2NDk3NWVlODI0NzM3ODViMzU2YTg4ODU1NWE4NGIzOGNlN2MzZDM3ODM1YTZmZDE5OWI1IgogICAgfQogIH0KfQ==" + }, + "*": { + "signature": "wOEuZoRqUVdP9zVNs6UuVok2bLy/KLV/0C5TI3aUvANHot+F5n+H10dfH4+BuDnOk8E35gD6BHSWg+W8aizi42YcHESxhAZ5IUIpJDIEeeSi1NBMpfhTFQbpMjZHrfw5iGw1ZuPk3dUJICaxzO7OZz6CUX7zwr3OpKYB2eIDrE/0NNlgf4sQkYKq4IP4aQV2lsuWWoNQxpS0XH7fiPoGOiG/pWmlSrc5Yxy4buv95Rz26BhS6Fx/SpK8xiKKwvTFpZ4RqIKX7qqM64VqwIW6HANVGGv/IuwsuFBHwFlcQnJxFFFFfTrJTxmcIlNEid+v+eiaNvOl/O9zvL/M9JQkri8GGKhWqCoKDGTGwJ2LAmxpr03V5+8KHyF2VAuh0rhGlqtyb/JkVLhiZ5jbxBSd5tVyix31NEiDlrvVp8W+LjmLJWZwgVvqNpIL8P0Ba2EA3ze6C6YPd6CLAiM8r4bTpQTK9z2Lffor5VSOzXSYBBLyulwDdLr+QJ8ZDfq/MczsEymkUJil/VLs1sqLluIPKte1EV09Rt0Ic/SV2ujr7CbdQyUPRyHwxk2a4TEGJtlQ++tAHylhf0Dz0Pi2BiiQZVZLjSuF6RanvBOBCZb6Xcj4fAFaWaOqqH/in9hKdalJb73f0SFcCQyKY67MfcyrTNjK3ByRdmfIJ6rVsT6Rh+M=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxNzgwNzYxMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yNmIwYmU2OTI3YWU5NmE5YTJhOGQ4ODA4MjhlNzU0MzIwZDhjNGE0ZjMwNzYyMDIzNjgzNDQ5M2ZmN2M0ODdjIgogICAgfQogIH0KfQ==" + }, + "+": { + "signature": "UnDc98M+8LhfuzNCYjibwJkEMyMPT6D2Zw//SrjsUj1+0rUmcojoyAHqurRiyQ3uOho5fWAGcsrJPp+nJmjjBpZZH52xFVjhBiZBy9eQguqSTpjTqziH7lHFF+MWRt5DjEC3vXbIXHUwyNnHPRFOsolWIcrsWsr2AObC9ynqKWmL9i9VZsa0RudPkSveDf57W0xg3MdEHxk1I9i1Rcf76yRyhHXpK5i+z8wNGmTyPe0ocpcRpFccm+d2wWwqaYje0tXicHgljTTs70r0kG+uqJvRY7f+G13ApXjogpmokiQ+v0Hog8FuRHrpYfYZhfEs06QM9Si7zGVBwiN+SJr7xJWty2FccdbGpqc2/4PPK3M+t+7gAJgm8dO+LyveWJLFR/zl7/SUwMbSE9d/CHmSc3vHcdqFlHjf6n3ElLkkcvAFhe54JEOLcLB4miPOGOgr+4ef+IkulU/EiRbWanjs4lq9BbSHN0jg4SvFZoNmkxqCuLmi1ZluwX8kCnmPoCHws9vRQZLJdfuQ+bXRXY5EE2VxVT7xXTXGJ8l3JD0KK6JvuyaBTHwFFDxzZyPT4mUw2UK2w65xqNLWJVxBryxx1HH7wsDcKEWgSdGjSbBtMDIJxhWkeB+yvptZOMVES0o27wrGHR40V8R+1QfocCeA531IGzXYRZQLJikTM6KmT0g=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxOTQzNzQ4MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zNjJkOGE5YjJmMWNjNjJkYmEzYmQyMDY2NzllMDk0NjI1NGQyNTA2NTg1ZWI5MjA2NjQyYTBiMDM3Zjg4ZWUxIgogICAgfQogIH0KfQ==" + }, + ",": { + "signature": "HJIblTH8V3Fn0JB/Smhtc0yNW1kSS13eZnH6Fs4kYk0ByLH+F19fqsmh2hQG14sazlXJOgDB6OjqcJjJjb1nz4VwMsqgRIOtpU8SlYKjO502LDmcL+0Wy8PPiESMxfMMr9rXDyzbjgxR142JKtZB15V4ZaXkldtPNYWjoglBZMun70M1Wvg6bQSEsk79I5HoS5DU48SUv7KYbedkxkwxKrzc7zPHn5ODVThOXgs5FMTacOYA5q2ErJhNeJujOgpKCnf61W5W9ndEx33imhxTJfMy4I5pqvv4EuAApknTK/63jfC0uWtRxxmJSwfOGi0q+TsZXdNHohIWQxaDLbx3XRCGkiSoc6KSTxZbGHU+wgS1WDss3W0sXmq8Gf84Kf8w7MRAcfeLNiQcf0xmQZoi++TqfZ4i3bvPdBoGkz0ZIAofZNyPYYPeopWNwEXqZoeFcXEIliHXL80YJ1IHYKn1Bb5oxroi/XAIcOvEJ1vJZVWC14slEds6BozOtCCBOUeeCKL1QoM6ddjkEokyX7JnUsApaheC/VUXb7yBeqgCV2GMotAH/AmY1oTasdQq0mkALf19CQIbcK3cGXHjPNn1WUu5kuBjwpt8q5s44AyGz0IQgtYs7+5Ppqvyu1FrKUCXwhCbUO9deiLT1Ocotdq0PHP/usdDMzVytbtDEGnBsEE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxOTI5MDIxNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mZjE5ZDk2MWVkYjNmYWE2MjQ1YjBjMTc1NmI2MzU2ZjUyNzU1NTMyNDk2NDgxZDljZjkwMTVkOTJkODhkZTFkIgogICAgfQogIH0KfQ==" + }, + "-": { + "signature": "vn6z1Yeb1rlVKg7A6PjT7Y/j0yjRm+DHN3jtv7fSvbMNpajod7fI1GuOeGkLvwdXJbRtYXMMJXSrovRzIxMzJnN/zrqyER4YozNeQVE/Gem+Yzd4Jbkw423ieh6zh60exkIFOg7f4tM9fEcQfvg/UsUyho5NSp4bqsYeYmKb6xgHv0RNVFTCvM3yfK5IZt1Vgsv8Z/pk3ZErXdpz1F0w8enhIGs0D09BPfNooPc+9KoeHf4V/9nYQqbpoQmePn7XJGhHboCGEmOg5kYWKq5dvZxVgyBpOnkG+z0kQH2ArFDhrX2rQb82BxGgmBoCTzyRC5d7TJsuJ7UkLfeJHe8wDM3e+KAo9HTVkU0M4CecxwXLJNlIfFLHMrIdEAvV6biwGhGena3pKQEK3jyvDrHQmgHOC02fP2orjte9SjogWityID4AimG1M3a9fxIzlYXamtwk7lKjHV5BLkq1lmxwGkg1Gi3NVULP1vDi9D8twwMRHJzWDzdO3TLVcwCrddAhpIqNNNF+ahMPHRTAdVsDIqkMeiHTe239bcQimkNYcECTtLrXOPXK/vuCBRD6PCi8HPAJY+qjr15Qw/pfp8TNNtbrlXK9bT05TgwFbyUt/9S8O9D7uBilQjXCLrOKs3mjjdA3fGsN66q+hovIah/PL6tBq3ipjpEa5uiU3AfCuRg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxODQ3NjU5NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82MzNhNTA0ZGViZjE1ZDAxODAyMzhmY2UzOTc1N2I4NGRkMjdmZDYzMGU0NDFhYTc5NGJiYTU2MjYwODk0ZjZlIgogICAgfQogIH0KfQ==" + }, + ".": { + "signature": "mc1kOwbfuuDF0a/V0C/bCGAFbrhP7R4qO8Rhm4OLmJ/k42DaPcrN/Ir/jLWIRxNwsYDornJSKOD7padLex49jLCZFEvrhrHpLsveulbD/FcPYfcKmg2H41QZEsdUQWfAXycER5EXJ0ViBWxSRoQlaoI4QdmWxa+7OFLzfJXrr6gGPfk4O0YEw/wGClDYcvD2to+/gket5sA+O0NMtEa4KaWxJ0Za9hg5/UP3WsXASki7YFM+0p7MLh/ihNDKA+B9K3YsNnrolmU/oHwx50ejHjqVWDyqfQupyXax19eA8HZR6uYOTAIGTHHMARnc7tbu3IKdWD5Fm9Z1xl7a6HIns9Se2rgXrWSvKBpwwH1NKuNLXbEjMuxv8WYFSzKOKSW5YnH6j8UccAmCTDRQU73/NjdeyDcPIhafFlTwn/vm/zBmnfnasTmK/qG6ICz3gQI63sbzFGc1J7inxRl+60lll5uax54OLnkPUjH6Fr4J5MuMlQndv+m0X+OMhS1Ld1pPDTPKMSWwIBq8EzpIxTkTgUhY53FITcwOsQrNMS/k2hTS/i5zl+EOCllcQkCLSaeiGVDIUi8ssRItytvIB+9KRyrCBjCoh740qEHLLbG3yXgDjZ3zh1/RMXoNpSec3OvIDY08QtMNMkvJpFACA/rA7Laqy4tETsyt3W42IkX/Zas=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxODgwODk1OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mZjE5ZDk2MWVkYjNmYWE2MjQ1YjBjMTc1NmI2MzU2ZjUyNzU1NTMyNDk2NDgxZDljZjkwMTVkOTJkODhkZTFkIgogICAgfQogIH0KfQ==" + }, + "/": { + "signature": "NUZbUJgYOIMfToksQD5grvZOs+nv71Kxevjlbg1Lv1RwYbcr4ErGWD9oPa5kjndtdJk0ATbSogIdg+usAkCbg+QbiPdPUOmJEEUygQc4WP/+D5TNw1b43bkzzsk9qQ8eb8eXekzP0Ltwb/iQ7QHz8ocnsbleE0OHiy1588MdJa7QS5TOI6Gnoucq6wSew7hIHiBwLP6fwYlh+OPvmqU7XcVtr1HxF32wJ/8EFLtNHtsq+i9DNZT+9HOk3fBpqtjU6mt07/+SGwBmgutdjItvFjpymofOtLSeKLgWJTVd0JR92sD/KQy0xFNdibcySe0dyoW0wYvBhpfIhQMSH01iDxpUHspcOu8j1jVQqx3SNmizr8gPHobs1AIriSmrcDL0n2kdSskwLLglmNIjlAABYo48DC7kk9D6VuLKaz/PZvb02JtqAqzfcBuo/qMCPI2xU0xMEvZMmlaVtH5r1BxOmm6GRkD5AehbwqXOCWlP9if2nQt78Ax83ZYZ7Xngtv/IIPkFmG93EzaC8SED26TC6I87YMfbd3Q9V3E+zh4o1VQBIDnLoWIxApTLfeW1H6p5JPqQ6eTnOnBSWsMCaoqPbQBVrYyfJqmEPKlFxBFiI8fq0dshJ4XQ7JM9jyRl8/0l/7b4AGF99WigkZ81a9d5JaiL+6hHmk0VzIouWaaS4Dw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxODEwNjQ5MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84MjJjYTlhNDk4M2E4MThiNjRlYTI1YTA3OTcwZGE0ZWI5MjI0OTM1ZTA2MWE1OTMwMGY3N2U4NDdlZmQ5MTFjIgogICAgfQogIH0KfQ==" + }, + "0": { + "signature": "ijAz0OSe2t568/R0+eIUQlsg/sCJM3qrkdOfaB9F7P7l9y3VrbNzXOEQF8NmmAWJ5F169WFdiXzhFNHzAsYjKNFsfs2N3oY9fqp1Faikc7qtsywB6ip0+IRplxa+rA/hDlF1ekpUnYx43hf2lCh2UhH2UqTqy8ABo6voYNYB/qdo0gKVkLpgPx5USwuJw8RAG0ijXPuuBaPEtgcVEsX5ENQaSfqS76xhkLd0hjBVH1joQZVNYvpMxScmzcaPV1k38SNnIOLr9WeynQ3hbxxBlw/L6JZL6jzfeqPDam3tjUE9CJqTcRa5BcL/qp3H3JeudIwYD0WHLfzZ+hHsHgac+BJtfvk9PYWPqOGatCm20ZQPv+23N7lG25nU8g0wp0KAwaMBoN33YYrMFJQ3RqciIORpoL3dqgINUPLwPhjScgP593PkOjP39c3TT4BULtkR8+8Rc9rT3msZzQjxooYDXVNsu6wfPzoS2Zo2cdboRN0KOPb74aPKJYbLXYrhCOyK4BFxP4TIpfA5IjamdYqR5WxHTP7Ev4vyzGGHI3szb6gZUtwd2mNCVfhXZPmDWckPKUfKvdjqf/MqK8ztdkkhYRaB0sTd9AgS/Rlgl00PXwhKkVmLCBJZj3EXiSGduMfEw5IVVA6pVyVYqnEyhJIflktevFRBi5kBTBehCc7I7Ng=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxNzg0NDk2MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zMWRhOGRhN2IwYjU3ODIxMGUxN2I1YjZmY2E0NjJlZTAwZmEwMGVhZDU3NTYzNzY2Nzc3NDY5MTkyYjQyNmUiCiAgICB9CiAgfQp9" + }, + "1": { + "signature": "hi6cyVzy+owSDJD5EZnxp+dqWRyxyn8keqYFL24QY5wSxtNYALPLCClqjz8forEIecZDN39N9+5iL/6/giEs0Cvr6h4V7HxU5JXnXAHGRYnEbDd299bkagIfomxJhe6luUAlPurk0AiZ9D/h4NLwjwsYoVCq7JNQEf+SFcYQtczcs3jVp2U03B4wOws5vQIrhF8MPAmRJRzN77aIdmk+/J27fL23h41hoFTyvyHCRVdrFZfiPypG+UgO+z9Rq+qWqZiOW1mQlbc3Pip1xkB6Czb+uoST4v5w/9Xr2w1BrkBWLEHFhGzb81HHsOYLlwHbE3x554NH/Ju+BjbDRxgAJuX2n7Uql/J76K+f4SEL00B65Jv2eQDzWV92gQtDrlHPC74od9v7QVWxsYrj/sSTq2+A9rfIXV3L0DhJX1BzcAvuEnvK3VwCQdaHm82FTAJrlt1NlKIqtAYarCcPKu0SstCwka8FMCxZ1EKzUK1qUo/VDgZ4Oa7uz8cvK3iyyT2O7PwBhjg9LGQk2cUaBkK8KO0ARyq/y6+qcqhc/gOMHJuZ7rTqm5hVxqI51yvFSamXS2feMAbiceSZDCAvjg0QztPnD5XJDym8JB35w18/lU5mfNZ4U4+ulSEXN6KvlUqgxPTLuoDr+iKngFbXjKLusj+kWm3V7rg2TaeYo9stBxY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMDI4NjQwNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83NmMwNjIwY2VjN2I2ZmE1NjdkNmMxNzIwNjY2NmEyOTZjY2QxMGJjOTZlM2ZlMzU2NWU4ZGYxYzhmZjkyZDY1IgogICAgfQogIH0KfQ==" + }, + "2": { + "signature": "mPRGk2w9JMlQP32QdC45bLghbZmoD24795HBZaQ3Z1dtGyWToU9PG9gG80AD88/XrKgB3grqGbBln5WTVqoOp1aVLlDg4lCKjCeHjsW2nkJJsP54t9ZlM0MICAkIaOQwjmWeIXeys9GUVOXLbQ9tGT/JhMZl4cVaP2SxZDbQKPTC+513qeGEfRhERcRsQi5cv7eCn6IoYvDkS+xz0wn7krqfMbITagYyctGdXiKQk7kF/EYCPI9rve1WrFiw8MawI+bEyMWT5PWa6UgpiKM5TUafkdmAZ33c6h22w8F0ch9NA2jDnKb0Wmb2IO8VAt5DNUvPKBllsCf24GpalA8LhMjfFAGUhfuoFMe9+9hHtDDlh4EMe3K/XNJRJmM3aSvcE9G8ocmHH+D60lu01cHbYwIeanHPgJlHJbcYIBF30m2uC/hE/5C2GkFSspnmNrHIcpoOPgbyM99uICOqmBOufmt0JIvgcLM3cwSw4k1iO12K4Myum2WAC8quEul9lv+IidXTLUH3MPJjGSo1J4+Z6zHSASUyQiGAl+QPHZpeXNvYwH69JRh8fPEZ645ta9AMsD+bPaSDlVG06tEYFqRMbdNwQCcNnb1Ro3FvCdxWelGqQA8CgXeI06JDp1gjlW2b5aOaQxil4REvbd083kMnkm2tDK6J9SUo7tVM+qXVseI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxOTUxMTY0MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zZGQ0OTU2NDJlYWRhYjBmOTE0Mzc4ZWVjZjRhZWYxNjE0NWQ1YmFhM2Y3ODg3Mjc1OTA4MjQ4YjQ4ODYzZDJhIgogICAgfQogIH0KfQ==" + }, + "3": { + "signature": "dYt2cT9tLgL/P8huh+PUttOgQKG69V/RCjzj4+DTtpe5CJxkiVGadUFxSg0v3rgVZo9Ql6MOtNljA0ChYomFxbKEy5kzdCuM7RCJZi5OJcFtASrT0GQtmjSeAGiKao+8H2z+tv1qFGxltijcmqiMzcqlwVkt1ATeCXvb9w7p8MAWa3AlSCUCP8o706mU4ndnrwZdx74qp9+u+2wiUw7vGi2h+/DdSr2WKNaPLFnzUPQXKDSYa7XXKsaXcuInyg7tvuCI/P8xl+qbHjDUKw4OAJJdrurKIwSRpyB3I80YXN2XZZLxhiWbOuC0WWFuUCxzjnOOnFsBm9J+X+tHEsPmdMLqpApfT1VOfAc7G3YPSK1GYadYjtus1uQWzzKoIEINCZKr4HAHgKSan6LkZIpKUwqGc9YFCgbVGPRd1W9v6PBe5ZH5LrcEheItC1EkAs5PYTWZHHycABstyN9hG9YiN8hoU8BDlnXrCPHDkOx7l348mOl0fTJFh1Y/QcZyFt4qC6XMFVR30ZPP91GtnSHeTr6IegZxTwlDRrdSqMmbDIg36rMg0YD4OZ1ncR3vTKxBoJb475b+bZrq3dGyy5LUK3qADUPO0UErFPBG69FF3PpGv/ZyqFoTaBFhSb0RH8pmxh5swtuJGYN2r02z2/egah0jDP6lDxaV2eM98abHQqU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxODczNDIxOCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hZjZmNzg3OTI1OTVjMWMyOTkwODZlOTY1M2M2ZDMwMDQ2MDVjNjJhYzc1NjI0ODI5MDdjNDg0YzU1NGVkNTRmIgogICAgfQogIH0KfQ==" + }, + "4": { + "signature": "WSXsmPyhtwkWgKdSsAbWTN0E3wNrHGsS19tI6WLAobiAiOBNDHawmf7ceWV3oX5tSZgi74GYGBRgMTTdQPmDmoQLmYSYRDv6EjOAYdrqexhUa3uR0FBTJrET64B2l9txrjkD+I7LtRPUOeetd8fEaVkSq9vrGlSac4WENZN3PZJKcbEU1V0g6lnpAlpf+BDktZU8q8YASg9q0g24X2MxZLIEea4pguHx10PSKym4GIM4LUxjTw+/vDHTA+CXFDeS7npP+Y/N10urrgDK87hlWFNkzue/wq5kEdpCKJyGzdF9Mxd6hSdDgCBgW0yuqOEfemSPSQV3vFwyP/dzoVpi8ZlxIQ3H/0JBy6Mb5/31flpCNsIl8ir6Y8ws+Zg1ljAkU0FCj6b97vXH6x5MlpydSZyXBAZWHVzl6CCuJ9DP1fMUBqL18FPRm8CHmN/od3vhtPWgdRx+PNKSo+/lDkyXVMMrU1AFDciJH3Xj2suG2FmOKw4lcao16eKHXK7GKjM99uS9X1FR2sZfhwJQRYB6/+LU6wNwe2z0YGLtabhBwpdjwaWTUOiMWIocPunsGLT30HoY5bn6/41CyGmB99mZmiUc09485MwbZN9AwXGaoa1EprdVsMrG2tVyKJvsmPM+AVrnk74VwARgqWY/X46xJY/T4tvzpfWMXy+7xg3B/LU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMDA2NTkxMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hYjU3NmYzOTgzNzZkYmQ3MzRjNTU3ZjQzODEzNDZkODdmNzliN2FiZDdmYWVkNzMzMDYzNjUyZWFkMzIyMmJkIgogICAgfQogIH0KfQ==" + }, + "5": { + "signature": "GSGWTIvuy7Wzkwzhidua3wuprV/yBt4H1DG93VpoLLT8zHr+lot/vch5EYeASScWiXM2nmxMiFHo5CR9MWs3XE084N0B6vBmMGo9AMhg5CwSX5mHFda1miFU44NlBnXVy0aGdEEoFQMcXFeR7ScQ0rGnj+uuFTp77YyISBZeclSr+sf+Ae4+reafPrT7iuOzwq72ljsnwu71z7kJyLXTNfrGQLaJHa8zY9j3j8VuPIEY1NA2Wms7QTUyvnc1vwoDnVY/fnavrJTeCpe4cqRtiDjv2Qtv1I27Keh2n7jNyw1edfwLZzzya5MInwmdRiTs9YyV3w84hXtAFp612YK6g51lGb7QDsVQUdh0mgStF4szTuKEJ6//FFoC0dAjTfw7qTNB+qZbNJUCVLhxKaXh/q5klgpVwW1bC+Ob5PPdbeOKQzKnYOPA6n4gHZD48auO6l7Ny0omR1c9bBWMEiXYRiY1MMxQfUe9nNljh5sCoSh0EjeUVXX+OsP8fcgN5i9doD3xnIbTJMMgLSqW2U0VqNCn17gxWI5HSsrV0jtA0nmq4+kgaOZbU2QFz04NUblk/8oV6vd4q7thfeu/ulmIhz3KYR0hAZU/6y22uUFsi0lrjtDPAfh7mecWnFjg5UYIY2MzLLCfpS0W7yZ6W1w6aq718iUwpaSShZgF67K4KEM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxOTQ3NDc5NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yMzMwYTU3NjE1NTFjMTg5ZmU0ZDdhMjhjNGQ2YTNlNjkxNDdlNzY0YWE4ZWU2NDU2ZDljNTkyZjRlZTgxMDRhIgogICAgfQogIH0KfQ==" + }, + "6": { + "signature": "a5nL36O/yI4ICg+InyFSpF871fr1kyGjuHocr5IehKIo/DM4OUs1pdZhxpnXJlkrGryeSM+3pEKbMXxuGIGPx+5pNwvDe8rYWSDQNwo0eLWugIeLWWI1EJxfh3m/gQIEBgQIhWdCQujc1PpDcQfwfnEUygf2y2WvzVzYGI+D5oqnlFYRRXvlGnJpTJH+rlfwWzShbHSJ5CEpVu6QiyyweZ93f9rhbZsyhMGRYYWw7SuvLlHb9soIrb1n82Ilwk+3vaOalBWgi0bXsRpAlC5zGlld6dNHnllcuShH8X6ZHSOIaZZtY8FQxpuA7fEwYsDO2FIQBkwykYtToWjT9HadODxwF8lh1qicik2YMCSkq7Ud2q60A2Ca/jH0STXAipxKupQ735nbQRodGtn/vNHA83qLgq/ZnRqR1q1DQLuyy856gVW17HJ9uQR3qQB4E394lVC8LbhflpPaQ/ccp45IOd+2iQwaRQBGp063ut3gNroQrVAGcl0s2TZHUh5UrvlKnlA8acnQdDeJxLax7MSEi13Erj0LfTguqGmfMKaYuU+/ovgT/QTr1E9iKKngLwAifHBHNoRbx6jBmMrrIdMzaSCT7lUp3NsRgIojOXV5VPSQ7IN/tpTht7O87LYzGUvJSHdgc7RY0+/+A4iQlf4q5oE2lm7LcGESrrR0iDAvKzU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMDEwMjY1MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85NTZmZjUwYjc3NDdhZDMzNmUzNzFlY2FhYWM4N2U0ZWI0ZjAyODk1MmQwODliYjY2NGQyNDU0NjUyMWY4ZjkwIgogICAgfQogIH0KfQ==" + }, + "7": { + "signature": "JswehBP4lu1SnUMWxWIqecx7oZVlaK+WfS+HU/456BwgMjfXOjVyhsOm6mKqtU+PKMKUOIaKY0l2NcGg/1qVJpnFg1Xz+hNCdSl0yZe2xzGmhllCh1PJzwZ3hHuc+Hx7R3C/CFlekTsFhKHJybu6BzzIVCx0pL7YuXedkTl3Wkkrcs5d9YYaoHo24S+h00FEeWoyOK8ClKkv6v4z/++5UhHSBPWZhja7C5muKwsCXmuZN/iF7fx047yjvQ/Q2+4wYA2JdLdiyBeKV5MCNHXqBzCArSHTOp8kfC9DxsxQWPsP8i3/ZzK8F0HGRRflUG2iErR5E35MFZzqPz7o0KnVE/u/FXegsiAmSIVTk0GDF9X6MghNHKTO60tFKQNX3K8kQttPns6A6WS6HMbJHkDSTgbsWL5Rn0RfzhiJVISoHaV1cGc4Wh65NaT8S++ouZ23pXQUVlY3azoM0xIIGXUKP4uabRAXCw8PLvSDYaLWdF4BUM5thqEMb7NMpXKE7VPeYMax6ARlbTU6hLTTK6ZDQsuEN8Kddcfj58KCgcPwU3N24lAG43DudlOvB96csVthUQ+zRyvSOVOaLO7nB6MSrSBlfdUjnu57pStDGuJnm8gxYE2yy+EuqAGjSVLdSox0fWUGGQZnX2ojuIXmpHq6uw4cSFdyn/9AvCUzSgTZk2M=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxODYyMzc3NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hMDVlNzU1YmNkY2VkMzZlYzUwNDcxYzIzYjQ0YWM5Mzg4YjM0NTM3ZjYzZmE4MmMwYWYxMzcwZDg0NGY0MmJkIgogICAgfQogIH0KfQ==" + }, + "8": { + "signature": "QTE4gHPP4C/axPWUGzpI89Rn4bNt1fAPXpn0zx5YVc2VSB0Yc3I4Hz4kmtUE0TR0hnZQ3hNiGWHyL0OOLXJej0gby6jgoslrBe8O7MQm4uobgWQTRg1/NP2/yPSq5qygfZzG8pFuaau0a/D6jE2BQNPzFvqZnYViDgmUwV9CZ7PEzmeJGiicTXE1Z2T9xI1G7d8GubcKlP3sBfOmeTwAdE3Y7E3afJ8No9dgC3AHZ7LvgMTQtCKIPkeLgXsUrta+AdJiuQUK5gvND51urT/4EVyG00gW30cqeokNd73mEXymU4yV+hnclqfm4GOhRGPwFl3T87PM2xL3uzuIh+ltNazTm9kE4yHTSYRLjuOonp5R4zoxcfKniL1ORxU/aFZ0yRxGoFxLCdStYU4hWxnWbG2ke7YsU5ZmbarQLyWbc4VqaLzaPwwrhmVlTtovJtT5pDXprGbfr/mrrgsVlTwmNfeULWYggoGuHU8qLUmqd2FC31pgliE+L5ajE2vJ28XdwHVIn1Po5UzbOUsiZ1DMYi5p6TElXLwzdyFsSIvxJFKq5SUwYJ5t2yBw88+L3pZa/ca6nKZyFeEUFfyJOd27YyLB609UdXyt09VsumWO1mRFMKubZydx2nndHpykDpl2g3HA1r5xlhPukkTrYb4V2fDGLl9DXe1grwJ03nXdmwI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxODk5NDUyMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iNjI2NjNkZjZiMzEzNWNjMTBkMDU5MWM3ZTM1YTQyOGYwZTg3YWVmYjgyMDczMWFhZTU4NjIzMjA3MmFjZWYzIgogICAgfQogIH0KfQ==" + }, + "9": { + "signature": "ccW2654CBP+ECegzQMyC5ORSBmd9kRjiv0YV3BNY3+60OZ8oapKnK2aLB2kG9HotlG6wFM2kmARCU2GrScow/yCh+BudlhT61tVMelfPV2SO3UOxMA/s8l9UHuJ98qPv5vGOjeOsG4j0gqpHxKBk/PcrHET2iGo5yJYe7CGzPHFRgiK/a0Ub7RyK4ZIZn2AfmKTm2SeLCKsHpLQ0sHg92xoGvLCFjwHlwKp5uTVZOTqrubjztcG764mtt+2jCRhrmEXg4i1qRCrOxCvyMed9mtXhhYpLagl7+G77mAfMeXQtf6IfsWpuReFAeV9CXW2yFuMtIqptwmQQyqUHSLnEYeDZbVBKAkjOasXAeFS91/jgz4/S5LV/LqZv74fJzjsPuTuC++XDJG4QD/Btu4OEmz1hfTA7GIA4jlg1Oxt2KImGBynFSlQ+Cm0fBSNehTGB908SSPCBlctzTH38bYRLBn2WqI3A8GB5T2AJfBgyzMbV6z8k9yktW5TsAhs5R2EAXRp7UJ2mD2kLiA1SpdM3/FpSHed/9Zyscpo7eGv83vaqADobLRkwzara+jM9wmuxfkmij9GF9FBGDYwEiA7pz8IOIykj8WQ4vbNXuAzVI7FOlwa5ijw2HwdI4htYR9VEBuiQwnX30RHLv5seMy9YkpGrA8fiXXaQ7YdC2IPkFGU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxOTczMzM2NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84ZmY5OTdhOTFiM2VkZGQ0MDNmNzFiMWQ0MjlmZjliYTk4OWVhYzFlM2NmYTE2NmZhZmJmOWU2NzRiNzRjNTllIgogICAgfQogIH0KfQ==" + }, + ";": { + "signature": "utVhoomx3g4PNx+QEldZ6PBI1nMOJzkvJCakqWFibJiBCXUujG8hMhKUHA1gtNkn10YQyIFC1TP/r+ewEljqXuH/IWTv9EWZDHXubFtqNVH25PVLax5wA0b06C8ixQ13ENepFmp+2BQbYBVs1bA5lmcnMOaTSR6Q5lewd3NhpwVaV+b8YuDNAxXpdrmcxEKeYN4MUWOZ5OB8SuMTL+wbCwz3HUzTlz55Ktq+vvPzsNIR+KcsyHaO5o3i2dYrlGgIuQI5qH7f13huA9sGhLtJcJGT4DbxeL5mu2dIbwJuIVyofHoLTT2b309HgvxX46AUxzWfLtzycaps4CCD8o/HMnhlnOsIoDqcE14V8C+OMRe/SY2HOmjbh+7sGDVG8ukY+dIXi6AGxFvbP3Ee5qmPlc9DeAHhg2jTqnNS25mchOGKyjWLYmQFtak7XUNXNokzVYOZKHIU0RwlHnBGF0esi7uBVifWHGQIJNJWiXFFjuK50gdr2Tp47vmQ6dTPy2/shUIkq57Vg8V+QkcegQuznHy4QI1khZUJR3TEXRgt48Ae+Na7FUSLxdkv8bXcYq7BHqiYSXhfCt44nXsZ+M4Igsg8sOh5qqMhPyyVCX/2/6HZ55wojjmlnhpoM/qfvP4I+83Uo+CEzgT1fFkay8LJMoppIh2roxMm/DF+d2djKKM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMDIxMjg2NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mNjAzYzFiN2ZhMGEwNDFlMTg2YjU4ZDRmOTY4YjA1ZjU3MTdjMWQ1NTFjMjA0NGViODZhYTc4NDkyNWYyNjU4IgogICAgfQogIH0KfQ==" + }, + "<": { + "signature": "mVYnIxXbkaWjiSw1E1izSgdNvwGiE4D3yS0cR1Y9BnLdss/5VEAtEfSKPhRETZ+7RvvQ3NA5YZAcrphcPzGkPwpyOsOfrKu+k6Y/JrUl53LyZ1UnZBoHk0kTe4cGOIh6Cx+xBOYkf5bLyirQuRR5o3e8G9pZduiOKba9FPRUN6K1uZoJq+NZANPbS+yjwoNXAXNNk5POZr16WYSUD+50VHg99NMBovzfJLj7cwRv27cdS8/qR2/26b/Uu3lPOtqUoIgaxjG8vnVjBFW2J+KAszoCgXTma/VbmSgnr6oOOSxufWS//dkx8IwG2651awgRuNDwfG+3Ixx3hfojM+IWiwp6b5xnAMDhi+WhbdRaWsntMM5WEUYE9q0GeLpAyevGd7iZ6InkpEOXvrO9i3nqWYfFrQ80ijxfVLe/MJqoIdnWcEUhkY5gCs7cCeokAAV2dcc1OBeGUjtOZn8FhHOFMPehz0mykpzy5vLEPPptprDP7OQmOb7s+AiBNCfmHgVKpKZKEn4aEvyUsNBxqnLvmW5T8spxQ5J9VxUdZentSW6huxWsLnRRjDimMhhU+buMyLorHsTp2+jcViCMsPkbU96wYKLMyjMfCcX8QIHe1G/0Q7P3QrNjmGbf2WEHPiQXZzp45goX9FNFCWcRETes1apV1EqvqVLYaORxbJruq2M=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxODUxMzMzNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iNmNmNWY1OGMxMzc0MzhhMjBhMzEyMTQ1NTZiMjIwMTAxODUxNzM3Yzk0MmZjZjkzMTRjNTdkNDkwOTBiY2ZhIgogICAgfQogIH0KfQ==" + }, + "=": { + "signature": "GrCAS9Thl4ZQZ5kYcvrtFP645OgBp3bfebfsHmJVkb1I3E1DatooWBtPirNA2u32Ls3Nd4X1vs/+NsNr33Y/DwNm49Eh6lGWsvc1LKZjDyHB+XUBtF8uMn2LRr4Era9jBe392iUI06356ySYcWUozn+EFoiRJAMEURJjoqxXK0RlZ038/uCsMPPETBJnBKlfkCtq/QjXyb5NhWYGBNukPOqCWx/gsPLayZxhjO6DvB5bCsn17zQYxvLDIzdR7fFsD54AGCL7ub6zTqejENkayDU2px+Uf9bGziXNB5AH00nWViCY+K3Uxd4DYn5yBdBVlg24rzupC+lTVZq52RxJ+Fiht5j6KpDkce+SreMZfHBERcq7N3Yh5usfGsWRjt/3RCAcd/3PNwB8EPQ0cNpjdq3SyGrn+mmRLLZMonNOJH/C3z4AdbmUhcJ8vgi3XQNC6QWuU6I1Ot2UsYe5H6Is0vYMZmsMeIcgFPgEzAddvBATfSwKPVZlKdqgo9VxloBw0tDJnC+Vx9ZDsNC3Bn0GWxSYlT6FtAzbFq8zMQnDzYLqE/GqDWRWKYRGg8fGVejt/LVfEIa63J/C69zfQf64IS+LlaFeazTO69Ffs6DjLgd685tyhyokHHQ/Vd8OFQHmy8k6VgNuZ1kx5SWtDIbjslZc3e5hxMsfAyrjRCFoFg8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxODk1NzUxNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80MDE1NDBiYTVhMjdlMDU2MzAyYzQyMDlkOTg0OGE3NjBmMzQwMDIwY2JmMzEyMjZlNzRiODkwMDhlNmQ1MGVjIgogICAgfQogIH0KfQ==" + }, + ">": { + "signature": "b2NLnBuu41LagoghWrkGdLJma002Ax1XL6TT/Oeh2GB8vtv6C56E8I5MBQ3mw35YYXxORR5Y70IgmL/gvgFbWlhTx7l6HUhQRXZJShXIxIOjPFdqr7Qz6ZaNQoiGNwm+G+wt9E10mdljwdwLAVp+rG+UN8M+43xhUTvG1oPZnjOtoPo94SkXWVq/OWs0/4Q0YfDn6e/CwI464xXELBP9o5JM+v0GojZ2+9It8FtYFFKy/PSFgrpbdbEvaQz9he1SIHB5gKjAZ3vXsb8wOOUnOuncHwkZ+XipUC5ITAYhDTnXBLVPcq1ZXLGpPNeln+nS/TVMplCaDweuCtAyT5ywQ4C7aFjnafcLlYy+93mLWmFK05Z6gwHbrocbNLL15oibfQkv7876KDB8dU80NAJ+Kvj6fRHrHjkpAfk2/VtrNYIkoCkmxwmcqpt6Jm41LAGF7hULtnCSBoIGY4W6gPBUDfvM4OsN2XPan87msOb1kBTD4Duu4UPFW8okcYuG4zxommkqqyh1qcHhJRtYZiGGVegnQume9jvsawtYZdj2YHWut5Y9t7M3ZB5lb9dp4K2w0s55d0RqB07iaN7KDmqpbYpf0sf3UKI8dGfnwptwfL66pSsMcIRrko11wx4KetFHaLnzAorA5A/cm8OS0MG6V3YC3wYLHCekQvAHkSufOmg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxOTMyNjkzOSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82MDg5MDk3NTgxN2VlMTE5YzI4MjQwMTkyYzljNDMwMGZjNjZjZDMwNTA2MGQyYmEzOWQ4MGFiYmY3OWEwNjNlIgogICAgfQogIH0KfQ==" + }, + "?": { + "signature": "pVfx5ODesxCPMZ5YrlcQ7V0sOmi9kIL/trwTSBAXh8rnxvMOH/bFfYcnxoOiJ1SOp8YnXv5QVqYsrY2AWCrkDEkITQP3Q0mJf1BEfuLLxqchquQIHpp6hXDIoVUNpeOVrNODXjmmRV4QjoIkx34wQZMsFjo1vyqWYYfSH7VyTkOvdkFrLEx6fX8y4t1++YceBccNVfexztIUWCrxg5Ezn+unWQpWP425J6T0IaEGbcqEoEI5F1ecP4kxCm8kAGTQKlJynayMRmemFGIoGZWo4OZEx+iCP5aDX3Mq3fuKxXs0ly5ui1AAcbb8zidQOQOr2dUX8Ap4WtMmk4RLg3C9rH57xuyyeX5o/HQ8dTe8Pk1OfwfSacn3a1IeUL27/l8c3aDNPB1xbiVZ+FoX1mW+IuuGd/S1+3mG6pzCdac7RjV0PCLYtNoPKESAG45b68G7trjR7AMchMAg9a0Ri2GIUOXG77xHJxMPyDVV+TLvWZ8yhThN4E7feEdbrIs+Q/rrZFKQbezULhRtmtWAEk9UX975LNBQ4EitPT+l4lS2nmuTfWv/xtIFW4YWGu/u1610gq4nijKqQPBsI3kbWPlh/99DBdLCSpeiOIjYkDDJYwSmBfCqIXQQNXyQgeEZVV/nScO0SxlaSDdAS176S3ytKhKREMVUlG2Ap5xuYxqw00A=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxOTkxODU4MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85ZDg1YzYxNDUwYTZmMjI5ZThkNTY5NmYyOTQyZDIyNmQyY2NlMjY1Nzc3NWQ3YzY3NjlmOTdiNGIxZmVjZWNkIgogICAgfQogIH0KfQ==" + }, + "@": { + "signature": "M0Dsgcu0dykqkEMj93x8x5XTRZiVjaQsUYclBhcR8OUu8jMBuGMt6PBPQogU8uaKj3m1JFzfGPprIRq2+fvB6XAiSkzCRJzs4mcqtxYSkCX0L5P+0qjDp+JgwEdEAzL3T9JSrAW7bcf9mgEd7XA57mwbNLphswp9zZOo6H0fplnzd65INl2W+USf0JKUkTIPP5Hk5xNNZJbiBc0RiW4xwcDBduG2zRjvobbj6aixV2wufZB9OnA8hdLgBuS8TV1qZlgCYbeD01I4q2ks5yLZPQz1Z2YgM0ynAmuVrdhnaIbV88eos5/dFBwGXOVqYyR/8F4FyTLOrHCkFG68nzimZ5SqYOw653cXoE4CoKFbErAfp4StwOjvlfj06U840jl/rzAlkOz10stMkTAQXhaHsV4KQvcJWbtS35e1pzBj72m/0samTUwLrHpcloB8HCw3hzlaDAkKxRO9v1D2xOFeTl9mofPrNhAamCFDw75ko5tir+qp1O/2tu3ciZsUYypJ49JXnlqh3h1EZWggc0rkNoDXb8hCIPQ/ZIlbVg6jjM1CdGr+oQgxJZ6d/SFBdj4S5LefUMohTOQDL4E6JHz/KISVJ50sEadgpdvYBO+QIh4+7rGxiUcVbPPYhkorb/1KQIwZBnPMF9F8O5sV6px6mzYolZjJ6HrW52RqhVmNldw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxODY5Nzg3MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yODcxZTYyNWE1MmEzNWNhZmNiYmFmOGM3MmU5YTVmYzk2YjgxZDQ5MGI3MGYyYjA3NTNhMmZlODlhOTQ3ZDA4IgogICAgfQogIH0KfQ==" + }, + "A": { + "signature": "IWrzmJ1ueBy/QJiMNFoNrHVLokRUUhVSqUNGgZ2hlG8Jpgjt/8C2oj0DJr3pHVgFxDFCexVCbqC+UJhOKx2Fzm6r/eomqErW8N1DBmccTsIkUnRwXijHXizcollKX9WJ4DILIbAlFmPxh8gJM0F+FtnYdP9kQSyf8RC670hLCKE2lE+w/DhlEqjQAij+GOz9FpXonhgBiuVUKiQnhdulNDARBTu/g5xv5b6PmBRoudxgfElN3KDxTRexSodjivQM+nm09HIq+KDRR67fANWpnhupPA2VtYo4nI/Qbl1i9URtN0wIMGu3mKlheGBVk+S5k065pixnT1loaAgnGmsQQmIWTNEZQ0UTmeiGd4dO3eLZ6qTX1BHEQUw7ndsuYFqLE+WXyGAQtMYj+tdQHz0dnqITLthQ6O9Qz4JSPNGYEn2TlUfZOkCDwC7787bIt0ipgVyaMFTDheXUFjf5/qtS0l/JFHzlGOQ3C6qlzCQoSkibTdNFsWdFpkwsXaGWTqR5cZRtElh1i9uiM4+091GYwmtgT4QZEuzawfp7WmWV2mMbteM0Vy84lKQnO9O/m2TsKnimpp1HzppNargT5P/qFZ7bjJZmLx7MkmekwEcK6Mvq1Yf+7oSE+H+7xYs2PhzBPU4hOTg6nneMvChF6AtYch9eW6cHwv4lQtAvZejC3d0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxNzk1Njc2MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iMzU2ZTdiODIyY2Q0YTJlNTEyMmMyMDcyNjY0YjUyNjI1MTFiYjUwNjUyYWUwNWZkNTNjMGI1ZmEwMDg2YTY5IgogICAgfQogIH0KfQ==" + }, + "B": { + "signature": "G5Ak5V8qvhGHmgMxkYTSwC1eErM4YMC+T7lEoWhz0uf7BVIp+F5E0nDHLJ5/9zeIRAZey76//Ba2gckt6CVlkByGjNfoeoIy6dBaA4O1qODKwpwvG3K3kTHvDn/TpaaVLsLyESOQcVu/bk7oV4woCf6xuPlYtECOwsqUlqXPwGhXZMf2y2QxZ63pJ5J2zo+MvrcH2HfI6+WJzmLW70gE3Iyn2GnERuMr/3HwMVYzzTDOmdVELGAI8kQmw6/H1ryjM0NFVfHwd+9Gs4cr98EWsv+t57gustsceh421LUb8e9mev9kJlsB1f7Ze9JGT93tos6/ZWY80cX2RAZhWD+v8e5Nd3X3X2vL3GtDDHSJO9id3oIw9XAvejVyMbHR984AHxeuhg1OEgFWxt+QE29oSF89dBnYi2fs3wG7tXgulN9syAlX02xWpvbHTXmzeQK+rsLaBTFsEBzqK386FZhrlW0QY+e1qni6vpnX6n7IGdUozPIi4I9Zv85U9l7U8AOFUaeBPpyAMPCEmv7vTeQu5sW5OAKbpGk80TY4r8j0DYvoCbBQFXVLhUshghlfvIp8NmsVyLB+0Jnig+xxctnsz6RB5oQh/29msXmW8qdKsI/GmSdTUpy9pN9ItM8rla7acBayKHAibdJZOTIqvtQuCrAGltW+xI7KRdOayhk4Y9U=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxNzg4MjQyMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81N2VmMzAwMDY3NTllZWJkNTgxY2IyN2E3NGZkMTA4OThkMTM1OGFhYTQ4YmYxMmY4ZGY2Y2YxOGEzNjhkOGU5IgogICAgfQogIH0KfQ==" + }, + "C": { + "signature": "mTfTY2H/LjbNp00fuL2PDYKkaD2LU4uBMge2o/1aNHYN5ZahZVqUY6euDEkhMBMP1Aw84n/LxxJs2bM6sD8Z+buDbR33+IdpCOQkA3CROZlh9BgWL0E2hdpTy6tm2QfQkcIWDlNCkjFm2XJrjABuxe5N/rbBjYFte3ZJ8MUHS2WA6GiR+V4wFrUut9JLKBqOyy7lnPrwIwPOo/9ylJ6bxqQQ6X6ANirw8Ap786vPJBy+gnj1ZXCtY9FRVCIVEprPSSF+e31Zlut8CkU8FgwBFWVp/6WL9AIWfB8m7faOrLnX3r3GbIPElSrwyFTddjMsVTstYItap1ZrGQWqduQvqHdLZ7xlVV6yG+fMEVsLHyGLotWvcz1leGfbMXmN9qcCOivg6L+HWVoSnmVO00Flosyps6V0IEdKJSM/B9AUTDlTL0Fa6kYQWxbuwx5QvXP1JuTFIi9yoEhn9Db10Y1/M7fOsV+Svk95Bm/MnAWHaZxmUPN4BD5626/h/V29UrW/nBCaMudHe+LY0u1hN9p4YwxDolaFFyHMSK6IsqFUCIk90O0Gl+brJ7h9FCl6lIX83NFwLgrvfiRpS9uN0sU+WlQVcVEZdTQVxiz+/Kok0upBFDHlRndGpTFQx1Q6uUlLswLP9uqxr1e8xvhBxNzfNYDid9FgXbgB/2XfcfiDuZc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxOTY5NjM3OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85MDhlYjk4NzNkNjY2ZTMxMWNjNWRhOTFmZmYxMGQ0Y2ZkNzhjZTI4OTJiODE1Zjk4MGI1YTk4MmRiNjcxMjM5IgogICAgfQogIH0KfQ==" + }, + "D": { + "signature": "xG+w9mbXZmgVo4kmdm1FDO2CNbjZIzMByYO8yC6d0GGACgkCvedPhitKzAicLQbnDQfaZOh6huCQ7pS2K/BXKPbtGfsp9IcPSQXNKWyd5Zj0xfRa95X530zmS1pNm1/tX52Cto7j/WNzLAnQRh6AL+H3cILh3n2S6QVxAf/r65NXwV/emjR+QTdkTkyG9yc7STLjVvnSbbQPsRb/sZyEPeRGvypmR7wdM2TXwPdK3PW8Sw2XoDUg7WurHVRnn2YJH2V7w/UxAnKrXG/byi9LT6P5/zz18Hf+98HXdBV7alQ4Zcb4UTepSb9IlTTAa6Uol6H5tfHzi8UHEhbBpkr/ozBF9KiPeVHP5sDiJLgqmnvDjCB3znElGSyX2C+8sHqhCMTK20ka2iS+ShJjxjyWlkPWFX5Xcyo3QA0xfLXQ3Vl8VPg3Pc5IDHOOTj5xOX+34lyou0GWPaYwyHN3b8Q+zZxP4TFNt4kuMe5iunJ/JR+iWfvTISq2LrOiC8VhRZKmfWRaeSlQwLGyTDDCK1aqj3o98WbryV2EUCJ+sYMiwnUwskvy2ydYmerXzu0+otwTkXJJe4f2vi3izNpCtRphwrcuWVL6t5x7ZUiA05wavlW1BNmx+wUFPOnIM3WhuWxPw4H3Hdg8CpJ8/9J+ztnxE488AtJEUONEzgAizDoCh10=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxOTI1MzE1OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yOTAxNzM0MjU1Zjc3M2M4ZTljZGEzYmQxY2E0NWU1ZmQ0OGU5MzY5YzdiODU1NTE4NDJkYzQyOGViNGUyN2JiIgogICAgfQogIH0KfQ==" + }, + "E": { + "signature": "nHUTedjOyea9jusU3Ce4CBi1kRnmK/z1umWFTobe5QkKZJEj1iDRyYpxuJL4ZUXfE76JObdakPnpqBWw6PTW8AjKgnbtC2uIDPBiYGNr128foKSrvJk4RM21pAXyi0pY5qMQFpPNUI4o4yBsz9CKGhs9WmxMiJ1TPCmhwVw7Ara3uf2nIRF0EEsXBQrpqIUsx3faTPaPTBoswh6Ut5kc3WcE8sV8kjXpTrkMhBIMoe7NcEERKVxqTRs1S8RTuIEI87ulzdT00P5OhZlz6+vwY6zB2MZccg08Q4L8lht9DuG80u2FtcXFfLJJDBYVFkzjaEwncVt25tgIrUf04DX9/YPKPWbB7TyVQk5w9EZxEaaTU+o+J7V2kRXSWCi0pTDyFbqKKrb4htqNTEvpkQAgBK2L3bmywY0G7K4nHZKJ9YPtMCuqUc8RphzYajj9/9K14YVE5q2F3jG8nTNcNDN0o+5s2UWLVjdELnCGvw9se8LJlxj+tdoVCkLFSzlJhaoovl8qUgh+sZE11l/mBe2FgPyfiqXjEe4jki20ArLX1wDrEG2gppgCjBfSBFOYaCHKaCNuPrJp2tjZ4Ih0Znez4C4+g8RpXlVDUKN4Va5ibB87fpDgvcT68A5N2uyG23UosyHMBIPGGU/+4nosnlOW3+DM5J7EujzVQH6xK00T1Ik=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMDE3NjE0NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84MTY3NTc4OTIyODBkOTQ1MzlkNjRlZjBlMWEyYjU1M2RkOGViMzNmNjU3YzAwYjYwMzM2MDNlODU4ZWUzMzYyIgogICAgfQogIH0KfQ==" + }, + "F": { + "signature": "h6be0ZV2H1Dje/aGeTazltuIT8rdXptKCJFAJ2SjSwq97FARJ/lZaSe21nYwAeou4rvXNnj7bcfJSqTHXpeM7zpSAPqk/WLLmcFgy++V/ZqcLOhhFaBd9POGRzDkCd1+hgBvq1mhpdohYxwI9R/QhkKuL3KvAYRNo8qrxq0osqzTlEpfkmA5DfjNaEK/OLkFq13ZVxk9VxTwcafLshS/wKoUhDhvLSXZ20sMZFgRdIqO92sqqUrKHHdpH04iPwPSVxxWhYLI+WokOPDixNfvwZgqsAwW/Kd+sWyk75rJGlrfMV5GeEfGRVg9hj0IyUY0ijOdgrRRDtacDMnzix5lqWeT6TsB/LswjietdxXdwKzZGscHztVc7Jnhlaltji2/QQOhHL7CufIcEixKH2qcs3vNFaRan+7ZA2noH8XIHFaHb9k/8IMQFCBa3UcVq7Cy9xq9t9kTlLcMNYHXGAqbtpHViv1yNWdIQyq1kvI3Yvk65JPHKqYeZWn22vnx+dlZWYZxzLk8+9CuDTqyTl668I2PZKHLJCcFyUk00pbNAAR0vMhxY8J1rl2F7E50Nle/BNxHC9rEzKIaEDSTYvX9v+hpWUGmqqlG3JJOM/u+CuNiy2x+IHkWadFqs2FdMHZDVEauQShcb22DMo3X1e8eGj9Bwz2xTCHL+Sl2w0AoxrQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxODIxODEwNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iZThiN2QzYjUzZjFjMDY3NTZhMTBlMGI3NDQ0ZWY0MTM0MDE5YmIyNzRhMGUxMjkwNDA4NDA4YzU0NDRlYmMzIgogICAgfQogIH0KfQ==" + }, + "G": { + "signature": "vOMOk4JbGXkjsuGZezoiNNlxyggSsAM8fpDxFvwCwci2qrGY1jFbV1g4Xp1kjTpPCwuz9pgG70pjNEg3DblxHn7mGn3+PW2YPYmSPuEy5SMPlR4Fjxs+oPeluMUJ/d5PrNoz/E/J+uY0y20F2meAb3JJJx3FRzNtILHxKMEtXnU5zobKIUBZYUNMwBF39N0fflKHop0irTxWAt38hN6mVfpV51F8A0ow1ltZp9nM1GBmqItuBEn+uJxrAfXmjPp5gYUWPeDhBQx1S4X8TgitidBXaMLLL12lVaB5LlMCsafXozTrLuHUDTLf5k84qU+AwtQxW+86INSLf5sepmLpH4PjrEp2xwX/vjSZ+/Q5hxqfu6ZJS/r3HVx9XYReXNndRpJtIt2cjjfi7dcD7ph1iukkVfF83F1/MK8wzk98aq1buN5+d0TIHwCydDZd8W00ONDBzcGkppjLqzCtpRaL3lyy7jWjkhXcewRMvGnp6PWOc2MRXf6fBw9AS9srKE7aY4j0908ofJXAdaBICrM7clUNz2zoc5djL4+hYmXEnsNykjyzG/Mz9D9HmRxxWV2Eor+ptWWAsf+g6uZoNvSYh/bi2PY5Ke4MZBRBm7gKOEZfBtAkmjahWCyMeSWgFOVfpYksYCiVn2J6ds0NZiHi4EcNXK6FveIFoWth93ZF2RE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxODQzOTgwMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xNDhiNWEzMDJlYmMwNmQwY2Y5NTBiNmIyMmIwNjhiODY3YTQxMjM4N2VhYTUzNDBkOTU4N2RhODA5Y2U3YmE4IgogICAgfQogIH0KfQ==" + }, + "H": { + "signature": "iG6DM+6Ovgs7Afoq80a+TuaJDv2wvy1bWlw/P/oXfpu2L0lQhcC86jOYFp4dY4yigU6zj9VYcKYR1cfcNhJkxwdbXb5E05cVTP4Af6FEKzFzOBNhtm/eoOb69zfAQpbTTcdBPa0iDBCbE/9bcFN9/LvmhAeZSx2moMHEpH1goewUrdicC4cE5nwmdmBY5oyng9oC6UAWNrmLLCG+KCZDPg7GxwxvuU4kyd05FKyQIqc9LMWKGT3jiBfEJzng8HDe33zds3IgSBPPHibBFUv1Vs/9G2e0qoQkMypuQSS22OP/efwenrckCTY5pmTcoh6TEDvfhJsfz+e0tZLpVpTv7FiUfijX0K4F4y8eiWlsMHR3qAm1dgALEfSXxKRL1cT98FLD8xkU0sBHrguVI8Vfdcpu3hLNyuB62WFw3R+BndOOUzp1v8f10aq8hXjcT9ohNca0jDSSKEv8yiaYJ/wiIhg7asvk5LWaVUGHbWQX1GdpL/2Qln9p1k15f5GPDIJ999lrrUOvanNuwid3Yfg3V3cvuyTQ8Lq1CF7mqOzBDkcoyq0kx4X4omHlyU+WWf9V10t3+SMrqiCbFim7YtItubkaE7YhtLEzR/P91C9RVlUt1CTYdlUpLVeBdI+h8MaVmIRxuV+3dIY9ssqheIxPs4auYifuy9jZ/aX1KdRGfcI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxOTQwMTEyMCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zNGY3MDgxMWY1YmM3OTNiYTNjMWRlNTA2NzViOWU0OTMyNzAzNmQ4NzY1N2Y5ZDBjMjViZGI0OWZhZDg2ZmM2IgogICAgfQogIH0KfQ==" + }, + "I": { + "signature": "Gx+I/WfM3VVvsLlkpkdlTBNWnxgwQG/R99P5gHO/n0D4su2y0LkL5Eq+mxxddXciErC2kRBzq+c0eDR9KOgP2Pl2+ICQfJn7z1y2NuWi11uRWRBAe+zHRKWyntW107k37aLlyU6U4BCnQdf/4aLBiZW9Lbv3fS2n7c6kqj6W1faBZBd0p6V4dR7aGth0vQrXfJLaOFVLEBgveQWHL8+1XmObLXpd/oqc79wI727WTUGv6VubWAxRuOkDH7+huic2w4bdinQ2wOsqCPFp4tmEUwlBECn7Z3EIkwyB/QVhaWsAO+eAMZJlBjblW9n9pqyn0CZmFXVXumRBrQ1ZvCNgzc/2w7fCQO/xunLS4GcXY9EI0RAJzzwS4TrM2mbyXmArBgHvMOCYZGWNWQ3MU62feLyfRMhPK4Ix7acJIDfe3hMdoTwxmR2Xsv+jZOKdoMN0mEWvWfhdueuB2++NkzJG29ziWJDZIBm24YEbealedDL80b3i78w5eEarucmpE20oUsOZjVQqS9Q7V9H/BReh0je3xfpBam+xWAQQu6UVLZGmD3BkQL018S3KiWBqbrMuMH54MQTDJx68y4vnM9SWXNhiGZe/Q4tvSR0COrRo/WbCi1KmnPzAfXeCJIi3RCtHDLtszJZE26IFC+0i5mdQ6548Z1l1nWJz1huWDnY6ATk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxOTA2OTAwMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yZDg2ZDEwNDUyNDNmZTZhZWZkYTU1OWIxZTg0ZDU1MTBjYzc1ODYzODVkOTkwOGI4YjZlMDY5ODM3ZDlhNjRkIgogICAgfQogIH0KfQ==" + }, + "J": { + "signature": "f1TZSK57WtJ7KxpedSRk6Kngmj5qGz8NhxPN0zlII0RbQViOLmEO7Rp8tNbbS4Yh5yrxyg7FcL6S3di3X+aGD1eJ4C0pXpHYB+hofWQYL0hM1LSzJqAVOVyCuLuHo8YMIKjglRlWKKVRxSL0hZqMM35zyYCod+3BfuIT+zXOPqJ5af7ZMpSVwF3vw3dtiPYVC37eSwEytX88t/lw5+KUFVo0Mj4Hb62lNc6d4rfE7OIBdb7a3el/2dRg7983HHeTvIjs+MEy08yS02O+g6Uf2v0MymkGEjunUEJ5cr3IPVK4ZzP5CCajfSmBIlUMwaDG5q4LCo6B9HtoISqNRK7PWaJMAncAPBjoNuL6H5J0pQYq2UKUnbXIyFsWuM1B7sSXTmNRRNhIdxXegZZ9k7Gft8wn1PSQos5cvbLVbz1KW6VBdkbMK3c2OSpLK4/DrezkBf1iW7UOhn8ucvQKGdRPuegfFJLvh1oCril5Ix/lu96CnjzPEHlE7OAAEvhmk8zx3v8ebzgXS2SaWbjE4DUxr9twjAHTCZKKBNWbsYjxZh2FXNtt90Sa8RevoU7gveNSI6nLqkDQwwwoyQCFb6k6wEAFH94/qOXaa9ROOmg49CifD186glCYNOOz8BMYQywyW9512kO8PDLg8FfOHa0vFuHyVEg3svqoh26lxRqzAUs=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxOTg0MzgzMCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kMDA1M2IzZDk5YWFlMDUyZWY1NGRjNTliOTYyMDljNTBiMjZkYmRmZTk4OTQzMjA3OTMzNTdjNzBkMWNhYzc0IgogICAgfQogIH0KfQ==" + }, + "K": { + "signature": "NRlpeT3JDvH77slwVkyUIQlLdJRd0RbfkNgIgjlsOJk0TwUJST4ysxOjvWpQC6BxC3INiAB6ZD2CfkRuMRMRDeECDP28Wrq68+U+IjxJu04nPBAiF2EnjwAWTff1iCzwF1ZnAusGDUEV2YjiRwqNs6QmRrOmeIZh++meAQzxLbDO+5ogUDtc9hD2yxJ9h4W+jFvW4eNMii7uZkAHYCwZ3FSQMXTh8+fVG90A8PDIUGvaICFInyAg28V3+Y9cgl7/UIsHeqOCwfSsMNkjKUd8BREHyb4oCvv17oKLxuTySGohVs4Og3RJdORcMufR9xY1jR49ZdIvA/YZ6kJOBLLvF/5vje7iqvqzeHOjPVSk40G85M/cl5JtW6dak3lyrleRC2rGKnCwQavK0zPOmDJUrPSIzWImWPegTzjDWpVYJPcoX+duYRlfxMDjGKAoU1y89s1HXbrtZ0e1qnfGY0DYU3LrtKdeozwwHVbVS9bdX3Qlx3njjsLjwniyWPupvHTMh6DHgp1Jfw7WhC/JtNBrdcJmO8PVI+iNGDgmAZkjSDYODb4AP50+goosDU2QAwOXsIodkknlxETLP3yjzOtBa4LR8WRrJIlK+78Hnt7UmZX/wZAWLmOGVCX9r/ah3Uid7pcT/KFPxdcjlBn5rPo+3lHtcpT0tR9ht9W4OqmbNX8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxODg4MzQwNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jM2VmODkyOWIzYTMwZjkzZWZhNWRlMjg0ODRjOWRiNzhmMWRiN2Y3Y2UyYmE3M2M3NTA4NzUxNTdjZjJkNzhjIgogICAgfQogIH0KfQ==" + }, + "L": { + "signature": "hMwXzgdixE+AMAOK2bM17hV7esJ6MYcoLegjc6rkL9jcDrUlFDQv9yiNBXCenSVr2nGkk7Nk8UR6oSbInB6n4isjZ2GWGwCyEH0/Tjlic9YtDnD7LaTpArR+3BVOY+UbgPzePWlPJVU7qyDzdEwzUonSolVZtpN0h8QmIAFc/wUHnc/q4SVRFFwq49t1UggxSk23vFCeYHnLixSX/QM/OMQfOzZrpVoPZgY0GIzyATNi4OWYo8+vodGPJFqxUkpBDn2ixsrsP4/i9g06gScKLvCCXPBq/VbawfY3H/JZxyHbgQzaNUpZLSVXNtwOSLjWQ/rNBbNm1N5DpUY2RC5GvnZMQ+tNyTte/flm2nec/1yUDROth2c3wlavZvylCVaEzlHgI0JYs+TGxSyWumamyS0boIsn+rI/1Nlj2ZuYxlDRCGo91POXHOmvfN+R+Vwb/pWe6phV26CT+xepWiWxE71LyapqRKMPRvFM9wXqs2qMa/VbJX6+wUGRThPJxnb+5uYL66j8sAQhlq88ZahVR8d7p7ui+Ie1YE7c5+CZZO+nudXyvKtLFhCL/j6L+O6hBjwP5MwxQgg7QhqaYqQ1oiBR8pvNRg3vx5/q1tFpzGgRfBb9DkizdOTgm50e6umxgchiPUvVYgcxGhiHoyBK3C6kAeYUAaJe3b2HCVrL5oA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxOTIxNjIyMCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82NzM2MGZjZDdiNTFkNzhlMmMyNmI1NDFiOGU5MjExNGU0MWYyM2NlODVkMGUzNWEwYTk2Yjc1MTc5MDhlYmM4IgogICAgfQogIH0KfQ==" + }, + "M": { + "signature": "WhUHnVa6XrHVWlWNOwBcr924khrW7QwhIalLNsdM3LyB71Yv42hiPYqVS1ZCn38xfH/0QHyJRMdAedFjqVCpNmgboMw/cd+6k84RKd8ZypxdLYr4coFmDHRKmriJl50TylxDU27YXO63esrxNquFDR4y4R/5B6Z/NgltLtRzl/XyvwQXG3h5nHeuF9xZnMTSlxLXUGbYGLsSY6q3xOb3AUES+eF64+xcPJkGTjqPeFOaOtL5vgojLZuxeGUylRV7J1ViknhHhFcd+lnJyc4o2l+uHvxBvpKgy1oToqARsAUrfpi/Qx0cbWHq/lClxSsRqqLH8JlqGUHfP9nLZRgtOnF4Fludnv7hNW+vnNk/8XfFz1V6qIF3tw4dOygP2K/3mCHPNlb16BI9F5GldFyLJXZX2+p1MHW85xcAKre3EFMvgRT/d11+NMxW8ZicFl0WcKqCZv2IfWWv7ZYSjjS8SEi4S1vSF9mUhWRXusjM1WdVYhY44nfQopt958zRcEm3/GOmddeCRuI139Nl6/hKvZO4fA6CcG6PEtnY78u9sLp+cczxKJCGSMTCrZJa4u0tx8HECx8+DkQXTjyWzbfEP64gEwDVUu5BfqMjdIq5It9pL1s0kSV97PBBB8Mb0jRhYHMF4D47XZtob32Fzrp3j38MycxyYteLTDHh0jvz6iM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMDAyOTE1OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hNWIwZDc4MTJjNzE0NGQ1MjFlNmYzYzU4ZWNlYjQ4YmE0ZTkyZjRiZjkwZDI1YjJjYmM4N2QxZGY5ZDdiMjViIgogICAgfQogIH0KfQ==" + }, + "N": { + "signature": "LfcVyY/0WV5lgN8iNejkerzSwXz5gpssH5+5awMLGtsMWePV39tZlupkEd2muibaptEntewHgPLxrLiSeK+R0kepT3njlnl1fwU4aoW3Nc86CSDPivRaH1D3fpkL1XdQgQWEpD1N6k7X/rm0RR9wx4FCkhyNggYxwk/atfPSlyBUDlMpZXF6KayLULAEzTnUMB6zkSECyNowZX6YjqxYdFrcGGeyUqNXH7gSYFcbcrctTLAZTU9NP8CDrLiaXKkP1z/7F5Dv+sUWjhx0ugZ9QWqspV5Cfs1D9NXnm2lJywtQ8H41B+Jb8nCPhmQT0YUFuX2/E36TSRpv4RTjzks0GyVAQnvQtgBqZjX1Me7+qjH9++V2eCsnZxGEVdG3d6+2MtAsRAZbQbx+7Foegi6ZyAwTyc122qQghpe/k7XLnsmM3/5E/uBZqUR3QgFy3Q/WPptUnayjYkZT9xkiUoo3qYVEXAdzSfLCKR1PBbqZBxX174LBePQhJEcTdNYW/sME7N255zs2Vd4Vx9E3lGm+YP2OVUxu1IcSgq4qJr0RWyk7l+67RPDXECjPQjVeXuhr+kQBcLDcfXS9QT1nWaa1J5Fh28I0hK93k4PYT2cPKX5RGYzSrPZjvI0vD64ndm6fIrKlFOOjj5zDh7NhjArOx/6X9nuJf5JPKzTmPcLuR8M=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxOTU0ODM2MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83MGZmNjZmYmI0ODk0YTQxOWUzMTg1Mjk2NjdlYjhkYzRhYjkxZGU3ZjEyODRhNTNhZTAyNWEwNzRjOWU4ZTk3IgogICAgfQogIH0KfQ==" + }, + "O": { + "signature": "h0U80tPRJMX/VYbvX7SeevneW3z9AEi1WOv+qD7rDF3e+ENofDLMU6fOhyzoQlsw7/4kfw2smqDt7q1UdoBL15VXFzpARzkGwJFBZVn+KCN9a2NSRJp9cyXx+bhD9lTeDbrMl8lVq7yZqWGPxDbXzTfYXWgjlY438kodY/QVv+aISOGbVSTraf+Zc7pf0vnN+Tl6DMz1588233t1UTYTXm6XGdZKlD8u4MLyvntWr28F5FpVc7Cp4f10nhghgsb5bkh+hFYvFILMfYu/vlIvn7NjIVDBoAk5JK9fRYgiHfMoTvIsGUGzz7NwNuVJkJF/++jjYTqxPIVad6uMI+Cq0wY/ju+/X+fmo9jjhy8C61zHrrTaXsPMAmD9ARQEJUNa/VcYlsPOMya4zzvABVbw3rZRT3qROytQfgpI8r77zCiSUwCwNxdF6T8dlBUNd2IZD/Ng4X8GeP6SRHWA+X2diZpO6xBbs/fFv7n2hTN5Re0hA4ARy+nfCduMxe49tgHpFQ50UKngKVDCygEDsnsnQ5rUnopBZcFg0q6aAqbD7sYqpd6u3t6B0AY1w+ANYo3Znc4IyKk6uhy5aqJL9MmbCKrJoNl2WCpGUTKKMeiSZeQiIc/0l1mH3bxDrZa9/I8PDHX0yMuCvVMPEgNGJqRxStXBPDZ8zeASej2Lx1/MWkw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxODU1MDA4NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iNGU5YTcyZWVkMzE3MmQzMWY2ODgxMmJkZTk3Y2E3NTI3ZjMwMDM5MzU3NmU3NmFlMTUyOTE0MjUyNzhlY2ZmIgogICAgfQogIH0KfQ==" + }, + "P": { + "signature": "uWPNbNk07AdaB4DxAqg5G4EJ2wCasaQ0EBTS9y17Gp06ZvCpWZJKUuaXgXARn/76usgvm/EWwcvc7C3oJ/ckVcsSUAeko5maRJ1Ns/wWxyyl+rdgMLMTTw9ilR7VrlcVGZi9cnY7geEUtbazylQDP88C/+I09vaFrqIvvDuPqL3B9lYOhGbYlrb3kyiToSD5rigd4XgmWzET1w7ry6vguCKnFjKyFiiqKnqTNFr+Pof14C3jwYyF3VU/hEXbJW7pz9tcUOyQeawNzx6cToLjfwdfGaVfg6uNrKfNhz8jLxJtBcwtXZUWzO+UfZ+JYnrNqzAzdtLXfn5z8fTbdAyUuiq4A3N5YBzgozphyy+bxusIlgVAyFLllFfc9qtdJY70Lm3qi5j+kdBQd/jMo4Jen80b/ueYHOtnU5qf1+MqrHp5zifrD+po7v5sOpd1xWtXW6sbCQfbS3oxDYAmE9yTwJJGULod/NU39YCCdruV/b1kbg4j4tJmdqfSOfdvYubAq6BNTCnUstbdSCIFeb3TpXwO057V640cQormrEsZC0o7FEVqDjDx+LuYpGUAtu5lmrVJ61ErMoK4cbin9MuQCohqbX/EUGY7pZQJVwAzDjalMl+V0v86hXo7U/7SHSLSYpq42R2nXNsDH6Dyw+8iloZSvP7zW5ZtzZjudO2zqRE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxOTc3MDE1OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85YzkzNWRjOGViYmQ1ZDUxNjJjMmM1MzEzZTcyYTYxYzcyY2Y4MjFjYjZjYjNmNTliZTk4NDhkYjljMjRkYjI2IgogICAgfQogIH0KfQ==" + }, + "Q": { + "signature": "VTrsThcuA2Llb4U8ucyAuFDvJZmm9N04Nmx6nYw9tiHRqkV9FM7Uhg8fr9y0F/cB8L5poCGI6D3pOXqmpeUsJI8ptpX0JkmdHqIV6uCYU0Y6/vrM64M9Da+nd6VK5sRYneS2n5zQ0XMm5BT8lmW2neMEvfRiHGia/j58QZINjuPec7hiRwxXqRvS6GkC6kxrA8TnoE7QPj8pCpr7PmagUoL0ZmeGJb3wupg83n+3fbFsCkv6yQSz4HZwGI+yR7sm88GmU8SqgtAFDVPnBxJC82guzCJciXG8/C9cMa0YS8Gqa46lMy5sXEnHNmh9yUh+2PPWlDW3rsfgSmqPFOs0RCg7Q0AOG4mPSknAEbAUax15cOU/HT3TFjwH0mjeMpm+esb0/w6RfKLcYuhhO96MF8TwDwI3Om4NnKR0PrzhHVOxufbScLqkQZh8alvKJJEeXef6bbZdl9AVk6Dn2u8CcJRP6HHoYPwxFobACKU2xJS2Le3tv168n6QerioiCpXIF5RWBH87bO7wSl6ULa7H142NzfOx5SEU4vFTc64ooz9wcJZ5WpkufvMfme8w/QSBfDO3U8FkGmGlCtCRV27fa3VBOjR/6DiDuH7iem5rOZc3H3s8Edw7+j4o+Z+aa7uCWHuHBQ7tFmkGZ98g5rON500lJWtqTjPwKlOCWcYOvEk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxOTYyMjY4NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84YjM1M2QwOTUzOTYzODU5OTE5ZjgwOGVkM2Q1NWNiOGU3MGUxMjRmZmMwYTc4NDljNzVmZGViNmU5OGE2YWQ4IgogICAgfQogIH0KfQ==" + }, + "R": { + "signature": "aRac9z8uyBZBSmaNq0gKRgBBwiTfuqKrSBU/wJ7Ppsj1gThuDfggPBxR6LB9xhgNscyTsRO5FLvXceAvkapr/kWl7VQmRKC0jZdtLMZSRLhxUvwjZz9NzdmnrE64RP6pWc/ueHt61/ZG5bRlvtckjMcdgxDvxcTNHpVzfjDLikwjlJ1TZ1zJU353bw3r83nvhn2sKoLQTDnIMDhe/yuIA5xKo/IWYDoMnHnK6ehot2+Jj/phwPj13cX1jB2VXMI/X93P1nKe1Ei4erXAOG8Klr+mBj6aaSXoyIyVT+MUlgvS3FS0CyKU8z5yO02LQaV+QDExgU5oKyUFUDyDEoToPFA0/XJQAtwXXAYFOcSYQzrvRnrZ/ZJXepUxQwBYrFG8tqiSmEz/6/7r8alpLQJwDHfiBpYQ+PRplYqRb7bQoN/EajOtyMg+3YwJFw0814jGsN2qg1vqXzqu8GbldhKuv+DWryjGPnP4wkpLRaWUhodw1a/L8OYGXOCqtnUByvo9YXt86dF25A8wb52RTQkks7ONOBuNG9W5k4vN18xm9lI6edR3jiOGi4RoZlT+FIiRodVUjWLlT2m1bW8EDJQECrQt3ipg7rRWdvduWIgNlb65CpSXKc7tqbNtMI4eE++cg/vPX4eArkXEodYALUfGUCZm1AARAqyMhcDySprGAlc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxODE4MDc4NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83NjJiZjIyY2MxMGM1ZDcxYjk5N2JiZGU4YThiYTQ5ZTI4MzQ3NDlmYTQ1MTk1Yzg5YjA0NTkzNDcwYjgzZDZkIgogICAgfQogIH0KfQ==" + }, + "S": { + "signature": "K7OdwBohQoppyLndPCM4lWFJeq1WrWBlj5g+4eWnEmb7OkipjGhPXnSYh3NRsa4zhHxYnK1ufienbOoN8w8X/DtliSGvFofZcC0cyltd3Q4dAf68B5qqf0IeRlR7ibLTH4/SE0S2akWbKqo50EZaav5ieIFRj6iMo0ZIi/fDKP7D3cZI52JDhfmj5UYH2/HI5Y2QkeVsLl0KVgOiWl4qmhbtH01Sk+GZcLMKNBSjoNExhlz83W+dovxJw0MbCXWKEGhnZvk1kuHZcXxbU/BMQBXnuPnUdhb2kHdlgjfxZ8VYwOQHipRcBk0cAvTmvLeXNImqQL5KtIaCcCmfuac+P0TpmOr7pQmV15UBXkbFyO801KMPDGFYyd0yczLgXEc6tjALBTQI1AFuhgdSXIQlr9oCEcUqSwovTDZG78lngJ38rNoFC0cqAcmeAmm3BpMrT87fGp+T0b8aVBV98PyWqYdetB3CztgbbZgPEc1E0fK9ynzd35YDoy0b8oxPBgh7tuJlIF7Hcuk3O6dZzQnk7HUOUzciYioS7hvnGBIDsIf0k+1IaONET79drScdMPFvxTLeE8V6SXX3gKL6eK+QmgREP3fSXpkr7CBy08PH4HiVzuBxGWLgLPKAIR1Z0rQgjlYmTAeaMLGQb4bGT3BOeYL2DTeTJR9YSzLl0QdtCcQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxODU4NzAyMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85Mzk0ZGIzZDE0ZDlhYTU4ZTlkOWVhNjg3MjQ3NWIwNTAwZDJkZWU1M2ExZmY3ZTI4ZTY2NjNmZWZmM2RhZDkwIgogICAgfQogIH0KfQ==" + }, + "T": { + "signature": "rvmT7zRoyDld8W5RFAAmsCV+r939nQBkDKvznDAxL8BTEXQHKBxAmPHb0NMExib9KO3IDi4Z/d5Q4ju1iJLtydNYAHgIi5fMhAaDIlDRUN6KfJeWYYDrKibLtm8RUvpdvDNR+Px9fmv7tN3X/dar6FJH9v5G6+M2sD5mxWyxooP+afD3dzedbAjc/DTxmQbEkrkjJq3mOggzOUEK5iaZAfVhB9zWOKmRVCHwOOYjopcpzOYZ9/hDmjRT+YkbKlkLWT16ebCDSgOo8Lfi102veIvtMYaw8tEz3UcuVwPex3FshIHo5j97H2R5tO9c0tFri2aHK+n/lQ1adh0E5CgGP5UHVt16eg4nGV8vki/N+0jQjg+i174glqYd8Vcnh2PuASyqHBs5NPl0gIJGqKp0egffcwLxUiiXppu5cIo4DfC7Ni4rlKy+G2xKEsNk1aaLVDKWTu808YnxLAdg7Mu7qBngKJMU0G0dt2q3YHJ8n51Pdda4c3KAXyZvrLYvZ290RfOhRJPFlfHjNuFg373LyRTF/pH3PyyWVsayhZeXo7dTzcFdzN3v9Fv0Sq3fg1+btkXweTktITFGz9SH57DxTFIxrhdPWo/6He8UB5IJtp9+uJgEV9A2THOuRBHeXodvVQxzIzjbaNQrwmF0h8gSqplEdocxiWjB3yL8bRBeJyM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxOTk1NTY1NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84ZTEzMWFjYjk0OGYzNWQ3ZTQ3MjQ4MmE3MjQwOGUzNGRmNGNlMTg3NDNkOTQ3NzMxMjM1NzU2MzM1MzYxYjEzIgogICAgfQogIH0KfQ==" + }, + "U": { + "signature": "rKMedv3+zzLRlU4LBO9O8Whvff+tGuQnp70zfFFxxD6p8w685dxIrr17TqLhr7yLPey3KvnN6JVoUq2ZmCK5hRQbSlWFPp34POs0y6e3ACH7sfR5X2+qIi3ZIxPuGex9rcIKF+vSNZO57lvmJhVWC1/1ET0RGcXCKF/XOpu50TAeEdw203L5dGAZKAT6uqlXUay48C8lEaTW2w2z6B3HeaAY7m5JOlB1dAHzA2hwI3yMf0+KxRM0Acy54pS4YAWFLyr3gjgr9uLz9Z2/UrtYVaPr7ExYU+6YmHqfEZnzovxtpAZKrh30O03xyjwaPdAA1/bvgSbqS70SRl6NB3KKG6A+qrDUZKpNb0udv2G7dXRrEisCjwWG7Eu/xH1ekqkzF8rZQxsbwTnAQsUaJhml7rMyaAZCjRrRm+uaXp/BPKcaptAxisHO+NZp0L0zMDvb3W2SpFWO2QQrqeuSWeGqodo4KtRbkzH5dx/IehWS+aYgyBn2UtxOkuE69ctiF7qOTFBt2sf9LK4q2TJG4ZWqxyEoaoFpAqQtp8liICtXsPLtXSGeK/DuFKKddeZaj1hHKu9mk9CO/pB6DoJVdVWalLF3kKGmz7PwsVqcwSn+gXWQORNNsSyKIinIg6rLXJ2XShleKwBBTgFl8hPN9F/1ql0F69byJkaW0V7mEuwJD64=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxOTY1OTQ2NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80MTQ0NjcxMDU3Mjk3YTU5OWFjZGQyNzAwMjg4NzNlMjI5MWE2ZTk3ZWRjZWQyOThlMDgzNGY1Yzk1ZTgwM2IxIgogICAgfQogIH0KfQ==" + }, + "V": { + "signature": "cWtMb8x6j79vubw0ttzPyb1xyoN5nHjzBQsURBXCIsv3fokzwztVhjW6UHUmvQs32WLcqj7fU4MVvOeGkGJm9dAwrEm2JyCIsSDekSc50TXvjDySKDG2NrcniLlYxImFXchahYgWyNLaGDV5fqToeXuGfn1A4yCGbjq1dLc2ODFoTlq7KxMM+M5rSbjSyU8yEHFdxDwM5LRXMek8P2f2n1E+XDoW0eewzzirPHPOD/PeVcazI5l1ItGHbjLDqmmU8tq2XsUmPFMkrfjo3zn+xtPoW5NVbhTqADxM6qLGUQWKo9fMUan303jiW0QHxZLA8b/3jZX1N3ENmqx8HUgRrTtQgbctwrBb6kYL4nc2JF8im0N75ITTzKrWpyDOZKBEQDn91UkNcXPZDQn15QsBNPMlJkG7Pl2TUZDpRSO+xf89UmK5c8VoJv2aNin4HaYCO0+nSUPs50kQdJeX4qM0G9LyHzPkElKXv3etzRxZz3MDmPAkNcCDPiFIhL2WMwdmin00B0tdamxDY4Q3yg83gEd9LxaCUkgcqF4xx68ABeBA8DOVEzXC30ycpoDtTSpJf2QKUlB9h+Dj6HCPNXTbHjoEi6BPxVlcEqS22WJdGbEFgLqtgd0OL8QSSA0SkGHMiQCkDXbLaLrDpIjIDdOfmJGorqZgYQajiirmYpx9Cvs=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxOTE0MzA2MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jY2Y4MjQ2MGVjOTY4NWUyZjc3NWEzMmJlZmUxNmEyOWZlZDQxMmRhYTQyMDQwZTE1MjEwNjZjOTFhY2QwMTMzIgogICAgfQogIH0KfQ==" + }, + "W": { + "signature": "oDqU/+auOJHo3JOeFDmolCrrSra31GsLV+uqP3lLEv0hI+CjTrITDWPZUaN3q0GVEd0ChnHHj5IFKAXsZCF0TVaZCSP4ltLtYRw9WUem0kIPW+LMpOIkQAoPNfb39BfYScP55RE8tjLj8HN9gTg7uhmoUKAtl5R+GhHMkHiDwWUq58omrOf7lHtP8EnJxiKjjBcz2HXWLVBrBC9ongtPB3snXr9+V4jk1XwLnxTIDBS6oNNxE+JaxfIMbpRmAe8c6M2WhbrkZ+D8SHo3mwgO45UjJ+KR3svV46z5uxHvq3Dp8nRmIxW521pnEL1nt5l0V/DXT1IeqNUnT/+m+VznAaeaUqzxyTBRIWkKEUmQKXcH96D+XEoprH/7xfckmfTWZawEMyHhzrim3MeiHCJW5NsS9SBG6qYGNk01qJQ1yVnfna8TJBtoc1SHCBNjKuK2psflSLx/8l0fdERANTxyDAu/ioleUIQzHR46jpdrx9xAzc5bl5J7jm2rN9GSwANQlveO//M2cAfywTlEE/bEDZA0aHBU5bilRIVlUEY2pAF0LiyymufWlAEAxkIS/SxY5PBt0nJt6PVwo9VhXxAYRr7BCPmBGNFOsFErvEGiF7oCCGAtvGpvnIHTpagUiUD0xHgeiqBTu0Rd53VB7GzGSgvZXTDhKxTD8k/9Dfi/0qY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxOTM2Mzc4NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kM2Y5ZDk5ZmE0OTI4ZDExYWU2YjgwODgxOGJkYjhmMzQxNTRkMTY1ODdlZjUxMDVlOTAyZTQ3ODk5YTczNTlmIgogICAgfQogIH0KfQ==" + }, + "X": { + "signature": "CvUfPgMLS9pttKX/5tCC9utQJE1DBYWl9i6ueL/P2QN5CUilRAE/sKffgWgod1Cl1VWEcxii7KYTmAwW9d4e/YanplpY9vqvs1bQyugMR9fRDoaVYCIuP/ODMu9FoT28NdRiqsbWLrtPSQLM+HGs/u3GaVYPjb6JjAKjXzyLYD1Xvr0Yl4SmQHY9m5/j0W2lZWNROT8ZyRyyzke/Hx1yDgwLYiSpuq7IICu1GEc5+WGzldsXXMtgnCRQjvZA7BNe+9Ot5RUXfFlH94PnIr6jtR212tNBIQJbtsdz82hTNINU1o7In8RsSdLA1xyyj2PTksSae63LIF9pxxMX5+IX/rIrzt91b3oEICfrMeKW8TikRK/UEYsGwjBzOILKltBeebzEGkoZn7fafAOvQnimmp5gpXVxObyfrRNOudFC0jr1DFkiqs/jLj34VIQTuge2mBaNycUNfEp/D4dDQV3z9vOVLgecoianSY2yv7MM8WgyRH9lKYiL3rooz2HAL1UAz8RZM5HFIkB/MrZ1pBIvUmaOBMtYJCWKUvd3AoNxb3/WT1wDWm4szoHTOrSpczNL2BEPrADMdH3dSrCLGED3w5YMs1x/lhiwJo/O+Y7yG3DBO40Ziz6IOanHAkP+23H88fiB81R/CK2PfPyukq+vGYKsXv68qyDXYGKEckX92cE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxODY2MDUwNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hOTA0ZTU3YjE5NjU2ZDM3YTliMDk2ZGZlNmFkYWIzZDIxODBhYjM3ODMwMzE1MGQ3ZDYzNGQ2NzcwZGNkNTM5IgogICAgfQogIH0KfQ==" + }, + "Y": { + "signature": "vbSwk3VAuBRAyQf3auiEy3JdJNtooSj8eZCHFfcS/tc6EARdgelK6NmccjpDbv+YvAeK8eM/m6WBmUcPx0LHieqFayl/LTsZTYuiWgOl4kAr2wet7seH9FZu3bzkDPLn9Ir8drE8rzT4VH0SwjNEpWh3d+tIaOHMqI8kCwWuijXwj84/EA8RJdGEWhJ9cyYjON/qYW+9Rk5TJojbp6VSkEIX5oPGlLWCswgQj+lGUl0Vn8lqzFNzHo2s8go2JpMwf99o9ZZUOBkiUm7OAEnUk75qqdXJmt96zDynXTJKy8pLV8XEsIuOD4usNq9nMkD00DM0liUuwjrb518566wrxdCQyaKlo2RAyV8PpNrmUYY+QqmnoJ0G0yPT0CK1/MW459a1AD6ngWPSLa1spL+vMrIXXycTA2TrNx0Tiu1k28056dD2CYZOnOiS8UhFrl794/jkP+2JXHpvcw8GKU+tL6edyMVzn+i8jjNuAQ8fV1a+r27kl0oFV9LpO1dwsxJLDJEX8C7DthxdUHjGKwtGpv9ozbwjcto9Crd9t8ndE5tlmEI5Y4ru7EX16S8jUpnvZ/YjMruE15dwgdN7PVea2LALbH2qpWsqcBq50Nc7Qi9Z+oMFWSWAPMy/wgXejJ/wSlunqxKqn6ZY+R2CxWlY9aSW0sgQ7gxQ0shsQjqaBAs=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxOTAzMTQ5MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yNzQ0ZmQ1ZjNhN2YwY2VhYzExNDk4MzU3MDlkODJiNTUxOWViYjBmOWZmNjg2ZmQyOTlhZWNiMmI1MjljZDcwIgogICAgfQogIH0KfQ==" + }, + "Z": { + "signature": "xC56Y3WJNur682q02Tbk3vDpW4kiY/+EmRCxaRqjipFPrKU2Ff3w0VnufUc4fQyJRY6n5fWCPPTAjIAPkh8AUcXmT9FnA/gET0fTZtYrm7asUVciMmfNNc0BnjbJaJVonaWYz2L1YNjYRrjzTu6cmpJZbjyDTIQTJ5FHDoFeO7SUmeaZkFa4r9zIu6ITkWmZSRQxlZ7L7wA8Xwr7qvXGKqaQ3gOAGA8hY9EmPEi2tXq4esw9lfrql0n/nFYnCwzxOGKUNMh4utWhrpOja327sXYAJwuTVtSBoVpH3yEVvWYWGr3bxSoDdGUjsgYX09kRl/b5NJRc1Z0bF5AMDyDUIL1iKUIlG3AV5wmLh83WQV/tZ2IqmdKKItJHdCrEt5zLiFC3XbZUXd1xZTwCyfVJX4iHriyjPv5Coq1vhjWXQCmXLmiCXBBEejV7z/aI2fbkVhsPb3fFtvzp/lJPeX4TF9qWPeC1ir89nQNfqjEsyTLrIBBhN6jbQxFyYYYsMnCs1274+SMCzCRz5ywimUzBBSq3BI1odaAyZz+bQ60dLZ8TBjb+HDJH2H5lMMYicsA8mOg0acWJ57caiL7za1XzGjwyIWgku1u/qfoXEGe+rUhOTlNhVr6dAEkB49R6C5aTqOAkKDZCvzdDhE0ikrsF0AABTmPtEehb++maf93NnII=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxOTE3OTgzMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83NjlkYzJlNmZlODkzZDRiYzk4MjI0ZjAxNWZlYWMyYWY4ZTFhYzExNWQ3MGU2OTE3M2EyMDFiZWVmZDE3OTgzIgogICAgfQogIH0KfQ==" + }, + "[": { + "signature": "wBYrDt8BcRmqaaUGopNZLbrP042Hs1tumIKckNUtPc82eJJ1Dpv4b9yGDw/mNVXE5CyjORsoAxCjH0mG+4y+mkkaS6pKCx5pESoxC2BgaMNAt16D+YHX0wy404ox+xbQiI/Xq1k0xy8rymwQ3XE4GgHAWKvOCFuLZSPM2Vn9/UweHl7Pi1x1LohoBmRn0Y1jqLzi6YIRhCSbGRDQWOrz2wWVxl0yl1oirgBRBWqGFTHLCAmHfjV0v8EqmqLqP5RWAARx9dGKQdJDZ2jXOOt2QE/wAV5FeE8ChUC4AJ9nLKYnTzVi5HbHQHhV6peUH3lrQDj0tMF5dDDNXnZbv+vqYLp3wz+n4sVsFo0JKK7UoesyZhi04IOjWsC5h65H44J5wBY3KrcJg7xuvfelog2Uo9jnOuaWFtXW6XFaiBoO2lCnd7h99wtpcGJy59VzktAsMKzGzS7ABbbU6elzuG3cXrQeEygzFyEF7A5+fmJvaGmJRn/TXgioedLegJoL8jhgHvvHHR90d3VLlDzTpdMcK6vEC9T1Ta6kc2xZZtuFXj7WB/S9Q2WYTL6wWKk0CVapOm+D8HFRhEAqvaaNe0jlCm9I4HqxI9zazBX7+ko6MlQD0NLjmw+hqgr/DB6Cr5eWzBpZg+Nat5rHGhygasU16j+8W1tl2KxawfQ8HfOeNlg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxODc3MTU4MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82NjgyM2EwZmM1NzBkZDdkZDFhZGVhMmQwZjE2YzUyODBjYzM2YmUxNzg2MjdlY2U4NjExODA3M2EzNDMwZWUxIgogICAgfQogIH0KfQ==" + }, + "]": { + "signature": "V6ic5tEQ8b2Y4FzmBuVKVKz8oZXaCk6zusldOGRzX5XwDiKWmb+Tezqo3bhwUqzmqA1yaDqzN74rPiYtU34FGa4ogSKhzfWYRA/jRWR2PFVeqgK+fwhUjN3cYD1StwpbDX25kOybFLlNVckDcUkmSH5XwTWoKRZU+QB2+OeJvCtnh4mQ80XxDwT4Bsp7hoKqkYvzXl6oxEiFmxIJ3rn1URNzPNiBXtcHz2RVpzXHuVme2iFGgFu/SYMj9Rs/VR4MfEv/c67plqnxjzuvvfnCuUIR9D0sAA/oFwHg08Uny0FWdZ5FBlKz8CoTdy6SpwSb5qUmLWSQn0HZ62Ftr5oK/RmkjKlyU6rOm5TFDfw2TPPbvBlVQCpCafD/X+qz5YsE6qK02aphS62d28497OsP604mIP04xSU2RfgOhjgPZofVlBZsqbmP3V5DKD2aS9QOJfYwfxUFkbBg7Q9c6zqEQPXYQJL0dvY3OB0BFDayybB/+6B9fH442oZZOvg3Q9N/eSWBLPEbOZUZhZ4xjI3qLSLeM4TRjys/3WA3sR0tHpvkhDYt43Z+TKd/kV6nvyeqPEi1+8F0HozuW4XgJPsruA8Edj1nN7/g0+JtipR16GgOh5tIuxNfVaDz9KaL4XVcK1n/G88LLjoQ/311FcOGQHrSzs7N7IRE+m/KlCBo7QA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxODE0MzQyNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80ZWI0MDgyZDFiY2VlY2Q1NzhhNWJjNzNjZWM0MTYxODBkYjRmYTNkNDRjZjNlMGI3OGRmOTg2MWQ5YTdhODk3IgogICAgfQogIH0KfQ==" + }, + "^": { + "signature": "IY4tRmPV5cPeU+of3gf1euO2hQhU2WgKuQDvKfyjiSOiUL49uDjqjDBmGi+7ggf/wKdkXdijJm/x85SUzY8/OVPYAfmFZR2Z+Dxgl+iSilHXr/3fm9/Vdm/b09x/+BI9KDYcs2hV5HYCNMseyyhIZLtLHzvRG4uj491chKrYrRUrL5SH5FeWutP6wZS2flpZcjuJI9iUl6QVoXik2XGX65hLz2aEOaNgVkMEEJDUjEUc7/pU8FvSJIBmsagZIYAt61U2uyveI66NluqEL/6gJBclV6VP320kX69xXq0wOZnpGITr/Yo0p9kXpppewUE33elnYCXoQtuB86k4M4majxIMR7dM4OuZiE/nVdNC92WcP5Ks14aBaOjEmmcodqW62o9k6743esMfZYa5AqZMxacAiSBzVOyZIjkbp601wZloORtdpo7FmGjeR3V8CpNxsCY3yVH8aWHvkIKL/FYCscO/S7bSJp4pQjBcQLQIyW10XW3M66Hwv2InVvw8mzTa7uyIc9lX4ZGegl6Y7R6/buNx1M1DQcR8xeUFEZ6un7T3KkCtAuFNOGFqj2goOO+rRMgTM00ZxtxJfUZacOQmNLT8hThrfdHXC9ZAkI8XyD0Dpf7EpL+AoC9kpnfZH4p6YzCRxKWFZKfi8pyXtg4TCud5SI1yIH8s5D7675GQHjs=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxODkyMDc2NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85NzQ0N2I3ZWM4ZWZjOGY0MTUzYWUzZGY4NTk1YWQzNGI2NTIzYjNjNzU3ZjE0Y2EzYTQzN2E0MWI5ODhhNTBiIgogICAgfQogIH0KfQ==" + }, + "_": { + "signature": "kEdSje+z3EoBHNAjR4uSjus2Mewppfwilc6jgR6YeHA5DQZB1ondTBCxjBv6ZWUZmGdjXlDdnYXlONRf43T+u97Utg3JC8mfNfOusZKi+6RiySLApjxPqgGe2sLor/q0XkcfWEVnky/a2tbwLXVQyqBV4BlSgf5gZR0Zm1Wv0KDfF1MfIQ+3eKe8xL/Jq5FwHMl2tubLj7Vmi9xYzmEF6GMWo0Z1vNTVYzmI3kSncpIPIdlV+zU0qM6UiquvSLy72UW/79I1iCuY21l3ZNHoRJ4TZobha4uSF6lgxnNSgxsoDeL0kbvFTLeK0SDR5P1p8M09+EzXTYYm8QJJmwkXR3NAIbJVaMmAuLJhojcOjSqquf3clA+Za0Emu8VEgk/6Y04tc8Unni3aPYUQgD3XpJIE5kjnof1MNsmOAJPajZyk1WSAoW0UJfxqMiv46KebeMVsLwB2/FKaTUBWiCYazxHRcZ24QVZa0f31zyauL+SxDL6Yb2DvwbHuEPNUArpdQevoaSmGrAGDBZckUg3Sn6Du/wr3K5Ssj4RN54JTNY+X3Be2epS/ao9JnhQAjCjltSl5Uy6dTiqDeGwjYUAKBuHMogMIVIJAQ/BNT54X4UVKmaYPE0z3HyUayjt7OucuXIY87MhjDLK94hhR34kSSDbKhAqX+zC9BHjXAi6r3ME=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMDEzOTQwNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zNTU3MzgyZDU4MWE5NzZkYTc2NmI0NGNiNDEwNWMzNjQzMWEzNjIwYzE3MjUwMTczYjI2YzY2MmNlNGY4M2NmIgogICAgfQogIH0KfQ==" + }, + "`": { + "signature": "wLnARduGdGqdj/pK3GjyhcDZhGr6DTLzmY+Tic/8hBKAmKWmCSDkWQZdu3adxHet70KjN3bg5MCgBSP01no2tVy+MNnfdOm2jL/izcOKx8+0N6NsOyifsqnzdNtpgnGb6w9uId+F58c8sjGCKN4UbKXIIs8aq+FjPgqrn5zoSppZ4bF/XzxfTnZay3fgOWNck3KTz0MSyAa6tvHkYZYS+Ex3Lmdp6oHFmoA9gXtFk4fUgzr4FxrVK2DkPt2TBqR6aindHSagxXdZbOzmnanwxySTXDNLLLh/xqSVp7Bj7E1Oot3iBUQbxBrACy+E12eAEJ16TZlgZEmcD2Hn9DabYLYTe69qnj1UutS9tF9MPrUfeCQyAGZ9laXwcWdbHujvnhHU/9OUYWarL6kroxyCM1rWna8XUMt7cNH/j6x86g3p2vGZroqv2/qrNNN3Z27OHwc8Spwmk7rtD/2/dUus8dAPCif5hVj3GA8fC7iYfSkt6rJqSAaNAmHaMX814bL5QC2vAkFC4rPgoaSUW6ou2IFYUEgjJN0wr5eP9e323Oa0n32uPGD6NnaYDWipH08/91cEKfQ8kOiksuv4ebyXpI50Tx4/Purq5mo3l+a8JBLVvTaLupmLzvhH4ZgDXdC3qeywg3q94QtKfIwHpMd1XHwpBfgwX8nK6IVCpO1VkrY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxNzkxOTE2OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83ZGZkZGM5MjUxNjg5NmE4NjE1ZGUyZTQwNGYxZWVhNmFjOGU0ZjQzNGQ3NDRiNzJjODNjOTliNGI5NGZkZmQ1IgogICAgfQogIH0KfQ==" + }, + "{": { + "signature": "JThVTD83C2Lq1XfVYBuOAMj4MuAOBrqiZ0hAxtoxTs6zGu/xPeW0le7hFAC7BdQ0qiOa33n+hbC0iX6ykKroCvhAFhtfhp7aGR8h84W8o2Vz6YT9HRf+loN4RazSSXd+yb7ffqKXlAuSTVZtCybuLksR/Uz2R46k7qr4e032fX4LzpWaRQXTm3MoKW5K3tFPPKyT9+0CY9QoVBHG+iTrA8OYEFdL/l1k0UCfErompZH6O1ouQ5MN2EB8PvQCznCrC6ekfYPhQWnv6Chqzf6xQS5/9KyYOvAyz52vGbC10p33PtM2jQ6vmE+gXr+Olj5rctOg0L+XlFc4BiNjEdYTHV+aGifhFaMQ+F2yINwOtuaq+o0n72UwRC9LslwyThVCbjVwiZ2sULHBTfVYwphFc0LX6dNrCQqqBLRbEotQD/7lmO0lOOE3Zel/dFicZLCdrcuIaMImYOEnGP6XqepmiMLcV/DxBKOQIm4cFNBnvrFg30XZO8w1XtxY7G5y3kj1fCWzuuKSaiUYzqhR6kUY8IAVDUT5xXH2scQAQ/Jw/LQN6cE9MDP4j7+6NtjBG5kDd4HqKvUun2Cu831qNQmPIOs26e2HNoXT7+MGt2VvEteBTthJRdi28h+fI+euQeAYGHIP+zwUvZu8bX3o1oMpoOmeDAdziUXC1L5y3/ThWZw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxOTEwNjMxMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80OGRiNmYwMzFlYzA5YWQzY2M4MDY1ZmQ5YjhkYmE5ZmZiOGY3Y2VhNTcwMDEzN2FmZmM5MmEwZWE0ZGMzMGU4IgogICAgfQogIH0KfQ==" + }, + "}": { + "signature": "SC+8S94yu/Fol9FsafKp8fWxT9veCQmq3/Nzzzid/TM269ncc38WEVa0VzUfxFDjKIk/ISbP7VYwg0xCEhuXUWmT8GJldG6wnf/5RZvn8QnFoD7+1piLeYRh4R2VaeI4wBa2lZv80g632LEYMO0AGSJdMqFuAA9Zn4lMJECifNWxp/VDs44hz+hke88YI5wuuAH7ommBY/6fukTVLhV1yyPDVTeo9K37Vc0Zf6tc6jTxXrDlYhQQLMgLcdFPB/cKcPdCsEOjHHDkmoofHXKq5IRbykSwS5qCvdSmXVAQtRhcHCc+b3eHsezjnzrKE14HREn+UyC4LVcYaKzvC+gcVzXzkfo45p4dRbrKdIRGGdgW34dUE07cyaeSaGhBPyQE/eSk49BVhawjaPbbm0ZVJ781iTCSQq30YL/e32Ad6LDweJ1LSqHA2NHpBVjfA6HcG+OtrtjE7Ew1uC8q6G4ZZKcjodrmXKOQLl5Fqg1TCrsfdeqbfgAdcP9BJ9VUK44z4jHtrF8fiz07d152JFZrZVNrE3RCWFcld3vP6qCE4c0l93oP3JzYXaiVeDVKMi12ePuw9XmobX08pyNoSViswiU/rjynMawC07H6r3JuOJ/j1C/mlcWWBdq9JGr8lm/qklGo1sPWHX/ygX028I9HEpLZP2cTM1s1BjLfTNz7OY4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxODA2ODk4NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yOTM0YjVkN2I4ZGM1OGJiNGRmZjk1MzBlZTgyMTAzYjhiOWYzMjhmYjUzYTMyMmExNTQ0ZTJiNDIwMjNhYzA0IgogICAgfQogIH0KfQ==" + }, + "~": { + "signature": "a8PP43FvQOheB1lvZvD1MYxGrJVy3I7yppHRexiWGeoy2w7H3aPvCaHlM6x60u/4zhq5M1NtfV8LuNf/NKTbo3auG12eLY5p+Bv4oDXyNMzCaVVr+1w5iEfsc8pvSVWfrhx0/cy8Zn32hfQJssz8fz6QFCg2fVJwHmenPNu1qEfPHK51XOW/J2NFgfD0Wr7GiiZ0Mx56Iqsy8bA8A5+/12pHEm5w2amPw1F0nOFE5j69S2zRNOfv9RfTyG5h7TMf9q//4o/+uI3yhlRbvgIIIwJRbBWlq1nsuFmN4vTRc2ObLpRprFHDHdo1yGnvQtkzIP69U8m8NzIdvawMhvtILFv4y0uDggwHhfsVsHdvKSbd7eJ9mEVlavxOgUMp1cJtRnk6F/et9wr2h5R+Hf6twVaRVBBfJRaTwLVI89JpXT7qCutlrelKML29XwTTreufj3m2S8VH2YmNLAuLrjJiJY/2KJIqpWEy1BjwW/P2AQhncGFlpMzRGkzCskEyHRk0In0Por/9Lu+sk9EjLbMGcVeqAmeD4toFU/HRiBZ8QTiommqqGAnIQO7eBEbwOrU9TmKz8r9kuix4AuaTam5OdpMCYPPFjBEZz50uzT4fTnt5/z2aYadlv6FjSJ/hSJtuALk1UZ/wYZ5tMznapWidlE2MQ48afuzmoSlvFlrtU/M=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAxODAzMTY1MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iYzRmOTA1YzAxYjUzMGVkNmM1ZTBmNmVkOGVjYjI5YjUzZjMyYmZkNDRkZDkyZTc1YjQwY2ZjNDM3MmRhMjkiCiAgICB9CiAgfQp9" + } +} \ No newline at end of file diff --git a/Network/eLib/src/main/resources/skins/chars/white.json b/Network/eLib/src/main/resources/skins/chars/white.json new file mode 100644 index 0000000..cb965ba --- /dev/null +++ b/Network/eLib/src/main/resources/skins/chars/white.json @@ -0,0 +1,266 @@ +{ + " ": { + "signature": "HnzgSJx4ZlUFJ7VYiOpeCEQF6mIeISLcxdhj5nAiOHD5p3XBKq6XqGbKTdeZ8I5lGHQ2ssam9a9Aw+ZAK5uAdngoi6fN60d9i4arWlNrOrQFY0t4CyqcEquRDkvYgxAhQkUI8kIgZt9ESTocsc3zmkWMERTdnMasTakrakTcNTIO6wARLo23V++o/7mzLWyW0+/hKH5Th8nQl3WB5El7sQmzbAN+tps9qTVR0gMYkI0wODW5aLygOO4P+DG5OxkFqZqGvZYRL0140yfEBHYCtM438SmPJKt1nVkIBB0s7FIsrwA/ByU9Jq+hkzj0fD0OjI5vzABop8HC4qzfHn42CGA8fAcGrjvIr4avM3li2Robf9kWcNbvi0DQ0jMvgjAwyeIFgNchWLGjPM+exO0Q/RxgOJmgkDQ3A22wNSsPDUeu1LiX/oA4puVypcDnQotKreS33YsSPp5WxZM+GWkJ5AeVxv7WsQ48Rb60+ZVyKZ2BmaFvMxY7cyZZu1xb1SEWmw1JnoI8kmXEByhldwAT9HZeLXYDhOeE+0wp5jJX8rsJ2lLpndbqCqRk910G//ToF560D4cewH6P4GKCp6xkjZ1bBQ6Gy6KLBZPqpzFiOX6lnUrNuo5SH1IEKPh/cg/EexoynoKbhJft9PsADEfd3vNc7ivxd6DyJbw2XFWH3i8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMjc1Nzk3MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82ZmYzNjA0OTY1NzA0ZWFkYzNjOGVjNzc4ZGViMDUxODcxOGJkZDFlZDc0OGY4ZTkzNWU4Y2I4YzVjOWRmODBkIgogICAgfQogIH0KfQ==" + }, + "!": { + "signature": "WFsLdQluiw4cKAX6g4mXC2x9ARhCLJdEoEITH1Wk4jM9kfOhHBBKsZ+clue6VCD33AOgHWkUNXopdGLA25wmF0gEBl9veLiXiZZmWQ4SNFIS0tVoqupgvGoNtZ9UUx0Fv1o+NZ6PmgeQWY4WxRBa7BV8SmWS4IYSWlOdOfKv/Mv5n9IRY1IQA9zuHzhZVgpulHy85F+HdviDuK5WgfcJGlFzFxkbiErwcTktcK8s4BkS4ThBjNYvB2QsrvJHgtdR1jzAtu8YX4JEhzUYSBVVaHCqnjJskTWPO68h2gEOSHE6vvPXU1ynIiCaqdxvJAb6/h5W5Ev21mBqI7xUBx09ZOWTMFu/TS4cojvio7gzSCiOy3jLJALp7T+uX6CBZeLJRahdMCp8x0EN9Eaxdcn9DZ1m1WsAy9T9WmtBfJW7XlVYNNWv/Cn98nau8JwHi9lJnA5gSz2TGlldOWfbJ6GVQOf0cWS5X1YuvZ250FXadTUeNLRi/c9E70igUX6ETWE747XKoPjf5O7GuVRZDjqgc4azOmYZ7CQEvLJErhfItjpxTXn7oXqz2uR9+nGuJk4L5HlnlCflmiX1urdtNIBWcX1D/wLfrNhuzQJInzQ3TEMFsW1zkcZ5UnjQBngs4hHqLwdQ2BoE83eb2svNVV1d+bPHnUv2mep8G9tzbOwB2Lk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMjA5NTQ4NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jMjA4ODY1NGFiZWE0Y2EyMTk1MDA1NjY5MjVmMjA1ZGQxOTYwYmE1YzA3NmRkOWVlY2E0ZWJlMDRlM2RlOWM3IgogICAgfQogIH0KfQ==" + }, + "\"": { + "signature": "cAmKuTbFVtEILPRp74+7M2NoXctQq0RhO+Ojb0LKsYWAeSs3kVDZdF9zCvHLAAsrBNh6nYr2l6BqnUwv3WA0NG9/00lz6CJ0Rqd0av1f/t5NFLmvrva4QCq4nWcet7knBLPVY6qidnNupZ7nzfKWatn7wsq+juw+K8eb2gYUp/1htlzkclmUJBDu90rmCJbfHWFFUfPKYO+OKfBEKkkerNCGgd65kL8H0SxZxg1rWHR3dw3XBPQqd91vb4pRj1NUt2QHyiL/DRzzkDbXKzCZdBwIk6VvkNr461ebrj/ryznf27bcA5DEZsa2J/DgvjuoxZ4LD7kZ84z1XbMbd3/dK7LWlEn3no/561+GuinYfTXHwP9DvnlFWtcSzcTr96tF5qJh26QpdMOenryhwIWDAe6zVMkCkPQwMYZXjtMi0SKNLliJKdAMf1OOaX6nfkSVlwWT60pK2COTqoesKqtMxwEAsBQG8MoneOrYMuQJ0FEfoVhn5APPte70GuSK2EG32e9HWIew3JfpryzNM0K+XZEd5IWPsRPhTEKU/E+6IgJOo3r90qGE6XuM+KlFk8nqp5MQYy8cABzvPO/dO1TDNbGI5ACPSk/pUXOjC8NGdg1sXMeyWV4qjqKPVKvNoBXH0///5mLuK17X5DaH/MX8K+t53DndQAsMdYhjLNEcwaM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMjQ5OTk2MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jNzRmOGU1NjBhNjY2ZjU4MTE0NmQ4YTY3ODgxMDlhZjNlYzI4ZTBkZTc2NWViYmFiYTViMTc3YzM0Njg2ZGY1IgogICAgfQogIH0KfQ==" + }, + "#": { + "signature": "xYz4zlTQZYWT3qmrqjaF4yeTkoPXEXrL+OwYgKi9wkykvLXH+bI7j+TLF0LVkN3LjnLevKCPf5JXuVm//zxih3pDHvvsNcD6W5X/o21ubwGtXrtBWQQHV9bEX8nJT1tIq7zYJKlGIiJo3zBvM5AdH2WVErf14zcYEVCL3S4OaHOdx7gOx4ncGVwWpN7QvaFUkfljEHx4cYHTqf+2SGZ2PmMwPYqOacDLjZPvZ4O+e7ZllX/imSw/A9b0+S4oKZEoazF7aMqW+S398S8d7HmV2yqCWFw4C6yY7KFWtdxK+/5W43FEgf74GJfIox+pjal/dHggA640luy3bf2kWi/5pdzHTROl1Z7W9dCC3cUVOLfrSl9V0BUnxAYARv3plM2GBrUM22uBfrLyUttEcZ0s22aN3BQ8xmqDmx+bB8at3kVAeywVPFILF+m4x1iFAm50mF4GCPhYS0VpsHH0qdG9RGT/tXm5nLTbLqJzRTPvohR2c4/ac9BoO15hmKMgm3rGunoaw1fu2JxLd0HA2D63WaP0b5g7iO06Wyu3EiFGhXK35W7UReHuQWA6jL4wH3Gk5s3FHZptKBW+PRpBWLI2mQnHxvT4fnlLC34r+lDYi1WUgXsaKAIRjwPZ51HAOus8QbcpwMmSr2HGjl5v3azvwXKEQgVkV6un+J+ZgJVnLxc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMjM4ODkyNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83ODQ1NGQzZTUyNTE1ZDJlZDE1MGVhZmI5ZWNmYjg5OTE3YTEzNjQ0MzFhMTY3ODcyMzZhYmEwZmIzMjRlOTQ2IgogICAgfQogIH0KfQ==" + }, + "$": { + "signature": "cpppplU6zeEJVuXLA7meNteU5fhS7VetdpGSoY6F6n0wDCnJOBbw2jX4WPpg3S/b+RLbAVpvkmMUmZgX+DsKImRko6sxMxbHhyYH0/CDDyC0aferIzHjH86ZWrecLNnpFyUoGc7waZOJ8Gk17kX/HnqhxcVCuDO7gWKHkVi+ncan5NC8U53qLhqGxxykTtacb5aGqg3YXWrywvKFTDfUiCkJGpDvz+j21WPxrg9IuI6fKMKTDtrK3ILhEiT7CPEEOqOytnvFsuqUlR6bRVouTjlcs3OIkDvXlIvRqgCW+sc9OYPa1g/vKcTJMT6ac2MsxdFDmTrTgNSw1KVrK+oOqYEqpdXSaHspNYDo65AZF/WfHZhnOcvtI2yiLJc5YGeZ1DIUo4ECzVykjh5hjRgGKeb9AkMZ8F8BbakIMVXo3K5TW9xqvg2SXtCA/+5lFnK8VPzWHgEIPj7f+7z5BrRbx9TOf841ZYEh9a52Ae4LjZFx3uCaCCA1eWPzs6VuipW0oX02m+VuFV3+Ibd8Yy0+P1wYPqKdAw7tZeQJW09H80lY+FhRLCU8bt169vElDSVrBHrP7XC/jeWFLWJpYoAScnGps2oa1gOPT3EVeZis2doWquByey4jrwGPw1AV5TT4cwcAqLNu606980qJLvKlzM5uS/4ASbAkukWMdhve078=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMDgzOTEzMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80N2ZjZTMwOTIwMWJjMGFmZTZjMmRlZjUxYzQ5MzI3OTQwMjcyYjk2YzgyYjM0ZGM4ZjBhMGVlMjQ3ZGE0ZTQxIgogICAgfQogIH0KfQ==" + }, + "%": { + "signature": "sAyzsH0LqRvv9+jkQTKLO8YxjcMGYdO9lqNMzFTd39haLDf/kzb7vwiknhQe+2sXoPlGDcFrHS4a4UzBIQyyohsU7E+gbLtXSK9YTHPcxidLawWPWKWBy0Z7gX/YcaSE1JUlew2f8WemGBpWtl2EAWxXREXw1JZ7Xc80oJYDltReYDi+sbIoK5xzJbYkHIEUomhb3dl3Qkn6Mi69FRJVU9VF27G1/QBWOLX4Tk4pvIBgNRojrEKNj0CyHFoCNro0blNxbiLEPLk3Da+/1OEsmuBRulfOISWE4zIA/T0j1TGmV141GLYixYeDeeh/ouCRFK7Y7ll3LzqRyqsM72BhJmbfvdEOkFuDuLjFkGmTkigcH47R+zXIaKzpyuJAjoXRF6ClAqh90qRg0ZXqTMOF6WYmfWLi3e9yRrjefvCUucP8+QnzNBlDGjfyuX67g3eaeo2k5dHMUSjTCylja66SEOiD04AMKCw3B/4aZPoHP8aFEJr1JH8jkPDkBB5lWoxh/52d1sRuRy+yNo0fry9kQtlEgUZNsLpQOnshaW5TQwKek8B7T2YzKBgNLHeXAEp6m56grQHCjvAcSrq7ASLunKtNxkgJg5BxtOzDjhPAd3TCuiEYqKYcIovG1T8Y777fboXaV2Jq3vZdFfdLOBOuoTtSF87k3NNR9IRSWK6XWe4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMDg3NjUzMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zMGJhNzhkZjQxYTBmMTA3NThiMzZiNGRkMTZjZTMxOGMxYTM4MjE3NjRhMDVlMWFlMjgwN2IzNmY3NDk3OTAwIgogICAgfQogIH0KfQ==" + }, + "&": { + "signature": "icA4IceqUjtnqQKX4ous1FhmsnNZshxh4u2TG2n5Eg8Rsy67GOQphIk85DhJi7oUi+gZJDsAjbvLTjasWi2J+Vl8UdASiSs+RBsTqAjxnZ4cvevJHGJtB6c6cbNpexTsTTYL5+B3gE8cEtWk/3KqfBaM27SXfpraQNvlsCqY/OAeHwFgFLKflyTzB6XkcyQ645WqlXvDV1SScIr8ahmugTgnVoOxSwp2a4wSLUt+dPPeB9fzA+qgDCeTw/E2NyyC2bPwfW/I1OdLXvhWgoqmA1jMqiaDyY8nsjGj/avj9rk5jsnQbmPwtack16sL6peNq5W3RRRkyaugP/87bMVMmdRsBz+PUAUzOvey6gbMPvrdLxTAgefwe7psmpMX3ooFJX9gtQ2adVoTRx9V2Kf6MGTYNt+EONZ88ierh49UflY4mLWhPErFOgIgDhnQStWcij/2zyg357GDidjyHjS4adejrAx/4mcflXY0rKqMRUYP3Po2HOdBD8TsIbpQb8OiJE+fSa/O/rt4OygC6AB3uN8ikBDIdDSLcuTjE0f4E0Do7BaUZfmIfXxX9nFiLb+kTQRnajIOpgG1kNfoTXxZTAiOVZmrmR2URbyCnW86Yz3fm2ez2hLQcCB0MTzNEGaCvOpq7mZlcCII4UI4taDK+H7oOujr1SmW/iEYAYI6GVk=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMjMxNTYyNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83N2RmYmUxNmM0MmQ3ZGU3OGRkNjkzYjRhOGUzOWMyM2Y2NTc5YWU2OGYxZjg3MWQzNzAxNzBmZjNkNjJlMTJhIgogICAgfQogIH0KfQ==" + }, + "'": { + "signature": "a40s80jpDWaLrmhTyRUwCNOhvmW3atqHuNTMI6WCoJQm4sZPpIabyv1AeE8PzXsHb9nM/AnRI8OaMzIvvRgWWvUpHIoqDY97RTvRnoYALko1sD+QfVl8yRTaz2EKM+dblIC0FBAjpI5kgFuceau6iXdWQqObDcR4yAPMRR7bodUspsNAl0smfkvrUH/UVy+43odbh82lmRpkves9eZpueDwfJYwrCA70wAUL1rA7M0BV4sr/flQS7exmzsZyHMxWmw/v/6DExqknSmwwFVaJJ7Nm+C2/HsV92YXTJfZd1IWaLkN8wvkMxhNyHGhaLoe0g2Z0KCMb7r+6UuriCra0+Fltz7FXxgxmgvfpaKW/i2DelWxHWFYezHbO/U2JCru3mrSkr69YdoYPNG2/7vTC+OO04L0xwU8SO6oeEX9fARnNujTVLr2dw6JMtpB2UnyZZzBeOVeAWXePq2dRXQkW/ydhDSXiH8jPr4UpeCnzT9jk3eToUuBnUshI1ePfz3pTMuF65+FlYTMja34/u239JFNzy66ZUR93xzw9FOu9pud1L2QuMq79g/zA7SxRzewXcd6ElPXznMFhvBxoTPU7NCxm7JlPhIxBKbZs7qqBJzHale90WW+/+aDt2s9ba4hRPSgDTnuglirdKQKbUOZ9+iZSc8T9QGi9ledJ5XDvy14=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMDkxMzQyMCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85MGMyNjQ5MjliNGIyOGM5ZmY0OGQ0MGRkODRlNGQxYzc1Y2M1YTZhNzkzOTkwZjgzMGZmYjNiYjY1OGVlMTA5IgogICAgfQogIH0KfQ==" + }, + "(": { + "signature": "SUT03eVAfm2tVuITya04SNWBajkQWeKLdU4ITe+EuCnJ3T0jPqILr1Ldspox+KlgsyuwWvMSxSxDH1AaO3IvQ/EIiJ2glQUfznV/qS0YDuN5wrNPRxrKqXrdMLgsl8x2kCNytOTIqXHETP89u0eqPogZ/jdu2wWvd3De7h/DuwKk5gTEanToL2OE2FRGldQPOa1cCgVWEpvh/++yaMVk1dS7pWX420t8SFS/5BPPusXbxgyImc7Z0ajKlMM13DEWOHiRb7ZfLU/VgmyKdTi/lvzk5iGeRL2LMmLvL01nKXgdzu3B6gP6XypX5KWan+WFqa9NbDMzcFbFUn+pReHA+Th+DO5bQDyRBZYBE132bDyFu7M2oKYmk9PJ5qNtgIBh4DflvpLPlrcskzxlQsz7rwpN8OPp3TkpMYZGh0QGYj+ua5A9s++sVlYya3sxgc/+8PD8DYy1YrBUU2zHWUs6x+azanM5PcZVEp2/USi5qsu2VUAgfcul5+pX9Ynts//gVaTFZdmdn/BuWfauO5Duk4lF2sS+u37XDdemxMFkwpdnVLHb8HWetl4bG6izMTlTbBknCdnrweDbPwLd5Cp+EaPo0baUjjM66c78dKa5kMcyq+CnRuyrMHKS9BMGbQcHDXmyCfj7T4TMLJ2EnQ7JgxCaIfcuEP54zWyRElMWgbI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMTM1NjI5NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83MTZiMGVmZDkwZDMzNzMzMmJkZDU4N2I1ZTFhYjEyNWQ0MWQzNzA1ZWE5MzIwNTBmZDYyMGM0ZTc3NjIyMmFhIgogICAgfQogIH0KfQ==" + }, + ")": { + "signature": "aDZAzB93xbkuUBX8iRfAHlKWjuiBeKnpqEvykhZmwkrWcLQwZaLfol5fydVZQKgcvXNVkHNXaDfiSw71nviw7ccBa1CE1IvYPCSzmwpUcqM1vD48lcDxxOknBqpTZFv0fa5Jhfea8r6kftEZYLg7GqNgeitR07YWjpD+0+FDV0Sudo4U+TBIH1bQsNPP9Ug4WDiAYGzCYh5Pew8Dxy/ccesNenM5h7s8Cks7eLPRygBegZyKabTOHlboLybsd6wyOZLCNOuO0j1szZtn/al/djyn8IXVgsuFivMIoxg7UNQv+VEr2v5+Lrg0/cGN5y9NOta69HGJhDmjQDF9AaI0IH0OAg5vJyO6NMephf+QwMcUHAQ9zcvO5g+G366Ch2T/HASZZthnqoKhkXclzLlCBw5v64OJoa8WfjNqMUKtdi5XT6P54IdNkldwZxOtNCyWQAuDkCaZ6BWEOa1lHyDqxkrupBftgnptrE4PhfTQ/ymWRxlRlZYyD39xEi7X8FvRNIoflt+MaJXXFC8W9/g8nZX8HQQefExl5UTSFQD/uiJibqjphEzl+2PJxlFAxuJD4Mzryrmi1g78Qx8SVAlgfJyfU2OZLzwA+S7C5fbMpcbNH8dyUBZkcODGkoPJAvtM/2L23thH8+g6B1NsXrcFr2k6FO44z3C6iHu5aBWUw6c=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMDc2NTkxOSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81M2Q2NmJjNDIzZjFhYzliZTMzZWQxNmZlMWVhNjYyYTdhM2FiOGQ5ODcxMDgxYWQ3YjhhYzM0NzI0ZDhiNTVmIgogICAgfQogIH0KfQ==" + }, + "*": { + "signature": "pLZ2PmjdOimPiYfRy/9HaND3YJtwkLQn8VKRvLHiB34QpP6eX+dcNwI36aaF/jyLV7cLbTl+3IZat3DIhBHuPE+/7iQyUP6NJvqBO51N1oC59Lj6MvCddEG0x8jxY6kxchlbqDOe8WekEOFeBhWnjMTbzy6w6g2BINUmBlwTZiS16T5ugdwa6pBUll5b/dU+NjXKvm8UZ20eIhl4kXYYUzv7DXGXt0+cBs+z4cwe4/gkqPOerDjoHNWBVK3wNYIYjFa9pq66dZlY0IJLXppsQdvYd+VWHArx7Ce3lbQwL7kGdJBy1f5JqSOpNrW+Ukahf/imRasLJTBQso9I2gEfEDBptMWpHTrO59ghK89qG0xPxkQmOXJfr7qLSBYcJIwNwoc5XC5V9IjaeYMEjXEiJe4rVjrHD0Zbd2911LJ6YY1QU3F8MLy3LbY9O0trjrfYMKrzybwa8GHrrvER8mhLc5KQdHMd0TGpjpXgAiGO7vNe9PvtGeA2KWwAq+dONnb5Blyhh3quRjYrtAUsLxUWrFavluucySpOOvLGs/PXHAzLNjnYaPUYgihzxH3ZC2k3uj86LesDnNH9CmF7ht/Kv8FHfbFJbQkBE84RfT9hmQ05e+rkoOWWKLlpoNRV6NjjlL/gXKp3i6tUq1/wGo08Qjfc9PldDFK0TWm9CZ+MYug=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMDMyMjc2NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81YzhmYmEzZTA2MDYyNzRkYmZjYzEzZmI4NDFkODA0MTJiYzkwNjY0NTc3ODU0MGVkYmNlZTRhNzg4ZjhhNjJlIgogICAgfQogIH0KfQ==" + }, + "+": { + "signature": "p8w0IHwTXrZPcSEOmiPv+M6sQPVVuOfQHo2TvgbJdNrE9AV2r2NiZh+Wx98izD+fNv6hRXcZeqoBuNrnLgrGWCbxuMjEdayd3PrMs7YslHsw7zFye/9uKE8MlgagPtyYdVIuMKsVdPKl9DdX0eB/lVb505H9zNA7Iy3bz7gurKal6DPGsquVe4WqY6Rhdkp1tZvXMGQbQeJX1sDcwoTO/DvP64GysQO/Ro8DgYvyxChcDX5qnGeJ8KUX75nfRPcWv0z3h5ud8PJXzgpK9NiXGFtJngKUPAj3Zs7sWOmW0mFiu6LyhC08OSg1dvT4767p+mnUcIQCuIy4+lH++sbcdD8h4YNM83/CO5i5NJL0Y2fl5wVGc68nGn8RuufhiTjpQ3aZAYnD7X/QV+5HXKIYUnQd+OxR0Iu07u3OMpvBA2qsCZ4qZvjkj9bUw7Blt1ztTLdGoQoDlMiEw+NHbRQ6rQPZWThgo37XCgvWz952uxP95bgPJCUNGhOt8Z9qRlcv0sqdNLx0ASj9s4xq+d8w9CvYlKDgGJb6Xfzr60Ze5/ZxjOPX8yjeVIIc4WCT1H+yvdU2kSKOLlkidC010WDowPO9xpd3SwCQ3JL9LE6tqIkEj6Ej8OAymD7uBTIbejPeWm40YCdiLmEwHKlMHC4D/BAuGkxcbebHre71NfUbJzA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMTk0ODI5NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yMWUzNTg3OTg5NjFkYjE5YTJiODNkMTgxYWY5MWQ4MmZjOWFiZjAzNzU4ODc3NTY2NGJjYjk5ZjViOWFjNjM1IgogICAgfQogIH0KfQ==" + }, + ",": { + "signature": "rDKfK2NwqbnqTWg0LHlwyKg+U7CDdW293Gy7XCURvO8UhXekWd2Rs0KI8PhcjvZDDih9DdgExzg5zhLo7DagRHNTrpbjE7ar+x8pBVzVTHOz+xClYsuzq6wpxqfLHtP3WmQy+BhyFphy4r8Ise9nqPpDUhuvsqv4FHky7O7QhEQtS8AKOWTvnuXK79PoolWyDHcbbcyorMEZbqu9pnNKHvSb058vaUMZRmzNff56KTbJ3xeNBcvoPVRIdu7pDPPWHptdU/hkatfiVsAWAAg6CSG5f4WbvlFzekhb0wbhMVyfXFHreS3w80oOwE9qWpPpMfHSWtGUu9wyD5/imboxDvM8Q4jmvWWzbgkyTJ9dcjwhpdFy5I8O3zoXQwzxdEhf2OWXadQFHT4YFZ21scREJexb86wCk3Xbcy06OPpl1JsR95koUR9VSHhvSrIv32FFc1+KzS+WqhlOVYyys4YKtNk+0BN6Gc2WeBxCb+Kj6xoLghaaauWWiI2grwu+NFiatG3en2Hyz1oNrHsf4eEHHu7uJ2eT6q83+oV5R7o9gb0yiKjnzpNdWyECYrjFp8/cF/HMqXu6oBIrblGQeROvqtj87UdE16zFVOOrxbm6NX5+GKpv2MFUKwum8evXmHGd0d8GDv7pPGOjcgfTXZ3USKlRTxPTf2+pbI2ndqjYDlA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMTc5OTQxNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84NTc3NWE4ZDNiZjE3ODczZTVkMmVmYWRmMTZiMzVjZGYyYWJmODI4NjI3MGFkMmJkYTNiOTdjOGY1MDFmOTAwIgogICAgfQogIH0KfQ==" + }, + "-": { + "signature": "FTbCDt0RDSCcq0iOWgThi7oPAAeKE7zBpbOwPTDn2of1DCTbhJhWCLayv1ruzCF9FE4XOGlikC0Q8SOAvAklDUCkMX3FGt3z/0s8XJrIFvptarCMiwYJCI7gyRCPdt+9/PT1lbO5ibfIEZfWsN9AgSWZDg/PTnEiHUBvZD0TADtRxnFD8Ph9VKNt7QWP3FDULERlMfhwHgacFwedo6KMFKwQX07wO2MppJsOiYbA8lxuwtYNxoCX/4yH7fCXAx9BYXxBL1L5S5lpX2RdLn+LYzeYQ12oIrzJj2GlyuMNtI923ZJ/sxmdIVLQ/AXHNbTUaHxJVjiNrF0IjigiVlsKSfzKggfaXQs8oQpXiOUMxCEwuKZOXlDId3fN25LTaW4+B7kmwHasSCBuuNeKR/Qm06HF/SBULTCkZabaJUAwWjEfjE3ha/g71BuVd2VlPPaxYcCARH0tlF/mndydDhCMlTAQ+Zj3ZzHqSos8W3W1d2vUv5R28jUx1umGl5lC7f6gTNW+/nWt8QXXHqk0hStWZmfVBPwIZFtug/KY8DkvLZO6Yc47Qz7ckdMOT6dXHHUst+JwUoJTCTcujKchTK6fAdaxY88xOAuj/i5rGMHgXMcCOCmeGHhvenyQhYk8vdZPuz1WYAD4YzKyDi+2j66tw59Xze6PwM9KUXng8EYzV4s=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMDk4NzY3NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xMDAzZTAwMjJjYWQ1MDc3YTBjMWI4Y2ZlNWM2NTI1ZWU2MGViNTM3YjRmMTg1ZDdmZTc4N2VjZDU2ZDgzY2JlIgogICAgfQogIH0KfQ==" + }, + ".": { + "signature": "GvJUbQZOeX+IktEiz8QMaKaBNUXEttZIDPzQoNCPeeNMlwDiXL5dZF8F5l6KIPakP+LVuMHAClEkc9ZiRXpxk9ERJImAlwxCgo2OreJxXFePsRK/3G5/q6VXFDOv7sK0qW1anwhHRiNPKbWfkC0PMIQGfanAPy9yIT7KiHPVd5Kt6wvz40yK3saofNKgUAHvWFBJkvbHVnIHEqo1gfNXTm1tORPqE0DnGwlWHzDBh7I1p0D6iQct+/8KnyrKP6tBvRQPEDq6WwuAJAxSufC2eNX8eJhNT1cYcIr0Xm720JIcYPytVSVhTTb2pttw1nZyfjCtWABXLMUYHnAXFUbV2jEVH1e1y2CnRLJxir15sPLAoyP/W001Gd2lfej4O1kl08TK6mg/FfAJAYa9f/b7LNrwN68h4NicbPno/vUSGD5+wM6I5V7+XGrufzX4Pmiux5IZBWtSddm38kF2IyfoAekCru0QaijSnNZAl47mfEUuNj7dNgXOqlYwDgiQ0cqPns6EjEdHLQ7QNVGNM6PQ0cs34RWHI6lKY32mN97AZzKFXOgPPhKcYTnGpgEXn2PEzZ3VyUMKWTPggsovhzTOqtI++j/vC8amWH6qNYeqF0dkSxRMwJ5Tkz7HFNzi/gwowNI54+xDFKMjobVEN8LA0NWVvahlzuiIlnwrAwAn8N8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMTMxOTIyMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84NTc3NWE4ZDNiZjE3ODczZTVkMmVmYWRmMTZiMzVjZGYyYWJmODI4NjI3MGFkMmJkYTNiOTdjOGY1MDFmOTAwIgogICAgfQogIH0KfQ==" + }, + "/": { + "signature": "u/+M2wSODrjU+g46/emF4gOvEvVS3FDvESawa8s8vHRocBdhJ1zL8pAZlYdASaX6FCnQBze13NC0VUHICvMiM+EOmeK3ULusplF2DkFNSKJ/70j+jH+OIr2wdbkTAlrzhrUQ9CffM4KERhrJO53NtpotGPCd2yeKIECuPnG1VQaZ0F77uKs1KUWpUKS4lzsBdushAHUO3WAzEv3rRACh7B8P/OnrdxO+tfdJ5JmcAY+U26QRXTLOpxU+IrnQmKM/AUBxwniYJBIgqT97+KhLoL5eHdz8OEHwE41OM/gAowQeliPEUZFHDIleVkjRXhlizKENhwzZZdMblNcsp0CNpU1UqlkRORyW+Lj7zfn069b3+ezjTTVQ0ftLvOkLzyRDQ3qSI/7JWkrpTMIxJRnsmW/uwPhef2XWd53wzoumQrf6sr0gvcsTm3mZLdY+8u+XV8Y/Bda3nT7dH39LC1+Cr1hqQwxGNDfiQY7ce83ZgL26Dq0miiUP8qZYBNuy/WE0xm0MnJnl4WmGZpqcqEZaAgIwnRiURsmvadSM5xJl8iLYs/5j6fxRFfcCmgRCUdOiIWDf401NZaxRub2eky67VF+C4hmnNoKF0iBimzK26FTnyd/0xfFFYi08AY2e8WqPs4+s7rkt+sD6N0k9BdrthH2gTc56Ge5mAondhHeZA+0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMDYxOTExNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83NGI0YjUwNGI4NWJlNzk4ZDI1YWVlNThhMTcyZDY2NTlmNDgwYThhZTAzMGQ5MWYwZmNlNzljZjg5ZTkyNmIxIgogICAgfQogIH0KfQ==" + }, + "0": { + "signature": "EpDtPU0b/HEUGWEuBGk5JhL+W7nEDnEYV42P5zwZI3TokvPhxnWsr5Mt2oi7ULJTF2Q6+pn0KsSAhk+eXaPWoNDmRg5mTUJ/MbtdY9mubUYtPjwWxMsbxl54kYHnEDCQWJLT2O7J2SlSLOSk91nkUgXfIDXXaYgQopTn0vc0N6kJchiKIyHI3DKC4KCoSM+GdNiwd1yddJKZCOHVvmyv+lfZ1DDCCObQL2smjbh1WHtvFjH/MTkX6vJ0F358QfQP8C9NY/o+XhydTPKUHa9wDkTB+BQVnAArWfXUmMKLDuhGMUwGIpgVDqP3NEnshOH6EU5yFFSnvzKZwNkwlqJX0CBKciDowSKa9y//fGkmH3J4XKw9HIZ3ugkS4iu+rmHSL4ccX6bVfzhuk6z1riu66e4zlGHZK4gKlZ5MTyPAj+a4tBcU19FCmQdfp2G92wofrghV6y/R9RbfkEPZ6LF/3VumeZPnQ6GWpZa0DpJPy1IjGTOsWK5eqLZUSfIOFEq7vdbDunneSx69iCY7LEG0rGHBBiM+KOWKRtA94k6K3w+PPMPFfz6yv60rkhDC0HrXAZWbgMnB/WhYZ8s53M0i7tX97ux6qJjw5Hd2tCrnBW5Op/3fjuFR1c4bj9M0Dtgv0gZkAwRM4+OiqkPJlAuJI0fNvaG7BBMROd3mXZAYvxI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMDM1OTYxMiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mNzBjNzc5ODVmOGNlOTgyOTQ1NjA1NTI1ZGE5ZmE1YTQxOTVlYWI2YTY2MDUyNTM0MjI1NGQyMzhlYzY1OGEzIgogICAgfQogIH0KfQ==" + }, + "1": { + "signature": "NiQFWRKtz7Zw8dYUMgNBGopGQKW14quDvZRHABmGZgYxAB3AcBFMZNBFaqElkzQXKuMly+/kNbXMuUR+FilSInlgavWv/P2yH6wMxKKRiusUq0cMrPH1Q6hxurrvbS6hNocXRH7x9VsUKpcqphjLSnsHG85EbTxODqjEwkOjSv1bDgPlgqTxlTv8Ju8e70aV7xKqePdyaVOQJiy3nBVb/uvbVAvW8gZXmMebkVOMJv1nmV7f3dmW9squQQGukBR4Cnkk+GxpPtyipiNq9LlOfRci7xs5zN+plND3kvkAHHWz8CqIGJDxni1CmC4Y18sJSQDrCrrnJIgUUztyMEGUKaKntv+BsywUKTXK/p8bK3py+VqmCxoT4fzMIP6ARrOjfPLIoRSO9lZiIoRucdzcUxfB5GZSbz/f/drt52arAuKh6nBwMCIggtC8aL/ju2VapPz3Kq5ZDP8v+1eDXDGGZuiZmTQhJoY2s+efbHFjXMVCF7Vpu2PO/GWKkE1T1L65Jy5xIovpEr2PYF4d5jTPqmnPvIOkpli4KvL7Z0KWiT/JpmEtXT98L47hjMlEiL7Wiz9J6Ph1mhZIEFTtZgfvosfBciyHLgIk5ib9A0/Y4gMVHWAqOiZ1IC2CsliMDvjnd5n0Is1AasLDkK8w2VieQM1clUKUxiXQFwZjF1AhETo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMjc5NDcwNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iM2UyZjQ2YTI4YzkzODQzMzdkYjljYWQ5ZjViMmE5YmE0MDE4MjZlODRlZDI4MDkyZDA2M2UxZjljYWJkZTZkIgogICAgfQogIH0KfQ==" + }, + "2": { + "signature": "S2JDfMz/cg0MSN1g7WBs1rCUJkgcAQ+//N9lvEwp3YFTWNBE5OEZkg28QnB/DyuWYVUw8MSblTH13YEKURCu9R+Ery/3FzgFWxX5BbnHNp1VZlW/muD1bs3VpTpyt7Ae+q/lz7i5JojPv3ufh0n1I//APx/+eqgEh35ABKJ/zUkwJQZPe0lG3dPLxxU2tQyMv5vZrNExJvVM+koOUpjagtLIEY65ORqrsGNhytbEd3PVIajFZh5RoQywoTTCtIN8GwUgePmgLcPTsPPEIlO8eSxpMzQYM1U53TGQJSyeGlEFuS1MjKPLpWKwxyRe2VmdceUcOtxmG1mPYIFM9AHN7+Is+lUorgzJIWEsVjbthEBmDLmfSi/kSI+YXwo3DuLYA7ObyWVchfeTZqqdwfCAZY/yvmBSQmnQsadR+Iiqa3eMJmG7a3oU+wVyIlSvaHTsH7O2jVmowH7YKLwopkm094rm79/IIZCjwIhyMPO3xJbklxlfaKT3bzT/8z4ISUcxUXEHweRKon3BXYjOlRkjgwh2xhC3T4OAJSvqfIZpzW836Ogc0xIfK+/5UBigPsHa+vqcToCBA46VtjXylVD9JtarkMJhYpuX9/FMCNYuI0N9HOyN3gA73YN89N8IVp32rFqqK+MpPDI7ZQm8tRAeD31X6Jcy/EqWQoZ0FaqXAag=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMjAyMjQxNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xY2EwNmY0Y2I3YzRiZWQ5MTNhNjg2YjRmYjVjOWFjMjA1M2ZlNjlmOWY2Mzc2M2Q0MjJlZGY5NjI5ZjcxOTNiIgogICAgfQogIH0KfQ==" + }, + "3": { + "signature": "xvnCb0Ickb3uFKkgpkVMSlLVZ4Ts8a4YSLNKxyU6KcQ6IO7hy3mrdNcC+cONpb40G4oVUiJIIte2LgKNODYdPF7KNOQqBrFpmY7ccGKCeoE2wQYR+iDewmyQwgMXDwKWHafWHaZJKb4xfNBVKeTKMuq7p0FlLDye7G02JPlNEJuPiHwGqeLaS5fyWFAJoUUKLDBQHVwCJCyZbYpOWnx/qyqN4vmDwzOFO2cYGAnyCzcCdWc2uDX0jONhQMOSg/AMpL7/2iz3fgsjqb4nQW4TSI6K8YPoVCNClRtrBh02M2b2AjiqAYdEMnl4HJK25hgUTv/n48oAre4vvTkfICQsCUm7srDhyhl6iEagkDfKXzsfGZ944Hko/+uAqQRzULhfu7MxqwW2SBRWzjs6zr3yF2CyFsSeVRqmfj1ABNFuudPNG+EZJ9QzsjosL1tWR8RCKkpTpYMLv+ZUlhzwcogLt/Xt+f1VlP37c43WHTYW99DASbwBorTK3cXu+1TjRwH0Yk1VRji0KI7KHtrJXbKh+As5iR4DYBjYvBrW4DSqnlAAHuKEbzw1XIfOKWQskSU/DAIsgCtNfUDgW6qAOqpwGNLABvH14kFMr6LxCWntaQuCMq8PdsxhknG2NtFO8UW/aU8Dl9pQxTBz4/Txp66EI/RET4+QiE0qBeNa+cBsiX8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMTI0NTExNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84NWUxMjZmZmY1ZGEyYjEwNzhjYmQyZDFmNmRmZmI3N2ViYjY0YWE3MTc5NjM3MTEzNTUyNzRiYjY5YTA4NjllIgogICAgfQogIH0KfQ==" + }, + "4": { + "signature": "Bpyml7xurA19U5Kilc4tVmihxNgGPYyDavGC6jHWCEXCWl9Pizpjggn/5Ea5Uhrp76yowaGs7Me5K0dYWQI62EqWZjI7INv2EovZ785fW75DtZvK9LLg4FW6zW6PPFbkJgvSIJL9lrynqRPF2DIl6Ps5ZNlyhJcHPVdHRA6wclo2TMdKceKttXd0OkzdQeU5y8saIAgoLUcxuGpGtPFnZc/SpdH/jtcHW5XUH0/Zc0B7DUSPM/ux7anbtxVaduuoJvUoRoikADzbyd7WpS2KdQRPA+d1TcLaa2ACp2S6CwITLsNZW/pw1XZwcRwbmCkH8XwEKmZEpwF/3dL1ddH83BE2G54Aay2tuso3eoQbOILpewIP1hLBs2VfKILun1R9dSdCQ8tilY6Ry6YnH5S2VfCB9eSaWxmfFsW9LWPH7O6Oen4MsiksZAnjrqkr7uJsUR3SJHTyrMjRy8dEp92fIV9S+vFkPVh0vtYkSi4zhSPY9oygbi9wW9h7gU9SinOiSXGe9Y/Qkl0rkFrReNJC4m7k7Ddj38R8wLcR35yW0TAjhbxjjTKw7DbAtet8tF/ZoGmFDPnjL9BmiUqQiQsynO1yxVNjyo7nYIfx5taqc3Bs3HTQC8K1tISqTmwErf9ro8QsRNJM01x4da6qUXt0KlmH7XAvA/xyi876ZtHILkU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMjU3MzY1MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85MjFlN2ZhZjNkNjMwYjgzOWYzOWZkMjI4OWM3MjA0ODJkMzdkYjRiMDU3MmJmN2U4YzU2NzIwZjVhZWFkYjM2IgogICAgfQogIH0KfQ==" + }, + "5": { + "signature": "QFKk/k65GCjvbFs6rtl5AhsoDH0G6R+RRXg9+RsONCockm4hrR/zuVESwbGJL3xk4DoQ2f1Ylop607kC0+cg8EZBlRaL2pKcIiYvWXE/dZoO+nDoV+j+iclmDPL0BbV+U5NkP6h3TFS0a4MpBBTrd7PgIEvgKJ4VyaJZI+qw5CBpHxwEOhB+zEwPcwFQ0MbTQaof9tqgvcfSpIIvbLBnitLgBXM3nFxL7hSpojcmaN9cr7wMUtzJK8LcwuwWKoWaCUOKghHyT/f4d66n39voRLr0yD1Ne1RbLeYp/Y5r2yGW2RRyH1k7LfqNKhS/nVeE8JYAzdM4AKVG/6wv132AI58mLvqmzP/eVh2n4F1IggN7xOrhJN7zpwIIlUw8SSycBGcRIb9LYMgFxutL6od+T/0roK3evOyCxDtcLKekECOND4RwH2I8ppbX/TCwpixMe44cxfOMVekgb92LCTwOLDjLHKrn/rvCxAwxzSu32J3A2tVAID8ASl3kl1NszsDcKySdI2aROQ/ZNCTMizPC/XIFxDMFtPTG17+xLh9kd96RdIavLoH66RRdnr5wxoL1x+zy7D7BM3ExAiCxg5d90R3rN3D2jSijodadGgd794s77WYfUBqF1mu5XLbiqU79qro5JYSH3woRasn1q0CS8m/ncmgPR+XXM3PGFZEzbk8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMTk4NTY3MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lMDZjZjRlNjYxZjU0ODY1ZDAwMGNmNzYzYTQ3MDVlMGEyMDM1YjQxMjc4OTFjMTBiY2JhNzhhYzE5YjVlMTBjIgogICAgfQogIH0KfQ==" + }, + "6": { + "signature": "w8nt8l9aSOc2x0EQ/O1RAZhqbz5pARTKGQL/MBitnDnO3wGsOB+bWRK8lzOm7ZDs+T0EivdU8axlzj1rZxnqAD3fjR+BbMVjFp6lyZkKayGWMgsxK98VJyS7SLqrdoDGAGFOgQi8FCfGdXF98mzx5UkqKG10MkPGWRhM02xrIYgUKOm2zoB+/DfNE6ae3KVy2KVCX42HtQEqHWrL045QxFIuDWeoIhP/khKMwByKljHKbZDUUqaUiW3ElZNnmZCiE9LAdo0XJU3Yi37iep9E6yHBOkC9mBwago9jXSML/2Cj81X633QiF22yMrsPjiEjeX3U7sLfGGoVa4VpT/qwj6rChtpE3K+J6N/EW9jmqDvxov1aioSjcQmpGfLrjbw9uGbfTZrreX+UKf0asNVdpzrl+xd2J8PdNTFUKVRmKjUCFoWUzI5QXQCYEZI60NZJM6BcKEN5XkaIfnswOjzQJuZR2an0Q5GIkk3FyOcuPOBaV5rSKxauI6uYqnrdMKWXGbAiGQUBhGkcLeatChkLIQQE5esatSQXrxyxnQBcZDY2U/vNvC3P97C4FE653ts67o3ikg4FI+Ah9quV+COo4Z3W3Y4p6mzEjObU1fkjlRzgVIRzox9KgvMcJdSlYVvaC5Rm6fGjEg0YhsahhfUKBzgwPOT41wsk4bAW9qLdd/c=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMjYxMDQwNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80YWIxYjQ3NWIyMzU1YjdlMjllYzhlNDRiODljMGRiZGIzMmY0OWU1YjIwNzVlOGE3Mjg2NTIxYTM1NjRiY2UwIgogICAgfQogIH0KfQ==" + }, + "7": { + "signature": "b7Tm86VvKN4eKqwTVevhxnEqkwoDPcntYtzxdZiVrPDXRUgWkYvd+k4KwcQdvv+UoJHifV5bZBt75Yuw5z+T/+7QhxizRAcVkJP4E8mqa6cxQK3yOYiWqDgPArkg8Yr+kiNt76/uOEf07ocKObEtXlhsdpIeXh4Ep42YKxWo3UV9jT5hRfccUeFftfi9WXvvLSWKY6xnMNKsf7g5Y/P0IvX7ng39KwZhYQUmnt1n5NzJKxYpMHWdx4tEhbSNZ0vM51vYOoaWs4MviBA2ExDyTgyOqclUvAdr5V4feKIW47S2Vc1CRrBSBNX5IbgjV0dMUvKcJSZqQRMUh9NkjL/1LHJmmKrWfTbrW++r6Rr+wncZKcjIen98LGLa4PfdeWyv296sGLQ9iw+B07ayQBIqo4wC+RFvd8rG/3JS/FhGAa91DtC43x0Bqh3cRe9td3n77dnpv8dJzFjFbgWPMg29A8zbzQOedim+PrDZo+kglnu9zqgnnc7Y+sL3tHCyt/N/UBYnplrXbSAoAK4s++sJdMxpfY5/00OmEoF1Iu8tgQn3CpHGt+C43KcfS+9Hy/ZLHjfSGLc1W9NWU9BXO/LPitUJKoQz1HzYotAA80DARpPk+uE76kzQu1ih+ZpTj1o+Fz7m/wPTU+8qYPCc/jZa+4vBRbnsJpm8M3HM5JIBDgA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMTEzNDY4NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xZjFmZmNiMDk1NjYzYWIwZmQ5MzJkNDM0NDI4ZTM0NmRiZTQ4MjA3ZmE0NjgxZmVkYjUzMzY0NjdkMWM0ZjdiIgogICAgfQogIH0KfQ==" + }, + "8": { + "signature": "uJ8UOKWB7UlE1asMVHGi0u3+3ROPGulh1nHit6Aa5ljshjKrYvLvx9/n7eG92Rg683w5bNUzCsNoJY1xWTfXRBXt1MOFYTvK0Nhf8rnYeBACdba7j3KE9blTBQWHqRN4kMcTVbaShXpvJ41YN/Q5nJFpjSs+QnhSGW1ed9K7koSthmUP9P9B15M+Rh3grQKQ7SVKuk/uYLPsCsCB0iST5pu1Nt++lQXEP/+t2n2+NMR7I6CT5hrvabwahwXABkkvhF+H3DwVOQVM3WTg2sEvfd/kO60wVfQT+J6Pz9qqnq5XQheRiOmmxoPunXQYYoEGdRtc0tNj55UAq7G94TA96COVxOICiwlLtROo1R9VITD2DaE1j5cJ6xZwSLXFS3l5I58BsdOa5bYHpYu+xYCaCBMcsVdaL+gdR/Zk+EguL2uv1mPw2mh5WzadI1WGGpB975xCZKmLGqruVwSAaJvB82CTDkT4WEG3D9D/RqGVXYvoHkiX7gMYe6VEcTdmGGXiLA8V34dkdbx/fWEXKC60a4r0N9+cSyP7CAKYypoGRzDY87DV3Cs5wf0mPKA4WhL1ICPkxICa+IPVGNzBRfRZnOkfCil0xLYCOAq4crfCY9LhAntPgm81As7oOxYokfsnAX5fVmRt+UeLbv8Z0aPouSTSAPxrgVG59x5xHB5MhpQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMTUwNDA2OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yYzRmZTllZGVkNDU0YmJiZGNlMDk5NGFiMWE2MmUyNzkwNmE5ZmFiNDU1OWUzMzY0OTMyYjJkM2FiMGQ5MmJkIgogICAgfQogIH0KfQ==" + }, + "9": { + "signature": "He+J09tHVEX1KzzEBRRnzw/8pKm2GsUnoyP3Sjr3AMj7zNRNovoqcR/86E7UKG7X/txBZFQ0DyZoUzAqsWYCz6OAPkqKxQv5t61OroW3ycVtjBGQ2pmUmAX7XD54qzIuCtHuYih2JxttmjOINWJI3qi6s82rug4KUBmcNOnpaEI+dEnc6EadWQ18iHRylS2+bB1F/GdJPj723dAjYGrHD94l6mlk89NfniHBjgl7Xhp3g88qN3s8ZZ3gYP2Asu0BCz7y+s0i3VTiz8+l6+0cYb/HpcEye8ms2uTHy+s1BLBoSJroRSQVdqJfLNbfw6HJpkNoIM8iufXpMWKkBCGkaF6y3EI/cUjXkySYMY6rtiK+VFI/HgtVm5+k9jYmMt4c+q6+pwBhThhrKIDtZhPPQbVVOuvL8Lm8EgzAFBTfiBIuWs4H3fbgyE5sebvkCn+HE5tKgAr4YvNpGcB8lRoDAJFImEHfH1SlEN3FraYJhpzWZRbIQ1bIlMj2NlHEsCOoZm5ZRfEevQ5ewhOf14HEy1xvogu1/CjLTf2wA8hF6RM0vlXUalr12yJJmunTHLueOm7wITPsJcTdT/uwAfWD9ej6MvYCYddUaWsC4g9RzenTZm1sR2GJ3Cb4VBVvLQNg/Kvo3Qy+etA+myckOJap2QIEf3VzeQOIvf81Yx9+w2U=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMjI0MjM4NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82NmRhNmQxMmIxMjA1MzYzYmE1M2YwMzE0ODhiOGUwZTZhOWNkZTAzMThmM2EzYWE2MjY3YWU1ODhhNDc5Y2NmIgogICAgfQogIH0KfQ==" + }, + ";": { + "signature": "XT+PZPXtmK62Kg3+FZiXGo2/RPfN/hVT9UV+s1AZOz5xq/qZv2QGRskkfc7fTZdMvCFPk0uASx/6mU8B6gvqudZKQUMJYzphWZiKApXC03iMrJ9tkiLusct91sDePAx69GxSnGS1buCQmoD1ycEdpQviZuQtSy/YCgBg0T3HV/6EwPyUNVzfsX/7nMQHmBaKi7OXC5sI5TlqOQgUAv2bfUY1JbhQynaNzkawzWNIoBodxPMQ/sWW62c1d/AlDIvPe2/XaGsa0GmKNKkiNymGq43iuy/Zq8ge5MlIYf366OpfJsGdOW7DvP8ra2YDWW5DYegFYC7BdSseDriRgm+ftE09N/7x3eda7o0NvXD9H9MQU6Y5kxy9mOiAxLXOq7xHt/zqCrDSEq5xY8DP5s3hALzCv3qP82fKMRT4oLSdcjqNWQsNbR5fpsNGXSbkV6WyDrHs4yT0yWlCpjqz3QJruDpf0FFhE+otIZhtHYOd1A+Gkkafzf7zfh3gm9tmgydCZ0Ut6meT/kgoTicD3kF3RjQWvbscbLblsb/ckVDmFmW3R6V26WZ5b9YPuSVnri6AvczxEXuwT60w+w3dY7yJA6f7+TqJe///0xIOFi5iPO7kdwNBjfEzz2Ju7hAOeFbNRRw7ny3b8ugvxjJMjGYm7kVczrSfkxyW5DMvFDWwS50=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMjcyMDYyMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yNzgzYjdlNTVhMGJlYjM4YjFlODExZjM3ZDZhZjUwNmM5ODM3NTEyZDdkYzBiZjJmODU1NjA2YmQxZjgzNGNmIgogICAgfQogIH0KfQ==" + }, + "<": { + "signature": "N9/cHru104P7gfY/u9ExQOFW8lrD01mYjpSZnQ0HrRjnlcTE+0CkZfb0+7pfUJ/LrlcHVRdhwKXWuCgrcF1EvKWJDxcv09aVc3ZKO1zLC7gmyr+SG/SU3hEChPQqMQ+H5t6LJC0Uu9NNAyZCxCynuwYgHRqP3lOB7pL4Wl4MvEauCzOMgy/kUhrqpOZKzmR5QB8m0vOMws2D02P7dBMJ4+OQFc83CY+PYfSzD5WdyGm2b5ETfceS55S6GYhe8s+dh/eOTtgLmRIFCn2bD7fO6cbecZF2NfZLLUf5XWshKTe2bu3Y/5RwiIt8SYokqO8vat/fmJh7/T8L4GTpcwOuImFC6i2rVn/ewnI8xoSeX49sDJQR0rIZCjn+n1V8+hRxATE3W1M87mJEKlqKdPYr5pgHWP5u/D2VYUKgz9hp5ZqjUM/xTyBLypMj60V24Gg/ySsU7OdUgZWutn6D+KufsZPl0hNwekhia1Fb7yZS4+5U3pG39jG2EwDfvFVgpdNqSCd2J5QtihCUS7MVAItwog7oNc357GniEbsPgQzUPjtC5UXQoWBynnchH1a+KoriLo54XNk0Kzqmy00FjkmvYJdPSIQ1A0NbkTi4+HY4CoxCqW8pgCrc+5vTbs6yYPg5vTqjIZGxe2e/lGNd+n/g49GVBPVTwk78PvKmYnZMSR4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMTAyNDAxMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lZTM3OGIxZWVjMWNkZTBjY2RhZjBmOTVmNGFhODkzYTRkNTQ5ZTBlYWJhOTE1ODJlZjMxNTQ4MDI2MWZjMDQ1IgogICAgfQogIH0KfQ==" + }, + "=": { + "signature": "N4ovgSAPz8XZulVKf20uz7rK1Dse/WauT020BxwezKymAQGOniyJDH6+9XQ5xkkF20JaFKvHX8jarVKh/GJfq35FO6rSlD93VCYAPjoATQiCA8sCMQH8mbxtaVdumDMLlFNlnw3S1G8HhFRFcwC8LUcEo0E5Tjef5favYPNxmhgU7F8NOEaAQbS8NmoKYkegSonTUSPYv0AW6MQP1C50ADXaBRY4isZSk4tRsqcdxeSBnDfUsQDFDQRYvObpE30Lef9uyS6mJsTott+0QLB68Q+lyGXO4HXhEMQSnNPuabSAr5svHjPiEd9YlPPf1RGIr21PVts9G13TanN3wbFcJNNCeNrPnzTiL/tFA0GDIE3a51VyofYtB0E1f2tjCkTj4kWK3uhoHLiZCNznvKQY4RTgnCM+s3v295/sPBNrVF5qQlQimD/0MUwsp1qZEqpXke3S6qkhb0auA9dZU5inVFlQDrd50IvwTkRkaBUqPtxvW2KR+28l929vsU7C+AqwDHgA1YUG3Tnn/wxFZt/n5GFwktkYqo033bRIrpLMnc8bVzxbTJ91szADCRo+/XKMY4i+aXrabOW+zEegg19xD5cl7h/VDWMhkoMGwzgS9brj+Ew8e1T80ZSEWPvhLGy7o7slyj1jmh0xa01HarqDjRXq3O59HkNKuPXPS53sWG4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMTQ2NjcyMCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iZTlkYTIyYTdhNGU0Zjg1MDEyZTk5NmQ0NzA0MTVlNWQxNzZlOTBhYWUwMjUzM2MzOTJmOWMyMjA4ZTdmOTJmIgogICAgfQogIH0KfQ==" + }, + ">": { + "signature": "a6Ay1xR7zYFnyoIe1XBgurSd/lIXyHTeFyGqoUTveZvj8g+B5i22loOqsNyP0vu1BrTN3q5OsNg6Ob7QYZEcrp4WbeslgabbX+IJsLxsVC1cwH3dfwcjOTvzNxSxA39NfRBgHU5HvcF9x9H0hRr1OEQ0cBbGQphhjbQ54Zm9iOgoG9j2uuxymXw510pCgS7ayKSj0nMsWZgZ2+NPuBXjjlW0RWJ7l3DXjkBjnxAsLAeJJYDhLiQR9yHt1mYVyX+ro0Ybudjurrt6a+KHU7jfkt5igG2NqNJpXuH5df2CC4WL9jwqYakxT2nluGOPUPIo+1bjWY14ZD4WgXw8kzbmKwu8ZySTZAtK7tsYy2MwsxrgtxNtWZt537F9j+xFwgbVIIAtXcAyKy4YaJCnkae/s5EdKv8+eQ9jesJAaEgLohFYujbsDk3sSuxc67An5/Ysnqcesj2rHkC6Xl6GcKhydNkaNMGZMEQ7DCsLZeVoirz13ElnVPOP9P0vYOg2khuV1h63RuKPs6MKI9GnmlvfxttT8VN/RU6ZyQU2EjcGB/JFy6Dt8igCyQT2LXkP+tFQjTf1nFr5UVboNuoxc6/a2vN/5bnv8NyCMMKBm32lzPkBBIcqNYOFYOShuibP2fbCG8CIB78n7/2QuXMY2H7xYAZSWMSabZ7QoC5ljuDrcxA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMTgzNjczNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lMzAxMWIwYzYxZTVkZDBiZjg4NTA2ZTYzMjNlMWRkNDkzM2JkOTg4YTg3M2EyMzdmNDU5NDMzMWE2YmM3NDY2IgogICAgfQogIH0KfQ==" + }, + "?": { + "signature": "Cdl2ZjZuvDfEuzf5C1YXwMPyd7WcabJQQRXeLnHlp3UO7ENtBZ5klgfWCbeUkO0p8Kn7MRoN3Hl5kAIda6KNExTAimTpIg9zSU0Y2aaY4SOaAHX6IBMSAjsaQsx8UKZ3K3ScIkTQa/BgpPo01mga9LglMYw2NY8krf3qbKpaE3kvaHiMZ3vDMDea2o3ppzxoO5Zp23GtsXVfQUw4OtT+pB9Kh3NkQMuPrzQ+wdvmbGvDHddzyC7fUnk8rW9u4He7kVQJRniscFUA9lpddz3yyt07mgNlU5DQnwyd791VwvkWp0fGAHbKHy9dBBD2OtZ0gfHEsCIy7XdT3N+ivtKuYjdGuT5KxgrYrVV+/RFWg4A1zfwgE4JoIFt/V5TYTwyjXOuEvaD7VpVUq3c2RHUf4n158xKqyF+KS+XeL+YD09VHGiiBFhRnvydtw+poi9WPwUuuG/DztYMOjcQ33XeE+aqegcgbgECjeplSuSDQkh/Sqvn/XQ06IQqZrmiaug0qqL/NdzosbmK+jVmUM1UfJ/Iha5M2AWR9UrVmd2QRGW88c1i0HFcDQNFIoDILnBWoVhS5gZNOYFJE3TZmqYN5Sso1+Py9FL0vB0tIgscfYSHRz25jCYavoXU2okoTms1sa3bSMBaw9KY1qF57oxvznb708HueLG+4TZy8zPQYgxY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMjQyNjIzMiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mY2UzNmE5ZTZiNjhjZmYzNTlkNTBjNGJlNmE1MjdmODNhYzM4NmM3ODcyNjAxZTQ1ZmZmY2QxYmJkYmRjYTQ4IgogICAgfQogIH0KfQ==" + }, + "@": { + "signature": "KnqEVv+zWniEIh7FvoI+jUz2aLGeNL8/Wo9fneHpqaPhFbUo1DFAe92TakRn/NmJowd8Ex96jBO/fScKBL2uJdlD/fYxx1BYuHtOsSgPBLX6JAlfduxyCkw/nyN+jZghPFf2EXMvsPhnqsOdxOWPeLRJA6Zqhi2fk3C/ErIy6EaL0YNybYFSusStLiuNFrks/M/g3a12SVz+oLaIlcBCJRSv1j0eq3oVM1BNaN3pVmrnXGxrj/uJL7bKP10nQFdCs5znA0K3Q9Wa2+qJdJpQOAOkMD5dTR5qluo/sdW/90mib4jxZ5XEKrzpAFFscHU1Y9hgzP4g5a8JgFIv0i/h1nGpFl2AzbSglfyDN43t9EWgiNQZPFGzeLyisrJe/gUsATkl2h1lD4J9KBQ9wxxrLIf/IYiiWaki1EjCmVbSIxy7LqwaECaFSaSTk0a5oPqp06MOkIG7ruWP1hV1NdF1WTzt6DGzE8FbeRMG+w+N/nI8O1HCWKQp34x/I3zV+kN3uIG1TPxJ99NGSKiv2Se8Ov0RjYyPcPJRGArNmyFSr7MIjV3R3G33hF1+zNm6YqXwhngX08s8y0yQVT3aI1nj980vubwg1MG1XOfXXASFnMA9y1nBii6UPdrSatM9iSAEwXsIkr7QvmF1qfMP9KVPhk3C+WkHu/3FNf3eEBvmW5g=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMTIwODE5MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zNTE1YTVlZTM3OWI0NDQwNTQxN2UxYjQ5M2FiODY0YWY0OWRiMjdmMzljODYwMjM5M2IxOWM3YWU5NjExZTJiIgogICAgfQogIH0KfQ==" + }, + "A": { + "signature": "e8FwEvSM91+fw9c60SVOBWiSE0LsqaD1F0YQDxSnYDptJRiznrAb1//5lF+QrsJ+IFCyaRA6expVnLPQTNcd6EBMtnjk/E+cC5uICzkRdu7iE1RDbAwZexmSbsXkOwTzAcWgry2ZfI3vpKxyvvwGLdsUVzp6MQW3fqsPtT4KUPcsvFT1ObcFSB7CTTpW8C2e9+8t9QE5U2hUOKH9s3AHh5QoDukFJnP2twZYBfPH1MH09w3Htl/9lSMvoIMFaH8Ol9RtQ1O3zfIc5QYBqaCSyy5WTbEbcjAOiJC8Xf6YMt5Y5UzUcm+vPCdRyyvjgOHLXHHW9ntFKKqHed8Wz1co7QGVq8GDJm37cYNShbCXYlx12SQcY4b/aWPUqtFt/PUjXLXGY1nDazCRd/I1TPcrrbMmeVTCN9uMBqmNY+CtBcCsYoZsPUYU23z8vjgINRKnNeQATokyim89xOb6taoQuOwN/Uf81HmhDp0dLW9E+wcJ5qdes779UEbFln9zU2t4wefULC7v3sdmLEkcpmf9e/Agh8fbV0d027pRtDOnsprMFlQHG+40CAEx4sbkVwD7eUNyDorD7iKVNDXR0AHccsgLCVwkvW0gPHVuaK155LdobBu7YM5xD/3goIpNV4hx6L9g7sQKkNLvg/fyO2IEH438Kl1HE7VIkvNmNB5PmYY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMDQ3MTYwMiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xMzYzZmE2Yzg5NTkwNWZiYzBkZTcwYmNlMmJlMTMyYzUwZTNjOGFjMWFkZGZiMWRlNTQ3M2YzOGViMTk2M2IzIgogICAgfQogIH0KfQ==" + }, + "B": { + "signature": "vRHQVYpHfvykf325zwOXPS8gMTSAfxMMYW0oI1Emg1SgDhfGjGjU6+ZWT+tLbZ3OU2MPg3aAxmOqZFea9SVC5eZbtNyBIqNO+H/gpkk0/Cx6XPqSR1FUboauSSPGqfnFzBi8YazSqDrg3+S95qHIWaoqmWLcFBU6svdMSq+YFx8ki3uttp/vTLjg031YOY2qMFFtfvLtdbWtBDP6Nx7V73qqamZ/TsKXAmS2tjqUvbnFaAkw0tdX2E1+6lw0rYm6KNIK2e/50sLVr6IGimUnx5aiVsaei4+huIhV5idkBCnko6UphRRJFHITm5khGrfzS+riMQD/v9lacJjHaYHD585Pe93zS2vvAhgyD7VwlGaRg2MwFXgJtTTz8bEkVwJlTo58RwN9tvLaoxGYXB+jsKk1rvEusPtLG80ta8ATgh3//a0s+rMQ+9oI3FGtq3PnrDaoqk/Xr0hNrdq9/U8X+zXGHl4QC7eFw5WBOlfZSFRoQPZZigT8h9qEJsSgWXKO8ypceBbQ2aiqK8MGlFAnhMllcTGnL4zBKxuuYkvXFXWVOVNn2SMnXOLovkSBLygpVpZOx5u44JKJ4EZD44f/NuvfRm0OBClh1e+fQ9+WpMZWzsX3/750ZnNMeqZIo+FwUZfHSm38WQWrivYatuxEc8tINQK0tyacPZY+0Yj0pmw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMDM5NzI3OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83NDhmN2Q1YTc0NjZlM2Q4NjZhMzQ0ZDhjYTZjMWJlNzUzNjc0NjBjMGVmMDI0YzRiZDYxYTQ4NGExZWI0N2ZhIgogICAgfQogIH0KfQ==" + }, + "C": { + "signature": "hMvCCXdLCWx3VGuJlTwlUzd+9hgwz/yd4WpZd3eBB+kvp+TOekRJ0tNxFMRQMvJ+k3DOMydMZFjnr3NH6qbCpVsztRECeyMzcz/dUAO3TiWMi1u32Wkj6iQ2Ve0BxOv7wt5UO/K3M8hl2/12+wtrsvdTLEYAqQJ2fUKEJFJh2QXwI0fkaCAbgsoQt85d39JLdCdIvqtIQtP20nkwpFQFWIrwov2nuTrvRwVC+WlTgWRzZ8mBDaXK3OHOzeaio2wvqyTtiE/uLysYJVzBuHDoMe8Q1zUvIQoOorRUmL89qce0JsV8THJB8nOZ8S//LPRh7e26ru74dMGzMfwh3jFH5sznby9RBzSAEzcp7w3tBfLLqm41r20iixpzsuxE3udNL2ZBtMLA4nBOfV2W1no62GGOgWpZVWoC2+0786iTUe9tY59K2teoWcgL+zB1L2ZN5h15fHr675pOiVQTABy+JWD+WtzEzIA/s++IXCBLxCm7vJfh2v3qyhho9llNhBd39VGhHSm9cZCN7/uEMo377CtGrArKh7ftHKA4t3Xq9iu0LJ6BbSnrPdW921bsti24EOb/C1BWKpoSYboQ3BYtbbu3dZwQG9CyND3UKE49DvSmnAI7QxJXodu3zj1NKl5cnzUCMXw4/mZpTcSgkGPFFtczqFZiTzCUWTBY+tWI1uw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMjIwNDg4OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xYjAwYTA1MDczYThjNjZhMDFhODBjMDJkZjk4MThkZTk1MGYzNGM0MTMzZTRjZTQyMzQ3NTY2ZTNmMTkwYTE1IgogICAgfQogIH0KfQ==" + }, + "D": { + "signature": "hhQadmpciWVIwo2QM1SjXk2XPKuwDA+MMankqAURP4sHd6ERJOkWS491Pb1uXK2mfGhKFQ/vBnw26Vw6O+pCIPvYEB5eUW6cD/mfjxrFJQ5oYz6URFKTzNbSz1bW0VRPK+2OTHJ0KpJTnBG7I9nZ3G/3bTnYvhN4N0/7sTsANViSR646op6b4SEPM/16c7y3H3GTSLzbX916FQZJ+QoXx0oOQr7of+y/B2KtsWlGruAgNofllf0ZgdgVNrofZFDkz2ghF4mVaB2LG2dIgVJ8peHu1NSzEaQjqqYW6vaDY1zZvZvkufZHphtnyN8BrQo0qMG+SZ/KQeP9y5bIEYCfLA3AJ4hlklqZ+QZfhFmdHYC1teD8y3ig1jr02AkQoHFJkkmRbKF26HnGwdEIAgVzyMFvOSyPMTkRBtPkKBpBcW7h9b+CRBw3NJZmJ21zVgyzv2L17KB283zChZ0lD+5EnA2yoiVSRQO4mJXqCs1+JSmlSRYOKY8z4n6jdc+bhdtxlgwQmwa8kSxDk7YlNAJf5UfKJbmDJD5QeEE9pQdbXT2U/Hw73g3WSilJ+9nm5BproLWWo5Wy2DkxTVgKJg+2JREFH+vXDOcvCboJ+JcLfk3U7Su0HEb6TeQtpAwNJZnEdbGLDg66izuAdSNxC0221A3jC0A/UcDXFCF2+/aIykI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMTc2MjQ1NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hMTVlNTI3M2FiMTQwNTA2NDg1YmU2MWU5OTE5YTE2ODg1Yzk4OWQ4YjRhNTQzNDVmYWJkNWQ1OGZlMTRlYWIyIgogICAgfQogIH0KfQ==" + }, + "E": { + "signature": "AxmHOivSaz93Ut8uyw5AwOkYO1IE846LFM1SGSORGpfnx44XWMF2U6EkQiTCJeJho8N7l+zcSkoNtxili72i1IaqvnDI257p+fW0j4Pgqh45RDIszm9I4LcabfSQzpAnkiXg2E4nFcOQlKn3+3yR060C6g44W71PhDN17O4JagaC1dBK82rn745LtVEkafQDLW7pa9wUDNJAna6M1qfx4zzPLfogQK+25q17a5m/yYB8Sv5InNGqCI1SgjxR3G9YA25/zbipPG4mw14qLUod7mWCadMd7zBUUECDF7JN6e4hRqvH6QrrzUpsGTSzksBOYablLDlhoWYvUB00hO/uXthjsMnR6ezcGGc14Fj3FQvcQ6r32YOvoNrI/hAnhtIk0rYqeZF+jwI+VWbFQkJDYKTiP6zEuhSuur8cSibg1X+yhyBEqvHh80Uzph/7A9+0C27FW+26A1HP2s6JAfnSv9NNNX6+PRUDwFJ7V1d9VkVlNP3uMyxOYZ3mIXdbbNgI4rXC7EScqZAdUDZfNJenl5QHGAglzSuS1FBXWx4fEpdTixyK061DYe9nZJVRf0cvA+/rfL2WejiWYoxChigLv2AXY3Sn3bT5ZCATv8aHJ+0Q+IgmmNIQNoePkjph81/TF79bhb+PL/v24W+dr5gg17WQQS0h/Om13Bk8ZghYVMc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMjY4Mzg5MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zYTk3MjMzNmVmMjBiNGEzY2YzMTNmYjUyMTA2NGQ1YmQ1ZGQ0M2MxNTUxNTA4NzIwYTdhOGZjOTYyNjA0NjZmIgogICAgfQogIH0KfQ==" + }, + "F": { + "signature": "MRS8sW0wGhcgXa4SaO3+JiQNmwgBeaef+aQ++7eBqzMMhF14qLZZ93TTQnRm2H3JNoPMVSZnkR2AcHf1LKP3BNthgBm6ulBqB9Sw3hRVP0LVeuG9PNM5wcIsPf0bphdPbpjsFg4/fkqkhV2iB51luJ26uXOj5bpGM51InGB2FkZ0TSUnMfNRrSbvR8O1T3VgiY5Ts0DWNClfuz6ngaCMAEPsygZ79UCGpiwvy+37ZYBMMNdhtK5Qnby4w9cxrt5UdrYx0meaej14vqShAmXpULUSmMMSzRo2rhWQ0cswi8E8ssRs3eFNcPKH+i7QxeYlo+ADOKK9V3OXmQf/5sOSseCzlaWRAtU1i1jnGwTG9cKNrbt3BQpDNxp46PXPT3yPWFJDmjG0abhI2LXh/Jl8N/3YOJ0kSN/w9P4lyLO6g/oZ+4dm70ftY5nW0wFRX0PYuyB+GyJkTZHoR7OATEeTYpKx7bYbqesdwm5CNe+6fY0yLexWMj0fkBEF0+T25qzGJ4Ks2B2pqHfR1H7JpCBQ9R/FlC41ZrMG66rSYQmBVgDf1Y3tMEeJ21EdqdE2np7en11dj+sqdf/yEBWg72+uDj0RBjt+oWE3gIUC9vCCXr8XV45CQwK9lbCvKsfraBj0aeb+OqqNTVSU4mjMvCrqPLZBzh8AsXSWYQ2FjqrF6K8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMDcyOTU2NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84MDZmYzJiZTE3YmYxZTkxMDljZGUzMGNiMTQ1ZDc2NGM0MzBkMmJiMDc3OGI3MGJiOTg2NDI5ZGEwZTcwNTNmIgogICAgfQogIH0KfQ==" + }, + "G": { + "signature": "xPqaX+S0IuTY5XlCw1Ok5c6w68KogNuVXu1wj6pgsvs+AneFEVjpjNr+vds+/MPtxSmgCj2Rivo9rftagtjzMm4QANk9WH5lWGGG0GkxIuyk2YiQjmGLeHIjNCPy6/M5J9zGLTTBXCtsZu9dPL4YFVNRGNzAHoXoYsy9o+5kizOXBj0AKkJsFYAveoTVUM+nkt1yTKaSwMYFaq5kh6KZ79etaNsvA4OpzEIeLUA0I7ATWL/RG0+REEqTn+i9YyOtUdOyewC2VqFBQBI3lhWmeP0PXpd65MHCKIzDsGYhhWmdhH2yizmWWI/hwJe+GYl01HDFtsn6f3kvt91Ge8hfny2Oa4w+rFJzzcSzB+xHLVx+0Jn4mHmBKn4vUWaQ/2/jRBamU4qVOknQw24oJPajdzVYbHH4tFME3VmlSFf0z/GHjSoigLHHIpvtoEFgm9uBXV85kq01V3QdmiPPRSdBbKmX2yIMb9yFY6JVEByuETUfVEt5f+vCpQKcDHVVyAcPSfwrMJql3xKSmFrF0fgtXk/AuKMebJttpepVM6q5KBmk/YL6iYKV96pNzayIUHAHJn5IZABIiikZD3AvfDMBazBsxUqpaHYPCV64nyB4gaf6F59Id+S5wjSRIoRm12FhPdEsiw5LjlC+s7m/exdEMEONeRPmFiIv3RbvKzIqZnA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMDk1MDEzMiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mNGRhYzhmOTQwOGJkMDVmMTgwMWY0Mjk1ZjYyMzMxOThjN2E4NDNlYWE4Yzc2ZWI3YzQxNWU5N2UwYjQyODlhIgogICAgfQogIH0KfQ==" + }, + "H": { + "signature": "B3msKWfGMLjE8/aWGdeM4ICAb++IKX6u2aqKTMCuflTwelz8aZ5FxVrfFi3wou5X1gGYZKsOi+DrKzm/EDh9Qn8L+ckK0GIJnfie+PJ0sFrqV4XQjFyr4TC2ltfbhTPtn7Xc7qFb2+oyANJwEs5+khTFOK8Kxs2O4AUtivjfdd8l2k0fz7NFFcHv2xH/izIhv3ZdtsCIl9L0VQsN2qLyQzbQ7sGgK6Cf2vl4lDJIOTV551EnwRmhd+H6aEL+yR3s9pBrFLORQtehmqxNggxRPhbVLxd2D1EgxtLtfaeCnCOWf+r05QcbVThHd0S1GlEhU5s7jjzfNb+p969SH7riLpm2OYZTXA8edFtz1tDwISd49cGt8f7A1SVekrBJl6A9i8ahg3/bA9MB4y+6TkFfhkVmUF/dZXbN/dJAcdkSKD/1VWYS1020I9VGQ+JdAEGjhpd0yhMvqPSqIJRepEEEMWZdFhhG+MqBdy1clLtsECO5TwN9+A8zPLnZbbOzHg11QdeklvpUfOoU2oBftyxjoVAP2B3QvKz3QzeU4k8eHCmIAyC6N0w87ZIMTZPVzD9oszYSSxpxxMBIePvOz0/wFfxHVtIYN6v3zwVM5gTs+teokRyEKs0QbUsiDcLJ52qZnEo5F29gGecNsI+F3vPlOsxzcN1LPFBctQ2DrC8BLQM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMTkxMDgyOCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mZTIyYzgwNjczN2E5NWEzMzAwZTA5MTcxNGFhYWYxMzU3NGJjMmZlOGU3ZTk1ZDVkYzUwNzc2MTdmMGQzYzM5IgogICAgfQogIH0KfQ==" + }, + "I": { + "signature": "i+XHp82y4rk3jlluk5apu+OlkJAFh4cby4sWR6B+L5dLCNDFhUuqhEWWn6b4IT3mlNyZKslyVLTp9uxyM2FniYfrNjYCLVRiZ+TosU4SlQdLh9Cjb8EEJ+zSjE2FZIBhNfPAQoxxqPyiBmqoOjRmk7Mmz7mWWaafIanYix7pr36/kSrJ7cZlGFqxobxGk81Sx+cmDej1MaDxplwb4f9DqkaG1WVyDodx3kjlaHOHWWT1aoXECPybk7lIxDDNcwUpLn3ZvFSFelg07H6oyUOyTIhc5+FkACljZLjOm/gq9o/AFLqkzw8RObD4eZBuerH9vnVOuwB/hCIwOMaonmpkfQi9cYfFzIHwuHVuvxELjLALsl6wvlU062ISkdUeAYNje3VCF4qiOruZo1+1l/DB2zKmR8ihHSvnvu3hVNr1MVicM2Tv7V382SSCfKrKGMus1l2yf0yENA9BWAnTjOU9oxairQE8ry9UCVITTpbch8SkhBFN80GcznPiMQpn4Am2wW9mnRjjaYzI1Ch597soUkFCEpP3EgavIqPNSxyf/+uIi8YdNclXXZEBdYl7LYSmGJ4BB7EdYtoxU+fNgkqWa1+SKi9F4QC7sDBM+xSnlRmrupu4Q/SSnuEJ+olUf1p60emEOqNEqDU9DIDn953eadDRqjTKAnUdraj834dV0b4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMTU3Nzg4MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hZDdhZWJhZjY4YTFlYzkyMmY2MzNiZWY3ZmIzOTMzOWExNjcwNDEzMWYwZDYxNTIxOWRhMWYyNDA5ZmExOGE2IgogICAgfQogIH0KfQ==" + }, + "J": { + "signature": "Rafo8fcKlVGZRL38EW8MNv1+pVNSj97knMEg8X/EMxamUKzqjO7Dwp9+wiFgWaFBMGWSXUZ6kSr0Hga/f4gO5vPuv+iMlLBQjqCJ+CPTpQdlrIEHVce7CuU/Pu2UuBn9yczBAeahiC/w/ear6ubNnZAAtwaMtHsSJscTA9xXPKuRoESwloynNu9I7zoCOjvNPUnS/2Eey77t/7NILVViwbjzu2JNhtuaWRJqKjTPVNUprNr7OsPgUvH9ppPkkGUucLJWJglkN/ar/KwzXmuioRuRQ+eq3viXOfuVsj1xiE+c1/6au1GsTrKEL1CXlC5+oSOjFRjlsEYQ492pyPFTkFeVHNiDy7LrtlxJKrIHenH38jiBz7PoBeyFGSSdy2dw5QKBInxCanjYt705B8GrZ+C/cTHMtC2mu0P157byfKuqsqa4J/OUDN2vKQzL1PaPPUc5VpQf075UYXeZJa4gNyjh4tQRwBmvo09s0nROvHwoycdM7L5CzDYJ5gFY741bXezofIOn24ZOE38H0VgBHdZmeMpzT/BbUE04X6Kmr8aa3azrc/X4RcfwW4ta0r5g+/M/x2AS+bxIspyWfiyiPZ6DVCAWj2Wnnshvvtw5FXZL11r5PCBZYJud8H9ly3R6fW4HS54uh4C5bCoOEUGeKZ0RggsTioIDz03EcRX7bws=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMjM1MjM1NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xYmRlNzQwY2RlN2VjZDFiN2M4ZjJjMjRiMWY5YTI3MzhhMWY0ODczNWIwZjdjY2NhZDFiY2ZjZDllZTIzYzc0IgogICAgfQogIH0KfQ==" + }, + "K": { + "signature": "idunkkhl2Lsv+Uqa33hzvVQd2A/EWVrJG23FNJmCe98xDNFttJVHAQEU6STC6u8PIK4ILIO2smV4nBM+WVhOXY7BE3ALcLKGGg6KD7j658k5tCOQ74ItHTy8WXnf818kJFHt7ITxKI2MzM9rpvShl/zlZTvXrfbC9hy/xWC9Z4MrDiWbAF85yt7ltoR67iGceT4fps3v0x8NlbzLNorgv1Y/zwjFhXrxtg38CBqwTsBQ2uA7BMt+Yub95hwjZm0P5TOx4hdD2d2dHfr0xG3NNhyoB2XhCjkUrG3ftZytbeOC/KJg9gmjIQ36VOTzOnIqdPgnKYNVaA1Dp4Uu99ogq8Y90xmdy2mGAbhuAVbK+4WJctl1O3YF6N1bpDY8GkQBFLzeXuTK40YmNFyEs0IjRqpIbY/kJbEh3lGTzAwiylLUSmloKgN/B7bjx0DcvIc53dKMfbXcoM375Z0E4w85aEyD/6wOqHv+ScGrEibw6xqqYQcgNH327HSVp/R21pn4EsUm7gUWSFh/0gABHH415TnNlRMHJEU5B6ZE/3AxbkpwvVOfL3xXREhiV4ZMw7BZeFE3jYV+rSpaVn4LFmkn4OdEbPNCyYS2HBfkdADaQlPGIHy+oPtyitJ9YD4D2lyo1+mg6KW3XIVluyBpgVESFOllZVunMH3KzR/AlYDtc2Q=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMTM5MzA1MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yNjYyZGY3Mjk2NGI5N2M4MDVjNjYzY2U1OWU4NzRiOWIwNmQyY2QzNzNmZWMyNjM4MTM4ZWY3NDdjOWY3NjNlIgogICAgfQogIH0KfQ==" + }, + "L": { + "signature": "NEYPHb4LVNtuHT7KjdmydgHAWgk/rKkpkq6KqeEWwxEJX7yHC1FZM/spUjI1tAUX5lUzP6fl+1tL+ziPs6cIslZFxvPKu0f4LCBnEyiYOWTwhAL7uua8/HBkaenaiEktw9aPNVCdZziEx88lNhwaxl03NvhuG0r5TzxCmyfhFeuZ/4yuHyvO4VrAtixmiVl5PZ+BE98e87fUyqRmcj3bMEDrqmMA4YcRNeB3Y/7ybj11pT2Soo80Z016jN12u6TQcC950eSBRAr87zyTLm9fCOmhzw+69zYpYHXEQ9Hvud1FCG8rOuoRXpWkfj7LdvOVcf2odQId50hKNPngAMLXsoYGTZxojM7v83laUxVKGqfaS/J39vph8I7qVZNx4/P2rUREJZ5C+L+10+EQEKyskwpxoRP/TlnooaEw3pw4TeAJUd93xg2RBlDjUldoeojdc+9iiwuvTcdpQjO3rKupZKG6Wsg4bX859zODBgwOQTTkx6JfUa098SSYg6eKwDmUjTdHJd9ryy6/aawxWobAIID9myth4UP23kwByEGEHYkwCaiLl/hOj2CYmKJGPKEUTEygX169ASkoRH1x9uN1DWrFn3xD5TRN6cL4WjqVNzgAQAcD3FfHKPa10T/tjuYvpIo/GF6wPhsxTJ+iiO+wTQMiXP3nDkZSpVqSjAVZ3N0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMTcyNTEyMCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81ODEyNmY5MzU5YzczZTAzZGFiYzFmMjUzY2U2ZGMzN2I2NzQxMGQ2ZWRhYTNiOGQzNDRhMDMyMzlmOTI3YmEiCiAgICB9CiAgfQp9" + }, + "M": { + "signature": "vNy3oDGAiQewNxt7X1I0THxkWtxrIhqIqPnuoCqVi1TJorE01J0I2/Y1PM9XkcnsveXFPaeGE+wpfJU/3XPCch3iOnXcv4t66Wkm8HiIBF5p/1bMuTkUZ1wj5aiqWh0BW+GddNzVaAQ0fqBCcuYLOaBqPMc7sNYlvX0fy+4naPw8/4dd91vUVnzO/ATc+Wp5N6e27oM7HM+rVEsINzdiSXa5NkDLF780rDmib1JHKj8aJhHsZPIf2yb469AxK0SAe2zZTW8xjm/9mz3qEwd8aA0P1sOM9SkVFcEkLl888OXAaSgz78129CeM3CaQP/awX+Bcb8634bllCR+ITfCI8eaLTscLneEceTxGJJxNSzAsMHOkcrbSPF6wN8zVFS02w6SEZrUqFxFzEKbIbpjVZrrIfH9DFfDMhje5o1WSs0lGRlf4CVhzQeM3ZlnGJ69NylKyHBt4VsZaBesOdtzwcsg1CSW/O85+7r80UP9WK7SPH43d02q6Rw5TWO5rj5ZhpyQbLzmhP31xWCcnFhc3N8P4QjMvUspXXHyWeUfGxQxN5jlVkjHuX3EBs3KoLOPS4NLiLjohIk4YAURcmj9TuYfIu6aZaSRUuwqeRPCT3B17xTquk3Ucnd2ZXUkdt5a/9+CSW78tXzFlJTPEzrGOwkJeXyJA+0jZHXvDRdeGEbE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMjUzNzMyMiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jMjk5ODYwODNhZThiOGQwMTM0YTI4MDkxYTc4ODBiYmNlNDg4OTNjOTYyYWY5Mzg3M2U4Y2E5ODQyMGNkOTUzIgogICAgfQogIH0KfQ==" + }, + "N": { + "signature": "K7iB5I0F4vuKbQnFiwpOWchY63ZcxWnMbhjT5dg+yxHvsJkNcsxawPRxWKoNM9x2OpzcprtN3vjnM85n/oDEe6MrIsSOLvrlzOKWoc+G8l0G48hYf3bB3pXjJKEtKp8Q3K+DUHi+7cqgOoNgr5PS9O+eYgPLsvX2Tnhdj7v9L7hE5oLMWF1E+XvdZ6QPRWRdXufN/BamqDobIygQVkPKX+7qcI8rc8ZQK1+thN0T4Xtv0BZcfW2TEOw9JBBNC7E6AKBf8d2zUFgyzE/RWxDdwdM+26lZfN7/poqexYROvn7QsjDc0yuPjFeYCQ4+V0WKtnFg17WgzNWc3PdChQnjf5EXvl84DSC5juGRda46w2lJG8I5WHcIInNJ6e0Jw+dho1bm9YYuIHBarWtjwDciX7x6txRJkmTkWHtxCJc9Se2Ks556yjqSI5VKp7CZyU/Rps0nPd3vxTXoLAbNO4XVgbLEQTvuq5HPKZR+T/Ewb2O+ib90CCXuqfkX3o+xp4L0hK776+LpdkX2ToA02W9MCJ268CXq0duXBQT2WsIjOB6lumXGiJ11ZsCudP12utu3Hl184N+uHcq/obB7j2Tr8IQC0XOrThge2sosXoO8MnWmfJKTrgWL51V+ij0e7DQ4aQwggIYSWmLinOXpx3yrmJ4Mr+Ek5aJ9sPEK1co157w=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMjA1OTEyNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zYTJkZjVjM2I3NzQzMDAwYTVlZjMzNTVlM2FmOTY4NGM1MzNkMDAzYWM2Y2Y0MTk0YmNlYjFjN2M2NzE3Mzk3IgogICAgfQogIH0KfQ==" + }, + "O": { + "signature": "vG23m50b5uR2p9E70yX34KI1fBb3H18Na87EOJDVysWG8yXpIQNfXxYs1HYV/BI+8XM5Zu2mTEIHVO7JVOJGnyJ1uwTWNt0/OQykBIxl66w5T8D09Azmjvx1HQFg1de0W3hgJ8cw2eGNpe/F78VL45X4K59nD4d8yb/B40DY7UdQmiSpOshbe7zkZVrQ/+P5uEcEPUEzGQhrxqCIQ+aND2XQYzptODJx/35kZijEG0KwmIJt4G0D1pwuF91uair+o88ktiTZx1Dv9RPNuKbxORoC2ASdrRAggfMR90sT/5yJ6h2JbhODTziaikGLTn7fydonjlG4TWyCC0qy/yKZPhXwxHmh50sA9b6r4l7W/85c7qTuzAKxcR1nY9Hy0UnV5T4+QhZkHLC4bu2m7kY4G7GGP5pdsKsMeQnHyokdLaKXMenDQEclrBMR3+gKszZpFxvopgOfjY0JVZhfOo6vYxPTmdYjCHEyBHI0jdpUTSyYkxwobPcq2IbRyvDlitcJ6QeZ2+hulvoWnwp+dlNhVRTke/+XAjWI1z5PSqpXPi7rkvjIoYtM1jF0RPgmlLiVNJ/8smrk/JXBYdVzjnyf6PlQKI0FVRN8zod/NM/rTbt8kZSXmiUcx2//6KEfEpD9Ez8HZ0MDrUskZhVmM2s4t7QFa0xIJ+EdPPj9yMw9egg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMTA2MTA3MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hYjY0MWMxMTI2MjRiZDA0NWVhMWRiMGYxODNmNmY3YTg5ODgyOGE4ODE5OTZlNmQ2Y2NkZDI2ZjNlOWEzOTFiIgogICAgfQogIH0KfQ==" + }, + "P": { + "signature": "EA7AWrvZOPa30vOH80OkjA1jwrtT2VQ19hJIarTGw2dBbnbC+xDnJtQjcWSKLKT9iyofRS0Go2bJ8brbnkTa6PZDk0qKaZfWXUo+jOMTWeQXczyHB3MluItu2QI3P2acZeqNZdWF+1jeL1oHV4x5ezi5Pmqdyiy9Bv8jPIg/rA1GxVSBYIBBVnEPvxqCzt1/Ymhio9zaZC7hFdW9wYwxzO3adXGkH7C9sLon5/9xN4ILgzobtKOmI+JDVhd0StULr7fAmJPk3ViBye8zZ/neTDXe2L7XgIw/6fxZjyvG/mJ6Fi1r6MTTKwcd+ARlsh8QXGuUkH/LU1mcVblrT1PvtqpHrm3J1YDuOOV4V0I7Ei5YzMYW/Os/wBj+q5zOZPrPEdYykYjE5etzGcv/6Y05b1V1YD56SQPq2J1d2h1y7JloidN+bqSJ2ABRSCfC/Pv4mxixNwuOwH6rqHc/X9N5htNs+mGbBYJ9bnTf+FvCuy9VxH3iNQvRm76gTGKagLPN6EzQR8lQz7x55AnNA3BsJRinTbPV3q3YaBHJ/Kf+yDJUpf0gjSY7C+miQTTVEuDTYYj4Zz48g05Med1kP8rVqYmX/5R4T3xwRHHDKUVSoAGXx+BWyq8xZZ2O1LAwcJQ3DLMhkjBxr1q+TU+P0mGp2DSpgYzo+ZgcDu8mFybLgyg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMjI3ODcyOCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jMTNmMzVlN2Q0Y2IzZmIyNTBjNjhhMmZiZTQ4OTJkMGQ5MTAwNTI3OTcyMTZiNzZiZDA1NDk0ZjkwYzE4MjE5IgogICAgfQogIH0KfQ==" + }, + "Q": { + "signature": "BWBpkviz7/KuPnz4x0LoKp7o1vCXkUzF1QCVuZjB43rIObsn7CZ59iYUwx/tP3Bfx/X7G1gGphZwjZAkdxC9hPPtVVtdrmq86NWwcd0eqAiM4vkqAW1VvJAZO559hX8LZJMQZl5mqwOqbOXxGNMGFFupSo+wq9OB7yW7K0C4gZiKTTbykWxprePRqfrCb0wQy7Wh+rIP+48jxDEwbOmreiaiCqvqIJyZk9bX0iGu0w7uXaumIStaYdcAR0uMHCkQ6Ylt50Awtsm9oEhpIcxX8VQvTkiKkFLIQUmDPvH1d1C2mhVSCIKg3itityGQcB1QrZuP878I8xsa/c0AaJq3rumiMj8LtIUMaSHXcoqYl9RSZMokzgMGK6R5YdorR9yDmopnbQLnGgoMZxIeVok1cuK2nSh9guXrlomp8lbrUXeTCM6GhGayxUvwRk+KoxRGa5JKw3yGP6U8pfYnPF+gi+PPtjDYpSoV9bP42HH5ynlcfCBv6QE55XRlIXFKJpI3K5PrX58T3RAdJgoSmUEWJiCFqYy0Qyx/rrVGBuVwV5+Ztmc0mCmJDGs+e18hKwmbd0ONKgk4lqAu/kL+ACBMS14FseRfZsofCg1/y1u7vZXXtGgrjATPz55STbV8KCV4CLk0RZLyWwuNVI/9EXdX0REyYV0haqDo04gEmYQ3LEU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMjEzMTgwOSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82NmY5NzI1MzhkYzFmZWZkN2MyYjk4MTQxMjAxZjk3OGNlNjIzOWRkYzhjNmM0YmQzMjBjNjVmMGM1MTcwODAwIgogICAgfQogIH0KfQ==" + }, + "R": { + "signature": "vx2VCNrqgAgN/UDoSA+wtVDYK90BHbitPa3o/Jzjg9/LXC12+pIFNaN15aqaXsxh+OdKPgYJnMYhnCazEitJLxznhuoHqlsbTyWT/sct30CFWEjytCoViw5Uw+wTpe2t+NgqCEz9xPmueto7Ztw0tLjIbUWLJH3ab9yGcPrBM0hquB1hbu3rS88uRizYqEOYHG1yorhaxa6N8SVEYIebh5L0sm5Ay3g0mOtx0tla0MYE6aEtfoZ2KCH/J55sIDLQrix4K30Gj0773fqO/L0auYo5E+jOUlQYwD925xaRxebEpbGhcy1A7NFD0G+ejlnP1r+6nna+7uh0r6sjnme1VRJWLsmXGw4NOd834BKUFBViSAcbeX5T5kUQ8s/YQNoIK+LBqSFrY41u2XnMMsI4lkelscm4F8wCaElIxvYhhLpRHcAK1Xu2b0VyWuaEBc4jYddG8f10Hhr5W831tKzU+zh7LvJzvXk0qgl+iC1uB1duLvez1v/w0eUnhj7Yh7MeSP9hmPPGu7yPGSXHzkaRWZ5fP14eg9MXd250m5X5tJtkIXeaDXmheJ9q8i7Ejiy6TVyIXfdQjCH9sXlK5xgv7BfjBrMnOFFxyYOiQqpXOVx/M3G4NroG6Zb6OMyTK0CO+0ANOC7Tywr95iQxyP9io9azk+kuTvpMnVEizyOQu2g=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMDY5MjgxNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zNWY3MTA4ZWM5N2UzOTQ3NGVmMWFiN2ZiZWNlOTdkNmE1NzgyMGUxNWNhNGZkZmY2MWZkY2JlMGY1MTgwMzgiCiAgICB9CiAgfQp9" + }, + "S": { + "signature": "yWJyUqk2kzAI9TeJtxbdM+0eaD41udLCczFnLBDiVcpmkdI3EK7Fe8INa0yRjRFY+SHXeqIhQ6GyK+QDQwLjwkGOv1fLC5pb7k/nVvsnzRFl/b5tDzQqUnhLXWHttisoeq4cSNCnkcGhp3p6G/8cbLnhcfLQicKJM1UEwpHoAbBrVQQm+34awIz2MY4+lZe4jihLDvYGaynuBdWeGRl3WnNuT8nj8+ddOyhdfHAZB3mDzpnD06TyM8QcLxoewQ0U3x+tZz1n6ayYkkCa8C66544/mY7ibezTIDPdtvptyH03CFLm0ZN9VrTRsCbu4g+s9lfJmoUQF07y3wpnlAWsjL4QnkkIhtkL9pHS/D9NmFdwZaaDzAAnIMwkSkQL/ZeGxxRCTGoxsG2yChcuebCUvG35QVPyIMxw0nwj00jyx9dY3zEQ9Ou5FEGpiKHRJ79U7W62mOmXIcXBSqMbQUmETGyqjG5o6cBpKOb7l5tfo31jr7xCjCWM4D+cQXziBKFVPAJB0XoqZnXTZg6Z3CVu6KnXJ0VtW7ts7lqg37zQCp2bDU9sX28T8Gpr+6z0f7iXEye4LKEzTHODz8JAjSni4GO7/K77i1lydYQGS6Ed+/b4ojSEv1YEbh4STMSfdQ5CwtxOxMHB59Im8t3NOSGhgbfswa1pyXAKRcFeR82KYAs=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMTA5NzgwOSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85MzgyNzBmZDZlNWE5OWFhNzhjMGIzZjdiM2I1YzI2M2Q3N2QyYmFkNDUwOWZhNTkzZWY5MTYzMDI0ZjZlZmEiCiAgICB9CiAgfQp9" + }, + "T": { + "signature": "W5G8dSaS/8a3hxa+DcOfdVRTWmFjPVCrKn7h0VF+KWpUUkKtcSyYx2M+J7ZYZItOmdrluuoSg6lEY85U3iNz66HpVk+swu0/yqmxJleF8wPOFSErCfoLCBOR3m7nK/mqZsXRjvQ53SvAxYG0O5hrG2oUO6v7F2Do0pd9wcNlL3lxlzu0WbAxNsTIMIT/PeSJxO0IOZH/KzRIjTNFOX7zcz1dVmE6BVeyJLvIMLEfGd+p0YZbtrZyI+cVaeg4U81rpZnWdMTAt1PYRd6Ux7GJ0H/BD4eJB5h0kqDIG9t3XWTwy2DGYeajNS/BKcGn8lguaonnmz0uf+9CaArwP7GuV+y3h1xgGIWq5lIabCmIIJD56dGsCJXzoa6e3l3cLKNNR0d74K3abK6NoPuvQLOxFTXv8hE0XABVOIKWnxm6KFxu7i8pI5nI7lJpR1UaphTJd5f3NYV9Am7HN2OCfB6+iEiBHzV2mXvvFNmYaf4TWpOHPa5r9ytakLWU2kP35QraCDR6/phLxBY/Srkb4+HUT6ThgEUryJApt4JtBtpvWHKDas1Nyuv40JuYMJoDyxIfuXhPRcv5nwnQ30O7fynVFkQZbsX9h8xrlr8lgpFakddwzSjoYWF/KfXiSyaZpeWTmRtsiyAKVDPOSWr2EeGJ3rk356sLSTI+cSzF3hlPLyE=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMjQ2Mjk4OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82YWVhMzM2ZWFjN2Y2MjRhZDcxMmVjNGQyNzRkODg1YmNkOGQ2MzA0YzE5NzZiNzJkZjVkZDJmYzU4ZjVjZGIyIgogICAgfQogIH0KfQ==" + }, + "U": { + "signature": "JHpctnvHkWSsWhBeZIifWUDWJ+vu06DHPXFUSwcwQ+jWiSV4MmeNcjCQsWZfZa3io24/L13/L1ore5XH7Ky/8pvcr/jh7pjNpmSY81oAcyPCQXlr2lsXA/fkd3BJSWONw/o8QDpiCJsk7gc3fNRsF8cUbJYpzX4UZhQbACyc1RQC1i3JnwYg8nO0dW4NpudBwhLup/OGD1/P3jrePSFW0D4l2z5g9DVtY+maky7StxQhBRdSrePvXXw8oUCsvssFeA8pU9+xKJaxESp73EJAXSxiGOhIT915b2hCk531gM42vNO5aTa8voe8O4El3QRclJMHre0Z0W01qpPh6j7uoSX0Md3ciwzys+MP9F7vuXRrG1TMkSHl4KXZbyQa8CVcXjgl4IgMzsra55aTdnNjVQr4c/JOKdqfYc/NA0tTVmNhZXw6F9yBg7cWvQWidJkIuPdG4A2Ay9HcZbzWcka/hb9WaNZlAOXbjlcPXYCmIWnAwQjTJc5P5cESkdU2egmmXFaqPH82sXwRNKkD1xMAZ7I7pNF7d5BQ9QsdyXOyolE9Lko5pj54pJvFwLNbKWzh/bFR8u+/xZWWaaeR/wRDO29UGnlKzlwXYoFf9s1HAAXPrpXGYEBkLp0IeT5+90cPUoDL8sJibH5FuPms9r/qjEF1i1XiTejQxyK16t9l4ok=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMjE2ODU1NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83NTFmNGQ2OTQwYmY5OGY0ZTQwZTEzNWE3MzRjYTYwYWU1MWM5Y2Q0YTVlMDI1MTJhOThmYmNkN2RhMzRkODgzIgogICAgfQogIH0KfQ==" + }, + "V": { + "signature": "gDVi7gTQbM2FbaPnVadtSDnht63N2QdHOyfih3uhKSCQjALFK1rky49ZHtRhEanKyZ6AAzuczecfE06roW+uw6jA8DSBqurg0Otc9hD2gFZx2bZ9rbz+dKEaUIQAMshdWuqBipdnUBqubm2kwD8Nogkis2Unp2mqNu0FoQBqByyawHylQryG2MnatmoYKIJI31GmzRz4tAwzSJwEiYrF5oq67RFkSU+L/EQB9bQq+xN6irSW/lHo/BdGfi8cvyicIvN/FO3CLVuiZRLxTY948jSTA1RkrXzK1RlHW99PJMiZ+Md5eHzeqMNTb0gnzewHEEsclVNhkycp4KkjN+/BSiVNd/VcyC1WtmQeO2U9fFLum8fUOQ15v4+evoTuaePCu3V5c4+U/JCjdvbWergfns5m8YaPLljVaHMxqrc7uux98WMCFNzdKAQqUpIDpletF1PyWYW741YWpt7jkv14OZD48mMFnZWYrmNrZIFQ+CLW+jEKI42/0X1WYQsprzOdU/DAHmT+1olHb467wpoOYEYFcOKXU9NeCzGJ9tq8N2fDZ4vcxMzFYFBrF3f9XuqG6S6TrcajPE8G/hbsy178euVdaj+vyqtgIUAVDZ9UuM72zcUiWerDgQq9x+SC8tT5tiiJW/8wEMCSaZi7Qe0cXY4ouidB7Kvug70CjL0fiWs=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMTY1MTU2MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zOGIxOGE0NzcyYTIxOTgwOTgzMWI4NjY0YzgxYTQyMzA2OTM2YzBkMWZhMjNkODYzYzRlZjIxZTE1NzgxOGE1IgogICAgfQogIH0KfQ==" + }, + "W": { + "signature": "MRIzg3BkPD4xsFzubLwkPYEUrhiaiZl8F0MaRnXI58gDE2RZ0P5C7vGb88ymfZGPbZLc1UpxnMlNn/k7xIKy1jh0nNvr5jzbRr8XcXKCEzP0smuYLwjiY77XOtbbKZk00Ps2S3gimQdvASqAgVa2tnr2j9HemPUmBIH3Re9XO7nK3Ig1/PsE1NZxcN3yMCe64Ce5VU9O915SzvUwk9OlGG+h293dcccrKxCsCe+qSQkmQECKRrKcP1wr7tV3gUV9ZKFh88a3T+PizXDj2pJDoznWqw835viDXphM1WLz+AaOeyhuhRq07ZkGBEoxYpYKVRQZ5CPyWRoAV7KLDAiGTUK3CmMUSPOdyoPqJIEMV2xkgafUf8tTLzbPPiEuicFvrJDyJmRDuEZyr4lms7OMl3ti3T7q5kox/z8I8EBgKmTMpRv8IDRkTnGZu+gdnwkq4x2vO+irAmnhuZg0vOtrUF9S6AAxgLUTndVk9MPufCMlj+dp+UNVbm+q/sFDjeAEZEnToS83U/gNbZzzpaUNddHy3hvBAcKsukK2KE0NPlv28PRCcLWAJuZGKnAR7A+61ZnhfPEDxWOGlSzyQZszMBb2NiJPyLrrFLjb7qGvmCqxXSbIrPLwT49vLYNLBXoj11Y2Jo39Oi6cYPAOc3Sts1/gcFTXkgonwELZrY106wc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMTg3NDA4MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83N2ZhNmQyYjRkZDY5ZTllYzI5Mzk4MjQwNzM4ZjQyMWI4YThhOTMwZmViNmNlYTdmMjk2YjhjY2ZjOGFmZGI1IgogICAgfQogIH0KfQ==" + }, + "X": { + "signature": "IvVgAgljr2JP69fFCLot/TD9mQEZKxxIkUSwSO2vVgEGnwX1l9rmS2gnmqLOsuHJ3jEIpTuBLsE5CMpAjNneQIgczLp2qVXn6B/DeE3p9NXSOXiIAXnRnoJCtaf94TFy7GAaXA16xRpu7cRs6mDRjNu5xWtcB9u48C4RpZs1ogpsZv14YujsWPNzFo0sUyMM2yl+YFJXIO/InaqqHuGWJFx7mhteNbx1xfdGwkXpkxxfmL/KZD2twn81wrqCWuP6ztxcOSQP/t4wlPAJHA9lfKjkeWH9LC8o+VIwI3Hytgh9H4qiaYrFMorPhhcRQiR3X/vreVVl/myEC9aenNgkgZWPdGuIL8ShMXFgfhoMwWxcYPj1+zMkmAwaO+pyhP+CcWmC5rp+iXStzYwBXA5fpxpl0a39PBttg5SbY2IPVmxajXbNvRkbUr2M1wlWAjwOdTABoZ7BatLBZ6GzctXZxUbgj6YsqUHffR/m/ccs9JuFfIco/m9M56EVojJWPj3dr0TF/0CUChZaWQFhBHkHi4OVAB5lPEc6gYLSzXCkvmfCzjdcuIrFLGfQptEjpeDVc/PXFImPfFxTQmqLdkfmFasUUHT35Wgb+QhwmXi/n0wVtwwanB5O3QlWhJsuwX6LYpRQVjywdmHj3loDuf7UTNVndYfmO40Iv6KaFNbgXPM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMTE3MTQxNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81ODA1YjAxNTg3ZTgwZWM4NTEyN2E3MmY0YjlmMGVkMmNkOTIwOGU0MjFlNDI1MjYyZDhkNTEyZTNmMDNiZWFjIgogICAgfQogIH0KfQ==" + }, + "Y": { + "signature": "Fn3UEWetcnvKn56Rpve1ldtLIBaJM2zvGZ+cDnELLLcx9QpIRj+Y4QW+KAuJS2fPRotd/6aNcP8pe07tYSofBQfIpfT2t/8+/Oqb5yt4LBOCZ3J2CLcpAqpibUzP5cYHALhpRkeTNwurFeagjHpBM0ybv7FMw3KDGHsNW/Gt534rvw+81AtGbou2fewcEv7l17jCgFNLjto1wnh7EGTauCrgV1A66wbZ7Mc1rRHUcPD9JxZiXaNxwB3z9Q2+CYYURRLrU/O/JcA1ohZa/sdkvzZ1DHk4xHKb+At4HSruZqMc+Md47rewn7Vcs9EMeeHoNoT9UvBMKMKgHyomGiBn/ICsqeGeFuH5ACwjR1Ff4I35cKoMlrxgxFA83KICJaEBsrGLq2C0YZqZzsNndYsVHXHVKk9jqkFgzFM3i+1WR8M/TbKZr3RkbulVGecMPbbFl2qUoEytac5H4HaX2gzrnIanart16N6MgFRf70zWfcbW/1r52mqxokdwXgo9kky4PIKJB4faE0Ahfz9tUtCtEjbM1RJxB5gRUhJQbwCfakysNiFbg0WMNey7dPquFMrGHUoo4bWONH4TezZIVxXAeHoGteHgwBc1SPfzCNbAeijCq6yf0R+u9Uu/E+nFz+xx8RZLmq9X9MRz7DNZvOkpJo01jvbvphLbrZKw9N+ngac=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMTU0MTAzMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zNzliZDYzNzdiZTViNjM0NmEwOGYwZTQzOGUzYWUyZWE4YmY0MTQ0MTA0OWVjMzgzZWQ2YTcxODAzZWRlNDJmIgogICAgfQogIH0KfQ==" + }, + "Z": { + "signature": "pKH4MYCmU254gsYDGLzwGWx8r3W8zMB/f0FYVckwuZUAG23w5ReQk7vR3aV/0dRQHz04DjMaAm5hJC+qX3bloDxsrx1a3iZ3S4InVFnbNJSWsEaSA+jnJiOHev7suoTbh8Ah4ErR/Es87YUlpX3ZhEJ8OC03Ned4L/LpBo22eTg8rnKssMYRXK/lBJ3A7XpLGq8YOrxhfdgjip2+O9hRGnMzcV8gIsJf5TJ+MM1Ur153QgTFgHQnDrGQrhLhbr6t4PRInSzM5DP6+GvERNqxmEOl/5apt3xQFP/xqB0k6UccGczKi6phVIBALIkWOgFYpM0Qu8ewLAQudT6cMrpoHGdCGkYuggo460cU8SKNduXzD8CG7mNxU01t6T2LpCdpJwEkA9WkmvjxusjE01nUU9F94NTZilRcGfcn0nH/EL1G4OJB5uARWY8mywT2PO8eUZ0qRQydpLpyC7dbjSdgH8xK0J1V2aMgQxzA7pCXMdgaf5TJ8IWHNPClTgN/wxRvcWqR4criumzhus3sGrysymaNVoz1OQx+vui/PdwBGeIzx4nyI5UT+msOhxcNaA3bh42yh+t11QixWEihzq/yuSCGAKilnMbCAV979EVMhztBo91cXFTv8UPpwrZoRZWuBeSdNi8RvWI6Vnuk8QDIdFnJiTKsFp5bLPsTTVDzKfU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMTY4ODM1NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lNDdhOTBkNGQyY2ExMWIwZGZmMjBkNmYwNjA3N2FmNGFhMmExMWZkZjYxNGRjNjUwYTE5ZmUzMGFhMDZhOGM3IgogICAgfQogIH0KfQ==" + }, + "[": { + "signature": "SPmO/To0GZkom6intODf3O+CAib25UO9xLCjlF9hrbrXNQgo1+FboR07s5x+MlmZJxZa32ujrvCaAs8pI6Ep0InflUTk5o+aFO6BfKd9sifkReo1nZMCXan9xnEGSS35fkxiBLZsoGt56qD+I+/D/mIXKRT07vIU9mT4UtoIw78MsMhLXW7FS7Qh30+AugtwWBw8ZxfpxquQEbki4wwgKGdAomfzMP7/sNQuWB4XksbQHnwVrKkskagE54ljKy8/wq4W6sKYU5ZjZzwsjGQfTSYdb7/E1nxqbTu4YJvnmJYni53GE8AUAkEpeiFwpWuMEs7XlQ3N6ub+mu72TzEdg1LOxwTkZFdakYhHWH4JNpoWcfWO3+/wsjWCOcAZX6mrijoC00nM3Grrgit/4DgRkd1fi4kxh87g/olqy3Hgg6k6RLXtqUDEmF6BbSSPVuixq13o3/PJBpc8DP2PmZD6L7mimPIn8J3CvigsXIvdEBZYFsZgF6qnUKw010LwMy1qTaE0pu1sZaiENw2ExUoq62my0+sEC1vVp2bxbdAXejkQqc99CXjB1uSs0K96u0Kfg6qPpnxNbM86C2XIbDWHRd4CuhEZ446dYHPhHtnEpCWDtNlRUQWdV8esKxK5RUHsWlujqGF/WdarQ1aa8N7FSRn75x0nEuXQRa9zwLOsGRQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMTI4MjQ1OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yOTYwYjAwZWI3NDg2ZWM2YmU3ZjI5Y2U5ODg5YTYyMmNiODYwMTM2MGQ2MWUwOTM3Nzk1YzRhN2JmYmU4NWUxIgogICAgfQogIH0KfQ==" + }, + "]": { + "signature": "NQ9h6fSZdNJaZMWcUo1+D6v5C3mgc6A0tcVMWQXNLFkWnDfrnhklgcueQd+yxRx1S3/9+fOE+yvJh3iNMZD09iWtdKp6IjHMFFUayAoVB5GRfLEnVD7n7GetMS//fbuK5yj0l0ghyoO06PldvkwC83BzOtKV9fCdCcI+01CX4xPHqTN03CdYOgS6rR91irlEDTsRpumjzwc0tvxQbtSyeXFyir53JuGbbnp8D7HZGwdtj0EZagOZjsrWINN5W9r+7YLO+qkpopW3nNBH4T+M/CfbuVSLkXhp7/2tNKES+aF20gVmLPXPbHpQJrVSQfP9/D3bzgMXf6SU5bYc6indwx8tBomrSMkwTfWSxsslLcw6v+nJxhHPnHVjYA7KiX6lQo2lUvV9OZv6dRj7ItwQlJO0XgQBkfyRd45RSFGjzEIIX2BdWpb51+Ghs2I94BuBYQkmoj91u/Jd5jzCeNwbXGKK+uU7OKeJxzFoPaO0+bJ75yS5M1GU/FfVJ1tzjbsXbRH7Hb4j6eP81fpznljBzAHR2R6FkM9E4s2PJQqE9mgV6P4hE/QXkalwatNWQbQh4Iv+uLD7dqhvVldaD92DO4o1zg5r0oZxZKsrnqf1vAXkeWJAQN1QrrmAuSxbWOkOXKAiHpiwIDhTc1tZLfQHCWWB9SOqFpXx5F5iXlo7FYg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMDY1NTQ0MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lNGU5MzJkM2IzNzE4ODBhZmZjMjY2NDA1YjEwYzJjZGIyYTY3ODFiNWJkNTUyNjgwOWYyNTI4YWJlOWMzNGQzIgogICAgfQogIH0KfQ==" + }, + "^": { + "signature": "qLmWj2xw+7nEYFKxpZcfuj+Osyymsm8hWw0fvsuB6AQsoqqn5Z8zAa1qnaSpZyQ69v6+GIcwYzpdnpQAinSpuh5AjSp6gfJiziBxkH0cgQv4Jt+0ymwrQnrv5vdC3r/j7wRN7WepcgCRMKOjHm9kM06i8lUx4DfJDsVEdQdNCvZc+3wkGayBP8GgJqE66JlvtLIu3kSsLU95YI7MOhhHb2YiKcmZvHIeVz6Cxkk4IjCTX0AL+CCqa864K9+6EVkd7l4tXLDq6Ixiz93S1lZCEHdrz7NY56FPTpaiFx3fF0B6luxAOaZNRvcEg6JVfPnIdswOxhaDLaLleuTsSRABx1pFJtIq87mI4V5AEYsGKUkc3YRnDv4FbqwvVrMHCbvKCLoLEUa4xr+vsYvnE6cRBCwWP9CA3/ramZwpKBJr7hQEBum/CpOIzeYPfKcrHmcNdnNr9TU0bHj+yMZekGxS2WRU5z5ZEFm7+P+eezBRsOEjCpI1rhMtFpFZUWEwXl5JveRwvBvJ7dOqHn9VTeWJZPNUW+u6YCbryK0eQkxS2FQJBHvHCbYD9YWoQD7Zhbd7CAzWxLWFyf9PTdcoaWnSCLOcSNe41T88gDsZodikk4RO3PuRq2gYXA0XyuDAfoIeJyVJj9E4vYaJwuDI0bhqEWw1iRslYg6CU6oqvKuxrU4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMTQzMDM4OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jNTQ0ZGZmYzQ0N2FiMjI0OTJmMmY0NDIyMmYyMTQ3Y2Q3MTJiMDI3OTFmZGZmZWMyMTQ3NDRjMmJiZjVmNDkwIgogICAgfQogIH0KfQ==" + }, + "_": { + "signature": "VSLsNwwZxpVPGhbQeJzer0AT9zpVVEcfbQ0gViV5tJEyfcPqRXEOvvz1oJG7VlJuAlNkITr0T836vgQ92/fS74amQ+VG/QCUhbMwm6Qi8Op7AoNlH2LPQWDOEBHFnGHesc6Uzyj8beUsgHeSK8VTSlOGhmMD08IH1nVJZ/xW3bf7mEJdSCQpm2e3q8p6ujcHM35YupVUq5aWX5VOh/TOhi9B+RqEgWIX5EWa05P04QJNbVUwotsOEAUWoNbn2r9y7aqEiHTfm4zy4z+4BOtF5ryscpPSk9ozeHHh0+sn1XhOZM8vxQ0OqjEok+4BqkSK3kcOVaRYBBep44dn2nPxaqKiUb4veT5MLw0Bc1bgeM2aqpkN7rLKNElXY2bfxvUVQI1WSvFQbFDenMQ9s+M1ePOrxmH1VMIshFnbX4+uRdb6MmNZ/SDECFq0kVKz5ueir6WuGLdYFfhoi79hHcWw0H5OLhcBJ5AhXAKTMqrg5cM8pxm7zUU9KMIZsUPtPZ0UKjDlTH0EDWhJLh6L48Ly7VC5xfPZ8iEJ8DZjlFWPfU557tbfzak/hvm8UPZqoee8CgYF9hJT5h6elwC5I9+dvS5PC14/m1N4DdYnwVrFPGI6WKo2Ifyqtus9389WG28aimuLcU5BGEfXovilVDwarJ3VaL0uMLkuiiMj3J5iZr8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMjY0NzEyNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zNTU3MzgyZDU4MWE5NzZkYTc2NmI0NGNiNDEwNWMzNjQzMWEzNjIwYzE3MjUwMTczYjI2YzY2MmNlNGY4M2NmIgogICAgfQogIH0KfQ==" + }, + "`": { + "signature": "xkvzkwPZTH82JwG5CFnAIpWbhXXiUhgCaouYtKk+QK76ZROcymxu5OPL8sYyLbej1gBwZt+YsfE2UH/Fz72Ga6CyyH2wEDcrW8ZppU98NGvcFwfSoUN0kqGUa3jlhlDrO8Y9P/PWVYPCPmTPPJTVv4aI5e8b2bNDn9xEUDiUXZjRe4BAAAF62wdA/PrrgRF3zUmmAu1QZY+LyhK2IHI+TAynphdKcWrjLABXHGbOHh6soabXjSN92xDY5RsyO1OHMbh86FCi7p/L8pZjYJzgEHYruDr+fGh27VXvSb9mt7qQ/unS5s2T/jERuRJ3b7nfHbyYlf57lfI5+TlDfvsJPiZU8n2lt9y0oM61CjGQ5yGWzsPAEb5C7xvoaCB6JJ24HbCvUnNjmMVwoWKOX9Id8xmEd77gLg2KMUNc9+J83Xt3ytfLFfAbsh7jnao3xI7gpNLUIx8qdikre7IwCvLUvkdA4p8rTang0lrCeOfSU/3brE+2jVmf27lBevhh9anTcdYlbxJGzzH6KGQ/fMLwQcKQRpSyn/sB28cfdKGxOOYfC2xhenWbFs4PqPoo7k5TxECt0+d1Xu5FegpXiXFofl3agZct/X2EDU6lvOSLgUTN16SohPprzWqeOnzhKLBdPHOCZQWNDBmaRrsoA/evyBicP2GXYyY31UPiDkCzdI0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMDQzNDIwMCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iZDg0Yzg2YzBkMjk0NWRmM2NiMTEyZjBkZjU4YWU1YjY1ZTkwMWEzNDQzMDU1OTg3ZmI4MDRhY2E3OTE3ODFkIgogICAgfQogIH0KfQ==" + }, + "{": { + "signature": "tyBWs/kMrilB74e2OPimaCQBdRrhPxSf1uOMdGtpegwV9CaQPoQ+vMw2h55fASMQW6yJCppUtKxqxv0xf+ueZAgr/8PI0G1ASDV2niK5dGDjDz3CJ5XxHEe75hPi+kqm/ZqnD0guaDeaxxT2k8C39DdA0oTEynhQGkTwk9zt96iRLCbtTkp2Nw+f6nYKTPfSffHFuObFZ4H9MAuDYag204eX4usOrQYiIbOyx/80hJXz9GCldgTfP8TyXN1O7aFy8HDGbWoM+jOpVvP4Ds3+d1f4awJjf+5jllVqQRbvO8k9pb7qtgdNFRq3O1Q4nYDsWPHPqMjSUUIDSjHERUjW6UnDQn07d5mODs/ijUA5o8/kaZVtmV6NfTqevohp+M25Nq5M/ApjiGyxTqlAGSNDEq4F/vlE/J7NzF++q1VHJIGZvBJrGBU5xfVFL+E0xAJjL9nxoHp+d+xKU79o0Z7OaPkEiyAKdHKeFtuQzqBdnp5IkhBMm9a09i+hCcG2aQiMDyC9/wWaxqmOMl+IS6TD3ZvPEq+Y11ok/cmRIRhoaFlFspM5yWawwtw21L7gXTqVr8fWdSmf3p1/ezKVKtEOCqSvXzf3gPzgY9J0+eISw3j76ed+jKtbxUKd6mHJIpod4iT/i3qx8lDpAf9ya8G+wE3/LhgrQGl+fftGIK1ff54=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMTYxNDYxMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zZTlmMzRhNDEzNTI5NWI0ZTBkMmJkNTViMzQ3N2RmYzJkMjc1YzUxOTI2MDc3ZTVlOWFkMDExNWZjOTAwOWQ5IgogICAgfQogIH0KfQ==" + }, + "}": { + "signature": "tK0WgkCdMBBrLdBSbhwKV+9VrtQG4sxibw3JfvgT9nPS62DJ1jfCL++4LPh1g1QbRzYoJMwqOQYGf3W9UBIE3771WeZG7I4BNbRK2aKkwdKiwfnTQ0rk1dDbqGKKTPmixIBBgaiY6OK8Oeoilougx/jJ9X7NCVD1faA3KB/ONmIJfoGezAcrUGel6fMBEf4fE/WJVPnH9nJ63N7v9uFUsXb3L7lEoer5egGigwMciTpMK5nwp2gyPrYhs4N6+JhiWxdYlRWD6+GoDYobBSw/cUWMuJRGxZIvSkt70sb9aFisZ7td22LgfGluu3S+W6OJlH4pCFXA/fYcU1rgBfDO/x2uwR+o3lFQ5TuDQLW5IqnuoBHCjHefzSbg9An1k9R4itdxaQC/dBgfufwWWJHBZtV57Yh4Oic2SXhGwTUoSoUsftuWQgvi0qv5eTXucL3FurFwWwK2xYE1ZqtfeCX7ET56+cAK7zVi788gJNDDZ0A3Gqcb0szh639fkj4Wnb+pwqO0VTqerB5emJtTzp9ijIPxHdJBOVoPk1SEht9rVdH2uGIlUrYBE/i2ekJEjRo1ImGe58RnYknWRmuAIj/39BwhODS+KQpdOTc33L2SfxAHiyVWhbk54jdo02slBYGJa4Yrr1H1uVC6Gd80WIcA+D/zfnlMLYt00AcF1LkVptA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMDU4MjA0MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85ZGNiYzZmOTVmZjkyODc5Yjk1ZTFlNzFmZDg3ZTU2MWY4NGU2MTM3ZjU1NDU4OWIxNDk5ZDQ4ZmQ1NzkxODlmIgogICAgfQogIH0KfQ==" + }, + "~": { + "signature": "WfjpUoUvLOPanKco9KlZIPsX+6HCBVajSS7F9tHKdLuBalQQj5u3yzQrvEOwQz9yUWQjB3VwOSVRvyJaWHQcK7tJERk+mfaB434e+VKj59DzNXZjwUGXLnzT+4JgJCx+hVPZXG5Z432ZI4jOgSOMj+bifPWtRaqj4ehrAmXvKK1iTlRYr7+gOfW+6F3t8r1y+AgaWhi3Zv0P6rygOIwi/z3p99F8fK7k8nKjHmfvWcprmW8bMjCew+ES8dGArcThAkReaashpDP+RxNZRZ/j29TqZZXd6cU4GXwKsZycxSHV21jTL9e5YTN9Gx4dH1MSXx1hCeLVIxs0uKsviytHwHASuF/g84fOQ7fNcm/uzQfkrHdHs9HgCrUTTXn/nxYUTXOeJ5KVzgPNeWEyx7k6/a7LFApUAPsMerX4m0lucxjguKjBLHWpJQ1ozGRcqT8PeZ80+Kf9Ufh9k7ZIXQeIpn7n4pQEncaFa2UJFjDbSj0l7FF15iqXwB/zx1Ko0Ef0W8HRg5YFWJUECmDf5K27XQ26TAUOIc2XEIcyXJKtg86LH3wnvE3/jiQ261T3fHjfOhh/VCJo69T2Mh/HrHEe61WgabQCTHy4hYkkQgOB2mR+Cl2Rwg+7S7Vgy4jLwQ7OZZhMf7jWL1akdGDPNo1TxKufNODOqIIWIre7GzK+2KM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjAyMDU0NTMwNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lN2Q0MzkxNjViZDNiOGNkYjVmODRkMjZkNzQyNzFkNDcwM2E5MWNlMWQ0ZmNiYjE0OTU3ZmI3NWQwMGVmNTRiIgogICAgfQogIH0KfQ==" + } +} \ No newline at end of file diff --git a/Network/eLib/src/main/resources/skins/chars/yellow.json b/Network/eLib/src/main/resources/skins/chars/yellow.json new file mode 100644 index 0000000..5dd08c9 --- /dev/null +++ b/Network/eLib/src/main/resources/skins/chars/yellow.json @@ -0,0 +1,266 @@ +{ + " ": { + "signature": "aF1n5sdUvdqAxRirHKrv4u7V0uBJlcqNUQvYoE83tFdFq2JbrvZWt43GoqiQfaKJiW/+5i0FYFdrD0yPLH0bC80ffaDeuk53sEzbX7Z0f6n1SmoptuVkg9GZlQ61FucZ7AYE/Q5Sk+rybbEHz3fdIyCvscGp6Fs0eM/mm5QUgfKypxQQEbQoPHODBhMJ8eg+Lv5FZGg3Q2vyZyZgsAFgUAFeYFho2nQOwwrwaUrD74hRecJPfGyLvbn94GdLA3V3+J1hTcSrenAVZB9n6k/foVz8SdlDaDSNO5WV05ybfNH+UQY6jdB04z28fLgbVIByFGui9JygV41K0AleZZREsm70+xItKWyZB9ML+E48agUnRSDxrg6GEdhBwW+zPWIT4k6VWvQPdEKN09vUA1xiYd5EKQeogiefYgmVvyKgqyp5LtgY5L5/Vx/WymzYUMP/H5e9GXPJ+IS9hMC8arRNXiewVXzv561ON79GnVRCXIL28tn4Pl4yvuzibcVmddNyhbF44hBj/MXOGIivE7jmbnB0Ti4om6tZXg2/VfTx8zQAPW0+Rg6G0jtnSNHIl+QaB2J3tyXo0lsyHXcVCyGj4wWreiMD5wAvYcyfws0rSqDLLxLk/si/SrumHBI7McU/q6TtUX8yeoQIZ+StSalEihz3NBvZp+SXVch6XhFcm9s=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNjQyNjY2NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xZDllZWJiNGI4MWEyYWIxMTQ4MTY4NWZkNDRjZmFhZDllZmVmYzlhMTFmMzA0MWI5OTljY2IwYjMyMGUwOWIzIgogICAgfQogIH0KfQ==" + }, + "!": { + "signature": "dMzEzDj86UiRWe5xJNmQjbln/FQYwor04lWpdGYanXaySFW0NX24D+OSgAquR0hOsD0YFBYH6RvX7fNZCfLyfD/rpPsre8T2N+/6WEBO1RXoWJIWReTqEMBN3k3fdHYVD8zOF0wB7FSTHalihi0njxjySeB2+zhG0J6PeYv9A39VrDPyh/ti+LNgpvw7XBCpTMo7YuxY+Nam1OarE6ysrrYrvChiAMQcStFUPCrTZqPZQB3lKWTm79UML7d5KG5zs19SPB2iRfUu1nDOV3C6MEDQ2U0zMKxxuVlvUgv68YDeNY4dFs9yXvT8zC9uV/shhdM9kYejf87efSDUezt203itgxW4jGxjlkN7X4FN9/SHS5Q9HIKl5KOZdNJ1icsyBelKstJevsfaFatZL/W/vGGUp54GKL9r/ZPzykRsbSqG9TyHy2+0lvNw2/dTRf5a9SeXWtvFhsINtg/mVd3+EF+hE+DxMdTuj3RdtYxhwIkod/gpjGfNdtPNLGYHWhO76G5vQEiqfWKcsfJ1OMd76zasVM3rBOzceiKzlGEtCVzicyVUsS68aP/q1x7F5cKNWLH70NpKNUmrfF+DfES13OGIkVp1fzfSGZxZNZffROWjmECmG/awUVnmpcYqgaXMV6G6LTye1JsyUDTQ/jgCY+Yj3Bsca3acVLIwPNv9Ya8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNTc2NDMyMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85YmQyOTkzMjY3MDI1MmEyMmJkOTYzNTc3Y2Y0OWM5YWM1MGQ3ZGEwZjBkYjI1NGY0ZTdkNDMzMGE5NTUwZGZmIgogICAgfQogIH0KfQ==" + }, + "\"": { + "signature": "Sqbc3UrepsWGq2rA3FM/EiqkRp52dCTPmCe6GpeVOwxAv7ZMUWMvOrkf+xwI8me/sIDGu9Ds13umznK3IRGsbm0xP/aEGXh6yugwXfqR/c8+kXvAtVeZUwZ22XdF8emo9EHVJj2DM0H7xhnm6RFiJ4gNmOzRcENZVzvwkdYjo9VK/tDIUqQrjmG4JP/UTjRMH4FHamckyc5p8fZICTJF00RGnsttPkuGaImZ769Bm8hCEO0vhobn/xKKpLg2PXzPDkEGFqhYTZRR8YX8N6dsKnrcZVG+ma/sEZAmox+1gtc/P0SzQMi6ZNvVWJeHBATivAzUcmQ1Vq4Hh4Hr10afz45/OiJeAspZ/QmHYBkD5kW/0SULmyKPJ8u/XqZYlWj6NN/V/poiosAIEWZwAd3XDTrIfwWl7EPEz6uCp33ZxH0r/IY14y2qSceVe2U4JM85pEl7AgTrHEoz3CvdovmClr3EriEGc3GJfzJF7sIYVInuUVMU3CC8hYIjtKos9NVZhl0ITs/JD724HXU43UdpM0+Ep83ktceN0wT0wIlT8BKBRbQT2inN6UElYWOPs58iti5Ehdckb7sABtv5F+1178by2bG6x0qt9Fcg+xT+CJxfsBVjRbNBZ3OSfoZSQ5lnqUtndVRdYPeNCi5uuq4GfzuMCx5VLCbYyJ/O6lLLRpU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNjE3MDMxNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85NDg3YzcxYjE0NzNkMmM0Y2ZmOGE0N2UzZjU2MDYzNGY1MWY4OWExMGVjNDJlNDFjNmU1ZDlkYzAzZjBhZDMzIgogICAgfQogIH0KfQ==" + }, + "#": { + "signature": "s2Hxk40mzR7LoU4DlfhuL+aEd9rf+AwJgnKrkPs2s1AdYEkpTavtgBdF29HfAN9BRb+wvu9VnKSKgpQNu03c5M0CW5yHT3iD9tWGnsB1aiMtBA1htoQgrVmcR+Qi5utMEBwkl5v6x4t3fyjHpfrKsFrYiB5NNtkpVuXgpDNf9K/u4ZYIMXWcbBUZUsSvBlolDr/RasRPXqrOG66SqjRLC2aa9KWAtLIhSnW1pxKF5j9469e1Au0081E0tuzACyCmLKOweQl+vUtMxyWqHUZIT6TLuv+06wWBdWPIScpxuJxg4oFaUuA9iwbx5Ur+hOMLQbG3lLXpfEr8YmWTpk0GzAZYSdDBrQ/k70bEVPl6YOUaXvN/yIvvtLx/jhUxmFCxPTt3Voi+oedeJruWWBeOj7BzN9/PL9LAq1xDhs4kC4oanA0Ck2UCauU75kiXYZcxS71oDF+tyhx94WabQfPAadkAvv2MeQmHdYubs4XGYeIPPgsBtx5Ab38YJkjWN9ZIk3tTpdBmtIFLEg/JbNCwmowDZjDZ/D5hkJHH8jLNbZyFwyCJvPzRAZWP1X8lSF6n5agcNpnYq/qeW2zS+JHh0sAjPBh2nT+Q4hIjLenBiZnBjhSilw4izGnWf4hOIXz8hb4NbzBhvZr+3sYSwSh0LpJWhqbDaTeasCD8RlQnfzc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNjA2MDcwNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84YzA5ZTAyMTJkYjVmMzY4NmRlNWU3NTlmYTczM2I3NmJiNjFjYzMyOTYwZGVkMmE2ZTQzNWJiMTVjYTNiYTA1IgogICAgfQogIH0KfQ==" + }, + "$": { + "signature": "v1xSm2YZL+ZTAegN15CLzusg24Da3I8ebk7Rp53uRG1kc23+9l66KFEQvZi0rQnbOERMbf5krd82+PiktzPjm5Jh9ccIIh5DltQHBYQ5x/mIMaTC4JTrTzeQWODCQya6LbJ34YwihnPYBU4SK2pMWQzWh0vN2hLr9GUcjTcTJ07Rb1K7i3hrXNCMz30NUjLhZRQFQqpvY65v7qT7+diI1CxJkbE/N7X2TE0FyLtp2qATiDnBNWElPGI0KUBzJg+Dgwjd561FH/CHwPSQH3kglEG19CSsDiJxTG48fcP69bqVR6jYr06p0zPdf3RhpOBmTX7Nnrr6DtiFvceh53PzZV4YbuNVENPxDe//WNknQC98RIlcIR6WzHWgryYylC41Fe8nvkXXo6rML4vyMWKCGeq2DljlBid3ILZou8XwnMou+4AmA2Ip4sy1EL4yPAQSNtx+C7ZtDMWJ4J3zqSMF1tB9Dzz3QlZ15hQA431i+HiDIboqgOgDjfjvV0EEPDNePw0IrceyDJ6suKM9an+edlrf3cbmsq8iVSB5BnYJWjP9W5UsfkLzYgbEhojKr03qYuJWd1UsOicZ7rcY3f7Wr1dfSFCdI7jR1EmZ59GFQhuvTwb1omHqv1XiCoyvbViC0VS18Sj7LA5NNHW05iBiXqgQDredSPp3xsEfvVXp0sw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNDUxNzI3NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84ZGVkY2Y0YWJiMGFiMDZmYTM1MTFhODhiZDdmYTMyMmJlNzJmZWZkNzMyZmU3MzYwODM0YmQwMTE5YTIzZWMiCiAgICB9CiAgfQp9" + }, + "%": { + "signature": "C+53NO3qa2stwcYkiWgCVCs+2Gk4WBtANdWi4BTGf0UCGy1etrPhm18Bzp5dnhEIfYyeVTYIpNa+t/o2LYjjXEXA8u5iX99Vgg09XKM/OTqLlMtR+Nq95mZbHL0N5eSf1g2hZwfelTA1qV6Khm7IP8ILiCADNeh7JrEDMBfjGtZ89slCF1y/yDIHAFHemVA76AaMK138ZUO/NrOs8fT9DmH4Hj2yljuPwqf60XYhcKFm1d2X0qAKRdIo7ImcMNie3jUOZRRoxFxb5B0LAmkOj0ArnwDVVm8n8CuQjCa46t+DeGIAvMLEFoFBOaOjUORhHIoBPUl8+llzqI8cDdji9yPQRa3AnBQLWdc75FPjozwkEDTMvZ7NkqNepbOgxl+wVFkv0a/Ofu8957SlbAHkZm8gjA++OolDPhO8Eatbbghqcy7fW2sDXu02WlmgKwwtrSU8epNiqU7ZRVU3oAhlzLDyLzxrjL9bLz5gge5xE4P/cJHVNfCdwzhVPjq8t1SPXqxAEq7Z8/qKgRl8yqiF3z5PYOXHe6iggHnDUQjZpQHhHbaZkx0jzFYrpbBSOM9qgHgQZIfxfmpDdRnhxV7M+75TDj2SoNOdLdU4aCmNMyCTnJISAIsRLUsYvX5TXxy3viY0OnUXfX4F7MTgGcONfsconqQF7khC71ANe769qOo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNDU1NDY0MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85ODk3NmM5ODkzMDY0NzAzYTIxODZiYzc1NDA1OTFlZDYxZjE0M2IxM2U5MTZiNWUyYThiN2RlMjAwNWY5NTdiIgogICAgfQogIH0KfQ==" + }, + "&": { + "signature": "FuvLFxgfbmEWASzGLSjAnQeMm48VaYFNhBqEhSr2CnVP4Vrmh5k11yvddhyfntcziNWF0NVvUkb4IUbsDVTfhm6lVMjX7UE5ee2AjRJF+hhSn8kzxio33ueF7sSV/pdbQXTmTRwNDdelYgy0dT7kMRVx/xF5oju5EJM5Xe9XlqV1itSTuu5osHnUvbqTkmsxcfkQQy0NmZbG0Y54ufN3BSlBmlYEY3juOvDcYvAxWU9uyu7Sq865VQH4N+tMAHiDWrv9ufvumIJKAYDEdWSY3CesrSYUht4HNNxJTAZc5fht/mXIxZgpxHtPw7OyzcC16vzOPCRN+/4Vcj/+YbwQa+qlUo3diUWkgJbsBjEBvIm5usMUO45hmW9IKiNd2Tdo/2LbR8veE62e6hJ8mZFwd2c+IGl/hacYhI1y5yaOuQYoaMKfyhmsf7Gqcdr3BBcuiwxP1J0Esw5LILf6kHmMAezP3hB8ROr5E1vYco8J/pUbBInXpz59CjfseZWPe61zsv6fyZ9u7eoWH+IbXcA7I7/Y0wmMoeS1ZOEkM1x3LeKeYrhLqINrnAh1LAgCpHMdPDdJvpckhBrybXhFOrvca53140b7RrHtjuPkfdgyglH0+NQpHhNLnOJ3rehQN37+rpqb3VA2wD3G8htrN+hZHCOmo/njymdQsxnUWwYjLrg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNTk4NjE0NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iM2ZjNmMzNTViN2M4MDJmY2EzMTUyNDJjNTkwZDI0Yzk1MWU5MDI0YTE3NGI0YzliZGU1MWQ5ZTk3OWRmZTMiCiAgICB9CiAgfQp9" + }, + "'": { + "signature": "xXMCm1ITsFIYjqfQNm2lmmiu0f32Onk9kGNljPkzeckqzQLCdgT9NU5aazstYuu1FT5VXIR+RecGrwPXU8BbRYRHKObAvMyRtcVTMlj2PqAWM6qncvQaUc/eAlxCq7BS8y6Ad7PfhmzOS9sUEgA7mCzDtokJ+SokpviQ9lVTWFv2RpVTYCD48okIAS7z5OGCwHg1g3QjSuzbM22LZoyfBiZs+NIT81EoJdW3/sYcEy5TfMniyBqkh9QXybv5fOGxev4qzTTauizi508uYZMElwpJ/u/G46az8Tm9opzNWJB58fpRWc3bkDOFd7YSee81yZjBg5TSPrHAa8Xril4Tcnt9zWAKYrOHRE8TrE0aYh0zVjqSnk32Knu0YuSvVgPHIfswW5/I1tTeyrsknB1PSHxOkVjgfzD07IYYslhVwtNpaoEop5pipak8lKROqHdEztM16ufFdUDsM2qnN9wJccI1sq5WUKyrPMEVf1PM8/kMyHNo6cnJrE0es2+inCcOrh9zfqFmSn8BTE87h1fsNCOcXiX6b8PvzzjFYgqPY2keekl9WKZYUz6se2PqvbZE5eSXZVdKVI+QDHqXEyX4cQEUom5M1hUGE+43HHRUp/Obx4UEry10SonHvc9AVONjCGqqnSxIbwb5YXv4ZiKF0NJEFKF9DHFAAYTdUH00cHA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNDU5MTQzNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81MjQxMDI5OGViMTcwYTQ1MjFjNTczNGZiMzUxNjM3OGI5ZjM1NTY5OGY3OWVjNzEyN2ZlODJkYjQ2YjU5YzM1IgogICAgfQogIH0KfQ==" + }, + "(": { + "signature": "Yw1XDF1LXhYqFM28S9c2e02tLVAq2oc8ZX+ox651G/TQvgIi3riXLRR0QxHW9imMalxQF0ppv0wRstp8BdPXvcyruF5Qh+tFqxPWN08yfxvKTv4VIfr0SgBBsdp5rl6DwSlSb66tpHE7Vhzaohb4aHD83w3/IEoVpH+3IFAEAM7pWnzUp+vBoRyXSVhdknMtMVUn6MQniH4pEzyo0TNyde6buu0ZueSsf30irBBMwwnvfWeosNS91v5CIt+RRolavfwMOkWmaPJ7pySwcqsO2g/9JtLkjha1vfiI18BKM1dvPpIaysmboO9XbejM7GRpicNrNDJvLVRc79tdcEugsJbpo+jloOCoBo8/oU3pe/Dg/4xLLVzZ7gC6p2vOufKh2gD20mVNUG/92oWKl/7gdWhxi/vyDnyKNTr28g6w6pQdBQBjQk4Oti8jTSZOGzRCxQpMZ07aNwzTiTu+IeqRT7kvKr39Vu9xxjohn2kRcpLIpf23JbYhudbWsw9g8yJ0ZWZwuifMDpmTd7n0YQHXw98p636zl7ReMFmjJzkPZTjsk04QjthcbMPEfJkDl2/s2oHlRyD7OBpn1YpdLzciaaXiWb4j/47jxIfnJ5WjelPA2sWJtTiB6sDm+rSKlEyydG+dKmzbF8IRMjkrNjy4JvvDmrPR3RM5eY+bfSq6pRM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNTAzMTI4NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85YmRhNDE3ZmE4NmVkMzM4OGZkZDc1ZDFmYWE5ZGIwZDAzZGNiODE3MTBhM2JlZmU1NTk2MWIzMmFhY2Q4ODU4IgogICAgfQogIH0KfQ==" + }, + ")": { + "signature": "U+dMCFBryy+Ziy4+7OYxJG2y5ko6i6/pwm+qANPekaiHTz2HQZ9HtN8nKh/9O7T0XJmvDZMlvMVhbT6fugnG68Mzt8bHiAjlBRqWYtk0T5cBhdxK0Tp9eerVUUVX/iiDUPcQBbiSc54KNPk5l2gNmKmU10yUixT0SP9IhS2fycwdLXzuYoVnKoq1wpu04EMnyBvRM7nhaWcPTXvzf7qXmRHaKyKcujOsocbckbk7wwGBlR/aepTL+pe6Xv8YzxoYFV2olSIPvVW3nJlZVAnU59mxSOsw/1KGBWPEb0XVI5E8w3TCRcgjpX+lmoNGXP/kmQndM7Go/CDIwp3HETj//d+8QvSZHeiuiZjBVJgK/PWSNaf624TwztbVFPZ+mLqrzdMiKc9YkqtBqQgyoYc3VbJ1dDIQMG105fu9/0qj8LiEgfJ+6K9CutjWAOxp12h3amnwucJujFxxDrWOQptGhUPJBGLv7+HX1Hr8FEuE970h/dhCqQowME338vM5NUtHRdFkn2x5E31WvdAWGifdsmNdfn1T1gVeDANTc3l6EBmSOaT1VAYM1lDNqOzrE9UrJI5t4Ktj4XgqB7PvAsoxAsRkM1SIgqVdF3R9p3LumLgA0fub1HandPWDHsuETOjH960wWixpx21qDhpc7e/reHNndrPJYkyDYpPXww0Ia2c=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNDQ0MzY3OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kNTE3ZjMyYzQyYjVjMTYzZDBhMTNmMjQzNGVjMGE2ZTg3MmVhNzM0OTdlOWIxZGFmOGFiMmFjNGMxZTE3OGVkIgogICAgfQogIH0KfQ==" + }, + "*": { + "signature": "PXLT7AJNgjLUxetPa08KQx76t67xkqPH4kiLH4JlegKGC5F5q6EC/rqGsjIWbDYiRlBD1IElVaz2u306C0BGYTPkecsMflLlPr27mrj7Gq+Abitpfmd5g368J0l2gxCEe1zuN7GRpTXnKzWRb09tQJZJGVVmKWPb2soRENLjd2VjqSKR659BQN3i1wBKvemixFfnVG+y0XTRv/32J2zehjv5A4APvYgYSf8/6JTRwxkKqCGrAnFgVhdDgecPwEuKZ7B04PTmDlE7rZPRVbVEocozJhBWrDqIXMQ7sQUOLTck4TYzi6ZkCdrxAh1X0h6UO04J5VQeJVL7gtO443dPuSAxk86xRrtE6637e6pFbCJ6ranyh2qzRrky9ANeecywBzXIEm1vHts5tx4BNA0o3DLY2tftMfnjCZU0duGOXqRpVo2dGnKfLdlbEKlhcGbRcJFyOK/z3HrYmwpOmMicxoqSETwzoeA7BnvXzqo93zfhZ0Bj2wtNntJS4eKjSF/4fpGTP8ZkJCzr+wrik/8Sn0vvTFoarsHPVO/bolCkiEpjS5IU/bDKtjjcTSO/7CQtTODPm/esFoQvZiqkDTdNr3NviTRvMsichumsnYWPRNYG1vhzlR412r0B2KOaQKqztPkgwGRuEUgssWX7ezh+L4jsazVwRK/IONA88KLLfwY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNDAwMTM5MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lNDMyMzdhNTUzYWYzYjIyMjZkMzRlMjYxZjU4OGMyODY3NjQzM2UyMjllODQ0ZjUxMzBlNDdhNGVmZjQ2ZTEzIgogICAgfQogIH0KfQ==" + }, + "+": { + "signature": "UnpOKUnqgtZI2KL9em6ArI3ljMcIwbSsay60PoKSAimfIP5N0cHsAlH6KBpFL0cihHNFA+4fP6dKL66WHDw36jFxiM564RJgfHdW82qQM5DS4gunA460Db8QeeH3Tap21o230OlS0Pzfghcksa0S0ZxHz8lHTW9iXZHKVVjBP9Q4mhCbOxWKe0HHK1ijV8UwxeVktXbbX/YWmyMzTJF73UJS0f1yCB1O1piWOF+AJs4TYsxreQrprlMxJ6i76Gq7wzreqjWRs5ZORNpBIidfau9TRLz4vA5RDowFBVjIz902x82rkrWna+n5kNO+MHh0DJUFQGArjA5K7LQ6n0K4MTLJYHt7pTjnC22w6h+PlUoaQUEr4YSyRiWjRvxGqIwfZQS39tjabpypBxhLKddMcx4nRVoKeIKOazYoeDutpiglB0kM7vVXbK9yItvL7JDJvdbWqcQRhSnEtSoohpWJpf5w0jvJr4kmOqqyhRwD2R1F2KN8TAPBWXsFOUjbOP00TZDiX/ZQfuMfLl8LR0u5RRKJ0RkGLjicPzucpecvc5puHg3avvgTzVL8WDbZA+Ky5n3LrSJbv1dRa0oKBpZFXKJxZclkwewzl/MkQVqbjeEd97EROfrXYONWWuKQ/YnyVB8uvU402mo9jtzL1FvYnMQBHvGhhWQfOGLnqQdJKe8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNTYxNzMwMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85OWIzNjM0MDZjYmNkYjljNzQ3NWIxNzIxYjYzZDU4YjNkOGJmMWNjZjhjMDMzNzRhZGY4MmRiMTRmOGM2ZjgiCiAgICB9CiAgfQp9" + }, + ",": { + "signature": "GxEZ6g+Q1Y/3dhSOQMpZcxEbUxFMs6NpDTY38ceeoOgk8f7ZYYe7ETIOL2a04jUmmCS5QgD2d6SVkQyUZl8EUL1nErKsLjxco2CyMc+AcLM0YIJ8hlsMLVR7LrZ/T6yo/eghUQzjWWhhCBSBhCxrgn9GDd46GZT1I3tNGoHhKMjYzhthfbBcOjP3LtVwQAkbZsSAnuWRit3GUD56rMmKddffdV569Cf+22RXCZVWiBiCuxvQrMYgvQc2Gq5OQu/DA0g9d0PFU+YVve2IsCHm8jZrMZWjik/caYpfC+eqx+6xq1DkSRQ591TX0cOD5/KGu30JiJvpZiagOvffPLE5QWoRDQq8XYkFTszv9iRqbkC4GU4yu/SsM3OEy3PKCCGFAfOwUaaSIYc0B+SWZUqrNEqFoHyWe8MUgmn1hnFUntnLNFBAjyik/CpoP+3c0/lzIB2/SDbGaJRmPisMFsYI8PtDf1YAIeg52q10aYEqK+pLHJiF0n9OEPl7L7R+rC4SHUEXYuzMXDRy5QoorvpweTYb3K1POb0U5wl3lHLXOiV6BwAdYEUHo2bQQKqIgIXTBLPU358GyOmVBytrsAgwfHCYUU7O06LFniP4f5e0faVMCMSxECZ3Aa7Jorli/pbHOQB06DncdoQiVhbG2101e+d3tHdaI1WK15spdtg4U40=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNTQ3MDY1OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83MmE1MTA5ODlhNDg4MDIyNmEwMTliMzBjMDc3N2JjZTQ5YTVhNTU5YzQzYTIzN2MxNWYxZDhiMjRjZDg3YzE0IgogICAgfQogIH0KfQ==" + }, + "-": { + "signature": "vF3poa+dl8qOkvrtG00rh0ORYYxVLAHIgF/tM3trJoH0DGIXv21/TyLP58fL0s3I1xm8NSUzcY+6UH6M0KFT7Zh69QgmlJGMColS54/lJKg/M75HAc5+OtkqbGuwnEQiEJk1Xfi3ErjxStdW+PDuEDR8cIWt5v2Ju0DcNT73Np4jevYmiOgw6Z1RyQUxPA6M8zLm7CnI+CLfD106h5D9ZAr/yuQdidTCzYicHXtI++J7T6YmW0e3LLaTgVczC7o9rltdyPHKFQSdYr2p+lzLFsCQQq2V7K51U8jqr7Pf0vPgF1YaOwXVwNvpHgZn0QxoPFQQimeGWZ1+OV92tysHFbgE1sw1D8fU0RWlVFyqM/5eqQ7gAfdbDg37qquKPGsW08oJ4FT696CnaKKA337MfL8vvciXgPjYqc/BgO+1WfjkGmCG40zRldfWw8etBwpVzj6sOpcwvymY61WNxGk5Qz7jwW4oNHW0NQdjwWY7eQ3USSpNOMF3BPLSxQAG6zXLNXD7QPcRjotWge18X9I8qSRH7164xKaNj9PChFUrxWgNdNcrakVCiM8tk5KJdUk+HI1Tbu24Lw1+B74lo2sEnJXvgCJP4+Ebi9JixFqx0v52er7+XsadMIApMBHiZzsBuVxnJGkpsVV46Mv8z7K2N+8MA3AYV8l96BNmzXdXldQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNDY2NDkyMCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xNmJkNTE2NjcxNmRhNGVlMzA4MGUxY2I2OGEzOGQzZjAyNDAyNTMyNDBjNDNhYzQyNDgyN2FiYzVlNzU1YmQiCiAgICB9CiAgfQp9" + }, + ".": { + "signature": "Omx9Ud8B3hXCSj53B99Vcz5524Te4NulfH/mTwddXiR1d9SaG/s3CJeoSc+FEjycEWSCsEGSX4j69nBGAvh9F3dmrKSlWeqvEbwqDqSEYXRRbkSAImqwRJ9yUeb3oT4FVUOdQNuWHsB51CP0ajcHMas3peJ1ih9Px930i1/aTGCkxlDAQAzslfOl+G+b/xY3xo0HeulYkuMGfOHMmXiCB0ZzQEL2Mx6/AW5X8Rgu6AwNFpqf0lkyUKbug59yWMpFKf3NcTAS1KvQ8x722HyBwUFEqijHjZ6oIrBbbzGXD9fASv9zlEIFF1whs9CwaBZNIB3MTDiwuuZvsemxhOyygg9Zfd50olHA1B7/xFdgzVs0bdeGOv2Z9+kYn1cZ6sUMRv1xl398r1x2GC7bt5MOBiTKJ355++uvKfd3mkUfwDgeHp+6mbKyJnnbQosP2ZdDwDXxJHWY1kH0l29oBNzBRibxnmJvxr2R5vQjyfbLqGRHg86RTpo7YM1Ajqtlc6Aq+O5EPM6c4lpLSXCFbcakK9ZftzBmqvdqyC/7uRsuVHjAxJnzVChNuamsjafBJAJuaHPEd2k+QcEY58xG9KN5xX2bYyJsv3BYW6R0cQXJNFPWfGDFXgTIDhWfJE0FqHL2gSz+lYip5nzAY1rPQO/KHRNrjoreRiroLaJlxctb7wA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNDk5NDkzNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83MmE1MTA5ODlhNDg4MDIyNmEwMTliMzBjMDc3N2JjZTQ5YTVhNTU5YzQzYTIzN2MxNWYxZDhiMjRjZDg3YzE0IgogICAgfQogIH0KfQ==" + }, + "/": { + "signature": "iVuXBK6Y2xiimf/4L3cAmzFAyVigoQEDQDls7VzD717rUdx1xuDB/JKIyCWH8w9ruQWjmxGLejUg2hmmvQYM4VXVgr9m+2OKXHC9DODsg3AyF/Jz7iLZT7tvbxYjIVC+l2i3cJX2AuHIcv9GGDCWhHg7nBF13sCbOHeSYug5svIPYTPnX26iHsoQ+mp9HPWHk8YlNlEZvXJHFtszqlMqAx8LQc7RcjJ3r/vGSbsfpQnKhtPfzkfmkb4Mg+bQojDcz9NCwrtKsHtaiiYviEI+7yK8HLzcKWg4J/ACmE5Vz92WtXnI5jyNT0mEkzk/RwxlHsMzHBUWWUaZKGD2HqZL7ad2Mj/h/8wDBHmao1crteZjKNtLOh4cezhX0s86dH2g1pGQ4I2HwdOGNucrea3QUybkXixLsILSQZAE9XOl8Y5zYaHrXoijvQzbwfnNJkQMppfsuEfJpFDMgo7nHalfmiAegHyd2kH6ShB5QW3ufq3djeyeKTTPBT0FRJ8dHWZFsQGqCqBSvute4ykibMiXeadRH+4G1h3sYu31N0WEaDvVWRK6ZOvijfEnHklzKEIBl5U4le+UQOMHSJjPmiO2zt8gIozjn6cpsctGymdtxdWtozXBSwEkdpxu+kLohkamE5nCAyiTsqK+M0jfo7oXzWn4hS1IzzTnitQ+6ge41ZA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNDI5NTg2OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84OTZkYjFjMzczZDBhMWY0M2M2NWU4YTgyM2Y1ZjI3YjQ2MmNhZjlhNmQzZDVjNzI1ZmE5MzBkNDliNGUxODhhIgogICAgfQogIH0KfQ==" + }, + "0": { + "signature": "bYP+KPhmuHbJVNj0Itt/m8Guz7oY5RoNcWN/XncBvdnvF2Uh8y5Iu59b4u8S9Q3rls4zQHSuom5n8NMKHPPUsGcwZYzp7q/lbRxV3WeMfRl6mDZn5kR5TvZqveqLo2CylgGNXqZnQKHjEx4HGjOpL5uKpTjFkqmJiHen3O86cWCK+9LcwHoZOA2pG3eqtG247hXxTnSwybCVqHIAntt+Wtbw4Ww2WZhaDMTDZuL24asYUKVT9/bNQkV9THOJK7IwDOIJOLAAiudvDJHXD/QHgN4zIPU4GTNwC8pqy1XoUY51Q5dWDpneHEcFLC0iqwrvId5b1NbkFwMEgYo3HMRp1ARalx2O/FX0AvEeboOjfhE06vFliuL4mEzt0s4zyqU27WXGKuzlsdaw1nAbZRwfgptOSP/ydIjuKlgdq0FYsof/+Xsxct3h5ql0yctxJ9+EK8bBUlXXtYyl/wj/bi65qEujou7dhcWLtbRDvRUPl2LCATQDmZ8X0Pw4kAeyh5Hf65IVi5yYsS2y32SG2+kwyXJiWKOaZt7o8gI8HGpFaR4ZR7OTrPWLjQpUSLm9DWo5t1S04+1NP3ZV0BgfXA5GNmqfdfmjI68Xyq/O9nqqivWyo14qFReTHvbiO0tKvpDpDmzGP18v2ZdTSULtyqg/dQgGlhRa1HBVnmt2aExFtXY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNDAzODMzMCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83ODdkNmFhMGQ1NzM4MmE4YTM0OWU4YjJhOWJjYmE1ZjkwNmU5OWVlYzFkNmVjMDYwMzU2NDE1ZGVlMmJlNjk3IgogICAgfQogIH0KfQ==" + }, + "1": { + "signature": "bVvoGUr0u7aQueoSoOb95W4OpXVZEyHd/Ll1jTuRvUhYOGUF3rzR0b96Zx6Ke3+/pv4Zlknzg4/9K9srwgqvlo9MRxJk4ef1l8Gk8W1g9DSI6MNbEhNb3wtDrJNg7Spc0CqUni4FIOBgLCuy2jyvch9xcRE1yanrA3i9Mur+tokTOHxW8aHKrfSSw7YsbSKEywCaZ6IiA2u414ecUBeYfLnCey0lKL/yXFX74zeiMHHH9YBbX2xI8NJEQsLaLlsE9FviAbHzMMG9X1SEA4YOvk81isupHhAaIqMrIB7AVE9LsyPG+jrv30a/qT4PpXsuyccKXTtgRKFS5M+oBFVsDmzCfLfo+2DNq/USoGttZvmzex6lOU1ed7c5WZgEtB3BzQEU+jRQKXfbLaOzefeDyOumbxJ/KksLr6blkkLt2XRXDiBCemnySCo/AZHrCBdW0w8FUaquCnWh4zEPyyDSNlcsSdy/puUfO94zekuxelGe/o4iF0Ic+j5D1F3y6PNuRRP5s9LMRhykGvQZTdb7lCHaUB0uEBiTrTjg/SPdUERJ6SVqc8ShKkRMBzEjnEDlXy8KMgzSGjZyYQh6qF/x6nmz5ci8LozOkEBJJAyW5lRDP/YRGXjgbA7tqF+fdpXfCRCeQB5jGc2QJeHf0Zr4wR80VqD3CjqvzLDaXKjNZlc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNjQ2MzA4NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jMTg4ZGYxM2M3NTMyZTZiZGZjOGExOGFkMWZjZDBkYjNhYTg2OWU3NTlmM2FjZmQ0ZDcxMmUxYTM3NzFjNjNlIgogICAgfQogIH0KfQ==" + }, + "2": { + "signature": "AxjgeLeht51Xwt+ZxL+hvVhRo16YkjeuCDiDbF8MZ6RBYzMhx8pUMBNtzb5JbKzesSrRr1nGbj2kvzbqKNn9eo+IllrVpQa6FPFPLVRImvd7OqTnUUqCWyWt2JZ5FBaZUJlz69jm/kq236AFevyXOvJ2rQaN2ZK/sGw4EmYC7PBqOH7p8QPGidyhRxzib6XRxAydjel2JfqpcjvRgHHv5jwjHzf2JyN19xga+CKtvHVYTBwtbUSRxxPYXBUvyGHa3tRnzpaQjIQR0PWGzGtiwdNNu/CUy2HoHcLlkY47Ld6BRUEN6kYToqvOv6HXLhJ/ZNVT4ZuE78Z5suffS2ycCy9mzoZ1NKgWrTVdlmTnQSFnw/yCaiGI7PaBp2vuPXim6sRA2kbllX2SuImrINAzcwVkZlQ2unGq7Z8IUStbnY8fB54GKTJx/L8+FgIbVmiP5b8oIhqBE9wfnozdRKaNuTsEcjXJ5n4uBUJfpFC4yWVlrPulPC0DW2KcPIK6+nzTyLwGcVKvCkqynytrrbEF5QZKjYaoK1+Bu4xW3JJlLO57i/adO9U/LzQlVoWB/TSLEODSqea82IUpWP58BGdAMa9VSZbFTqL/WxVRNJIQjn6xuMVJ02qXuvRir8ymXrt0u8S6ULIPIGx/muyJ4KbXvYus9YDkNMI5hoiY05OxHIY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNTY5MTEwNSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mNDllZDk2YzkzNDIxZTNjMDMzZmYzMTE5YWIyZDQ1NzUxNzg2MDcyZWI3ZjRiNDkxN2RjMTIyNDcxZDE5MTNjIgogICAgfQogIH0KfQ==" + }, + "3": { + "signature": "omw+hoO2Czle02KtJYRrGLaKZsb/+XQC3Jn3iLE1KpLDbslf8FF6LILV5maCW5lx+FhEkDKUP75s9cynNJsfG1eORr1dxYoxTPa6VKvQpf69iLT8oZaNvmgHol2QafGEajBjzGomcdiGShoV8TO/ZIhLsaI4fQU/jKRiCpvUtN/APTZ7Itm/coz1yL3mtJ3fdfnZMCK3AGJhBCW7ny0r4BLI9U8mUayeEpqFAJDkV/xAH2oidPBpychaa5ZkvShZhssGcGivTeaF8KDWcHaA8AhGPJp2II9MPj74hAG0pmCNJvAbTnQNVhDLqt87jrs+AeUFRUBMWv2JMIumprvw3OZiKnmobzkgNSIkO5FUmwnqSN7E6Y4zQ4vROhmTJivQ17mGvKHZCT4ApYLcrtGnRsBLaFuMiIkd0adu3FpEEys3LAGgWXv2TJ8+S4797Zqlz8AS70oT1vZeyMtyRSIr6Y+LNX2YCATPtGAKwZ04n0Q0d79f5vNiGKdLeFPNOcfXgyKcT59wR9HU8sJB88P3+WN4nHVJ3NjwHFw01Sc2MocbmyBQHzDG6APpL3eXApj1zrKIookpQyckh/X44oZAHWjY0rglIqh/Jnj5Ag+ZbcA7/DswtetUjO2wRpN3MfJg/r+/AYjkdKhaG7v4XrobUsihVBCDNkboWliWZsEwbj8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNDkyMTQ5MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hYjZiYjRmYjQ5ODkxNTE1NTk1NzIyMGYyODEwN2Q2N2ViOTkzOTBkZGNiY2FmM2U0OTNhMWYyNmIwZjlmMTU2IgogICAgfQogIH0KfQ==" + }, + "4": { + "signature": "gTnPLg6H89bgDxx8RwNQuhO5oLQykIuhhNM+pQm9YvNTjttqyX9gvrnTf1+NLI2wBPC1GxCae53sDczQulkeQJEXOr+M+UKcQyA7t9p369QMdECYcQwXIGSbVSixvKpFDErSJ+YCS3zodb54SVrlkBCSKQSQoML1qZy1aTlusvEHi7jnRYXY+m9LDSSmAzLTEQ9n5aMClEUT2y2rCo3M6ZeRj8G2Wqabm7Xzy+2grCe5ZzUPYZSfVysoolVrQLAm4vM2ekDDzZbjpbUZyH7NBoQfu0ZXXOUnxboLPKq8I5PLnFuYpAxGIlcaBQwzW4IjFPdx6A9GyUAHD03DlG5VlHvcFV5UJzpF3YaWpdNJ7Fsb3UKHQtQfU4dYUB+MUkVni56NfgCpIerO1QO2BK+y8MLVr2WBSJXYBW4hxMKonpRg8oLrK5cSTu/TsT+J98aawv1W5Vqg//NTDizRogDXQipwfRknftQP3MXaG1yIFhnQKuTmYI9EG8Zw9cdrtEU7mll4XR6Nhc+g8Ok66ZoZ6rVtgQOZqDiu8Q/aRLPe2ro7lEFP3vVK7ZhoGI2IBpmsgKO+cIXyzm8eMBk6ohhABGZrZafT1j3v4m98mJ12c/hQ58LJc471MibfWMS+oxdvKkj022E+l0hNoSFE2esfcwVaC88qHgAdR+YxZ8LWJ4w=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNjI0MzU3MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mMGI3MWE2NjlmMDVkNTU1NWYwMzFlNjAwYjc4ODhhYzc2NTlhZGIxNzQ2OGRlNGIxMTJkZDY4Y2U1NWE2ZGI5IgogICAgfQogIH0KfQ==" + }, + "5": { + "signature": "AyQzkzultSvI94bxdn7rw9r58+VD8xmbqAwDDHo95+shgBypzOWW3gJMBA88rs5svNJWB93SYLlgcgx8q+UW276wA3E29H6TCFRBa6k7lq3DSZsp05l+D2F9INICgdqKlC4J526ly0aXrpsrqthkwIgWZdHeRmRDPNJxAUl9Usz+BBDNME+0R9si4fpW5Xms1WOx6XMO3ov+Ji8tC96qf+BDqw5FWgI3XYHx65TKpcLzsk/uLWS+8t1gochlPPgCQrx+zFWQ7JOYEYLHWLgkGRCXLnMI6tMxNFC1UKQjJjoSRPit9s5+Mac7P4WQd9pFshN2xQctEZ3TtmlaomAJ7P79B7JaCqo95bXnz1rmlp1PxJan/GvkiT7lbPAqgHbioX58geFH0kik1LZPlc7+F/w7CRo083sPY9uU3oEwrRhXfxiuAz66EnR8Pgl1WLzCKiDlffRnd1lPsJPdnF47O8ApSuve4a2TTqR5N9RDFgQP1Zl7FIUG7zPsWjrC8Qh/gZgSyPg9YT0vivtcMBRBkESUtKE9mYUYPg/YmI4qEpNDQXG3sAEA6oqJvG5cTDSZjbf/aA4ClRUWM0yMdn/OQ6ifEa/l/ey3KVC2nWvLiy0Vg0ujC+zzhdu9fhZyEE+V+ZcjwCSepky164uVREOLNQtaNzAxU0hauimHrmESV94=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNTY1NDEzMiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kNTk1YWZiZWZmZWZiMzM3MzlmZGRhN2JlNDBmOTU3YmYzZGMwZDBlNWE2YTVjZDc2ZmFmYmNmOTcwMzc1YzY5IgogICAgfQogIH0KfQ==" + }, + "6": { + "signature": "gS8Kq7Cn+qXbyQjSBKOXLWuNXVXsrK+iOrB6OYG7XwipXqo4NSJG0zKl7s/PWXQ1f57hKL6Ojrqdoyy/jDn0/oIF1LRy9sHJalkCfh/BubERKJILYEY1azoX7OWEBjPVt9IzSybcmbpVnczdeNcBRiecZnQ0E3e2XuRYVlD4HcUjmbM9sDyCHI0WMtyqQhyDvNrDqGnYbdoqouPb2HtpPP7iHuRveQZUVPL2A9PlwjF3Zt2AQLfRM5dzosIjb5SPiwaz9JQ983Zq9nrdgg5NOM1xDUwa+EpoZBLnoyV5H+Jft07A19WCfHBoj7zKSi8ZVz/O8VNDs3aL6XaVwEejaQyCKd0IB6U5VzlfmGR2p4feIL/PA44PgK8RsfKVELZSZq7Kfl6ojmfRaNPXc/fVXQzQC36CqmG4NHfmoTCPnZxRi80Emwci+yJ/TdagZfY4fJ9FBMbSokoPcwFKiF1NcvJsIeAbhR6MUfyOz1F+evNsKnzRu3GNZxYK5ukX0e8Gx+wNta49HCm3ycvyr+adbmNOPIWaXhYMSmlh5HLju/pYmzIdCX/JFrjxMYjDlcbnw3CgGNZbO1pu1a63oaECqPKNlkKRdLVsJTGElXYZHV3RjVTxSdq0YwTlfeu5LV4ZYcTzFfSe7VukTkslRwK6r7yOjwTxPC4C9orfDt5JVjg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNjI4MDM2NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84MzU5MDY4ODVhMzhjYjBmMGM1ZTRhNzBhMzhhOGFmM2JiM2YyNTkyZmE1YTc3YjkzMTA2OGE1NjY0NTM4YmY3IgogICAgfQogIH0KfQ==" + }, + "7": { + "signature": "eACvYGBlXkau44Ky4rn24prOpUAlHw5O7uzMeswk+Cup/c7a1MarXbOALIqgGBltNSq9ccrXDQSeUcDB26fQGzhtL2f6Zgc7+4BUnDJMhClGZ21T5cWJATr+t0cIgASvy3Pq7OvYV7Ntp62QdVUMuWo/5sPRmyBfJn/b/R+kkdoQez2wk5nVTf6i8Fk3avZZIRgEvhRrMQQK/zABcfiP1oC9aUHDdBo0y+6UuV8wmkWsRaLCISveSivrpbPZTJPjtguT7pgH1JNKC04dcvkRL5tEsm4iGVUnqkbIpr6fUDE1eQRK+cMatbHCfMub4YEY7J3WqkWAk1RfIeg/5PHprEF30fRdtmRlwuhNYbjBqTEoCcs+734k4q35LVnaLqM/GeL3j38BZaXiw2HvFvD6aOOjN3wERXju+MOmK1lPQxUGinZ0sAI9QBvIComswAPQtIQlP+Kppf6scmek6pAheI/ydx0SCHh7PJJcQ0tii7DTXFSBn9QFJ/c+CqHPH9JobcDr5qacpEUGMXFt26BuRQMJwQybehuoUd6/lcgnrkJuPv3ETm8EwnMEjJTIIMv2RCoL9BZcvwWUoTdivvGBwTgZV11ftmxvyHK/X+2ISUYOhim4NJST3Ie5JYPu+0+KfWMStvz+OzMdCuHsiSx6CTjZOgxw2TiB4svjte9gaNQ=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNDgxMTEwNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jYzVhMWJjMWU1NzA5NGUzMGQ1MTg0ZGQxMmUxNTlmNzQwZjY3OWRlYzdkYTQ1ODcxOWNmYzlhOGE3NjI4ZWUwIgogICAgfQogIH0KfQ==" + }, + "8": { + "signature": "u0TwuwDe5BdyY3Exq+NtThI2YnYxR57qNkYaqcZctjHpulMaEK/LnNL8Uokv8GBlIDW8Q7FMrBHpWLSHtsPSWOl3kw2hqYfWTAf2yoprbeXOh5wrMjDaGE0lmApRNyLQ9g74YTHzRJxsHHs/GISZPRO8BPHUpY8qhIxvRLqJzUV1pS7oHPhnp5Ehk2qEeIIStfYPF1o9wl7Plk1HPB4uVUhnvMgU22IPc1Iu6usa0sDN97J/E/dD5sE0jwhiNIU3vfuiQgzbWiRj7mFzoJd/7ET7w2KLiY/omV1kQgXYrVJoqVBSEE3vtIfRURxkJ2rlpbfMdHGOv/IRBvrdbF6ThjVEYJSSARspPAFw5hQo/GpPDMqgKkEZH/uz4Fklmyg1N4wuInh11/B8ghwAcEqZAIxi7dDYWn5Oi5m1PWw/uwZGp4TpeaJVQ/vMaioFGpKzIDrADvdCTSbV1qVCkPqBkcMSnRI0/cQJzz13zNY8E63mbFnCtBIsDE9dfA500rkMBNdny1Wod2MTWBogor5CCJFUhCw84Pp6w2RVGXoYGEABPgmkLm2q2BwQy0glFWY2xqO8kXwRsex1MYhRPepo5HUDxocQp/VP7pP79kwHzeF0yWWT4LHeQwJXRV/YAKax/AvETofJAo0Y03igpmavFttsjWzAAtODcmyugdNuiFY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNTE3NzUxNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hMTgyMzU5MjZjYzM5NmY3ZTY1ZTMyZGNlMzdkYTZhNzVhYzg1MDEzNGQ4YjQwNTRkMjQ5NTc2NWEzMTJjZWFhIgogICAgfQogIH0KfQ==" + }, + "9": { + "signature": "oZj6HazyMyp5CjfCPl/NjAI9GbhcUMu4BJK0OO7zwE9wF5T3MFOiAJORwZFS2R4fbSJ7z5kSJusmHyH41y5Ka3vtiU2SvI73S7loaLDlfdhuemkoWHVqY7jHvlZxf5PNOVmyvlwJYyhLuoDlGX26l/7KjB7RA8XI86ie2Q2+yqBVfo5E1D4xR5ddv7c660cglSJiJ8n20hbI9mpnGUEzu8B6dHKR3kBwhKBXg3dB8cDeDMvceqGhRNC+Rjh0wtZsdcbueTlnGbRDCYrVoFq768fuc2nYJHTJkSo7408GFt0f6AVvTV2sSVXn27pSA8p+Dlcg2CaVlNTMId7rGFXdxV1FDdOFLSxPOjicgCIlqGzWzQwRx/aS8rUu9sxJ4xHgy+pu5T+gqAbXwzX2q+OMbUmf6E7sCP2zHXM06EfMyqJ7fU5xrMfTW/dNLw/CClaWEp7iDF2gxUIwyYc47MQX5B1eRqzxENp58dU+tfkKYbX24IC08uwMFjyYS7dkpnxeRld2UAAaZfcFxFUgW9zaD58tFYrx7mF+uy1HX10zSmW3JR5CU2goLklIhYxS0gOX0svWATrHErs2/2ohyg8PP1GYrtjvbvb2VP6eBczzk/YhOnSETh1obJGikjKqvGHMK/aeGNoLkrIRPhw4tK8xB8q5iobGiQ9firOMQS6fMxA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNTkxMjM2MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80ZjVhNTVjMmQ0Y2U3NGUyOWYyOGFmZWVjNmUwNjdkZDUzYjgyYjhkMmQzMjBlZDRmNGVmNmIxMWUzMmRlZTMwIgogICAgfQogIH0KfQ==" + }, + ";": { + "signature": "JMf37J/A3rPnaUo/eOT9M5vBfKxfi5cPclknpnRfTvofupZ5TH2CXeHrH4wX+jQkNjBb7atFBUeHn+sdVgyqZkp86uyHlD5pE+NlbflFKU/pr/NHOSNJIWiJqiz/eVFfLiRN3JPsE+0OJQ00XAUxYEJ7JE8t1wdiBOGMAc8S5SRkwWsY8Ube+gwmdC593CrIO6UcogrNkNNHO4k0EWJZ7nJoFiuSMWV00UV2a2IVpceVULyoaudHn1j1Ssj8I9TjGhtcW5vtbVW4503qtz3MuRC3+GZGb9SXsTj2/0bQbZzkjB2lyp5iwaydfrnsKK3Nzx2TYpJZ2JQXuTBvKYLQaaBJDs9/NAQppyztg7+dPMQ04yXBx+zQqs2UjyWHC1lJgbN/ko7eri9NgJdRA+yHm1MgFbUSS55VZf4FhQraeD8bRfneyNs3tClHm9ywbYl4hPb/hU394vQFlXIucFXUOjJMYposK55X6qzuTPQwNJW3x9JALAKWwW9uepTJwtQGyX3JRARU1szC1Hplt054GHvXN1wWTueeDuQrMh4oEeFqIfXkZmUR7vjI5iyj5lESqsNKTUabiCiTZbecvP4RxLq51bT9sPFjR8CfVeOViZ9KHkVGrBHulHiR1U+HcvKDrORpp4GNnBhP8wGRewlOBsQ5rjvxWApBQZre1c6QDWY=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNjM4OTkxNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81OGIzYmI1NTliOWM5NjJmMzU5NmZlYThlZGJmMjM4NzQ2ODU1MmNlYzk4ZmJiYWQzZjY5YmI5MzVhZWU3NThkIgogICAgfQogIH0KfQ==" + }, + "<": { + "signature": "Hq5bPDyiSsm01TB/C8ScfBcZ+ABX7e/cI2No0Pg1epz714ezlilZrU8iRNSit9msDDkpjVVaZ1E/Ll/FwA74aFfYLkclAdAQlm3HfX4NCy2kItUkOYQtwHNJ6dZPwMN29rSW1qc5P5gphgX+m28K5qBebP/f4rAFF/3vS1agXsq/IrUCzrQ3xHFIPyzwteWG8EApDj+4zISMRxlhR4VpmIIzSv1Ej2qo3bJxA9N2cWQWArPgR9aiTT2wTNCtJf0Gxwducfx/y3LaAfWLO9Bv9NPon/gMq51f5+KYh11HUG/RdQ21n+c2oktuB4gwVbGE4t4h60Yd+msnVZi5Il/S6qvY8pMqho6whq1SkGubebs8iyhbE49bVr8WKFauznxuswoUUFk6IToS6iUPU1hi3Ujf8vabDd5hwdfWnu/hxvfid39aytktaUjJSmBh61D4s5ZRXfSLnA5gZFfrrWkndHNrNixvWvM8uhGUXCaLYjwp3HpX8nQpPOsP/zANAs56vsooIEZTAZzZmfioq7bO3G9NU7fEeBxIhNelw3pCudr5vJZGZmfCTHk7KGqCg4KXi+L1I5rTUy/+QNzI/YrpbVsEv2DZW2gq02EVfID+m4oUWd/puZH4KDAThPfuykCROU0iYD+50ummtQ3cWAK2jpqgWAXwyEsCpqbXJEs3C4Y=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNDcwMTU5OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mMGE5YTg1ZTU4ZWEwODczNGM5ZWUxYjU1ZWQ0N2U5ODM5N2U5ZTM3NzY2NGZiNzg5ODA0ODc4MjczYzU2ODhiIgogICAgfQogIH0KfQ==" + }, + "=": { + "signature": "PAGVAr1dK9L+Ru49bSj8NuQjX2ie9NfmgNzMFYKNTuuC0S8RtIW1QMqKOdF7puxSWOXs6kPbaWGqHkpYBQHmgjpa8UoCRLqs4ekiqV6J6COrqrTkvDOaAwobRIyZHE6338bE7WsEIJ7idEBuZdscUlGm3MLctayC5CJtMhwXKHRXpL10Dbrqtqh6kPbGGSVz251kXt3/KTFaS6o91FXXhk7O4K1CZMfxpmfP6ThFKG6ZEAvIEgwdW4BbvgRxxnwMVzGm8IPtZIl9Cb9YBF2pFBLpg0sfm1hNFCRJe5A5l49BO1R4r1kjg3IWzihO2nCvB3N3Oj/+7IFODHabvV4taYXA+ibRQeD8AL4jLHriNXNjWSvgS8fZ/wTiBkTrzkgMv5xefjuntYlySJDwE+yWpEVvG3t5kVoLK38vewhAhF/+Ic5+FRLSGetVYQzljLPi2EZLYKkxmIrWw9ZcVlDD/zCONWoCSD46lsXq27884VpMYOBCqlewx31mE6CI3mUX/AWDTsakMVhTZCtzCV4opJlHrkAK0xtonNmnLUDmS2xxKb4UA1jBa9W/sOsBFfS360yf6gvweC468+TmbnpXpcMqsqFpF2DHJjgne/lxwmOy6fDD8it4QmkHXUPPi532Ec/APGdE6On8ZontrJz6k5TRITNoSFU28jSsgFjTzHs=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNTE0MDc4NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jN2M4M2JlNGYzMWZkY2M2ZWUzMGQ4NjE1ZGE2ZjU3ODU3Y2QzYWE1YmQ1NTg1ZDMzZmQzOTg0NmNkMGI1ODk2IgogICAgfQogIH0KfQ==" + }, + ">": { + "signature": "yPIY9dEVr1IfXzOfr0f471GoprWtVdoxkNnX90FIRlQ4wjqCGFGdbJw/4+bNKFUTgR+CFJXdLoOouc4rDb9w3bZlCOwC9CerM6ZDSPNuMlrb1ZRn+moakv8R7YGudo18N19uzcFfOX2oYBhZ9RZgHFfBB0klpbleAOPxGtGaBIuVPWSsALONOcbVeJATMjvcaiJGyNV0a+hNZkcagb/kGuA5uc5qksFBDCFIG50aatf5hIKPtE3wOMOPYxelFgc4jwSNTqLHt+R9mkctDXd6I3JRD4YFxUVmcGdF2wDJxMlP3nL1NJlRUrFkM5AsIMtX8qjJY2zwzpMTbyKtm2PjnmsPOsMCFpK021xvnZL173kJg3R9nl0AG6rgnqKODImZoZ0vof0DRkV9wuQIQeHGxvYJXBDmFeBkXqLrRltI7pq1V61m3VeG1QDzUXt2M8aBX970mf4noVUMlQCojYZm3aNY/LDC/l3Bk2Ufhl86T3fDbvjjGVzlTFJpydsskX+nGPQHWqJbKOvUSNt418ybmjk8yjx/cvM9w9FHYNMbwlucCLmHOsnPT4mHAPLXNA664xDyuIXaIMjqL4m9tBSlDOn3F16gmehenpLeNsX90nDpzPk+fx0LotmJI7H4eU1E9GWUWhTh2KfvW8ss3TD2gkL9EBPF6wFPvDqrGW+ds+c=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNTUwNzYwNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80YWQzNTQ0ZjBjYTQ4ZTViYjA1MGQ1Nzg4ODJhNDQyZDVlNWFlNjNhN2Q3YzY2NjkxMTIyN2EzYjE1NzIwZDEwIgogICAgfQogIH0KfQ==" + }, + "?": { + "signature": "nrurkRzVDRaGORSxMrfn6t02R8sAPLKQsoD3MSA+mQ0+sY8OTd3K3XG51dgrE6nOxH3ErBHvyh5iUankCTYl6kYzc5ZesVwILp7/ngv/P53cebPmh9IuUxtxAR6p1cxb2U/vI6dgZrm4c37QeWlaDn5E2ItOGf3rEwjjK58vZKv5bCVZoPyB9dXNA7HsnQQ/2MNFbDhzV9MHQeWdVZBWQMpGc4lc+JPnLHGCmXbzYZNTsfWwnQC1a0pMFoOmHlbx/Hcq+9i0WejOgnT2MfQKL30KyZ2+aKO64RajRH9Ji3h4clp1UhdNdQ3l33o9hYKn9F9DffK7yXOxktx1dQ/2Xji9uwwWc6UDigRvRd0HQnMalUQcTRLT7ODIMsPqaAy+N40Aqxa9lFF1GQ17JtUJkM8dhjKbqgNZJw0YgprrgFFZVjyPrrObjXiogrqrJOVGlVSDfW2W7QZvMIupSB9BbnXN+hFfA4mRh5xF9rAWPDBv21ZDYlLqLBcEc8g6zEjuuwFcI+hRg24snW/xUg+M0AYKCZ95F27VP5efbC/ex3k5H7NHOQjPubNd4JpIg1diRi80KC5REKK79g3E5cD4R4sDCx75y601QZ86/PA+cbNUHvorMMvaHXRqKrpjXTLQ7YAi4oeqHXb9P2HBNbR+ai7s8cLnhpxbx5+PEfOv7co=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNjA5NzEzMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jNWQzODQ3NGVkODkxNzc0MDc0NDMwMmU4ZTVhZThkYmEyZjk0ZTQ5Njk4OGIxNzhiZGU4YzQ4Nzc2Y2EzMDM5IgogICAgfQogIH0KfQ==" + }, + "@": { + "signature": "qmSHPB2tdCCut3xv7fF+mZwoQQ80pxvPaROIV2eINeX1hTFFtbuNdmXzpL6276Q4Vv/x+IFKV1dCWjiORrZ2yTDni53XCVG7pQCyMiCCkQAjO6LaNDzZXmfl+0GP1n2s5GCWdasrUxoRVM93AskqLUPMb4xd3oQJI8hIffptCnXooJy+IwBQCzMZe0np4w5N974Pe/dJUMvzYMXvLlXI3OEi6PP5Hz4qk94uMs7u8Ik0mLmaSjBsvSQ3w++Ee+7MAPO5b4g23wuqo/AI3met3CZFEA00igb76eWcHLfRCM4m2FaxGkKgTXRx+bGHTMzJeeJ2cX+FqYPX5ETvYrE3opnuPYfo0F8m6UuArqfldSO+4PlmIWY/QYyAzIbLCPM4E7k+0J3wZlEQIfywSFZVRd+5ah6MmkS7JmXMkjz38ahTYeQC3QA+nuIcchlNAKjFPV6godIuoVk/QqHr0sQq6cBVCsZ2xZ7VMXzt41mz5OGaAiOrJwmROpZoWxc135u1KsH9hOXnGccgXc33YNy12r31vCAdj4uamskN1d+yaB40idiuoblmFUFkuiU0p5qPvBmSSUZ+sqACivTPEZvfJNM1uzQCjEwZNwm5VEFr469OjGIGDDehJILBP/4J1wEKqSvCktJ5vtRrUJmiY/eB+vyOdkoMf/YttdAyP5DncWc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNDg4NDczNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hZTVlMTNjOTRkMmM0YTFhODMzZGY3NzJlNTMxZTI1MTY3YjU5YTlhMTczOTY2YTVhNTE0NjU3MTZhYmE2NmVlIgogICAgfQogIH0KfQ==" + }, + "A": { + "signature": "qM0GkQiPuzrcm0JOsWZfobwF/lokkpIpxi1oiO7g3U7KolAcV5fZZcZ93+Az1s4KLa2GYWXctZWGizMGDvubX5BTxqNdnbUIn0KMwXybd7bqo0OfNEPqs/u7pwlZ+rbOR3WfjcyDaPijQjJMNBW4LlmpLWIc22FIjJ8owMRAtEzSk7tgp2VzNlBCFWMEkWh5opx1jU71WIFd1Lzr005ncQEzSTCCvsfsDflplUTWqrSxj/v/S7qCyh9P8WJV6VUTvah3ynZoF/+2gwAOSy3S2KMVuYsnVbP/tVNWUC5AJtwcbYLqLWFsFf+IzOBESnqGb8/57g40gKUbdVMLE90Pf3XTbACf+6ooDKnu1fuZiN1/+yJQkUYkRjkSWsZqKlitaFMfy85ks/At+vo99mt8kIZODYNv4ovCB9X60f7Z8IO3c8/5qZLg82+LIe6G2sLyRM8xLNCyxfPeOqRu6k02TTdfkNi9F2aVWwSX6I20pbZV7bfEMXodXBfGFNoXbnkB1VibmOVxN0JP//2hbBGbFeE/9GpY3OgFHVK6fN9vNP/IwMRc8i6jS+mkiv79pTqVOVuX8aN5csJ8uNC6453hkQ/q6cfsvInqZXMU7S98KEzf/mf/jAr0au7zSCQ1xe5sl8ohdAcaiB8hAhKHsqlWYpRyqme/oHoPYyvSU+gTwKU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNDE0ODk0MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xNDJiMzk0M2VhMWQ1NTQxN2M4ZDQyNDVlZjAxYzI3MGIwMzEwNGQzM2VmMDhiZmQ1MTJmYThlODllY2RjZmExIgogICAgfQogIH0KfQ==" + }, + "B": { + "signature": "lEJmBU921OkE/IB9H1Bf1Sg7v/wtQ2pf379qN1fCSNt0NAjoktwxmKeIbsi/ajolHI2sMXDO0VNm4PFAisBNa47qIuAHCGosb9+zuNt6xW0fAZ4QVJKOgvmeHT/GHR3ehU/6D06LuT9mpxtTA5wXiFO3g5sOp9Y7MGAT0D0SL/U9zTlW1zYxZfoLUHUN4L7J0rCioYhymo8OoHFPgmTpvyC2BDO/vEYzfl7UVijcBJUjkJLpQTamxbQq+2841LXcf0i0ep6QyX6AawKC//zIqIYsoqfShUvcA52KZmwPScmz9uhzYbK7GBHF82h+09kZqeOoxM7Owk4E3EcN0MOOkwm4TsFtTreVjV3/M3gjq46SzFYlZuxBlTGTafuDH04l40lXhQZNdmkYFNktVscczfnV0hf8yU9FPUh306nV/z1N80g78fJSJVgwDSGuZhAlmq/M9E0D+kto4MxSwDnwz1hz5tjw8I0eCq5mhUcAgyPfIzOPz7yzB1fgMPxLdppDm4PPrGGuY7yCsVUy2KMj1wqX/AsfQL1g1YJC0fzIvFdJEl7WRqb9cz71YkQLMBzMhl0jIZCWTVbYKqYuG6OrdNqzbp7VS3F6Wbgr+pV+LQ2iMEKAtb4ihLcYWznhk6yRsseYldUIbBH2QVVTWxEsuMagD1+pgAF+2xkOdRUsZCw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNDA3NDY3OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iOWQzNzdiMWMwZGE2YWVmZWU2NTlkMTRiMTRmN2E5ZDVlZjgwNDQ1NDkyYWExZDhlOTc3OTBiZjkzZGE5MTY5IgogICAgfQogIH0KfQ==" + }, + "C": { + "signature": "fq5bVGSSg5/F6XYjchHlWOXOziyAE708R/xBrNFVCgdLs8z6WeRqA5pcm31h8yB16RfhserUce5biG7N0pUNuHe2OmX8ky4KnuEBCKD/ewgmgRUl2WakO25At7lx2AUBaqdbvF5lNzxxi56+EwsKEw8vzAqR9vuNT1FEb8ECy//2pC24FqGIc8ptvkfD86BC+OevK6iNamfx5tHojVQzzGddpsXClI6aAKwpTUZ9WL5b/l8vsVNwBvuhN/juTgeLUyRnb8NdKZ4Pta+ZUkKCG8kyTS53CaUdbvcfav27vSUKqRNwfBISd4Q3TtzvxqEvUI4p+LKCKWn/c9I37ond72go9XbEmjU2Z18Z6trH7oHdIpW8HXng+kGdazhrgX2JxobUZ4qpWjjmuRxb4eja3c+yZsFrMhzmt4NUp+uNg8uvgaHkPcRYZtEpRyWQ9d7aR5FmgyR+a5cAk1ZA3cwhDJEPTewcobRzYuAEShPdCuJ/rpSe5H8dBA/VnYcWrl5LYQwmDXWm/Xcr+IyaZBHlwkgL6OWP2FiNxdKmGEa6Yu05ANYFPighPVTl9ZepbYh7snybtf84/jYOMns1KkmC6aAjJ7BmMjYIDjuppmnBJRYiJAyGwLG64tu3chYan6OBCFjb9h8Mq141Q/qKDHBXgq15lOP1jlMvJnK5nvj+lLU=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNTg3NTU2MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84NDc2OWY5NzA4NjNhMzgyM2E1NTY2NTA4ZDJjNDdlNGVhNGY5ZWQ1MjA1MzY0ZGMwNWY5MGRjOWM0Mzk1M2QwIgogICAgfQogIH0KfQ==" + }, + "D": { + "signature": "oVOz9YMPv3znghzaqFM68+qHFYEr6arofgpCuslFRRd0bhXhCpiwYZ4Ve5JObZWikG0kc84SSlm1ftZL3y5pDYqRYl0FMgKep3ZArFVAbe2l1hDJvCZbR9Y5use17EMilhWGwKrpdZ+15E1hxMgyxj8zxEYTVGuTPgn59al7Zv3u9Xh1N12gwGh6RT23W9xa8Ibhe3uZmDec/5eZ8j2B8pC+qHIjBbuSQpILWuv5dVAzdd9cysTbb/pvO6dBeSepfAqKX0oTTjNmvEC9uMSethOZB3kbWHuEIOtHOmt4mT9l8tCakcd899Zvbrm1o47n2b9WwPUVYcadMRFiwTF5y0g/lgHceXw/O3iVdNaywiBxHHwHghJMAdVY3pQXXIlkX6iSF4Q2ABhlPJOLR10KiF3FJv8vjl8uh/PPbw0IivaDt6Delqe/UVqzsTb8yE/qYUOjXcC+9Rx9/z7n4RHYVOD1kOqtt6S/dwzXzwKtrW7qWHYe0oGwFT1u2APgPqS9seUDOncP60X1lsZMRic+kjqJNQKs4zDWuDJXdm1kSDCsJhkFAnDPmWbWyUc9KOXFs4X7pwXVNAnohEm0CUMnlXdWdltKO1oCr46S+yPfqw8q6l9K+9cs5QrOvXjlJW5aRZPv/21E7+SRBZvgSopQ8CRCrlxJgneW9MsFWgYVJdo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNTQzNDMyNiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xM2EyZjZmOTRiZjQyOTFmMDM2MzM2MTQ0ZWQxOWYxZjViYTMwMzgyNjNhYjRkYjBhZGRkN2MwODU1ZWUxMGQwIgogICAgfQogIH0KfQ==" + }, + "E": { + "signature": "GUoHq8dcMFdE1m8JP2sxjsZi/kR5XDWlClMQSIx6hsY7cXdXq4IMa9iU6RV59exUuygg9lqLvrrnqQjp7R2cm4uDCkOSadQ0axvch1BxpNBGLXFe1suDu9jUwiU3Q8WJRmaJw/05s8A4Sc7HL9yNQfNcf1soR6uvOO2F3RNK0+hKpT3yBrfviR6dXpuj446tX1C57JQ2tIOnAje3m5Y/p9tNCeilffOEzgg+hbh75oFQrAgLwm8yjqRGqSLJR1tCusli6JSUdmxpypKmhjXCtRJkxNINaFVqAWr6S6o+hq7UZu9MuQk1Rz9NAKd0f5S1NpfnmXqb/a/fUQNXyq6EN0W0yedyLtNeQh/pQKhzgtJlDlJmtt3N3+Z+WpmsxFYMsBEpvtvDeRdsuWQE/fA/dRSx4tOo2VRbM3d519TMpnyTlF79Aay/lyfvTmcTQUH5P2enJs4cdopU8qTvgUXuoRGzoV53aSB/0CSsbw0QRHB8Ho2Vx+AW1iKZrRtW5SJ9WDot5uqn+Ps6dtqWP8LMHBLvTd15SXB5lSQhkhInSTOOk67s1lntGXyHFbNDGj3vY7rQU1/BKrCzGDTqfg5WJYCj23MlktwpZas35Rw1mLR95uA3AMkftXkMAWiGfe8zprYrl3soylJGG8CACUSesxJbvZOXiPPJtLozwQR6meM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNjM1MzU4OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xMWQ1ZjVlMTc1OTk0NTEzYzFkYjI1ZmRmMWFhMWZiZjg3NTkwOWVhYmMyYjY0MjVkMGZmMDgyY2QxYjEyZGIxIgogICAgfQogIH0KfQ==" + }, + "F": { + "signature": "Lq32Nbpdc2+rhOkNk0D8yRFqltnKtpI9hTzYS0/axXLnTxpzNQTNBXJKAV2f50/QJ4i4S1upywhaXu8x00N1Dv6OAPsSG1dm7gosAvwPxN2MBy0C2uG1tXO1mW48dyhoAjy4ViCVPkc/tZPxaesJTGILkXcR7RtUSzdw9VPKk20OQrUVRYtnfOFffs4gEgBTGFpgcgLewIa/QMqAL4D82zOLdoVIi8KUuQ+zVCSRL6DihWTeg0gPSCLmcrK4JGfZs27oGSzQ7ntkNtFsWGtD/k/5eG8XD/5xjWSMaGp0imUWJEImPXdV/AJg7OW4n+Qnpmb6OsTHtxu08l2wDKXy+TlHxqLhMcQXeG03hGDEWQ6DJXtzLsom/wHL1VMcDouIO0VsRCP4vMsiwLxpKkTrUWAQzkIFPqKwyocDixIh5iO59OjYY4i8oZKhY68gCCWVFWCjLWvFGmtv/Y/teq619M6X5yX03hRAFXFDN7kpmHKVXgtR30VORuetG8lA8uXy62O/4BfH1WY7LGQTyxOKMhtYbW+laXS5LneTsbXGbZr0QwnHeF9LOF9HfJtL2E6jVYztJ57aAYOwnhidyvpWJ+hJ1aCC1QFmNN1EDw2dDbuRjgU1mNmWAXriTeCiP8gfzOv2hWuX1+MSF8qNgZJ0edGfQ/ZJZzaC2z45qTZOp/s=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNDQwNjMwNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85YTg0NWFiYzlhZDM3YzdiNGU0ODc0NTkyNWVmNjdmMTZhZjIxNGRjYWZjNjUxNzRkMjU0ZTFjZTI5N2I2MmNjIgogICAgfQogIH0KfQ==" + }, + "G": { + "signature": "v1CH6o4chQzDZqia8sG7rfwILsBW4t7FW+GxXKCz6jogovftidhHZ84ixHlo7a8WEpbC51LOxvlFxrd6NpA6QZ0SdFNQwBfgNWf+i1gOUs64LA2Q3An1lYEOkt+7bq1x5HNE9PfTi9Xakb4yb4u0eg4sGl8UT6EMfe25VpdYglbmSroVDOB8i75cirIrI4d1NmZkdJDUHy5MpmIOhBrqPqupBe9n6uW8zdMzq5CUK0Hf6SKM3vgEB4zzTRt8Wj/B1xDOh9BiSfiKecVXFEmyYIUXln/+k+eIrnJe+NniaqnnBkcYN+V1rL/88ONOFrpkXzPoS7dFOnBhuK+D1F+WTFL6lRBPLKQuJbw/05FGeoNPzm95WbYCv8iP7h5fSqo/HOK2m0Dn/jE15l3LFpxt2FrJTWesI+D3zXIPOeV8p950yJnNBVmp9ZjMbFGgzaP+YiLRdjIK998empUrmO7ucZIReCbqVUjUfiTEuyUJ9XLYu8FX2fcr6PAn56T98dNT7JLgmdnh4882hE38q3IaEIlXwPVPzVqEW8g4EJaqO3AbkYpEegCEc3AHxG4YvR9ACgOAyiHIGNmcCuV/Vy8F6MyYGcTo7Nv3iVVhQidF7SK/eBA14vSDAIDOKOdMNbGoaWIPY9iO6ANxdA18xFzbAwfyoU9rMvJrFCSLRCTffuM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNDYyODE4NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mNzNkMTdlOTUxMzIzMWVjZTA1ZjQ1NTg4Yjk3MGYxN2IyMDlhYTM4MDZlMDBlMWUwYmM4NDAzZDFkYjZiMjYwIgogICAgfQogIH0KfQ==" + }, + "H": { + "signature": "TZ7IsQGjNJWD/RyY7EVdA0Rt6jLbBfRJ/yqicUXcOus4KxqGaLr+M7bHaxMXv3gkvUScv8TTl8hSqVUVylOSD1OxMXYila0eaTwT+jUMsoCgvkrbYzzNDvVvHvGEXwYl5lO6J++ZXvLCcqfovqNySy22b3O0M6wqlXW3A+SCw6RE0uwItaMyQyxaB3wLS/I2oDprNUuBDTdr/HNrhFNx+ns+8YKwtVmdEncs0onz6CYFAJihQxCFwJmmipaKa3swBaw2xlN/47gcTYSfHy8lCTeRKi2U62qe/BD5EBrCmJ8uvicPOSQ6JJXMl469F//ZCi/LEtofvWRKjdhQZA941NqywW9WQhhqUIrB+21J4bpiSw+OqBR5gKa1fGpL7eP4md3v3AP5mCAfp9gdGC1ZqHe8pThGNGn3EcgX+3SC4UbXkvcpcS9p5a6MnZdIazLCAnHakoy3wzmEwfOF4CqfW6RYkBSpVphepOziIOxs0D56oXTKYbexkMHerAVs7cBPn+M8bcSQBVeJKGtfIyX2o3ml1GhQidlhsg4CgEaXETmogJcqB40SDfMgPhjUQ6pcylX8m48ueBt1Y3U/yogDmwEJ1g0B+fVN6V3mT916fNcGirHEFLMDhiKLPdAcSRtoemGhWHrurU+Mo/2bjMgb7CuARnrKrvTVuNUZYoyFx70=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNTU4MDg5NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80ZTBmM2IwOWMxYTE4ZWM2ZGJjOTA1MGE0ZTYyNTU1NzljZjA1YjM0ZGU1YzEyNjExODgwMTljZTc3Y2M3ZjMyIgogICAgfQogIH0KfQ==" + }, + "I": { + "signature": "JEQkuwyMD0ZBJjXE2dXlBl3f9nmoDVsNZRKzjIJf4EAAHXY9BwNKf1DAcWsKOKaqqWLhhB5TXNIb/gamvPWwRbIbZcBzT+dxeUUJ7t4Ii6BIOV8Y3cASo/Qj9/zWQrtcex9yEtxcDbVR1yHD+09iXK4tsSC5C/Y/Ko5ulIR16zeOI5tCCamHjA6XLan92NKlaL3p+EMR1YO3Q+X8T2VT68KNcQJoi00Sa+JGK7ver1tiCZ7SlqSejI0wClTlmhC8ZrAlfSpr1lXuSNxR7sPqCYlVRiVXn7Xk1qARhWvtb2Fb+JfVqWRZLWgPgHLjb3zws/oI7plvdHng2PwfA2mRODk1DuyLOZDv5EvirTu/uUCgOtTpLg4qZ2tAKgQ/2c6JDDYdklkVJ+HsqFrf95wSgE4kMEzZWARZFkg50mp0sK5PFVp0VZ1VSss8al1SPHchD933Pa/qAu+tt5XPb9d4lm9603vUhTIxsg0sYPCe07KfsVl59rDLxo/9NK0YB6R/zaYaoMFPDSoQJOdSCb2bbMBCG5Vdu02xnFgs++bRIQIO8CgTJ7mQR+1DvKT2edVT59cAX4r/TcRUJUmredH36OiPSYZmnexA5vpzBcptfTXjOzCJ5SoSOUwHiE4oYxIRPEJyxdhnU7x4gPgJVhrGF8ymfLw/yS1awNC66ch6tN0=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNTI1MDYzMywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hMzE4ZmNlM2FkNDVlNjBkNWIxOGNiYTdkZGNmZGJiYmUyY2UxYmU0Y2MwZDg4ZDA3OGViZmFhNjhiZjFkZGYzIgogICAgfQogIH0KfQ==" + }, + "J": { + "signature": "ExwW4lzQkj/Z1PK2AMezclRrtZbzkgEtZWUAKP6oqiuOpCzll4KIaCbM0n2pXlpf4IvEy38elhXDgA3ezCRzWOt2AMeEeLyYaorNJAwI8Y1pe3wT2axIcgkFsDnRaTens7wYD0jiP+dkzhikZUKI8GxvQmByUWjNK0wyyYNSnBdRYSrERPtsXxUas5nMXAuEXITzeEoZ0Zuvt3dUvGXrNMvNzPYfUOfs2aHp4x5Mrou4Wrqc84PWIvuncLenR+Dl1mEEnUW3NR5yRdPmR0NYel0cgaafxUFsbwauj/SAGh8G/7Cq8MZnjumB1XZNsFAcN7jgswFXss6Fj8P0F+D1YLAzIIBm9iuJV7Rnj2hdujxJa7g72W4jW54ZXeu+fi4goJuHxRNUnAOLe13tdNX1qqN8PB69jn3qdzQab1g4f8PgrOTBRMkSwoV1drFO6eAjo/2Z+L337vkOa0Hwsob37rDIFBpr8Tk0cQiwgE4EryXlbBwdqHsm0chdqhRmnIw+ec+nDsWpqKjM2yFpRhfoCK4Fo6ox8641fcugC0lcnmnFR9xR7AyVck3YpAXYfocDcu/llsknE676JToXj1cG6gdpJHsC/e5e3jCa2TlXY9BMj/0Xrbgx07Lh8NBlaFZEKG8yWL3P2ycTakpZLN8MLrIDDp+ZXplrPLKeQFEBOUM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNjAyNDI5NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80NTZiOWI1MDhlY2NhMjhlMTc2NjBkMDEzYmFkZDMwMTg1NDk1YTEwYjQxNmY3ZmJjNjJhNzY1MmFhOWY4YTgzIgogICAgfQogIH0KfQ==" + }, + "K": { + "signature": "MtECpy9Usi+YeednMkEtYTr0EZr83pIdaMSEIdLj8TNgxVh7U0vNi7P4d7h+KXgETyxHYUy64LGB7zREQMMA6cIXa75h5ZvVYwo2Ki/DdJs8aYIEL1wFFnixafI7LoA14cqsa0+WCwLBhDaQ8TwuxLc7ve5XV7sRxVj4EfZK41MkB0+NpvAJOQEPO5EmKdQpT93sl8Hxhqtd5NqQhYq91iVdELLkFeReCb8NF32nbPX/LudBpKy3YfPIVrfg1lKKGZfV5GM5RXknwz3dg6Xo9h0FIQ6QpEMYAnBEszz5uHjLDSyX0XJNL/OXnI/FbVnLPJgAU3Lyo6IoKEwX57kqO0qy1c28wmtuGaXzqUbI5UxCOKwFr/wNorN5GZlaqq3ooK9z6g6be8Wkks7YEDWyi0Tbols35a3BHAS7cGdx8kMV7dbuwzszGFqnhCEKutuQ/qw/znc7mreqTi2WKoNVkacwFnKi5fUbtzrY3b2zo7jcutl7fMpdy/qZS8yHc3iQUdHhwmumpVEzIgT79Uiaqp5F2+qNTmBBmVSZg8+fk7Xpo8Dio4DhTkSUgohXfABSKJINjqGn1Qp7CA3MFiZR4B1T/GndSykMxil/HFpJYtOIUakWgNCBa2sHPDKQ3CqN2glGusotdtWFxzyRVWOtcJIWzjnLFqE4g3VgMHnonn4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNTA2NzY0OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80MzdhMGIxNmEwOGU2OWNmNzQxMzRmOWFmNmFjZjE0OGNiYTMwOWQ1NDk4YzgwZjM1YWY3MDQzNTk2YTE5MTFkIgogICAgfQogIH0KfQ==" + }, + "L": { + "signature": "IWXs1Ffya0Bww46kNIz8UxrQi/euvaH8YvKG0Wx7cby6TduxxyGveXSJb3o3a0yFWwu6yra2OQ1bsLYk0Wu1nfK8miIe8+jqQV0eVKsjwRN9kXElVfqJn7eXq508Fl2w9KNtuPgoam9D3n9bVEOM6i5qqAPzJPmVzGA/IK16kscC7OBTcQ+igyLBg6OGr6Ak31Xr0r2Ka2fKNltUUpiEe+efx6mYWqzFnqp9djaT08Qxl2W7f+saFJF4FYJPYadmDwLeP/9dq5HpJwLy3pB3dS9bJjZZojC2V9br/5Yxv1xRLo92515YZ3BNftE0p19BEGHBUUL8c9iWGUaGVHSJ3fZePgfPnh+QeaPM6iF8n7oqxYk+RyQkGSE6pW94ISMuWLWAgYeJZI6wOnaIAzL6Cot0ty+q2BBt4ojBRNU509uf9I030L7Vc+TyDDTdmaqTbSu35ndfrvEcx+pXC+QD0YzXq0L26vbBMt5A36KtuT1bw8671O4GIRCjiTJ5yA/2VSHJsc+ZwWizNuLnNYZ3pWYthbmo74jV5xL2dOZp765+mtaMIekPVsR20YQFyTzHE0n5Nizk9FAN8QTAUGgcSEZfXKxOC8BsRpOaGQEp1Px75DgpZ+W9hDgZi1Hmcba4aDnUpRwbGx+kVrupWvyur0eh8QRqZHEe288GV0lMnrg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNTM5Nzg1NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82YmJlOTNkNWJmODE2YmM1NjdiMWY4OTE2ZjY3MDBkYjc3NjI1MWY4OTIwNzVmOTk1NDdhZmE4NDY1NGM5M2VmIgogICAgfQogIH0KfQ==" + }, + "M": { + "signature": "g80VCfeHy9GGyNgFwutEhkP2BNOulp4zxCPGOv7412rNCUh8/NxAwf3UzpzmWmM9V4dBNyr+A2nseIXNVTyMMrc1Mxf0+Q9bZIuImZ1K1pNifCTNQoyTfdiT1REV0hRLm/Bd5d5eyqL7wbfM2um4cdqAegPxpyzAdt3CwhNOOeiCuX28ESJIuCojpd6sMJwjCKBSyckBNUzOfbdPGbw0JLfeJFoR8aSq9YNqribyDG1szvcK5s++sg+tTpyafpNQLAiKdJH1/NQ9PcwVINzDmlSZwcJFlYvJKidc8hv+ruew4PfWq9HOniPoSRnTiXXAwMc3jRoO4YQyiC/Z/k1Q/y5XJ+evRzyIGb4N2cHAKCkqbFMX9xJkGOzkVWURxJNK/OI/EV/7Q8J8P7LCwCiM9MEumfWRzBSmJLWXHALpvkhMX+MUq4BD1gdP0skDVYTB7AHB87lNVodguxdT14Zb0sWHk49riGmZ/mYZs+/VbU+osCu+G1O5FtHX0GO2wDJ4tjXXQwD0AESn8xEaCz8+oJkg0kHFIf5P+OF87EONDbr9F3FJFl2e/NG/V08Q2TgYN4QLz0gEnSCxeGiqzI/cPN54FYfYhD3KvIYSQ1VNlncqGa0m8BQCVrjAcNNH5zzmORu5sr7GUawc141CCB5iET/BKNpSAkXl7I2oyHVpoN4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNjIwNzExMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82OGViYWU1NTYyMWRkMDhlMGVmZDc1N2Y4YmVhOTQwODM3ZDg3MWRmYzdlNjQ4OTFlOTUxZDEzNmNhYjVkZmYxIgogICAgfQogIH0KfQ==" + }, + "N": { + "signature": "cRI/Tk6TUhq5ZxKj6nmMqJ55QhulHJolHLu6vZX1ZERqLeDUnbtfc472K7YvhRVREqfCQnvqVBN3gxiSvtgAHe2lulAZs0P4jtsQ52HTKpFvT9Kw+YOTP3Nd+UCFbxp5bKwJPZJnXWZD9KXuLVUunJvCD9/use/TBpLYRrz79Fbl5MwB8fbOWzwXRQnnVxTRtUTO1BRSSVAl2ilEgE8wzuLUV2qoeQ1xdMJEpRevKoJd2lXxT1EDYdl3Ghg6+ZQpw0af0Iez0XLLEeRBugnz+sC6kE1cIGA0hcrT+PJDXXCf6ikXA/xuvp72Zg+GJV8HuFhmyJ3E+4JkclW6A2kDrwNle1SVgOsr+eu7T6iqvseXhCJ2a57rouFiKZ/kJe07LMcpqt1vcGJzTE6n26UigCFSe8+X2tiJ7a82pWt4YrXvITX7riCqRkjhXLjA9QRR/mLdvpAkv/YlxTSic07tgU2xmPVUWQgObSrpb8gUZPzgoeDFdz+ARUJ1Q8bNQs9VDwnrLrXME6wYvyNi+hNafWAzHAHmXAchiz5P/Apb6ZEXNauaIELtpiZetqXKju4LhtiyJu7KK7YHEEeep+P0KY28Y0Pyx/8g1r4e5MmHQ5VgbxWpg1VRjpjTYFAVShTneUbUwxJWkB4PWIVvEpOm1pQkaSwRrb2+Z8/TTkBDnsw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNTcyNzkyMCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lNDgzNmYyM2IyMzU2YmNhMTRmMjA3NTg4NzRhZjU3NjVkYzBkMWRlY2FmMGMyZTVmNjVkMzk4NzY1NjliNGUyIgogICAgfQogIH0KfQ==" + }, + "O": { + "signature": "iayYrUT5vPNch9XUJLcgu/WuntZA50l5uJAIbiliXPijw8QrU9fJp+l56C8j+MwpL89OY5iXBdyt/gQ4RF+dJNy/OI47XT8SvvuasvR3l3iBbNSFXZNVL6YBw+VtnO0cxW5rvItfn78ZL+Tc0bkPmIyznDUhbDdohzqMqgC3x6YqoBNd6m8XXJwyW8qwtgoraa7kQYXz57UN4u+EstvudLZfzEDaQgggyf1d1CiE3jH38k7cxRmwhaen+D+BI5UsFXiOLfC7K+E8ZXS8oZz0EbYVxWxTk1qVBKOwXpFz0TMNRRS269t3iXslVh2JSdxFx8JIGBWLR3usWvzYNwW0lPYoI5bHT9xdpq038ATn2mKn3raZ2qBoVRNpwXgUcWSJNWOvV0qar+Oe5Xoanh6+14TGm0roj0G3DvKP98UdbhQPrcAhW6Px7Jr4wsj8LnhIzPapuaKAYqaET6ljAmj/ejVfbjxyimwf5XfXLVnymIrz+/95bqm8zdPNulBdGqx2ykkjHIpaUHPHVv9Z0PkbjwgDbRdg/CdI/PkImJtGFC3X39S2LakMtr6zb33EsOjBDpmO6Dp1/y9GSOA7jwaM9u2DA9X+NDLah39TmDPNzz/1tN7V6QriUnfTftK3InT0DpefdVr4VRoZYD8D/Av0qJzoxMqgfm2dMvFhSqSqnWo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNDczNzkxMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yNjM0NzA5Y2VkODVlMDdhY2QyYTNjZGMxYWM5NjY3NDhhOWJmOGM1NDE5NTBlZWRiMzNjYmFjNDAyMDI5ZjQyIgogICAgfQogIH0KfQ==" + }, + "P": { + "signature": "X8Vn9kZUKnGh4O8JLPmCqY7XF6f4vjCPaZKZWDuLZlC8sccfA9PftMxAFkE9x963YCuGVysE/mPPfRjZobD46tKlbWxZ9PGPR8XPVKGIPUG/V8srRBVrPEkfaxJJ7ca9IIkNTUMiIn6zcYklqNCJ87K9MpvEbOzUkNd7kNmSnZF8sd3XC5ZvZR+cH02/cXG7cFWusz3rLOWc3Ux20rbS1WE8myLG6DyElxdkzbeywVf08FJBwxHTjOxkRU/2DgfM3YdFvEpyq1rb3s1VBoUkKsyri88MTLKk2+kvPfmqYPtwXfovR9Cnt6rXMidetQZVKkVx2mv8wjpDeqKDCqSWtnRadFS05Hp+7gA7ZaGnaWBlDIJP/wWNzZH/JnxNRUvgldvOf6e70kwWDBcm23+mw1MrcnZRISTpTON+5THYoFxoMwQ11NsXLZZVZ2G/TK1ha0WuJFWNUOXMqd6h2rvKq0zsPaS1IZdCFlnn8uyMC3tWTYycrq06ZOHuzTNSye+ypanvdZmYBzDvVs0TUOgyUGMUZsaRb1NzP0RVgcqFCCI4YZTjUBrzE3wygce2H9IXsrDNlcETFydzh2JQrauG3OXT50+XZ6//WSkNIsEAuArjJdeQWDpAZX5TlL+79EPcLGfg7tJgS/dz7i1pdkR5gkfT3Gus2JF0CcM82Ify/Vo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNTk0OTI2OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85OTQ0Njk3OWExYTA4NDk4NWY1YmZmYmRlNWUwNDU1MGU5ZTRmNDdjZTI5ZGVlYWIxYzM2ZTExZjljYmExNThhIgogICAgfQogIH0KfQ==" + }, + "Q": { + "signature": "m5KSxmDum1zQcibCBUFkOeFpwKbjkOqtQyoMmY/ZKSuXyd6kMObQGrlVI/QqYe/6ADsDMSe6eOdLxrE1xgpWHjWqPTXi5PirDmz1JNwjC75LKGyML1dIclPSP5AJ8Pr1/TSkQmaQdezxkUawIcOgbXTf39fE6zJeuMxXGgQSk4SOTAEe0DV6Wsws6W7jhPSiXEm/6aVW71SWiaMH1gaHJhxMw6P3/dQ02mogCrSSdHkIl62W5G7HiqbL+r67DSD8zwFb0Qi+KG1I9HQKcFeU05DZ3aACThqbt0QeUOuhxCl/6PL/nm5ufbB518XwHi5BD4Ry7taL0xmdi0HN0GpN/9tV0OHvqGJTTgURppTkOMURr3NayC8kQPYL91LV86bR1UBbbPa1Quei5YhP3XXp6h+5QALXipjuVfO0TEw6AJLmiz89J9+fO4EpHbk+S5b5VNkSu42Tw182xo08aj0ymKf0lxU3A8q9nMu5vcwo49NgbwZmc1IkKExwLYteJu+9bCys0f8FaknqGPHIxnKuZBo1ADl9WuDPNUCo+OiE06okXMo12+LxQ82bagWAHeMtT8tas4W2MLE5lGnot8upqwRgkwqbV88j44jI9nLgfIG9ZNJOs4qWmrepMKRCaxNhSpbnkIEkjVTjApHvrOzzkRGv6F0DPdbHYlyEUf7fvv4=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNTgwMTMwNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xMWI4MzlhOTc5M2M1MGZjN2ZlMTczNDYzN2NiNzdkYjc1ZjYyMTIxNjc5ZGQyM2U3Yjg2Njg3N2RkNjM5MGJmIgogICAgfQogIH0KfQ==" + }, + "R": { + "signature": "DL5Q2AbL73Vi7XSmJc/8mZO9HTDqQRMUpAlmr05ZIyQs6Xkj2xxRsXUZEWHjm2fJ3QcmJVRBcKe5EhtAq2iuSh+LzgpnOwKpHQq324iqcv3HLIGii66XuTWdE5WvswD71H8/Y02Ehsqlm0VVdG9NLB01wrZ707sB+6he4Df0skxFND4cgmYc414yACBXukYoEbVdiMssUvjBYB3hFYMhEXFK7SL/SilHKHZK5XaNJ6npZRv95TM7YaTecCIwmwhibYAz+k7pBfRFQ3VBnVoV+znUm2oR6DeymAyZTFSQWjlGOuUt70J+Y6w1lzNZ3pivV2/wWMPfJvvnpyMBeY5q+ooq7+nbpgel/MQZCWFRGdbsbmDDHudhR3x7ZPcY3VezNxRYGz4BDUJpgeEB5dOXBnZ2KsvD/rVs3WaNx8rWa7UKF4GAIAhslUhsosNR7GGI8KJ/rOn9RcjOJewIM1a1G+4CN6m6bmtaf0DiEVQ0WD2bneJy6m365K7rLGWGgDmYR0RA1BpjtC/pXoglghfdlNufNFcJw0sq7ZMQTZZJDAMdaXfjCNM6/QGeT6A/YQVGwt2JG8kVhibEODxvhdSWRKJJmOs05vBNQeHFsgBPe4Lpx9IhM7dP50+3PBYILkElWwsGmklp/WjOKx9/2Ut9+ktXtwHhGAl7niFbSbAU2Cc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNDM2OTU2OSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hM2QzNmY5ZGNjOTg1ZjViYmJmNjIwZWJkZGJjNTRiMDcwNDlmYzM0YTgwMjUwZjU1NmIzM2M5ODlhMTJiM2E0IgogICAgfQogIH0KfQ==" + }, + "S": { + "signature": "MtKJrhOe1Ep94oWCCE9vwr23/mJvPXtpoOD7e9wjh59G3E0Utv/ydcHxME0mYtHKPLcAG8ccADGAM1NJbQaLybJW1ZcZ6m5PkgMmTsPgFTbNEhvi4dulBtC6n0Q3o1VpkZ5QPeR9ue+x1Gr+UdCjQWL9XPyuqIiYUD04aui5w05HBD07gHBoERxDku3jQd9gU5Y53F1gw/fWH6JL9Dj2730XQRNzq13rMvjbqVJDsO43P9QKo1iBbty8J7O8rjIgDSm/CbaGCqOAPhEwFsCnrozYu+TxntjxseJOrYzr1+ZGQZeh0TprRWfaKOI5BNpYOhCYI7w6nXxlRdmVK3M6kbzhN11e/TL0cKRFXjPsj9ZCT/eBFO8HwUzbP277UItgJPGRFNahOfalDYs9v6WrFsD9PdGRqMXKMNmKHEPGeEki+L1yqJlORh9XOnNrQQ1CmQo51VHNJ3HLwRT9O/ikdFwU2MTWmMJmHwB2/WbKTqnTYRJn2Tx6K0QyB+miC8qYpKi8cEiC9sM1xlD5fc6wtfxkUHzi2fMzb3XG3YrMz0MbqOtfFw/vQnV0Ri1e0Qw14nvlfOhqSKeDbX51ELXwMTHqkac8o44Ujkes/4DQPV8Y6QNfnodl0/6sH/Lxg8DHdscfgXLeEp/6lqpCGZUT/aaO9MSr6KYimRzoiDeN+lM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNDc3NDY2MywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82M2UwM2FmM2MxNWE5Y2M5MjQ4ZDRhNzI2OWZjMWVkNWQ3ZDU5NDBiNWY0Mjg2ZDBkOGJmYzY0NmJlYzZkNDdjIgogICAgfQogIH0KfQ==" + }, + "T": { + "signature": "rHKIjVwHtqhA2brQ7NuSk7kDuwLHUJhONbBvdpsT+iIuDZQKC/qcvbeR+zBoedUpuvOzgN/A1oPJD8PwENiTeKu1pFIId3OJfHg6qVUNZkfv1pnldzfbDfpLN4efpnGq8smGE6NODzoR0GzkIUgy2M1nhZyxMoE5Ca6d53kE7Hpq/WssEE8ZffwlXff6JQuM3lRSeJ9sQbsE6WFNW7Yt5wRfrU8vXDodOh9h8jp8MRjNlxLx2KmV2dnHwq2bQcCFeOZz2RF1NcZsg1vMuk/eLjpSq8molDFDfjUFos4QLmVLEJk8WEHVULdBjLRisozIs/VMF5s9I70d2Qdcw2XNGbM7hsdekk0qiYFk/elgF4TiJAXi49Eoz/4KhTccsmAyl0k5Ch0t8P49Fvux7OibzhEaCkMkxaIjVpEVypQxPbpgFE/pxhARvYuCDKsm4j0NJlXs9bMeQ7MhBaJUL0aLbpda6HKFLkg8gZq5kqllp9FCadWOnF2qrb1vZQ7c55+B5vY2pyf1nv7t1J+Gn5dREMDEMVv/9kAotHNm8jQ3VKUlSB/jVdXZJBRgouOkPfB32Av2B2y3o1rykZlge4PBXsWxm3RIVoqvqSdg+XeggQvS6IMQPIie3Il03X5/G8uKwYbIT8Rt+ec463ipJnr3gIHJhsdPMn07dTCubj1lHMw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNjEzMzkxOCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85YjJhOTdlM2U3YTg2YWZlOTJmNWY4NTA1MzViODJkOGFjNjNiYjU1MzJkOGMzYzJmYjAxZGY0ZTU3ODcyYjY0IgogICAgfQogIH0KfQ==" + }, + "U": { + "signature": "tD+E4x8RHcp0PpiXsHb1oHodmFbgBnDv3Ad1au6L+RVML69TlNj/qXSUDDKZ/sKkhL4Ezka1hlvMGRxSwPID3QITMeXYRtVEoR8SFUt0tAAmvRRgIeub1sM9efwqlC22rv+NsKCTtg3sNBU+0vOWFhSRQfIV9pynNhIGkQqNB8vNRoUdamBuOzPiMmpjZBt39tejeOMMu3rq3dccmGoL8CsF8p+0+kNi3Mt2Cog8jme/drx/QQaTe0fjUtOjHbNBqw9uyBJ74HxUplY29p9n0qeclDq/GngCK+XUHN7R0pyB/4aOhNOZaF4yP3emMyw0AIpycnZ51oEePcyDD6LE3nOE4cN/2JXBSxxeJxegU0uCQxW+hcK8btCsfCqwc6Z6oBxlk7LJ2JN3BHr1SmUSkLRnZmOhOqMxL6S/5L2CmC/BlN94l0FiTuz85JTeRqhJpeFF02lU8rF6iE9aeI5wCzUcHO992nxia3YGNL9tsE/41QiRLFiQYzbuCeDn/Qn6jW9XHAhEiqBsarUXlsICdUuV7GY8MTTx33BWGVZ60hjl1XQ/cA6j3ybCa0syrh6qbkZP7lGVHaC0uZob466PvV7Xq8UXY1GQ6IqVBx1s2NjIajryebLJzpEzkc1+ZNjqFFhIL+OvjqkWs3wpBlhbJeR4Oloh8bJa1aFu66u+DCc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNTgzODA5MiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kYmYxYzBjMzk3NDgzMzRjNmNmNTE4MGQ0YTViODcyNDkxNDY5NmRlMDBkMTBlNTJhY2FhNDliZmZiY2M1ODIzIgogICAgfQogIH0KfQ==" + }, + "V": { + "signature": "PCvm0j/xc2tt0ukqeRZ2l2eUzTsMkNzNiY/Z4kSmlDd2SIZvXalJnx91U12WWrZsuC+TdLC9wX2prGK5Cjbm8In1RJF/MPgHywjNY+WVfFXfOcvUgHabxEXO5jH77oxBrIhdf7Fptjd/qfi3NLFBN15AuSYOIHGueDXL26innfvjd3so5dz9Z/Gp9r9VS2V7YB0y7+q9lxkGyE2tdGdd7QwtuZ02Zgzkv9JXPG/+MGeKOyef8plw0wjiWMNWdAL5Mhd1czAFrLO+S/a0ca3aDXH+LB5coFeyR2l4rDt9riC9OA+ydHKHimYDT4l78aRqMe1UMdMO21KxbNOhr62IKqWETL/DXyaCkP9Pn3NqqJ6T46MdPGkX+04iW4N8Xk0jiDBCV9R3u4scwr9QbWk1cbQ3RdwSUFDeMdUjnjjR/MwpJIQKsntqcCW08kGAWhUuK6CQyHWaqrUZsxuK+lrULX3IfTJOZzKijwI4CUuHTgFArd2TsbF500dgQB83HLCJ8QakMwC7XpvsNXTjBx3cQ3AA/jS77xWjc+nuo+TrlXtJGp900Ne5os/Adj3wOtGpu316aCKFq1HI4+JFwBVb63/86F++GutwJC95/DH4+J4gjd2yFmD+VFxqa2FcOyTFzczL9ugzItMzBTzZdvFwO3AG8vKas9HK83FY7Jb0/I8=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNTMyNDMwNywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84MDY5NWZkNGZjN2E1YmNlNTBkOTc4MjRiYWU1YmZkODI0N2UxYTcwNjc5MzFiNDNjOWE3YjU2YzM5MmQwYjJlIgogICAgfQogIH0KfQ==" + }, + "W": { + "signature": "EOdjtrDvLNye+2e9jDYG+zCPD7EPZHAhFVZ6I2oni8yujYtM6LEOPklHauL2HRFlPX4ZBlingj7zHnQCtUTH5KZeBNxe7bIt0Du+PzA8ROqS4Dtw1xYQgCu6Z/4n5Z4NYNaeds+ER5sVJItC/0cR5uX+gbymj303oLxTwbWpgnGwTOdMga+bOPRTFVflijQCMWccRJxo38pgYSl9P/nFdN0m5BVe6MBusjYgwEcxIYowuJkp2g7dSzBta2RO4U9G1dePRgKQVeiNM38nz3W0qLA9f4pMXX8ddSwZZGnkn4/Hnu2eT9Gs0RYHNfzhEova/s00SpI4PFTVuGHcRwfeGZm3BNwz2IrH7f6uCvUDKH4A0BeB8FZspzLgWbVCFj2u6U7w3Lxb0PpIMeDGdSHHmU3JhzLoEhm0Suk4Z8T/vmnCTpCvPETIGIZnfzv8NSsPLgAJYlyH1EpluVHgsPz4Yiwotv/OLsenS3cyrEmXDhHDD4YH4RdCpjrGJVv9b7r2JfehwvKO4Hx+vOz84bqIJimrTMyI3L9ivIOoFbB+tyyOsnzy61QQWSDqtFeB+hAATWRgfcHJ/Wk1Isi+svEA9SMUKmyoz0IcpIfZP0lU4gSxYz03tmB+X2k3hU+C+k2gDPrmpIpN58ytVXMbyYiHKN5MaGyjZ9ezGCbD0ZqxhYg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNTU0Mzk2NSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lZjMzYjllYjMzNGU4NDNmMzVhZDI3ZWUxMGY0NWQ0Y2RhMjQyODZjYmRjZTY3ZWU5NTU5MzczZmE3ZDRmNmYzIgogICAgfQogIH0KfQ==" + }, + "X": { + "signature": "WXBu0wqNKHBXFKXK2pbeYFFceN7p+OUU3tVKMW2C0Es+SsFdFyagbQenZ34RI3W02HH0SOW2M4bapwNsfGb4xkeSsf2SLqa1VB4SRFQLhfafXJl/9Bcqon9WR9ad7KPb08txdZCBnd3Ra9UHTaYtKJ/IILGCREAR0Yg+9RAugKglvN2nz4Usy/CmF2ELFzQtQ4k7hSsVcB4FoH82ssYyZkLihgTmHSGe8WfE4Aq4j1i15sauRnHBtvfrQ44HupAY/7XhGXfsO/v8gK7I4bUteFc/QQsOHWXcGFcdLsFDt+XRYhreTjVj7Liw+8BNL73VXGPEBDjaBPZUeuritsWjyJCK7PgEmxo92WYoYxD5A5gHEIMpv+GQ1PZz5q+spcg+8yOBG+zw6rA73wqU5qYCOdFGtHsg2OM5Jm4HxoN0PD6fta2nLd2re4FLQwA2DKjvl7oE9erbhDWVK/jiJ2+YxPgPnNPWP7EX7Z0edtwYzYEfZlEGkDs2zCkQwlyBXKe6RiGJoVxtQrGEY92E5ICvsGHCMHrJUYwc6XFk0E/LOp9Uc1n85ZGszMfTU3IGgXkw4LfA4nH/eM7QMCtASnwlsTDum2t5LrZx7jnq5c1c0+7woxrYfFuhYnYXn1l6iw+f9tkJpgQH7nTTrjLO2l8+IUQAOJ0r+YBVmEVUzVitEmo=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNDg0Nzk2MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kZjRhNzg0MTMxNjdjNDkxNTFmZDg4MGNmMTFlMWVjMmNhNWIzNGYwMWY1NDg5ODI4ZTRjMjNhNTg0Mjg3MWUzIgogICAgfQogIH0KfQ==" + }, + "Y": { + "signature": "exWqMlmpYJxQG4gEdWlktjj3rV/NHc/KLIMt/1rr9begz0WKs1g4wIslAZYOcGnnHDDBEwBkaKo+bTMr8bqLvzVtDS5PC+AmYzRTF5F3Wwx8claKZP6rpFnI72VAbnwx8QLVkJcTFb0DaNi926DbKR3sBjkjEmWFUxU66o1Dah3WcEOgNAH45e4kFEnwa/6QATx72pYSB5OtDpF/b8U7rDrz8Ssq9/BZVmAWD4RXnA2zBzxCJwRZ6jQsAOzndRu15bbfCLpFOapN7SCoIlW0fkdeuQTX80p2Nj7j1LanrK/9K74C4NQItUfkd3psLblVEHLYuwnHg8+E+f/mexS4D53nvJ1hdA8coD95jUUn/lCCBdbxTw8dSFnvT21zRRIa50Lfa6bQW2flMOeNErw+pUHFfWN5O2MeGshFfwpbKVd9BqFQnkomi3LvzAfNJnKAfoLPjyFdSr5YpACngaj+PaeRfDx0FTNRABUvnHOV9dEkob+MSklxkx9YC1Q5eykjNoPpfyotKklYk/P4f5XDyAV+sX3m6J1bRVzn6PTHG2llMwGUP++/EtO6HXq7hAy9XVkLj5DBxifqBN8BhxxbkjxgrY4P5zegwKi+MXR0B/a3z/BtgPL10XEsRiCOucMDhhbZIFynQPziX+uTJ3jqIeydcjyPiQxV3Inbqwbq76c=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNTIxNDI5MSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85YWEyYjRmOTI5ZGY4ZjkyMTZhZDdkOTY1YzY2ODI4MmIyZjMzZDkwZjRhYTMyODZkYWYxZjhmZjg5YWRlZGRiIgogICAgfQogIH0KfQ==" + }, + "Z": { + "signature": "SVwKrRoDCI+izAWhvfxSRHckKkvJYOe7m/yAMAqg1gONbO52GXRtktjkNXcu7+HpGmaitMCE2NpvoHLUKgPSEYLR/y7I3DyhWN5pOi7yDBERW7zP70dBRtCbwCfPb7T3qcajwR4ISlgt20iJKlf5oinYyZx30+qZ32n8YfFWE57Pp3ey8G11cpPzVaBmfF/TgDTKFr3FnaWP20ttoFpkIPrilhwXi5RDXkcKpckfnSbvApLWVV2bW+v+svLcOxiqIAum9QodEspW53e3U9I+9Q02dxHEr1lptI+ORBFwguQnjotZ72HQ/pV+9VGBF+xTdzYMQaXQVt1AguMWnWGki8bP4i6/MktADZcmuwNR015+tElwS5xjLUg7ZCxM4zK0NgwNTQQyUPFHoPOoWBXZjJrUV4xRYQWzNzIn+bOKimZZOj9TL7AF97r47AXms6zbJYSqmf06ljJ1YtakM8etaGMeKx/rMYtSe0e121SonNHu/KPmSRN+FYcUq0YWnjqT8QJZUqfRMFLS6R8x+iefdE9ailtn9fJnaU5rpRqByEm0FYZDRculd2+kDvZzxyIKTz+Kygx4+BvixfptrZGSA1YpX1kGlVvrDHjRi2i18V3w6Zw4vWNQ9AGVEBawvfUjco/OwOlitwHiQZUfbcOZaTlEJxINJOw893ili6qJM4s=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNTM2MTE0MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS80ZWE2ZTkzNTIzNTQ4NWRmYTM1ZjVjYTZmYTRiNDBiNzg3MjZjNDU0YjZmOGQ0Zjc0MDJmOGM5MzU3YmRiOTc1IgogICAgfQogIH0KfQ==" + }, + "[": { + "signature": "ihUEJEgrdbF+eHR2JdGwPQGbdA9eA0MGnvgrx1KPy6fC9Ad+97LMsw+1zstRDHEmnk1BWGHtuVXOkZRnIB2NXZVHw6jBILtr5aeifLe4O4sXu2mtB52o3yvz3ZCALftK6/+PZy0CfiqbR8FQfUd7f/h2JZQKR55VFC6wd70droVaRer/8iiEOxFdi5TTdJnjRxHG40CFeR3QhQidNPJs+bjeXn1kPnEJLm4NRCyMKxo706OGU2kRC6Zg1JJlSt7u8yW2ZiffLil1kfnEluOvTOE0+y7rKUpMC5l6AWzi7oB9n1CgY72PWMt8AP82/9sMHcHoeQJ71JXmvZUUhZzu/oxRLzv1UngQgTORIZgPSAfgmP5NxLZiuFg2W7Je1RyRskTSeveVwiwMlblQgqnMLDx/iLUZb8JEF8ZEo6pBAvvTMy0paM5I7BZ0ZgDhwloEgHqF1itMuwILUlO/J5kYjqNAiC5w63qEwUXUy4Q04J2N08T8xvcblnZeMDSH73neXUJxBam0w1GOfxUc3Uo12aQn8fLDYyYhJIOr7xz8ae4lPxON6FCZqRkOp98DuFDt2z/k1TMuFoXDM76BgUciasNWHRM6aVPqfxEo+KQINbegXrOo0OE3+TBZr0u+6Wilqf7UTE57AIGS44Dtx4+7/Eo9CnVPFLeyQNtNq7igYTg=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNDk1ODE5NiwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82YjBlMTdhYjIzNDFmYTIzMTc1MmRlOTRjYTE5YjJjNWJmZDY0NGUyYmQwNDliMzc5OTY1NGRlNGQxYjBiYThhIgogICAgfQogIH0KfQ==" + }, + "]": { + "signature": "BV2xiuG5De1ODwhTeiU8f+Lza77u5UcpQ5MVNMSjZ6YZjdkcQxY//mPKfObvchvERjXHYHL325+HEFqCmQwjZ/kUILEzRKPry97OOIz8w4jdJZSpDJ2pTTuLHqfX8Gxlx9S2TCQviNdgkRy7VafFkOn2klMgdv3M2hImtsBaxdpVYgw8ZeyWB+JnhxShDI3Jc0/8kHJW66awJBG8vJQZaeLzuFgI/zNJs3LHR1gN4HUo7xj/49i7YuVHxY8doNID6zbeopydNLGnK+IyRFX7kb5v+WCzouzTd+30pSJZZDu0BtLCY4xeNcfZAaY3xxllOvhbBf+kfqJKRP9AEP9gCsKijAWUCSMwVBa6wNrrWANBGd2x3lH5OpgqNo6sczqQwA0B6tfOLNzVTJ7+H+BvUh3NzagrOKaypDOI+caU54zpB02698GLRsIQCOFFyITUNQqXkv1Rs40yxDrI5T+02+D+/PrYD0wiYfUMaW14/ZWHTSkFRXmo4wUMvGNO5SahII3D9FE7dg8/Ea6L04GyXAwlPcCe9KqlmiyS6JdmJYEJZld5LwRy/pqP9RsjeaEIvlDTm1k7WNMbQMCuWryDBOG5ChX2YqcFxDtP8ZiUXDZB2sOjAhMat3Yvh2gyCoBKmpKCF4C3lFUUJVfZe4busgXIooSX+iFieqqw35JI5QA=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNDMzMjYzMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yMTE1YTUwOTZlY2ZiNWY4NzE2MGRiNDVmMDMxOGNmNDcxMWY3MWE5MTZmMzZkNGVmMTcwZDY0NmQ4NDY4MmVmIgogICAgfQogIH0KfQ==" + }, + "^": { + "signature": "n0Fqex+hMtaBfII/A6Jfb+P1DzYXcjSv3Ra5xpjurULBK0q0xPan44/18aYwJ804mChrGm5KFGQ1rG5WZwBiCyc2/OT5pIuNsUAob6CZKENl3+p2vIoPBej/FYzBblaFd5bXfPsO/4ik8vxr45dhJI5SdDpGdceieBkBdxoqt/3nAueI52nOhxcVYoXSA4hnKT58urudAomszNrpds9vAhw4SZ2eMgiz6bU0XB+Kl3glx+nUOD5UsTafJ/j2DA1SHxNCqyyJiWGP9djUh9LpmwY62j57srhyHeXCRoqTP1gzxLeSI9Hb6/dxfvxtg/vzWsbLauyW/wbu5oHfbAR0GA7/zdFWHUpDAlfKP3+DJllYEHjWj28GXDSomgIxr2X5m5wbBIwfQZbO3kn7aHW7b0HXk7xcPAkvUotx6N+imTfk2aFczdMId/EhtFDUr6rdUbHRQHhWcx67DcFfucTIF+Wm0pg2/ZCKqbVTlvz0DUw0AwObzO4tsgMFGHl+1zBu+AzZeDoDGeLAH3T1gSvvdPtAOcC9UU4OtyQSBn49OiXKMOZoPuoFcUuthIfi97aL/pNPRi3VzkKwdhHvGeEOu/if98ssChx3xjE++Bzp3OMgr9ECK4fX+b4jHmzEs4qw8FhCx2WNpAAkp/kcUjkV+Gw5Qa+ndSHKUsJMyYM6qQI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNTEwMzk3NywKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84Y2U0YzdkZDI2OTQ0ZGMzZWM4YzA3ODFmMDgxYmFmNjgwN2E0MWU0NWUwZWUxNDdiZWEzMDg1NWQyZTA5ZjUxIgogICAgfQogIH0KfQ==" + }, + "_": { + "signature": "vJg7UzjYP5Bpcd4rHaTa4pSLmsf5MJfh41EyNttrM8lUP6h1Cjt4DM8l1pQA/YbwQQ6A26HgjQVwDFDL2z7aE2l44pJTRnrmwG67iurYXHX8K929Jx1uXM4s9CmmdjfzptqZ0obdd8ndosSzt/Pa/GNEmg9vy81T8yWvoXg8SvM/gSFo1YmrBCjapAEY5wH3cZvyXRaJmRSx1Lkv9AQtVFvBdGvkkoDr4xEqtd/IdAApb7V87YDyTSdCrN/+ZgxQggh2IIry9JPat3gY/8D6SnKcHdJA03BgcRGINc3UwGGb57jVPctgMyhp///fo91uCQNRmX6Gk7LLqbCDOkW5urDaX1Duwq7Rl5giF3rUSD2PuJ+G0C2zYguViWMOAJ4xMVWrZtrv+Y4/97fSQegfq5xTea3xCjFxL1C4r7KqzZFoMFm1grr+4C0TrOQlIrGn1BZJms14ptFq5VQcQulF8uZqkecG4OR58UAyozQfCnsxCdCSthVy4u4id442wGR7OV0tqGuZ2hQKHjvZZYtcQxoyjfdpmpYvXHXWznLkhQAGovGx5b3CsH9BgLJ2Nzhhjqd71BCSxvT8fia0R4f10p+C4ZlLQMqBFOL1Gd75LIl3c24IRT/EOfs+7JFSNU7efJZO6fC5Pgl+plAvN1VFvysunKut1jLZr3cJxRqeWaI=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNjMxNjc0MCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zNTU3MzgyZDU4MWE5NzZkYTc2NmI0NGNiNDEwNWMzNjQzMWEzNjIwYzE3MjUwMTczYjI2YzY2MmNlNGY4M2NmIgogICAgfQogIH0KfQ==" + }, + "`": { + "signature": "j9MBHqBZ4gCC+xD6OBT+2MS10MgvNZj+ApCfvbOxn6wRdLPm2RL8PkhTXRTjO5ZnxEkLoL1yqZTjGgwMF8h0Pk2euOrV18F3XA6uilkpanO4qctEosgbKtROFQfwMVYXu5QqX84YQy3y9ZbDIVPiVZdMLTsxrS5My7SqL2R+0lEGCIncigDb1sshM167rI8UpQ4r7B7XhPw42ij8Z2vVSX9JZT05uychKkPc6IJFWXhetsN8JSObSODUF4hsG/4hTZozBHikk2IAZfpGthfbtKkTli30WhglVkRHX0AF5pPf0F3IZDrS+wnepdneG78j0Vs0sFay3jwcdi2TLvmVLAfR77SaDAXE97YnuxNrDmJmT0rpr3k7OGem4zxOnyOKv1caigvuKkYS9p18gpxzG5d7qkEroK8aZkJt/SiCFY0+Naop4c0DST724qiZMBCrR/dKjS5YABs5vY6CURxoi+xGxYfkbutUnIU7KJXXBwS0NHfBTKIi/vbC7MWTnvKmDvz2BtFqUDhTMhVh4y2Ro3mlr5bu2MxDoEV0cCVwIbKU7B9f0D5oUzanbB8E5hfHxk0sDUBCF/XR0/MMKGNPmxOqazBtkz3Dc2p6ss8yaIYTcNtVirfl0tTLxhSEEOkqtwP+We565c2Dl/EoEzfHvMC8eVjxLDPK42HOZTjG6lc=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNDExMjE5NCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zMDcxZGU5ZTJhZGFlODhhNTg4MTk0MjU1ODM3MmU3OGI3NzYzZmFkZGI2MjM0MDQ5YTQyZDg4ZWYwMDg4ZTUiCiAgICB9CiAgfQp9" + }, + "{": { + "signature": "Repwhggq25Xu9iSkEA3Gy4PJaOzgl+ZwabPua6cnmfb33+LMPEByphNVNsIdPahEFJwCeizSlz48eui8AfqqoM4ADL7RgGcTZjwvupj60IjAhvX4E2UlicjOLYD3v1MKbGGVEOaaydB4GsnP81C9yV0R9piMV+AkH4cgrVEpc4O7gJXXd4DoGxiwT7taoFu0X7cgeeOTWKgZpqiMco589JuVYvTlqPMsTs3ynbpHEwBaf/hRkZRGvb4V255wrIGNbdgiOs1bENazesMymrKUhcS4uwWg7MTOU4QsDFucmYvbUI2JpnjPXUq6Bw7kj/0rzHtTQUvQsoQJvhWCA01FCq2DK+8g764GwbaYG+AE0IyZfewcFcON5kmk+5rNerh2oPqk/fpIT/OZfE0Uq79/tUnDSX3SZ39KWrFB5S+EKldFJsYoVGCcUOd4gxHFCOCQMT0oQdmBZ9LL8L/mTLtABfA4+HdypD2Q7QPqvEgIQFL3nHjhm0LugBDpeSaPqB8kjIXjRtkRJEVHtu8lWBlXx1L4nPLPSvVW/Gcn2DGz1qKda7Nl0qq6jDrkNm0C01RFccCNWe9q4Jhaq1MoXYMA89UBvWKoWvzFT+95tEFnle4/oWK8xtvkXg3q3clUkTxnwx1OR41Fzj0Yebbtrg75V92unIvZpVNfhsWu5bT75TM=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNTI4Nzk1OCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81YTk0OTZkZTRkZjgxYjM3YWJiM2RmM2JlZjhmYWJmYjZkMGRlYjE5NmUwYTE5M2FhOTJkMDRhYTMxNjY1OWRiIgogICAgfQogIH0KfQ==" + }, + "}": { + "signature": "lSoSJkKIw5WOzqkPV3mPjTVlO4J++W4VnL3yODVpr0y7rYeyhATmiFvjaVTJT1Izyw0yTdwSICb+xvcpSqHlUeoseNe7q6HSwG+GbYgVF+F0Yo8E2nTYHET2AOB0C7SuHi9QNZm8gi/7b5uMMMublwUa8SG1D5HLl57nthqR0npyXgh+4rFb2nfvQrveRXCE2djm3o8IjKnF4DD6y/idNnBmETSJuX1WmKk8Fopr7BK9xQyn+a7Xvg6MtpfXh48G/dJYhuaxsRzdzBgs9fS0Cqp0RwAoFOu+3Z7djYvItYQ8lf8ZP2fX2EDUH0Q/ZliAQ2pf03vIEz6R8KWCGFC1/1BIm2TTzPn8liS92Z3vwpdGZWwsSe8OOhM3SuKOagp5wXjQ1Ni3kV+XJXagXsL22Js3GsavYjMVWZ3kGody2OulMq+kTQkbvesDfHWTzR44AquCshEwLuutCePDPAWJScnuKH9StZLwu3e6Ow9xGnZLnccdqK3xUK8Es9KmJpAPBku1GEiOOfUdoNtzYgQCJP1PpJdxkN5ZkNqZWS5vk3GXqT05OSwZDaFj1Y+EllQt0MN9C4GG7k4yRvjy0x6RP7WF54tCJFU3bv7xWk585aHk1CB0VK0Y17/z4rd3yrc8fB/NMK+ucZuhtKR9RfuWgzs+eVMPXJzLkYkkvKOF2zw=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNDI1ODkyNCwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85ZDM2NTRhZDBjNmZmYjRhNTkyMWYwZThkZDkyODk5ODlhOTU2MGRiN2I3MTExODYxMzJiYzZmZTcyNjI1YjU0IgogICAgfQogIH0KfQ==" + }, + "~": { + "signature": "C5rPDLaJPKz57wXE/M5QLY37wrgaNCXpNPdpQDYIgA8XmflgTOevacy/vR5DfVKamf8I24ySGfib2DNLIi6Zub2rcBuUGUnZR8G4N8YABTY54oMSFfbAnqux+3fKyerTX0GWCK03so9qQ8Xbt3n5XDmKE/PzdltkjsQsSbv+rf8/DpFSghfn0bUPW27MMRyv4CjFg4ga+eQX2zVXthm8ABIz296QaR+yBi7JQoT1m0n0BOVuOJcD1BYHQ0weTZAxUm8Vx6Mv2dD3QMkigA0Qp0h10OLyvMtw84WEQI61wOm4rA/e8w9i6Zbmr3LbVSyf7QdAnI1XRRRyb4b0Op/KemByA0Cw4SGrUwD+mgnkGisSUBYucWXRUmICacZyLUoTWgUIu4+iDkq/TBqRZZ4voH6e/x3vHc8cB8v+Y6kwyP0TMO8SMae5X/oJIiq7UwglYvUWMULdeVhsbZG0JWMndj3gQ+PRklyaSTYRR1rhhcWHLjrUbO/+SmI/eOC1bW8RZDuS74xCf1OK4t/kf1e+jkoHFed7RVunpjFNDxyyWU/U6qOaWx7cmWnOsuGeK2BSjsgvIHWXiVKqUrvNgZjbX6QOUFTiTz7xKYEdSw5sHOfqydZRWJ3WM9mlrfLWu4dedv01BRJehkbLTOS4ry8usuxb+mXduPL1l97pWGNAW6M=", + "value": "ewogICJ0aW1lc3RhbXAiIDogMTY0MjExNDIyMjAzMSwKICAicHJvZmlsZUlkIiA6ICJhY2NjNTNkY2FkNjY0YzEyOTU3ZGUzYTIwODE5OTZmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb2RlckFsaSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8xNjM5ZDYwNDI0MzkzZTE4ZGU1MmZjOGI1MWE2NzM5NTBjOWJjNTVmNWRkN2NkNDllMmQxYjdhNTJiNGFmYjIzIgogICAgfQogIH0KfQ==" + } +} \ No newline at end of file diff --git a/Network/eLib/src/main/resources/skins/premadeskins.json b/Network/eLib/src/main/resources/skins/premadeskins.json new file mode 100644 index 0000000..e7db8a4 --- /dev/null +++ b/Network/eLib/src/main/resources/skins/premadeskins.json @@ -0,0 +1,6 @@ +{ + "gray-skin": { + "value": "eyJ0aW1lc3RhbXAiOjE0MTEyNjg3OTI3NjUsInByb2ZpbGVJZCI6IjNmYmVjN2RkMGE1ZjQwYmY5ZDExODg1YTU0NTA3MTEyIiwicHJvZmlsZU5hbWUiOiJsYXN0X3VzZXJuYW1lIiwidGV4dHVyZXMiOnsiU0tJTiI6eyJ1cmwiOiJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzg0N2I1Mjc5OTg0NjUxNTRhZDZjMjM4YTFlM2MyZGQzZTMyOTY1MzUyZTNhNjRmMzZlMTZhOTQwNWFiOCJ9fX0=", + "signature": "u8sG8tlbmiekrfAdQjy4nXIcCfNdnUZzXSx9BE1X5K27NiUvE1dDNIeBBSPdZzQG1kHGijuokuHPdNi/KXHZkQM7OJ4aCu5JiUoOY28uz3wZhW4D+KG3dH4ei5ww2KwvjcqVL7LFKfr/ONU5Hvi7MIIty1eKpoGDYpWj3WjnbN4ye5Zo88I2ZEkP1wBw2eDDN4P3YEDYTumQndcbXFPuRRTntoGdZq3N5EBKfDZxlw4L3pgkcSLU5rWkd5UH4ZUOHAP/VaJ04mpFLsFXzzdU4xNZ5fthCwxwVBNLtHRWO26k/qcVBzvEXtKGFJmxfLGCzXScET/OjUBak/JEkkRG2m+kpmBMgFRNtjyZgQ1w08U6HHnLTiAiio3JswPlW5v56pGWRHQT5XWSkfnrXDalxtSmPnB5LmacpIImKgL8V9wLnWvBzI7SHjlyQbbgd+kUOkLlu7+717ySDEJwsFJekfuR6N/rpcYgNZYrxDwe4w57uDPlwNL6cJPfNUHV7WEbIU1pMgxsxaXe8WSvV87qLsR7H06xocl2C0JFfe2jZR4Zh3k9xzEnfCeFKBgGb4lrOWBu1eDWYgtKV67M2Y+B3W5pjuAjwAxn0waODtEn/3jKPbc/sxbPvljUCw65X+ok0UUN1eOwXV5l2EGzn05t3Yhwq19/GxARg63ISGE8CKw=" + } +} \ No newline at end of file diff --git a/Network/eLib/src/main/resources/skinsCache.yml b/Network/eLib/src/main/resources/skinsCache.yml new file mode 100644 index 0000000..76cc66c --- /dev/null +++ b/Network/eLib/src/main/resources/skinsCache.yml @@ -0,0 +1 @@ +cache: '{"07fd8ed7-f51d-4cee-a57b-51463ad3e947":"{\"uuid\":\"07fd8ed7-f51d-4cee-a57b-51463ad3e947\",\"value\":\"ewogICJ0aW1lc3RhbXAiIDogMTY1MDIzNDI3NTU5MCwKICAicHJvZmlsZUlkIiA6ICIwN2ZkOGVkN2Y1MWQ0Y2VlYTU3YjUxNDYzYWQzZTk0NyIsCiAgInByb2ZpbGVOYW1lIiA6ICJUb29NdWNoU3RyYWZlIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2Q2NDRiNDFlNmRmNzI3MDg2NzEwZGM4Njc3ZGM2ODg2Y2MyN2UxNTcyZDk3NTE0OGYwNTNkNDg5NzliNjNmNzQiCiAgICB9CiAgfQp9\",\"signature\":\"AWbP44mrVx6/0wBUr0nDQXHOFptpQhML2/jGP0OeGooYjfy0ZtBXvCZrrlVfODzMYYJHDimvRJyP7K6ykZCt7XhHpfExp7CBSL8hMfwqw3hORqDcozBgt36NxtR19IdzKY8zc/EZMr2e8E9TwgWfNWnvA9+wQEAQuoGV9kq28Qg/jT4+IP5+Ihmfv9elyxpYpP6//W/y+NR/2djoBCsKI1VgqP/qUK7HZp1GIiEeaJOT1vK8lhBNc+/COI29kbbvfBJgkrEbA2M1RofOd5JX0RbBywTv1D8MhX+JRktIhw7ZjjSxQf9qsZbBamQhE0L4/hazMhsrikq4Cqfm/di4RpRJwHDQ0Ab/hoKMibTmbzQVQKU6BVQ0NSFEhAAikOCsd17pmMihrXhD+ads7oC8BcIxO0jv+m3yyxg6y7d1fZ+ikpVQ2NlnYIevvHBFrOLdrFyk/q1VnaxgvItyvJhPsdHi3qkeYdjkXWnjdkIw8wOzUoD66yY26/5ZebkhMuQvJJwlrrONC0lf03FMf3ElB3WN+jQ7qI/ydjteNnz/pJyDYYCJNiIQ9qRXG/cYq0eS26Y/uetgd2kYwGxqGm0PkdHU55FZs6XsYurUyaXYlmQGW/l/Rn1VfivUkMxpNN9zVv0g4B/st5/DamNoWPnI4Wn67cf8GTFtzGcQMhvYKW4=\"}","ee83c945-56f8-4259-963a-274e873c0b26":"{\"uuid\":\"ee83c945-56f8-4259-963a-274e873c0b26\",\"value\":\"ewogICJ0aW1lc3RhbXAiIDogMTY1MDIzNDE3ODYzNywKICAicHJvZmlsZUlkIiA6ICJlZTgzYzk0NTU2Zjg0MjU5OTYzYTI3NGU4NzNjMGIyNiIsCiAgInByb2ZpbGVOYW1lIiA6ICJBaXJNYXJzaGFsIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzllZWRlZmQ0MjBjM2Q0ZWYzNjFmNDEwZjM3Nzk5NzQzYzliNDk1YWI4NDk3MzQ3ZDhjNTAzMmUwOTU2NDRmNDEiCiAgICB9CiAgfQp9\",\"signature\":\"LFR4dPDpliY9oOtZ1usAAtLVFziYI5+GEMWbZMQ5UxAStuntw1+lYObzBZrcz/vhiLMYtq+oTS9tMM5bnZBi4ImHQjjUU+2RqkizeuVUoRLsIhLcK2IeorHCa58lBp5ynt3AsB3aDbKigQOuZdXHEjAmTNvab6XbI00w4Lfe5E0doEJhIB9F4DWaVJIN5uem7agRb23e6kzjge58r9Z/yqieh40HwgimWTMZhxNlEDYqSKf+EiBrjNYyXynkXKyVxZ1T2qH1sO14cmdzScRiyGxuMr30D5adM0SHPBwWU5Z/LCmJPcflEgPPaISuCdE7mblIQ+co4KMud5NwqjoGMs+T519yd+uT93hzokToRi6KwMSV/LNVnHRUnbuBIAKEbZpbPE3FpVJ+SCQmLRwGqJDrsgcs8P4zh7uMojajGxORDZAzakGkfFBUKc6i+I8wY7KQ9neu99IDmvdwgzUk4588vy+p8zrfiICi9cV2P+Gd/Sj9+hIYicY3X3KjSvCsiT28gP/7bpIJWgw2lW9n5i0sqfTa05Ffde+Thvyokdd2WyvYGW/ZJluwquQ8hvfBlTxSXj9H1Tn8GsIs4mqmHdM5lb7skfodcTVrTYqXv+VWoT+GwrQ9wevHFlTq+d8KWHlJ78mOqcpgWsuVq+76xB6hN86itERx20VURallUqc=\"}","a811d1ac-4d79-4f57-a44b-c9aeca1c4a80":"{\"uuid\":\"a811d1ac-4d79-4f57-a44b-c9aeca1c4a80\",\"value\":\"ewogICJ0aW1lc3RhbXAiIDogMTY1MDIzNDI4MzI3OSwKICAicHJvZmlsZUlkIiA6ICJhODExZDFhYzRkNzk0ZjU3YTQ0YmM5YWVjYTFjNGE4MCIsCiAgInByb2ZpbGVOYW1lIiA6ICJCdXRjaENhcyIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iOWUyMjU1NWFkNjQ0YWEzOTNkZWMxZGI3YzU4ZGVjYzMyNTgxZDA5YTM0MmFjNTEzYjczMTM1Y2JmNjM5ZmE5IgogICAgfSwKICAgICJDQVBFIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yMzQwYzBlMDNkZDI0YTExYjE1YThiMzNjMmE3ZTllMzJhYmIyMDUxYjI0ODFkMGJhN2RlZmQ2MzVjYTdhOTMzIgogICAgfQogIH0KfQ==\",\"signature\":\"t5U9KMbu7Oj9REtixAkz4qibKnNV2jGmkTQsAJoMsiYURpLDIM7HHZDv9lVVu0keKGSogSh1s3fsoPz8rtK0uo4HBmkeiWDdwD3gOxotBZom7iZ9quMuOk0qHJneFvx15yDhgPRoniv4wZTNDjFYNolSj1CXRzI3UX6t/1G9eOz5cHctII0G5wH63ZzBPjOPC6hdo6mSDp5ojgOBD20OhKXMEgCrH+QTapdHa5bWhw/6tbeJ2fTj2dHumT9Ke3n/6nXNwIoPBUSsi4M/2LC5uQpscvmXas8FzyhiUDrEcy/AVie/tH+wwwmx1AGrajGr83wsAwwNQk5C933RdMVNZBgGcAYVxw9Vg7yYeodBmEPeR0nd5RDxBXL93sWpzYh19ojkFDjEUeOIv2KARfl5WCeMtdODKp9a+d3pe99XEBefcpuuWOfTFcOzVZBk4QgjSNmh2MFsskC+u+mMUcrBXW5dBAQVoFTYDUIqFJI66jh3cbLx14nepRS7OpWs/yDNB4wrN6IJNnBIuoq4eZhDJfJ7qeVO9ag0eFyrYpjCvLnRWDyyrgGmn+pQ8sUfmWJHKnFQNhmEqscYAHSv7IXdZiPqLSQt2YXdUiAIBKm7LAax58F24Fkk8Oud3Qa5uBIA6ARRhhjm9RtQ8sFl8apbYqiOPJyKMLuzudChJ1eRrJo=\"}"}' diff --git a/Network/ePractice/README.md b/Network/ePractice/README.md new file mode 100644 index 0000000..913d0b1 --- /dev/null +++ b/Network/ePractice/README.md @@ -0,0 +1,3 @@ +# PotPvP SI + +PotPvP Single Instance \ No newline at end of file diff --git a/Network/ePractice/build.gradle b/Network/ePractice/build.gradle new file mode 100644 index 0000000..b333dfb --- /dev/null +++ b/Network/ePractice/build.gradle @@ -0,0 +1,81 @@ +plugins { + id 'java' + id 'org.hidetake.ssh' version '2.10.1' +} + +group 'com.elevatemc' +version '1.0' + +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 + +compileJava.options.encoding = 'UTF-8' + +repositories { + mavenCentral() + mavenLocal() + maven { + url = uri('https://maven.enginehub.org/repo/') + } + maven { + url = uri('https://repo.dmulloy2.net/repository/public/') + } + maven { + url = uri('https://repo.phoenix616.dev') + } +} + +sourceSets { + main.java.srcDirs = ['src/main/java'] + main.resources.srcDirs = ['src/main/resources'] +} + +dependencies { + compileOnly files('../lib/espigot.jar') + compileOnly files('../lib/primespigot.jar') + compileOnly files('../lib/lcapi.jar') + compileOnly project(':eLib') + compileOnly 'redis.clients:jedis:4.1.1' + + compileOnly 'com.sk89q:worldedit:6.0.0-SNAPSHOT' + compileOnly 'org.projectlombok:lombok:1.18.22' + annotationProcessor 'org.projectlombok:lombok:1.18.22' +} + +remotes { + webServer { + host = '51.222.244.184' + user = 'root' + password = 'XN9?sx2#8E3L*d_K' + } +} + +//task deploy { +// doLast { +// ssh.run { +// session(remotes.webServer) { +//// put from: './build/libs/ePractice-1.0.jar', into: '/home/practice/plugins/' +// put from: '../eLib/build/libs/eLib-1.0-all.jar', into: '/home/betaprac/plugins/' +// put from: './build/libs/ePractice-1.0.jar', into: '/home/betaprac/plugins/' +// execute 'tmux send -t dev stop ENTER' +// } +// } +// } +//} + +//build { +// dependsOn(deploy) +//} + +processResources { + def props = [version: 'git rev-parse --verify --short HEAD'.execute().text.trim()] + inputs.properties props + filteringCharset 'UTF-8' + filesMatching('plugin.yml') { + expand props + } +} + +ssh.settings { + knownHosts = allowAnyHosts +} \ No newline at end of file diff --git a/Network/ePractice/gradle.properties b/Network/ePractice/gradle.properties new file mode 100644 index 0000000..d0b282e --- /dev/null +++ b/Network/ePractice/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx3072m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M" +org.gradle.parallel=true +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-all.zip \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/PotPvPLang.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/PotPvPLang.java new file mode 100644 index 0000000..9a153ed --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/PotPvPLang.java @@ -0,0 +1,59 @@ +package com.elevatemc.potpvp; + +import lombok.experimental.UtilityClass; +import org.apache.commons.lang.StringUtils; +import org.bukkit.ChatColor; + +/** + * Constants for messages/strings commonly shown to players + */ +@UtilityClass +public final class PotPvPLang { + + /** + * `&7&l» ` - Arrow used on the left side of item display names + * Named left arrow due to its usage on the left side of items, despite the fact + * the arrow is actually pointing to the right. + * @see com.elevatemc.potpvp.lobby.LobbyItems usage + * @see com.elevatemc.potpvp.queue.QueueItems usage + */ + public static final String LEFT_ARROW = ChatColor.GRAY + "» "; + + public static final String LEFT_ARROW_NAKED = "»"; + + /** + * ` &7&l«` - Arrow used on the right side of item display names + * Named right arrow due to its usage on the right side of items, despite the fact + * the arrow is actually pointing to the left. + * @see com.elevatemc.potpvp.lobby.LobbyItems usage + * @see com.elevatemc.potpvp.queue.QueueItems usage + */ + public static final String RIGHT_ARROW = " " + ChatColor.GRAY + "«"; + + /** + * Example omitted - Solid line which almost entirely spans the + * (default) Minecraft chat box. 53 is chosen for no reason other than its width + * being almost equal to that of the chat box. + * @see com.elevatemc.potpvp.command.HelpCommand + * @see com.elevatemc.potpvp.party.command.PartyHelpCommand + */ + public static final String LONG_LINE = ChatColor.STRIKETHROUGH + StringUtils.repeat("-", 53); + + /** + * `&cYou are not in a party.` - Self explanatory + * @see com.elevatemc.potpvp.party.command + */ + public static final String NOT_IN_PARTY = ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You are not in a party."; + + /** + * `&cYou are not the leader of your party.` - Self explanatory + * @see com.elevatemc.potpvp.party.command + */ + public static final String NOT_LEADER_OF_PARTY = ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You are not the leader of your party."; + + /** + * `&cThere was an error starting the match, please contact an admin.` - Self explanatory + */ + public static final String ERROR_WHILE_STARTING_MATCH = ChatColor.DARK_RED + "✖ " + ChatColor.RED + "There was an error starting the match, please contact an admin."; + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/PotPvPSI.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/PotPvPSI.java new file mode 100644 index 0000000..2c79fef --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/PotPvPSI.java @@ -0,0 +1,283 @@ +package com.elevatemc.potpvp; + +import com.elevatemc.elib.command.param.defaults.GameModeParameterType; +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.tab.data.TabList; +import com.elevatemc.potpvp.ability.Ability; +import com.elevatemc.potpvp.ability.AbilityHandler; +import com.elevatemc.potpvp.ability.parameter.AbilityParameter; +import com.elevatemc.potpvp.cosmetic.Cosmetic; +import com.elevatemc.potpvp.cosmetic.CosmeticHandler; +import com.elevatemc.potpvp.cosmetic.command.type.CosmeticParameterType; +import com.elevatemc.potpvp.deathmessage.DeathMessageHandler; +import com.elevatemc.potpvp.events.EventHandler; +import com.elevatemc.potpvp.events.EventListeners; +import com.elevatemc.potpvp.events.EventTask; +import com.elevatemc.potpvp.events.event.GameEvent; +import com.elevatemc.potpvp.events.game.Game; +import com.elevatemc.potpvp.events.game.GameListeners; +import com.elevatemc.potpvp.events.game.GameQueue; +import com.elevatemc.potpvp.gamemode.*; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import com.elevatemc.potpvp.gamemode.kit.GameModeKitJsonAdapter; +import com.elevatemc.potpvp.gamemode.kit.GameModeKitParameterType; +import com.elevatemc.potpvp.hologram.HologramHandler; +import com.elevatemc.potpvp.hctranked.HCTRankedHandler; +import com.elevatemc.potpvp.tab.PotPvPLayoutProvider; +import com.elevatemc.potpvp.util.ClickTracker; +import com.lunarclient.bukkitapi.cooldown.LCCooldown; +import com.lunarclient.bukkitapi.cooldown.LunarClientAPICooldown; +import com.lunarclient.bukkitapi.nethandler.client.obj.ServerRule; +import com.lunarclient.bukkitapi.serverrule.LunarClientAPIServerRule; +import com.mongodb.MongoClient; +import com.mongodb.MongoClientURI; +import com.mongodb.client.MongoDatabase; +import dev.apposed.prime.spigot.Prime; +import lombok.Getter; +import com.elevatemc.potpvp.arena.ArenaHandler; +import com.elevatemc.potpvp.arena.ArenaSchematic; +import com.elevatemc.potpvp.arena.ArenaSchematicParameterType; +import com.elevatemc.potpvp.duel.DuelHandler; +import com.elevatemc.potpvp.elo.EloHandler; +import com.elevatemc.potpvp.follow.FollowHandler; +import com.elevatemc.potpvp.kit.KitHandler; +import com.elevatemc.potpvp.listener.*; +import com.elevatemc.potpvp.lobby.LobbyHandler; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.nametag.PotPvPNametagProvider; +import com.elevatemc.potpvp.party.PartyHandler; +import com.elevatemc.potpvp.postmatchinv.PostMatchInvHandler; +import com.elevatemc.potpvp.pvpclasses.PvPClassHandler; +import com.elevatemc.potpvp.queue.QueueHandler; +import com.elevatemc.potpvp.match.rematch.RematchHandler; +import com.elevatemc.potpvp.scoreboard.PotPvPScoreboardConfiguration; +import com.elevatemc.potpvp.setting.SettingHandler; +import com.elevatemc.potpvp.statistics.StatisticsHandler; +import com.elevatemc.potpvp.tournament.TournamentHandler; +import com.elevatemc.potpvp.util.VoidChunkGenerator; +import com.elevatemc.elib.serialization.*; +import org.bukkit.*; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import org.bukkit.event.Listener; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.potion.PotionEffect; +import org.bukkit.util.BlockVector; +import org.bukkit.util.Vector; + +import java.io.IOException; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; + +public final class PotPvPSI extends JavaPlugin { + + private static PotPvPSI instance; + public static final Random RANDOM = new Random(); + @Getter private static final Gson gson = new GsonBuilder() + .registerTypeHierarchyAdapter(PotionEffect.class, new PotionEffectAdapter()) + .registerTypeHierarchyAdapter(ItemStack.class, new ItemStackAdapter()) + .registerTypeHierarchyAdapter(Location.class, new LocationAdapter()) + .registerTypeHierarchyAdapter(Vector.class, new VectorAdapter()) + .registerTypeAdapter(BlockVector.class, new BlockVectorAdapter()) + .registerTypeHierarchyAdapter(GameModeKit.class, new GameModeKitJsonAdapter()) // custom GameMode serializer + .registerTypeAdapter(ChunkSnapshot.class, new ChunkSnapshotAdapter()) + .serializeNulls() + .create(); + + @Getter private static final Gson PLAIN_GSON = (new GsonBuilder()) + .registerTypeHierarchyAdapter(PotionEffect.class, new PotionEffectAdapter()) + .registerTypeHierarchyAdapter(ItemStack.class, new ItemStackAdapter()) + .registerTypeHierarchyAdapter(Location.class, new LocationAdapter()) + .registerTypeHierarchyAdapter(Vector.class, new VectorAdapter()) + .registerTypeAdapter(BlockVector.class, new BlockVectorAdapter()) + .serializeNulls().create(); + + @Getter private MongoDatabase mongoDatabase; + + @Getter private SettingHandler settingHandler; + @Getter private DuelHandler duelHandler; + @Getter private KitHandler kitHandler; + @Getter private LobbyHandler lobbyHandler; + private ArenaHandler arenaHandler; + @Getter private MatchHandler matchHandler; + @Getter private PartyHandler partyHandler; + @Getter private QueueHandler queueHandler; + @Getter private RematchHandler rematchHandler; + @Getter private PostMatchInvHandler postMatchInvHandler; + + @Getter private ClickTracker clickTracker; + @Getter private DeathMessageHandler deathMessageHandler; + @Getter private FollowHandler followHandler; + @Getter private EloHandler eloHandler; + @Getter private TournamentHandler tournamentHandler; + @Getter private PvPClassHandler pvpClassHandler; + @Getter private Prime prime; + @Getter private CosmeticHandler cosmeticHandler; + @Getter private AbilityHandler abilityHandler; + @Getter private HologramHandler hologramHandler; + @Getter private HCTRankedHandler HCTRankedHandler; + @Getter private EventHandler eventHandler; + + @Getter private AtomicInteger onlineCount = new AtomicInteger(); + @Getter private AtomicInteger fightsCount = new AtomicInteger(); + + @Override + public void onEnable() { + //SpigotConfig.onlyCustomTab = true; // because we'll definitely forget + //this.dominantColor = ChatColor.DARK_PURPLE; + instance = this; + prime = Prime.getPlugin(Prime.class); + + saveDefaultConfig(); + + setupMongo(); + + // We do not need to load the spawn world since that is the default world. + Bukkit.getServer().createWorld(new WorldCreator("arenas").generateStructures(false).generator(new VoidChunkGenerator()).type(WorldType.FLAT)); + + for (World world : Bukkit.getWorlds()) { + world.setGameRuleValue("doDaylightCycle", "false"); + world.setGameRuleValue("doMobSpawning", "false"); + world.setGameRuleValue("doFireTick", "false"); + world.setWeatherDuration(0); + world.setTime(6_000L); + } + + LunarClientAPIServerRule.setRule(ServerRule.LEGACY_COMBAT, true); + LunarClientAPIServerRule.setRule(ServerRule.SERVER_HANDLES_WAYPOINTS, true); + LunarClientAPICooldown.registerCooldown(new LCCooldown("Enderpearl", 16, Material.ENDER_PEARL)); + + + settingHandler = new SettingHandler(); + duelHandler = new DuelHandler(); + queueHandler = new QueueHandler(); + GameModes.loadGameModes(); + kitHandler = new KitHandler(); + lobbyHandler = new LobbyHandler(); + arenaHandler = new ArenaHandler(); + matchHandler = new MatchHandler(); + partyHandler = new PartyHandler(); + rematchHandler = new RematchHandler(); + postMatchInvHandler = new PostMatchInvHandler(); + followHandler = new FollowHandler(); + eloHandler = new EloHandler(); + pvpClassHandler = new PvPClassHandler(); + tournamentHandler = new TournamentHandler(); + abilityHandler = new AbilityHandler(this); + HCTRankedHandler = new HCTRankedHandler(); + hologramHandler = new HologramHandler(); + deathMessageHandler = new DeathMessageHandler(); + clickTracker = new ClickTracker(this); + // custom shits with better code | who wants to be using static references. #disgusting even tho its easier + cosmeticHandler = new CosmeticHandler(); + + eventHandler = new EventHandler(this); + + + + new EventTask().runTaskTimerAsynchronously(this, 1L, 1L); + + for(GameEvent event : EventHandler.EVENTS) { + for(Listener listener : event.getListeners()) { + getServer().getPluginManager().registerEvents(listener, this); + } + } + + getServer().getPluginManager().registerEvents(new BasicPreventionListener(), this); + getServer().getPluginManager().registerEvents(new BowHealthListener(), this); + getServer().getPluginManager().registerEvents(new NightModeListener(), this); + getServer().getPluginManager().registerEvents(new PearlCooldownListener(), this); + getServer().getPluginManager().registerEvents(new RankedMatchQualificationListener(), this); + getServer().getPluginManager().registerEvents(new TabCompleteListener(), this); + getServer().getPluginManager().registerEvents(new StatisticsHandler(), this); + getServer().getPluginManager().registerEvents(new FancyInventoryListener(), this); + getServer().getPluginManager().registerEvents(new EventListeners(), this); + getServer().getPluginManager().registerEvents(new GameListeners(), this); + + eLib.getInstance().getCommandHandler().registerAll(this); + eLib.getInstance().getCommandHandler().registerParameterType(GameMode.class, new GameModeParameterType()); + eLib.getInstance().getCommandHandler().registerParameterType(GameModeKit.class, new GameModeKitParameterType()); + eLib.getInstance().getCommandHandler().registerParameterType(ArenaSchematic.class, new ArenaSchematicParameterType()); + eLib.getInstance().getCommandHandler().registerParameterType(Ability.class, new AbilityParameter()); + eLib.getInstance().getCommandHandler().registerParameterType(Cosmetic.class, new CosmeticParameterType()); + System.out.println("Loading tab layout..."); + eLib.getInstance().getTabHandler().setTabList(new TabList(this, new PotPvPLayoutProvider())); + System.out.println("Done finished loading!"); + eLib.getInstance().getNameTagHandler().registerProvider(new PotPvPNametagProvider()); + eLib.getInstance().getScoreboardHandler().setConfiguration(PotPvPScoreboardConfiguration.create()); + hologramHandler.registerHolograms(); + + Bukkit.getScheduler().runTaskTimer(this, () -> { + int online = (int) Bukkit.getOnlinePlayers() + .stream() + .filter(invisible -> !invisible.hasMetadata("modmode")) + .count(); + getOnlineCount().set(online); + getFightsCount().set(matchHandler.countPlayersPlayingInProgressMatches()); + }, 3 * 20L, 20L); + } + + @Override + public void onDisable() { + for (Match match : this.matchHandler.getHostedMatches()) { + if (match.getGameMode().getBuildingAllowed()) match.getArena().restore(); + } + + GameQueue.INSTANCE.getCurrentGames().forEach(Game::end); + + try { + arenaHandler.saveSchematics(); + } catch (IOException e) { + e.printStackTrace(); + } + + for (String playerName : PvPClassHandler.getEquippedKits().keySet()) { + PvPClassHandler.getEquippedKits().get(playerName).remove(getServer().getPlayerExact(playerName)); + } + + instance = null; + } + + private void setupMongo() { + MongoClientURI uri = new MongoClientURI(getConfig().getString("mongo.uri")); + MongoClient client = new MongoClient(uri); + mongoDatabase = client.getDatabase(getConfig().getString("mongo.database")); + } + + // This is here because chunk snapshots are (still) being deserialized, and serialized sometimes. + private static class ChunkSnapshotAdapter extends TypeAdapter { + + @Override + public ChunkSnapshot read(JsonReader arg0) throws IOException { + return null; + } + + @Override + public void write(JsonWriter arg0, ChunkSnapshot arg1) throws IOException { + + } + + } + + /** + * It is important that the default world generator gets set to "Practice". This setting can be found under "worlds > MAINWORLD > generator" in bukkit.yml + */ + public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) { + return new VoidChunkGenerator(); + } + + public ArenaHandler getArenaHandler() { + return arenaHandler; + } + + public static PotPvPSI getInstance() { + return instance; + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/Ability.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/Ability.java new file mode 100644 index 0000000..1243af1 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/Ability.java @@ -0,0 +1,110 @@ +package com.elevatemc.potpvp.ability; + +import com.elevatemc.elib.util.ItemBuilder; +import com.elevatemc.elib.util.TimeUtils; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.util.Color; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; + +public abstract class Ability implements Listener { + + public Ability() { + final List finalLore = new ArrayList<>(this.getLore()); + + this.hassanStack = ItemBuilder.of(this.getMaterial()) + .name(this.getDisplayName() == null ? this.getName() : this.getDisplayName()) + .setLore(this.getLore() == null ? new ArrayList<>() : finalLore) + .build(); + + PotPvPSI.getInstance().getServer().getPluginManager().registerEvents(this, PotPvPSI.getInstance()); + } + + public abstract String getName(); + public abstract Material getMaterial(); + public abstract String getDisplayName(); + public abstract List getLore(); + public abstract long getCooldown(); + + public ItemStack hassanStack; + + public boolean isSimilar(ItemStack itemStack) { + + if (itemStack == null || itemStack.getItemMeta() == null || itemStack.getType() == Material.AIR) { + return false; + } + + if (itemStack.getItemMeta().getDisplayName() == null) { + return false; + } + + if (itemStack.getItemMeta().getLore() == null || itemStack.getItemMeta().getLore().isEmpty()) { + return false; + } + + return itemStack.getType() == this.getMaterial() && itemStack.getItemMeta().getDisplayName().startsWith(this.hassanStack.getItemMeta().getDisplayName()) && itemStack.getItemMeta().getLore().get(0).equals(this.getLore().get(0)); + } + + public void removeCooldown(Player player) { + PotPvPSI.getInstance().getAbilityHandler().getCooldown().remove(player.getUniqueId(), this); + } + + public void applyCooldown(Player player) { + long cooldown = this.getCooldown(); + + sendCooldownMessage(player); + PotPvPSI.getInstance().getAbilityHandler().applyCooldown(this, player); + } + + public void applyCooldown(Player player, long time) { + PotPvPSI.getInstance().getAbilityHandler().applyCooldown(this, player, time); + } + + public void applyCooldown(Player player, boolean sendMessage) { + if (sendMessage) { + sendCooldownMessage(player); + } + PotPvPSI.getInstance().getAbilityHandler().applyCooldown(this, player); + } + + private void sendCooldownMessage(Player player) { + String name = this.getDisplayName(); + String time = TimeUtils.formatIntoDetailedString((int) (this.getCooldown() / 1000)); + player.sendMessage(ChatColor.DARK_GREEN.toString() + ChatColor.BOLD + "✔ " + ChatColor.GREEN + "You have used " + name + ChatColor.GREEN + " successfully and are now on cooldown for " + time + "."); + } + + public boolean hasCooldown(Player player) { + return this.hasCooldown(player, true); + } + + public boolean hasCooldown(Player player, boolean sendMessage) { + return this.hasCooldown(player, true, true); + } + + public boolean hasCooldown(Player player, boolean sendMessage, boolean checkGlobal) { + + final long current = PotPvPSI.getInstance().getAbilityHandler().getRemaining(this, player); + + if (current > 0) { + + if (sendMessage) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED.toString() + "You cannot use the " + this.getDisplayName() + ChatColor.RED + " for another " + ChatColor.BOLD.toString() + TimeUtils.formatIntoDetailedString((int) (current / 1000)) + ChatColor.RED + "."); + } + + return true; + } + + return false; + } + + public long getRemaining(Player player) { + return PotPvPSI.getInstance().getAbilityHandler().getRemaining(this, player); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/AbilityHandler.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/AbilityHandler.java new file mode 100644 index 0000000..1002353 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/AbilityHandler.java @@ -0,0 +1,186 @@ +package com.elevatemc.potpvp.ability; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; + +import com.elevatemc.elib.fake.impl.player.FakePlayerPacketHandler; +import com.elevatemc.elib.util.ClassUtils; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.listener.AbilityListener; +import com.elevatemc.potpvp.ability.packet.InvisibilityPacketAdapter; +import com.elevatemc.potpvp.util.Cooldown; +import com.elevatemc.spigot.eSpigot; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Table; +import lombok.Getter; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +@SuppressWarnings("UnstableApiUsage") +public class AbilityHandler { + + @Getter + private final Map usedItems = new HashMap<>(); + @Getter + private final Map abilities = new HashMap<>(); + @Getter + private final Table lastUsedItem = HashBasedTable.create(); + + @Getter + public static final Table cooldown = HashBasedTable.create(); + + private File file; + private FileConfiguration data; + + public AbilityHandler(PotPvPSI instance) { + for (Class clazz : ClassUtils.getClassesInPackage(PotPvPSI.getInstance(), "com.elevatemc.potpvp.ability.type")) { + + if (!Ability.class.isAssignableFrom(clazz)) { + continue; + } + + try { + final Ability ability = (Ability) clazz.newInstance(); + + this.abilities.put(ability.getName(), ability); + } catch (InstantiationException | IllegalAccessException ex) { + ex.printStackTrace(); + } + } + + instance.getServer().getPluginManager().registerEvents(new AbilityListener(), instance); + + instance.getServer().getScheduler().runTaskLater(instance, this::loadStatistics, 20); + + eSpigot.getInstance().addPacketHandler(new InvisibilityPacketAdapter()); + } + + public void loadStatistics() { + this.file = new File(PotPvPSI.getInstance().getDataFolder(), "ability-stats.yml"); + this.data = YamlConfiguration.loadConfiguration(this.file); + + if (!this.file.exists()) { + try { + this.file.createNewFile(); + } catch (IOException e) { + e.printStackTrace(); + } + return; + } + + if (this.data.get("abilities") == null) { + return; + } + + for (String key : data.getConfigurationSection("abilities").getKeys(false)) { + final Ability ability = this.fromName(key); + + if (ability == null) { + continue; + } + + this.usedItems.put(ability, data.getInt("abilities." + key)); + } + } + + public void saveStatistics() { + Map configValues = this.data.getValues(false); + for (Map.Entry entry : configValues.entrySet()) + this.data.set(entry.getKey(), null); + + for (Map.Entry abilityIntegerEntry : this.usedItems.entrySet()) { + this.data.set("abilities." + abilityIntegerEntry.getKey().getName(), +abilityIntegerEntry.getValue()); + } + + try { + this.data.save(this.file); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public Ability fromName(String name) { + return this.abilities.values().stream().filter(ability -> ability.getName().equalsIgnoreCase(name)).findAny().orElse(null); + } + + @Command( + names = {"ability give"}, + permission = "foxtrot.command.ability" + ) + public static void execute(CommandSender sender, + @Parameter(name = "ability") Ability ability, + @Parameter(name = "amount", defaultValue = "1") int amount, + @Parameter(name = "player", defaultValue = "self") Player target) { + final ItemStack itemStack = ability.hassanStack.clone(); + itemStack.setAmount(amount); + target.getInventory().addItem(itemStack); + + } + + @Command(names = {"ability reset"}, permission = "foxtrot.command.ability.reset") + public static void reset(Player player, @Parameter(name = "ability") Ability ability) { + if (!getCooldown().contains(player.getUniqueId(), ability)) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You don't have a cooldown for " + ability.getDisplayName() + ChatColor.RED + "."); + return; + } + + getCooldown().remove(player.getUniqueId(), ability); + + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Removed cooldown for the " + ability.getDisplayName() + ChatColor.RED + "."); + } + + public void applyCooldown(Ability ability, Player player) { + this.usedItems.putIfAbsent(ability, 0); + this.usedItems.replace(ability, this.usedItems.get(ability) + 1); + + if (ability.getCooldown() <= 0) { + return; + } + + cooldown.put(player.getUniqueId(), ability, new Cooldown(ability.getCooldown())); + } + + public void applyCooldown(Ability ability, Player player, long time) { + if (ability.getCooldown() <= 0) { + return; + } + + cooldown.put(player.getUniqueId(), ability, new Cooldown(ability.getCooldown())); + } + + public boolean hasCooldown(Ability ability, Player player) { + if (!cooldown.contains(player.getUniqueId(), ability)) { + return false; + } + + return !cooldown.get(player.getUniqueId(), ability).hasExpired(); + } + + public long getRemaining(Ability ability, Player player) { + if (!cooldown.contains(player.getUniqueId(), ability)) { + return 0L; + } + + return cooldown.get(player.getUniqueId(), ability).getRemaining(); + } + + public void resetCooldowns(Player player) { + cooldown.rowMap().forEach((uuid, abilityMap) -> { + if (player.getUniqueId().equals(uuid)) { + abilityMap.keySet().forEach(ability -> { + cooldown.remove(uuid, ability); + }); + } + }); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/listener/AbilityListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/listener/AbilityListener.java new file mode 100644 index 0000000..c700472 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/listener/AbilityListener.java @@ -0,0 +1,52 @@ +package com.elevatemc.potpvp.ability.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.Ability; +import com.elevatemc.potpvp.ability.listener.events.AbilityUseEvent; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.match.event.MatchEndEvent; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.PlayerDeathEvent; + +public class AbilityListener implements Listener { + + @EventHandler(priority = EventPriority.LOW) + private void onUse(AbilityUseEvent event) { + final Player player = event.getPlayer(); + final Ability ability = event.getAbility(); + + if (ability.hasCooldown(player)) { + event.setCancelled(true); + } + } + + @EventHandler + public void onMatchEnd(MatchEndEvent event) { + Match match = event.getMatch(); + GameMode gameMode = match.getGameMode(); + if (gameMode.equals(GameModes.TRAPPING)) { + for (MatchTeam team : match.getTeams()) { + team.forEachAlive(player -> { + PotPvPSI.getInstance().getAbilityHandler().resetCooldowns(player); + }); + } + } + } + + @EventHandler + public void onPlayerDeath(PlayerDeathEvent event) { + Match match = PotPvPSI.getInstance().getMatchHandler().getMatchPlaying(event.getEntity()); + if (match == null) return; + + GameMode gameMode = match.getGameMode(); + if (gameMode.equals(GameModes.TRAPPING)) { + PotPvPSI.getInstance().getAbilityHandler().resetCooldowns(event.getEntity()); + } + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/listener/events/AbilityUseEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/listener/events/AbilityUseEvent.java new file mode 100644 index 0000000..79db679 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/listener/events/AbilityUseEvent.java @@ -0,0 +1,29 @@ +package com.elevatemc.potpvp.ability.listener.events; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import com.elevatemc.potpvp.ability.Ability; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +@AllArgsConstructor +@Getter +@Setter +public class AbilityUseEvent extends Event implements Cancellable { + @Getter private static final HandlerList handlerList = new HandlerList(); + + private final Player player; + private final Player target; + private final Location chosenLocation; + private final Ability ability; + private boolean cancelled; + + @Override + public HandlerList getHandlers() { + return handlerList; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/packet/InvisibilityPacketAdapter.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/packet/InvisibilityPacketAdapter.java new file mode 100644 index 0000000..d640fe8 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/packet/InvisibilityPacketAdapter.java @@ -0,0 +1,68 @@ +package com.elevatemc.potpvp.ability.packet; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.type.Invisibility; +import com.elevatemc.spigot.handler.PacketHandler; +import net.minecraft.server.v1_8_R3.*; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.craftbukkit.v1_8_R3.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +/** + * @author xanderume@gmail.com + */ +public class InvisibilityPacketAdapter implements PacketHandler { + + @Override + public boolean handleSentPacketCancellable(PlayerConnection connection, Packet packet) { + if (packet instanceof PacketPlayOutEntityEquipment) { + PacketPlayOutEntityEquipment packetPlayOutHeldItemSlot = (PacketPlayOutEntityEquipment) packet; + final int id = packetPlayOutHeldItemSlot.getId(); + final ItemStack itemStack = CraftItemStack.asBukkitCopy(packetPlayOutHeldItemSlot.getItem()); + + if (itemStack == null || itemStack.getType() == Material.AIR) { + return true; + } + + if (!(packetPlayOutHeldItemSlot.getItem().getItem() instanceof ItemArmor)) { + return true; + } + + Player player = PotPvPSI.getInstance().getServer().getOnlinePlayers().stream().filter(it -> it.getEntityId() == id).findFirst().orElse(null); + if (player != null) { + if (player.getActivePotionEffects().stream().anyMatch(effect -> effect.getType().equals(Invisibility.EFFECT.getType()) && effect.getAmplifier() == Invisibility.EFFECT.getAmplifier())) { + return false; + } + } + } + return true; + } + +// @Override +// public void onPacketSending(PacketEvent event) { +// +// final Integer id = event.getPacket().getIntegers().read(0); +// final ItemStack itemStack = event.getPacket().getItemModifier().read(0); +// +// if (itemStack == null || itemStack.getType() == Material.AIR) { +// return; +// } +// +// if (!(CraftItemStack.asNMSCopy(itemStack).getItem() instanceof ItemArmor)) { +// return; +// } +// +// PotPvPSI.getInstance().getServer().getOnlinePlayers().stream().filter(it -> it.getEntityId() == id).findFirst().ifPresent(it -> { +// +// if (it.getActivePotionEffects().stream().noneMatch(effect -> effect.getType().equals(Invisibility.EFFECT.getType()) && effect.getAmplifier() == Invisibility.EFFECT.getAmplifier())) { +// return; +// } +// +// event.setCancelled(true); +// }); +// +// } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/parameter/AbilityParameter.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/parameter/AbilityParameter.java new file mode 100644 index 0000000..7490e41 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/parameter/AbilityParameter.java @@ -0,0 +1,43 @@ +package com.elevatemc.potpvp.ability.parameter; + +import com.elevatemc.elib.command.param.ParameterType; +import com.mysql.jdbc.StringUtils; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.Ability; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class AbilityParameter implements ParameterType { + + @Override + public Ability transform(CommandSender commandSender, String s) { + + final Ability toReturn = PotPvPSI.getInstance().getAbilityHandler().fromName(s); + + if (toReturn == null) { + commandSender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "No ability with the name \"" + s + ChatColor.RED + "\" not found."); + return null; + } + + return toReturn; + } + + @Override + public List tabComplete(Player sender, Set flags, String s) { + List completions = new ArrayList<>(); + + for (Ability ability : PotPvPSI.getInstance().getAbilityHandler().getAbilities().values()) { + String name = ability.getName(); + if(StringUtils.startsWithIgnoreCase(name, s)) { + completions.add(name); + } + } + + return completions; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/AntiBlockup.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/AntiBlockup.java new file mode 100644 index 0000000..038d4ee --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/AntiBlockup.java @@ -0,0 +1,228 @@ +package com.elevatemc.potpvp.ability.type; + +import com.elevatemc.elib.util.TimeUtils; +import com.elevatemc.potpvp.util.Color; +import com.elevatemc.potpvp.util.InventoryUtils; +import com.elevatemc.potpvp.util.PotionUtil; +import com.google.common.collect.ImmutableSet; +import lombok.Getter; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.Ability; +import com.elevatemc.potpvp.ability.listener.events.AbilityUseEvent; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.potion.Potion; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +import static org.bukkit.Material.*; + +public class AntiBlockup extends Ability { + public static final Set NO_INTERACT = ImmutableSet.of(FENCE_GATE, FURNACE, BURNING_FURNACE, BREWING_STAND, CHEST, HOPPER, DISPENSER, WOODEN_DOOR, STONE_BUTTON, WOOD_BUTTON, TRAPPED_CHEST, TRAP_DOOR, LEVER, DROPPER, ENCHANTMENT_TABLE, BED_BLOCK, ANVIL, BEACON); + + @Getter + public static Map cache = new HashMap<>(); + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Material getMaterial() { + return Material.BLAZE_ROD; + } + + @Override + public String getDisplayName() { + return ChatColor.GOLD.toString() + ChatColor.BOLD + "Anti-Blockup"; + } + + @Override + public List getLore() { + final List toReturn = new ArrayList<>(); + + toReturn.add(ChatColor.GRAY + "When you hit a player with this"); + toReturn.add(ChatColor.GRAY + "3 times they may not block up for 15 seconds."); + + return toReturn; + } + + @Override + public long getCooldown() { + return 90_000L; + } + + @EventHandler(priority = EventPriority.MONITOR) + private void onDamage(EntityDamageByEntityEvent event) { + if (event.isCancelled() || !(event.getDamager() instanceof Player) || !(event.getEntity() instanceof Player)) { + return; + } + + final Player target = (Player) event.getEntity(); + final Player damager = (Player) event.getDamager(); + + if (damager.getItemInHand() == null || !this.isSimilar(damager.getItemInHand())) { + return; + } + + final AbilityUseEvent abilityUseEvent = new AbilityUseEvent(damager, target, damager.getLocation(), this, false); + PotPvPSI.getInstance().getServer().getPluginManager().callEvent(abilityUseEvent); + + if (abilityUseEvent.isCancelled()) { + return; + } + + int value = target.hasMetadata("ANTI_BUILD") ? (target.getMetadata("ANTI_BUILD").get(0).asInt() + 1) : 1; + + if (cache.containsKey(target.getUniqueId())) { + damager.sendMessage(Color.translate("&c" + target.getName() + " already can't place blocks for &l" + TimeUtils.formatIntoMMSS((int) (cache.get(target.getUniqueId()) - System.currentTimeMillis()) / 1000) + "&c.")); + return; + } + + target.setMetadata("ANTI_BUILD", new FixedMetadataValue(PotPvPSI.getInstance(), value)); + + if (value != 3) { + damager.sendMessage(Color.translate("&6You have to hit &f" + target.getName() + " &6" + (3 - value) + " more time" + (3 - value == 1 ? "" : "s") + "!")); + return; + } + + target.removeMetadata("ANTI_BUILD", PotPvPSI.getInstance()); + + cache.put(target.getUniqueId(), System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(15)); + + if (damager.getItemInHand().getAmount() == 1) { + damager.setItemInHand(null); + } else { + damager.getItemInHand().setAmount(damager.getItemInHand().getAmount() - 1); + } + + damager.sendMessage(""); + damager.sendMessage(Color.translate("&6You have hit &f" + target.getName() + " &6with the " + this.getDisplayName() + "&6.")); + damager.sendMessage(Color.translate("&6They may not place blocks for &f15 seconds&6.")); + damager.sendMessage(""); + + target.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You have been hit with the " + this.getDisplayName() + ChatColor.RED + " and may not place blocks for 15 seconds."); + + this.applyCooldown(damager); + + PotPvPSI.getInstance().getServer().getScheduler().runTaskLater(PotPvPSI.getInstance(), () -> { + AntiBlockup.cache.remove(target.getUniqueId()); + + target.sendMessage(""); + target.sendMessage(Color.translate("&cThe &f" + this.getDisplayName() + " &chas expired! You may now place blocks!")); + target.sendMessage(""); + }, 20*15); + } + + @EventHandler + public void onPlace(BlockPlaceEvent event) { + if (event.isCancelled()) { + return; + } + + final Player player = event.getPlayer(); + + if (!cache.containsKey(player.getUniqueId())) { + return; + } + + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You may not block up as you have been hit by the " + this.getDisplayName() + ChatColor.RED + "."); + event.setCancelled(true); + } + + @EventHandler + public void onBreak(BlockBreakEvent event) { + if (event.isCancelled()) { + return; + } + + final Player player = event.getPlayer(); + + if (!cache.containsKey(player.getUniqueId())) { + return; + } + + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You may not break blocks as you have been hit by the " + this.getDisplayName() + ChatColor.RED + "."); + + event.setCancelled(true); + } + + @EventHandler + private void onPlate(PlayerInteractEvent event) { + if ((event.getAction() != Action.PHYSICAL || event.getClickedBlock() == null || !event.getClickedBlock().getType().name().contains("PLATE"))) { + return; + } + + final Player player = event.getPlayer(); + + if (!cache.containsKey(player.getUniqueId())) { + return; + } + + if (event.getItem() != null && event.getItem().getType() == POTION && event.getItem().getDurability() != 0) { + Potion potion = Potion.fromItemStack(event.getItem()); + + if (potion.isSplash()) { + PotionUtil.splashPotion(player, event.getItem()); + if (player.getItemInHand() != null && player.getItemInHand().isSimilar(event.getItem())) { + player.setItemInHand(null); + player.updateInventory(); + } else { + InventoryUtils.removeAmountFromInventory(player.getInventory(), event.getItem(), 1); + } + } + } + + event.setCancelled(true); + } + + @EventHandler(priority = EventPriority.LOWEST) + private void onInteract(PlayerInteractEvent event) { + if (event.getAction() != Action.RIGHT_CLICK_BLOCK || event.getClickedBlock() == null) { + return; + } + + final Block clickedBlock = event.getClickedBlock(); + + if (!NO_INTERACT.contains(clickedBlock.getType()) && !clickedBlock.getType().name().contains("SIGN")) { + return; + } + + final Player player = event.getPlayer(); + + if (!cache.containsKey(player.getUniqueId())) { + return; + } + + if (event.getItem() != null && event.getItem().getType() == POTION && event.getItem().getDurability() != 0) { + Potion potion = Potion.fromItemStack(event.getItem()); + + if (potion.isSplash()) { + PotionUtil.splashPotion(player, event.getItem()); + if (player.getItemInHand() != null && player.getItemInHand().isSimilar(event.getItem())) { + player.setItemInHand(null); + player.updateInventory(); + } else { + InventoryUtils.removeAmountFromInventory(player.getInventory(), event.getItem(), 1); + } + } + } + + event.setCancelled(true); + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You may not interact with " + event.getClickedBlock().getType().name().toLowerCase().replace("_", " ") + "s as you have been hit by the " + this.getDisplayName() + ChatColor.RED + "."); + } + + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/AntiDropdown.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/AntiDropdown.java new file mode 100644 index 0000000..b3f89a3 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/AntiDropdown.java @@ -0,0 +1,239 @@ +package com.elevatemc.potpvp.ability.type; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.util.ItemBuilder; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.Ability; +import com.elevatemc.potpvp.ability.listener.events.AbilityUseEvent; +import com.elevatemc.potpvp.util.Color; +import org.bukkit.ChatColor; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +public class AntiDropdown extends Ability { + + public AntiDropdown() { + this.hassanStack = ItemBuilder.copyOf(this.hassanStack).data((byte)1).build(); + } + + public static Map cache = new HashMap<>(); + private List disallowedMaterial = Arrays.asList( + Material.CHEST, Material.TRAPPED_CHEST, Material.ENDER_CHEST, Material.SIGN, Material.SIGN_POST, Material.HOPPER, + Material.BEDROCK, Material.ENCHANTMENT_TABLE, Material.AIR, Material.DROPPER, Material.DISPENSER + ); + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Material getMaterial() { + return Material.INK_SACK; + } + + @Override + public String getDisplayName() { + return ChatColor.RED.toString() + ChatColor.BOLD + "Anti-Dropdown"; + } + + @Override + public List getLore() { + final List toReturn = new ArrayList<>(); + + toReturn.add(ChatColor.GRAY + "Hit a player to activate and for"); + toReturn.add(ChatColor.GRAY + "the next 15 seconds all blocks under"); + toReturn.add(ChatColor.GRAY + "them will be replaced with red glass"); + + return toReturn; + } + + @Override + public long getCooldown() { + return 90_000L; + } + + + @EventHandler(priority = EventPriority.MONITOR) + private void onDamage(EntityDamageByEntityEvent event) { + if (event.isCancelled() || !(event.getDamager() instanceof Player) || !(event.getEntity() instanceof Player)) { + return; + } + + final Player target = (Player) event.getEntity(); + final Player damager = (Player) event.getDamager(); + + if (damager.getItemInHand() == null || !this.isSimilar(damager.getItemInHand())) { + return; + } + + final AbilityUseEvent abilityUseEvent = new AbilityUseEvent(damager, target, damager.getLocation(), this, false); + PotPvPSI.getInstance().getServer().getPluginManager().callEvent(abilityUseEvent); + + if (abilityUseEvent.isCancelled()) { + return; + } + + int value = target.hasMetadata("DROPDOWN_COUNT") ? (target.getMetadata("DROPDOWN_COUNT").get(0).asInt() + 1) : 1; + + if (target.hasMetadata("ANTI_DROPDOWN")) { + damager.sendMessage(Color.translate("&c" + target.getName() + " already has " + this.getDisplayName() + " activated!")); + return; + } + + if (eLib.getInstance().getAutoRebootHandler().isRebooting() && eLib.getInstance().getAutoRebootHandler().getRebootSecondsRemaining() <= TimeUnit.MINUTES.toSeconds(1)) { + damager.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You may not use this item whilst the server is rebooting!"); + return; + } + + target.setMetadata("DROPDOWN_COUNT", new FixedMetadataValue(PotPvPSI.getInstance(), value)); + + if (value != 3) { + damager.sendMessage(Color.translate("&6You have to hit &f" + target.getName() + " &6" + (3 - value) + " more time" + (3 - value == 1 ? "" : "s") + "!")); + return; + } + + target.removeMetadata("DROPDOWN_COUNT", PotPvPSI.getInstance()); + target.setMetadata("ANTI_DROPDOWN", new FixedMetadataValue(PotPvPSI.getInstance(), value)); + + if (damager.getItemInHand().getAmount() == 1) { + damager.setItemInHand(null); + } else { + damager.getItemInHand().setAmount(damager.getItemInHand().getAmount() - 1); + } + + damager.sendMessage(""); + damager.sendMessage(Color.translate("&6You have hit &f" + target.getName() + " &6with the " + this.getDisplayName() + "&6.")); + damager.sendMessage(Color.translate("&7Every time they walk over air or water it will be replaced with Red Stained Glass!")); + damager.sendMessage(""); + + target.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You have been hit with the " + this.getDisplayName() + ChatColor.RED + "!"); + + PotPvPSI.getInstance().getServer().getScheduler().runTaskLater(PotPvPSI.getInstance(), () -> { + if (target.isOnline()) { + target.removeMetadata("ANTI_DROPDOWN", PotPvPSI.getInstance()); + } + }, 20 * 15); + + this.applyCooldown(damager); + } + + @EventHandler(priority = EventPriority.LOWEST) + private void onQuit(PlayerQuitEvent event) { + final Player player = event.getPlayer(); + + if (player.hasMetadata("ANTI_DROPDOWN")) { + player.removeMetadata("ANTI_DROPDOWN", PotPvPSI.getInstance()); + } + } + + @EventHandler(priority = EventPriority.LOWEST) + private void onJoin(PlayerJoinEvent event) { + final Player player = event.getPlayer(); + + if (player.hasMetadata("ANTI_DROPDOWN")) { + player.removeMetadata("ANTI_DROPDOWN", PotPvPSI.getInstance()); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + private void onMove(PlayerMoveEvent event) { + if (event.getFrom().getBlockX() == event.getTo().getBlockX() && event.getFrom().getBlockY() == event.getTo().getBlockY() && event.getFrom().getBlockZ() == event.getTo().getBlockZ()) { + return; + } + + final Player player = event.getPlayer(); + + if (event.isCancelled() || !player.hasMetadata("ANTI_DROPDOWN")) { + return; + } + + if (!PotPvPSI.getInstance().getMatchHandler().isPlayingMatch(player)) { + return; + } + + final Block block = event.getTo().getBlock(); + final Block firstBlock = block.getRelative(BlockFace.DOWN); + + if (firstBlock.getType() != Material.AIR) { + return; + } + + final Block secondBlock = firstBlock.getRelative(BlockFace.DOWN); + + if (secondBlock.getType() != Material.AIR) { + return; + } + + final Block thirdBlock = secondBlock.getRelative(BlockFace.DOWN); + + if (thirdBlock.getType() != Material.AIR) { + return; + } + + setStabilizingShock(firstBlock); + } + + public boolean isStabilizingShock(Block block) { + return block.hasMetadata("ANTI_DROPDOWN") && block.getType() == Material.STAINED_GLASS && block.getData() == 14; + } + + public void setStabilizingShock(Block block) { + final Material type = block.getType(); + + if (this.isStabilizingShock(block)) { + return; + } + + cache.put(block.getLocation(), type); + + block.setType(Material.STAINED_GLASS); + block.setData((byte)14); + block.setMetadata("ANTI_DROPDOWN", new FixedMetadataValue(PotPvPSI.getInstance(), true)); + + new BukkitRunnable() { + @Override + public void run() { + final Material oldType = cache.remove(block.getLocation()); + + block.removeMetadata("ANTI_DROPDOWN", PotPvPSI.getInstance()); + + if (!block.getType().equals(oldType)) { + block.setType(oldType); + } + } + }.runTaskLater(PotPvPSI.getInstance(), 20 * 5); + } + + @EventHandler + public void onBreak(BlockBreakEvent event) { + final Block block = event.getBlock(); + + if (event.isCancelled()) { + return; + } + + final Player player = event.getPlayer(); + + if (this.isStabilizingShock(block) && player.getGameMode() != GameMode.CREATIVE) { + event.setCancelled(true); + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You may not break a " + this.getDisplayName() + ChatColor.RED + " block!"); + } + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/AntiTrap.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/AntiTrap.java new file mode 100644 index 0000000..685865e --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/AntiTrap.java @@ -0,0 +1,325 @@ +package com.elevatemc.potpvp.ability.type; + +import com.elevatemc.elib.eLib; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.Ability; +import com.elevatemc.potpvp.ability.listener.events.AbilityUseEvent; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.util.InventoryUtils; +import com.elevatemc.potpvp.util.PotionUtil; +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.potion.Potion; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +import static org.bukkit.Material.GOLD_BLOCK; +import static org.bukkit.Material.POTION; + +public class AntiTrap extends Ability { + + public static Map cache = new HashMap<>(); + private List disallowedMaterial = Arrays.asList( + Material.CHEST, Material.TRAPPED_CHEST, Material.ENDER_CHEST, Material.SIGN, Material.SIGN_POST, Material.HOPPER, + Material.BEDROCK, Material.ENCHANTMENT_TABLE, Material.AIR, Material.DROPPER, Material.DISPENSER); + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Material getMaterial() { + return Material.BLAZE_ROD; + } + + @Override + public String getDisplayName() { + return ChatColor.GOLD.toString() + ChatColor.BOLD + "Anti-Trap Rod"; + } + + @Override + public List getLore() { + final List toReturn = new ArrayList<>(); + + toReturn.add(ChatColor.GRAY + "Right click to activate and for 15 seconds"); + toReturn.add(ChatColor.GRAY + "a 3x3 of gold blocks appear under you."); + toReturn.add(ChatColor.GRAY + "You cannot break/place/interact with."); + toReturn.add(ChatColor.GRAY + "blocks on top of these gold blocks."); + return toReturn; + } + + @Override + public long getCooldown() { + return 90_000L; + } + + @EventHandler(priority = EventPriority.LOWEST) + private void onInteract(PlayerInteractEvent event) { + final Player player = event.getPlayer(); + + if (!this.isSimilar(player.getItemInHand()) || event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; + } + + event.setCancelled(true); + + final AbilityUseEvent abilityUseEvent = new AbilityUseEvent(player, null, player.getLocation(), this, false); + PotPvPSI.getInstance().getServer().getPluginManager().callEvent(abilityUseEvent); + + if (abilityUseEvent.isCancelled()) { + return; + } + + if (eLib.getInstance().getAutoRebootHandler().isRebooting() && eLib.getInstance().getAutoRebootHandler().getRebootSecondsRemaining() <= TimeUnit.MINUTES.toSeconds(1)) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You may not use this item whilst the server is rebooting!"); + return; + } + + event.setCancelled(true); + + if (player.getItemInHand().getAmount() == 1) { + player.setItemInHand(null); + } else { + player.getItemInHand().setAmount(player.getItemInHand().getAmount() - 1); + } + + player.updateInventory(); + + player.setMetadata("ANTI_TRAP", new FixedMetadataValue(PotPvPSI.getInstance(), true)); + + PotPvPSI.getInstance().getServer().getScheduler().runTaskLater(PotPvPSI.getInstance(), () -> { + if (player.isOnline()) { + player.removeMetadata("ANTI_TRAP", PotPvPSI.getInstance()); + } + }, 20 * 12); + + this.applyCooldown(player); + } + + @EventHandler(priority = EventPriority.LOWEST) + private void onQuit(PlayerQuitEvent event) { + final Player player = event.getPlayer(); + + if (player.hasMetadata("ANTI_TRAP")) { + player.removeMetadata("ANTI_TRAP", PotPvPSI.getInstance()); + } + } + + @EventHandler(priority = EventPriority.LOWEST) + private void onJoin(PlayerJoinEvent event) { + final Player player = event.getPlayer(); + + if (player.hasMetadata("ANTI_TRAP")) { + player.removeMetadata("ANTI_TRAP", PotPvPSI.getInstance()); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + private void onMove(PlayerMoveEvent event) { + if (event.getFrom().getBlockX() == event.getTo().getBlockX() && event.getFrom().getBlockY() == event.getTo().getBlockY() && event.getFrom().getBlockZ() == event.getTo().getBlockZ()) { + return; + } + + final Player player = event.getPlayer(); + + if (event.isCancelled() || !player.hasMetadata("ANTI_TRAP")) { + return; + } + + final Block firstBlock = event.getTo().getBlock().getRelative(BlockFace.DOWN); + + if (firstBlock.getType() == Material.AIR) { + return; + } + + final Match match = PotPvPSI.getInstance().getMatchHandler().getMatchPlaying(player); + + if (match == null) { + return; + } + + final Block secondBlock = firstBlock.getRelative(BlockFace.SOUTH); + final Block thirdBlock = firstBlock.getRelative(BlockFace.WEST); + final Block fourthBlock = thirdBlock.getRelative(BlockFace.SOUTH); + final Block fifthBlock = firstBlock.getRelative(BlockFace.NORTH); + final Block sixthBlock = fifthBlock.getRelative(BlockFace.WEST); + final Block seventhBlock = fifthBlock.getRelative(BlockFace.EAST); + final Block eighthBlock = secondBlock.getRelative(BlockFace.EAST); + final Block ninthBlock = seventhBlock.getRelative(BlockFace.SOUTH); + + setAntiTrapBlock(firstBlock, player, match); + setAntiTrapBlock(secondBlock, player, match); + setAntiTrapBlock(thirdBlock, player, match); + setAntiTrapBlock(fourthBlock, player, match); + setAntiTrapBlock(fifthBlock, player, match); + setAntiTrapBlock(sixthBlock, player, match); + setAntiTrapBlock(seventhBlock, player, match); + setAntiTrapBlock(eighthBlock, player, match); + setAntiTrapBlock(ninthBlock, player, match); + } + + public boolean isAntiTrapBlock(Block block) { + return block.hasMetadata("ANTI_TRAP") && block.getType() == GOLD_BLOCK; + } + + public void setAntiTrapBlock(Block block, Player player, Match match) { + + final Material type = block.getType(); + + if (type == Material.GOLD_BLOCK && block.hasMetadata("ANTI_TRAP")) { + return; + } + + if (!type.isSolid() || this.disallowedMaterial.contains(type)) { + return; + } + + if (!PotPvPSI.getInstance().getMatchHandler().isPlayingMatch(player)) { + return; + } + + if (!match.get_id().equalsIgnoreCase(PotPvPSI.getInstance().getMatchHandler().getMatchPlaying(player).get_id())) { + return; + } + + cache.put(block.getLocation(), type); + + block.setType(Material.GOLD_BLOCK); + block.setMetadata("ANTI_TRAP", new FixedMetadataValue(PotPvPSI.getInstance(), true)); + + new BukkitRunnable() { + @Override + public void run() { + if (!player.isOnline()) { + return; + } + + if (!PotPvPSI.getInstance().getMatchHandler().isPlayingMatch(player)) { + return; + } + + if (!match.get_id().equalsIgnoreCase(PotPvPSI.getInstance().getMatchHandler().getMatchPlaying(player).get_id())) { + return; + } + + final Material oldType = cache.remove(block.getLocation()); + + block.removeMetadata("ANTI_TRAP", PotPvPSI.getInstance()); + + if (!block.getType().equals(oldType)) { + block.setType(oldType); + } + } + }.runTaskLater(PotPvPSI.getInstance(), 20 * 5); + } + + @EventHandler + public void onPlace(BlockPlaceEvent event) { + if (event.isCancelled()) { + return; + } + + final Player player = event.getPlayer(); + + final Block downBlock = event.getBlockPlaced().getRelative(BlockFace.DOWN); + final Block downTwoBlock = downBlock.getRelative(BlockFace.DOWN); + + if (!this.isAntiTrapBlock(downBlock) && !this.isAntiTrapBlock(downTwoBlock)) { + return; + } + + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You may not place blocks on top of a " + ChatColor.GOLD + ChatColor.BOLD.toString() + "Anti-Trap Rod" + ChatColor.RED + "."); + event.setCancelled(true); + } + + @EventHandler + public void onBreak(BlockBreakEvent event) { + final Block block = event.getBlock(); + + if (event.isCancelled()) { + return; + } + + final Player player = event.getPlayer(); + + if (this.isAntiTrapBlock(block) && player.getGameMode() != GameMode.CREATIVE) { + event.setCancelled(true); + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You may not break a anti-trap rod block!"); + return; + } + + final Block downBlock = block.getRelative(BlockFace.DOWN); + final Block downTwoBlock = downBlock.getRelative(BlockFace.DOWN); + + if (this.isAntiTrapBlock(downBlock) || this.isAntiTrapBlock(downTwoBlock)) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You may not break blocks as there is a " + ChatColor.GOLD + ChatColor.BOLD.toString() + "Anti-Drop Rod" + ChatColor.RED + " block below."); + event.setCancelled(true); + } + } + + @EventHandler + private void onPlate(PlayerInteractEvent event) { + final Block clickedBlock = event.getClickedBlock(); + + if ((event.getAction() != Action.PHYSICAL || clickedBlock == null || !clickedBlock.getType().name().contains("PLATE"))) { + return; + } + + final Block downBlock = event.getClickedBlock().getRelative(BlockFace.DOWN); + + if (!this.isAntiTrapBlock(downBlock)) { + return; + } + + event.setCancelled(true); + } + + @EventHandler + private void onClick(PlayerInteractEvent event) { + if ((event.getAction() != Action.RIGHT_CLICK_BLOCK || event.getClickedBlock() == null || !AntiBlockup.NO_INTERACT.contains(event.getClickedBlock().getType()))) { + return; + } + + final Player player = event.getPlayer(); + + final Block downBlock = event.getClickedBlock().getRelative(BlockFace.DOWN); + final Block downTwoBlock = downBlock.getRelative(BlockFace.DOWN); + final Block downThreeBlock = downTwoBlock.getRelative(BlockFace.DOWN); + + if (!this.isAntiTrapBlock(downBlock) && !this.isAntiTrapBlock(downTwoBlock) && !this.isAntiTrapBlock(downThreeBlock)) { + return; + } + + if (event.getItem() != null && event.getItem().getType() == POTION && event.getItem().getDurability() != 0) { + Potion potion = Potion.fromItemStack(event.getItem()); + + if (potion.isSplash()) { + PotionUtil.splashPotion(player, event.getItem()); + if (player.getItemInHand() != null && player.getItemInHand().isSimilar(event.getItem())) { + player.setItemInHand(null); + player.updateInventory(); + } else { + InventoryUtils.removeAmountFromInventory(player.getInventory(), event.getItem(), 1); + } + } + } + + event.setCancelled(true); + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You may not open or close " + event.getClickedBlock().getType().name().toLowerCase().replace("_", " ") + "s as there is a " + ChatColor.GOLD + ChatColor.BOLD.toString() + "Anti-Trap Rod" + ChatColor.RED + " under it."); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/BallOfRage.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/BallOfRage.java new file mode 100644 index 0000000..5b09fad --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/BallOfRage.java @@ -0,0 +1,115 @@ +package com.elevatemc.potpvp.ability.type; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.Ability; +import com.elevatemc.potpvp.util.Color; +import org.bukkit.*; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.entity.Snowball; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.event.entity.ProjectileLaunchEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.ArrayList; +import java.util.List; + +public class BallOfRage extends Ability { + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Material getMaterial() { + return Material.SNOW_BALL; + } + + @Override + public String getDisplayName() { + return ChatColor.DARK_RED.toString() + ChatColor.BOLD + "Ball Of Rage"; + } + + @Override + public List getLore() { + + final List toReturn = new ArrayList<>(); + + toReturn.add(ChatColor.GRAY + "Throw to create a cloud of effects"); + toReturn.add(ChatColor.GRAY + "where all teammates within 5 block"); + toReturn.add(ChatColor.GRAY + "radius will be given Strength II and"); + toReturn.add(ChatColor.GRAY + "Resistance III for 6 seconds."); + + return toReturn; + } + + @Override + public long getCooldown() { + return 120_000L; + } + + @EventHandler + public void onLaunch(ProjectileLaunchEvent event) { + if (!(event.getEntity() instanceof Snowball) || !(event.getEntity().getShooter() instanceof Player)) { + return; + } + + final Player player = (Player) event.getEntity().getShooter(); + + if (!this.isSimilar(player.getItemInHand())) { + return; + } + + event.getEntity().setMetadata("BALL_OF_RAGE", new FixedMetadataValue(PotPvPSI.getInstance(), player.getUniqueId().toString())); + + this.applyCooldown(player); + } + + @EventHandler(priority = EventPriority.NORMAL) + public void onInteract(PlayerInteractEvent event) { + if (!event.hasItem() || !this.isSimilar(event.getPlayer().getItemInHand())) { + return; + } + + if (this.hasCooldown(event.getPlayer())) { + event.setCancelled(true); + event.getPlayer().updateInventory(); + } + } + + @EventHandler + private void onLand(ProjectileHitEvent event) { + if (!(event.getEntity() instanceof Snowball) || !(event.getEntity().getShooter() instanceof Player) || !event.getEntity().hasMetadata("BALL_OF_RAGE")) { + return; + } + + final Projectile snowBall = event.getEntity(); + final Player player = (Player) snowBall.getShooter(); + + snowBall.getWorld().createExplosion(snowBall.getLocation(), 0); + snowBall.getWorld().spigot().playEffect( + snowBall.getLocation().clone().add(0, 1, 0), + Effect.EXPLOSION_HUGE + ); + + snowBall.getNearbyEntities(5, 5, 5).stream().filter(it -> it instanceof Player).map(it -> (Player) it).forEach(it -> { + + if (!it.getName().equalsIgnoreCase(player.getName())) { + return; + } + + it.addPotionEffect(new PotionEffect(PotionEffectType.INCREASE_DAMAGE, 20*6, 1), true); + it.addPotionEffect(new PotionEffect(PotionEffectType.DAMAGE_RESISTANCE, 20*6, 2), true); + + it.sendMessage(""); + it.sendMessage(Color.translate("&6You have been hit by &f" + player.getName() + "'s " + this.getDisplayName() + " &6and have been given &fStrength 2 and Resistance 3 &6for 6 seconds!")); + it.sendMessage(""); + }); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/Combo.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/Combo.java new file mode 100644 index 0000000..8a99373 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/Combo.java @@ -0,0 +1,125 @@ +package com.elevatemc.potpvp.ability.type; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.Ability; +import com.elevatemc.potpvp.ability.listener.events.AbilityUseEvent; +import org.bukkit.*; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.*; + +public class Combo extends Ability { + + public static Map cache = new HashMap<>(); + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Material getMaterial() { + return Material.RED_ROSE; + } + + @Override + public String getDisplayName() { + return ChatColor.DARK_RED.toString() + ChatColor.BOLD + "Combo"; + } + + @Override + public List getLore() { + final List toReturn = new ArrayList<>(); + + toReturn.add(ChatColor.GRAY + "Get a second of strength II for the"); + toReturn.add(ChatColor.GRAY + "amount of hits dealt within 10 seconds."); + + return toReturn; + } + + @Override + public long getCooldown() { + return 90_000L; + } + + @EventHandler(priority = EventPriority.LOWEST) + private void onInteract(PlayerInteractEvent event) { + final Player player = event.getPlayer(); + + if (!this.isSimilar(player.getItemInHand()) || event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; + } + + event.setCancelled(true); + + final AbilityUseEvent abilityUseEvent = new AbilityUseEvent(player, null, player.getLocation(), this, false); + PotPvPSI.getInstance().getServer().getPluginManager().callEvent(abilityUseEvent); + + if (abilityUseEvent.isCancelled()) { + return; + } + + event.setCancelled(true); + if (player.getItemInHand().getAmount() == 1) { + player.setItemInHand(null); + } else { + player.getItemInHand().setAmount(player.getItemInHand().getAmount()-1); + } + player.updateInventory(); + + cache.put(player.getUniqueId(), 0); + + player.sendMessage(""); + player.sendMessage(ChatColor.GOLD + "Successfully used the " + this.getDisplayName() + ChatColor.GOLD + " item."); + player.sendMessage(ChatColor.GRAY + "For the next 10 seconds, every hit you deal will get you a second of Strength 2."); + player.sendMessage(""); + + this.applyCooldown(event.getPlayer()); + + PotPvPSI.getInstance().getServer().getScheduler().runTaskLater(PotPvPSI.getInstance(), () -> { + + int seconds = cache.remove(player.getUniqueId()); + + if (!player.isOnline()) { + return; + } + + player.playSound(player.getLocation(), Sound.EXPLODE, 1, 1); + player.addPotionEffect(new PotionEffect(PotionEffectType.INCREASE_DAMAGE, 20*seconds, 1), true); + + player.sendMessage(""); + player.sendMessage(this.getDisplayName() + ChatColor.GOLD + " has been activated. You now have Strength 2 for " + seconds + " seconds."); + player.sendMessage(""); + }, 20*10L); + } + + @EventHandler(priority = EventPriority.MONITOR) + private void onDamage(EntityDamageByEntityEvent event) { + if (event.isCancelled() || !(event.getEntity() instanceof Player) || !(event.getDamager() instanceof Player)) { + return; + } + + final Player damager = (Player) event.getDamager(); + + if (!cache.containsKey(damager.getUniqueId())) { + return; + } + + int amount = cache.getOrDefault(damager.getUniqueId(), 0)+1; + + if (amount > 10) { + return; + } + + damager.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Found another hit, you will get " + amount + " seconds of Strength II."); + + cache.put(damager.getUniqueId(), amount); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/CraftingChaos.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/CraftingChaos.java new file mode 100644 index 0000000..035ad0b --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/CraftingChaos.java @@ -0,0 +1,151 @@ +package com.elevatemc.potpvp.ability.type; + +import com.elevatemc.elib.util.TimeUtils; +import com.elevatemc.potpvp.util.Color; +import lombok.Getter; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.Ability; +import com.elevatemc.potpvp.ability.listener.events.AbilityUseEvent; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.metadata.FixedMetadataValue; + +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; + +public class CraftingChaos extends Ability { + + @Getter + public static Map cache = new HashMap<>(); + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Material getMaterial() { + return Material.WORKBENCH; + } + + @Override + public String getDisplayName() { + return ChatColor.GREEN.toString() + ChatColor.BOLD + "Crafting Chaos"; + } + + @Override + public List getLore() { + final List toReturn = new ArrayList<>(); + + toReturn.add(ChatColor.GRAY + "Hit an enemy and for 10 seconds, every"); + toReturn.add(ChatColor.GRAY + "hit you deal has a 10% chance of"); + toReturn.add(ChatColor.GRAY + "putting them in a crafting table."); + + return toReturn; + } + + @Override + public long getCooldown() { + return 90_000L; + } + + @EventHandler(priority = EventPriority.MONITOR) + private void onDamage(EntityDamageByEntityEvent event) { + if (event.isCancelled() || !(event.getDamager() instanceof Player) || !(event.getEntity() instanceof Player)) { + return; + } + + final Player target = (Player) event.getEntity(); + final Player damager = (Player) event.getDamager(); + + if (damager.getItemInHand() == null || !this.isSimilar(damager.getItemInHand())) { + return; + } + + final AbilityUseEvent abilityUseEvent = new AbilityUseEvent(damager, target, damager.getLocation(), this, false); + PotPvPSI.getInstance().getServer().getPluginManager().callEvent(abilityUseEvent); + + if (abilityUseEvent.isCancelled()) { + return; + } + + int value = target.hasMetadata("CHAOS") ? (target.getMetadata("CHAOS").get(0).asInt()+1) : 1; + + if (cache.containsKey(target.getUniqueId())) { + damager.sendMessage(Color.translate("&c" + target.getName() + " is already in the crafting chaos for &l" + TimeUtils.formatIntoMMSS((int) (cache.get(target.getUniqueId())-System.currentTimeMillis())/1000) + "&c.")); + return; + } + + target.setMetadata("CHAOS", new FixedMetadataValue(PotPvPSI.getInstance(), value)); + + if (value != 3) { + damager.sendMessage(Color.translate("&6You have to hit &f" + target.getName() + " &6" + (3 - value) + " more times!")); + return; + } + + target.removeMetadata("CHAOS", PotPvPSI.getInstance()); + + cache.put(target.getUniqueId(), System.currentTimeMillis()+TimeUnit.SECONDS.toMillis(10)); + + if (damager.getItemInHand().getAmount() == 1) { + damager.setItemInHand(null); + } else { + damager.getItemInHand().setAmount(damager.getItemInHand().getAmount()-1); + } + + damager.sendMessage(""); + damager.sendMessage(Color.translate("&6You have hit &f" + target.getName() + " &6with " + this.getDisplayName() + "&6.")); + damager.sendMessage(Color.translate("&7For the next 10 seconds, every hit you deal has a 10% chance of putting them in a crafting table.")); + damager.sendMessage(""); + + target.sendMessage(""); + target.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You have been hit with the " + this.getDisplayName() + ChatColor.RED + "."); + target.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "For the next 10 seconds, every hit you take has a 10% chance of putting you in a crafting table."); + target.sendMessage(""); + + this.applyCooldown(damager); + } + + @EventHandler(priority = EventPriority.LOW) + private void onPlayerHit(EntityDamageByEntityEvent event) { + if (!(event.getDamager() instanceof Player) || !(event.getEntity() instanceof Player)) { + return; + } + + final Player target = (Player) event.getEntity(); + final Player damager = (Player) event.getDamager(); + + if (!cache.containsKey(target.getUniqueId()) || cache.get(target.getUniqueId()) < System.currentTimeMillis()) { + cache.remove(target.getUniqueId()); + return; + } + + if (ThreadLocalRandom.current().nextInt(100) <= 10) { + damager.playSound(damager.getLocation(), Sound.LEVEL_UP, 1, 1); + damager.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + target.getName() + " has been put in a crafting table due to the " + this.getDisplayName() + "!"); + + target.playSound(target.getLocation(), Sound.ZOMBIE_WOODBREAK, 1, 1); + target.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You were placed in a crafting table due to the " + this.getDisplayName() + ChatColor.RED + "!"); + target.openWorkbench(target.getLocation(), true); + } + } + + @EventHandler(priority = EventPriority.LOW) + private void onPlace(BlockPlaceEvent event) { + final Player player = event.getPlayer(); + + if (this.isSimilar(event.getItemInHand())) { + event.setCancelled(true); + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You can't place partner items!"); + } + + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/Debuff.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/Debuff.java new file mode 100644 index 0000000..d754aa9 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/Debuff.java @@ -0,0 +1,116 @@ +package com.elevatemc.potpvp.ability.type; + +import com.elevatemc.elib.util.ItemBuilder; +import com.elevatemc.potpvp.util.Color; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.Ability; +import com.elevatemc.potpvp.ability.listener.events.AbilityUseEvent; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class Debuff extends Ability { + + public Debuff() { + this.hassanStack = ItemBuilder.of(Material.RAW_FISH) + .name(this.getDisplayName()) + .data((byte) 3) + .setLore(this.getLore()).build(); + } + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Material getMaterial() { + return Material.RAW_FISH; + } + + @Override + public String getDisplayName() { + return ChatColor.RED.toString() + ChatColor.BOLD + "Debuff Fish"; + } + + @Override + public List getLore() { + final List toReturn = new ArrayList<>(); + + toReturn.add(ChatColor.GRAY + "Hit a player 3 times to double"); + toReturn.add(Color.translate("&7debuff them for 20 seconds!")); + + return toReturn; + } + + + @Override + public long getCooldown() { + return 90_000L; + } + + @EventHandler(priority = EventPriority.MONITOR) + private void onDamage(EntityDamageByEntityEvent event) { + if (event.isCancelled() || !(event.getDamager() instanceof Player) || !(event.getEntity() instanceof Player)) { + return; + } + + final Player target = (Player) event.getEntity(); + final Player damager = (Player) event.getDamager(); + + if (damager.getItemInHand() == null || !this.isSimilar(damager.getItemInHand())) { + return; + } + + final AbilityUseEvent abilityUseEvent = new AbilityUseEvent(damager, target, damager.getLocation(), this, false); + PotPvPSI.getInstance().getServer().getPluginManager().callEvent(abilityUseEvent); + + if (abilityUseEvent.isCancelled()) { + return; + } + + int value = target.hasMetadata("DEBUFF_FISH") ? (target.getMetadata("DEBUFF_FISH").get(0).asInt()+1) : 1; + + target.setMetadata("DEBUFF_FISH", new FixedMetadataValue(PotPvPSI.getInstance(), value)); + + if (value != 3) { + damager.sendMessage(Color.translate("&6You have to hit &f" + target.getName() + " &6" + (3 - value) + " more time" + (3-value == 1 ? "" : "s") + "!")); + return; + } + + target.removeMetadata("DEBUFF_FISH", PotPvPSI.getInstance()); + + if (damager.getItemInHand().getAmount() == 1) { + damager.setItemInHand(null); + } else { + damager.getItemInHand().setAmount(damager.getItemInHand().getAmount()-1); + } + + damager.sendMessage(""); + damager.sendMessage(Color.translate("&cYou have hit &f" + target.getName() + " &cwith the " + this.getDisplayName() + "&c.")); + damager.sendMessage(Color.translate("&7They have been double debuffed.")); + damager.sendMessage(""); + + target.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You have been hit with the " + this.getDisplayName() + ChatColor.RED + " and have been double debuffed."); + + int duration = 20; + Arrays.asList( + new PotionEffect(PotionEffectType.POISON, duration * 20, 0), + new PotionEffect(PotionEffectType.SLOW, duration * 20, 0) + ).forEach(target::addPotionEffect); + + this.applyCooldown(damager); + + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/EggPort.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/EggPort.java new file mode 100644 index 0000000..5689a97 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/EggPort.java @@ -0,0 +1,129 @@ +package com.elevatemc.potpvp.ability.type; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.Ability; +import com.elevatemc.potpvp.ability.listener.events.AbilityUseEvent; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Egg; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.ProjectileLaunchEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.metadata.FixedMetadataValue; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class EggPort extends Ability { + + public static final int SWAP_RADIUS = 15; + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Material getMaterial() { + return Material.EGG; + } + + @Override + public String getDisplayName() { + return ChatColor.LIGHT_PURPLE.toString() + ChatColor.BOLD + "Eggport"; + } + + @Override + public List getLore() { + + final List toReturn = new ArrayList<>(); + + toReturn.add(ChatColor.GRAY + "Switch places with your"); + toReturn.add(ChatColor.GRAY + "enemy that are within 15 blocks!"); + + return toReturn; + } + + @Override + public long getCooldown() { + return 10_000L; + } + + @EventHandler + public void onLaunch(ProjectileLaunchEvent event) { + if (!(event.getEntity() instanceof Egg) || !(event.getEntity().getShooter() instanceof Player)) { + return; + } + + final Player player = (Player) event.getEntity().getShooter(); + + if (!this.isSimilar(player.getItemInHand())) { + return; + } + + event.getEntity().setMetadata("Eggport", new FixedMetadataValue(PotPvPSI.getInstance(), player.getUniqueId().toString())); + + this.applyCooldown(player); + } + + @EventHandler(priority = EventPriority.NORMAL) + public void onInteract(PlayerInteractEvent event) { + if (!event.hasItem() || !this.isSimilar(event.getPlayer().getItemInHand())) { + return; + } + + final Player player = event.getPlayer(); + + final AbilityUseEvent abilityUseEvent = new AbilityUseEvent(player, null, player.getLocation(), this, false); + PotPvPSI.getInstance().getServer().getPluginManager().callEvent(abilityUseEvent); + + if (abilityUseEvent.isCancelled()) { + event.setCancelled(true); + player.updateInventory(); + } + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onCreature(CreatureSpawnEvent event) { + if (event.getSpawnReason() == CreatureSpawnEvent.SpawnReason.EGG) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.NORMAL) + private void onDamage(EntityDamageByEntityEvent event) { + if (event.isCancelled() || !(event.getEntity() instanceof Player) || !(event.getDamager() instanceof Egg) || !event.getDamager().hasMetadata("Eggport")) { + return; + } + + final Player shooter = PotPvPSI.getInstance().getServer().getPlayer(UUID.fromString(event.getDamager().getMetadata("Eggport").get(0).asString())); + final Player target = (Player) event.getEntity(); + + if (shooter == null) { + return; + } + + final Location shooterLocation = shooter.getLocation().clone(); + final Location targetLocation = target.getLocation().clone(); + + if (shooterLocation.distance(targetLocation) > SWAP_RADIUS) { + shooter.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You need to be within 15 blocks of that player!"); + return; + } + + shooter.teleport(targetLocation); + target.teleport(shooterLocation); + + shooter.sendMessage(ChatColor.GREEN + "Poof! You hit " + target.getName() + " with your Eggport."); + target.sendMessage(ChatColor.GREEN + "Poof! You were hit by a Eggport!"); + + this.removeCooldown(shooter); + this.applyCooldown(shooter, 20_000L); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/FocusMode.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/FocusMode.java new file mode 100644 index 0000000..24de61c --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/FocusMode.java @@ -0,0 +1,119 @@ +package com.elevatemc.potpvp.ability.type; + +import com.elevatemc.potpvp.util.Color; +import lombok.Getter; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.Ability; +import com.elevatemc.potpvp.ability.listener.events.AbilityUseEvent; +import org.bukkit.*; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.metadata.FixedMetadataValue; + +import java.util.*; + +public class FocusMode extends Ability { + + @Getter + public static Map cache = new HashMap<>(); + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Material getMaterial() { + return Material.LEVER; + } + + @Override + public String getDisplayName() { + return ChatColor.AQUA.toString() + ChatColor.BOLD + "Focus Mode"; + } + + @Override + public List getLore() { + final List toReturn = new ArrayList<>(); + + toReturn.add(ChatColor.GRAY + "When you hit a player with this 3 times"); + toReturn.add(ChatColor.GRAY + "you will now deal 20% more damage towards them."); + return toReturn; + } + + @Override + public long getCooldown() { + return 90_000L; + } + + @EventHandler(priority = EventPriority.MONITOR) + private void onDamage(EntityDamageByEntityEvent event) { + if (event.isCancelled() || !(event.getDamager() instanceof Player) || !(event.getEntity() instanceof Player)) { + return; + } + + final Player target = (Player) event.getEntity(); + final Player damager = (Player) event.getDamager(); + + if (damager.getItemInHand() == null || !this.isSimilar(damager.getItemInHand())) { + return; + } + + final AbilityUseEvent abilityUseEvent = new AbilityUseEvent(damager, target, damager.getLocation(), this, false); + PotPvPSI.getInstance().getServer().getPluginManager().callEvent(abilityUseEvent); + + if (abilityUseEvent.isCancelled()) { + return; + } + + int value = target.hasMetadata("FOCUS_MODE") ? (target.getMetadata("FOCUS_MODE").get(0).asInt() + 1) : 1; + + target.setMetadata("FOCUS_MODE", new FixedMetadataValue(PotPvPSI.getInstance(), value)); + + if (value != 3) { + damager.sendMessage(Color.translate("&6You have to hit &f" + target.getName() + " &6" + (3 - value) + " more time" + (3 - value == 1 ? "" : "s") + "!")); + return; + } + + target.removeMetadata("FOCUS_MODE", PotPvPSI.getInstance()); + + cache.put(target.getUniqueId(), damager.getUniqueId()); + + if (damager.getItemInHand().getAmount() == 1) { + damager.setItemInHand(null); + } else { + damager.getItemInHand().setAmount(damager.getItemInHand().getAmount() - 1); + } + + damager.playSound(damager.getLocation(), Sound.LEVEL_UP, 1, 1); + damager.sendMessage(""); + damager.sendMessage(Color.translate("&6You have hit &f" + target.getName() + " &6with the " + this.getDisplayName() + "&6.")); + damager.sendMessage(Color.translate("&7For the next 10 seconds you now deal 20% more damage towards them!")); + damager.sendMessage(""); + + target.playSound(target.getLocation(), Sound.ANVIL_BREAK, 1, 1); + target.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You have been hit with the " + this.getDisplayName() + ChatColor.RED + "!"); + + this.applyCooldown(damager); + } + + + @EventHandler + public void onEntityDamageByEntity(EntityDamageByEntityEvent event) { + if (!(event.getEntity() instanceof Player) || !(event.getDamager() instanceof Player)) { + return; + } + + final Player player = (Player) event.getEntity(); + final Player damager = (Player) event.getDamager(); + + if (!cache.containsKey(player.getUniqueId()) || !cache.get(player.getUniqueId()).toString().equalsIgnoreCase(damager.getUniqueId().toString())) { + return; + } + + event.setDamage(event.getDamage()*1.2D); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/Invisibility.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/Invisibility.java new file mode 100644 index 0000000..0f0c760 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/Invisibility.java @@ -0,0 +1,198 @@ +package com.elevatemc.potpvp.ability.type; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.Ability; +import com.elevatemc.potpvp.ability.listener.events.AbilityUseEvent; +import net.minecraft.server.v1_8_R3.PacketPlayOutEntityEquipment; +import net.minecraft.server.v1_8_R3.PlayerConnection; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_8_R3.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.PotionEffectExpireEvent; +import org.bukkit.event.entity.PotionEffectRemoveEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class Invisibility extends Ability { + public static PotionEffect EFFECT = new PotionEffect(PotionEffectType.INVISIBILITY,(3*60)*20,2,false); + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Material getMaterial() { + return Material.INK_SACK; + } + + @Override + public String getDisplayName() { + return ChatColor.DARK_AQUA.toString() + ChatColor.BOLD + "Invisibility"; + } + + @Override + public List getLore() { + final List toReturn = new ArrayList<>(); + + toReturn.add(ChatColor.GRAY + "When you right click this item"); + toReturn.add(ChatColor.GRAY + "your armor will no longer be visible."); + + return toReturn; + } + + @Override + public long getCooldown() { + return 90_000L; + } + + @EventHandler(priority = EventPriority.LOWEST) + private void onClick(PlayerInteractEvent event) { + final Player player = event.getPlayer(); + + if (!this.isSimilar(event.getItem())) { + return; + } + + event.setCancelled(true); + + final AbilityUseEvent abilityUseEvent = new AbilityUseEvent(player, null, player.getLocation(), this, false); + PotPvPSI.getInstance().getServer().getPluginManager().callEvent(abilityUseEvent); + + if (abilityUseEvent.isCancelled()) { + return; + } + + final ItemStack itemStack = event.getItem(); + + if (itemStack.getAmount() == 1) { + event.getPlayer().setItemInHand(null); + } else { + itemStack.setAmount(itemStack.getAmount()-1); + } + + event.getPlayer().addPotionEffect(EFFECT, true); + + this.sendRestorePacket(event.getPlayer(), PotPvPSI.getInstance().getServer().getOnlinePlayers(),true); + + this.applyCooldown(event.getPlayer()); + } + + @EventHandler(priority = EventPriority.MONITOR) + private void onEntityDamageByEntity(EntityDamageByEntityEvent event) { + if (event.isCancelled() || (!(event.getEntity() instanceof Player)) || (!(event.getDamager() instanceof Player))) { + return; + } + + final Player player = (Player) event.getEntity(); + + player.getActivePotionEffects().stream().filter(it -> it.getType().equals(EFFECT.getType()) && it.getAmplifier() == EFFECT.getAmplifier()).findFirst().ifPresent(it -> { + + player.removePotionEffect(it.getType()); + + this.sendRestorePacket(player, PotPvPSI.getInstance().getServer().getOnlinePlayers(),false); + + final PotionEffect clone = new PotionEffect(it.getType(),it.getDuration(),0); + + player.addPotionEffect(clone); + + ((Player)event.getEntity()).sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED.toString() + ChatColor.BOLD + "WARNING!" + ChatColor.YELLOW + " You have been hit and are no " + ChatColor.RED + ChatColor.BOLD + "LONGER" + ChatColor.YELLOW + " invisible!"); + }); + + } + + @EventHandler(priority = EventPriority.MONITOR) + private void onDamagerDamage(EntityDamageByEntityEvent event) { + if (event.isCancelled() || (!(event.getEntity() instanceof Player)) || (!(event.getDamager() instanceof Player))) { + return; + } + + final Player player = (Player) event.getDamager(); + + player.getActivePotionEffects().stream().filter(it -> it.getType().equals(EFFECT.getType()) && it.getAmplifier() == EFFECT.getAmplifier()).findFirst().ifPresent(it -> { + + player.removePotionEffect(it.getType()); + + this.sendRestorePacket(player, PotPvPSI.getInstance().getServer().getOnlinePlayers(),false); + + final PotionEffect clone = new PotionEffect(it.getType(),it.getDuration(),0); + + player.addPotionEffect(clone); + + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED.toString() + ChatColor.BOLD + "WARNING!" + ChatColor.YELLOW + " You have hit a player and are no " + ChatColor.RED + ChatColor.BOLD + "LONGER" + ChatColor.YELLOW + " invisible!"); + }); + + } + + @EventHandler(priority = EventPriority.LOWEST) + private void onPotionEffectExpire(PotionEffectExpireEvent event) { + this.onPotionEffectRemove(event); + } + + @EventHandler(priority = EventPriority.LOWEST) + private void onPotionEffectRemove(PotionEffectRemoveEvent event) { + + if (!(event.getEntity() instanceof Player)) { + return; + } + + if (!event.getEffect().getType().equals(EFFECT.getType()) || event.getEffect().getAmplifier() != EFFECT.getAmplifier()) { + return; + } + + final Player player = (Player) event.getEntity(); + + player.getActivePotionEffects().stream().filter(it -> it.getType().equals(EFFECT.getType()) && it.getAmplifier() == EFFECT.getAmplifier()).findFirst().ifPresent(it -> { + + player.removePotionEffect(it.getType()); + + this.sendRestorePacket(player, PotPvPSI.getInstance().getServer().getOnlinePlayers(),false); + + final PotionEffect clone = new PotionEffect(it.getType(),it.getDuration(),0); + + player.addPotionEffect(clone); + + event.getEntity().sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED.toString() + ChatColor.BOLD + "WARNING!" + ChatColor.YELLOW + " Your " + ChatColor.WHITE + "Invisibility" + ChatColor.YELLOW + " has expired and are no " + ChatColor.RED + ChatColor.BOLD + "LONGER" + ChatColor.YELLOW + " invisible!"); + }); + + } + + private void sendRestorePacket(Player player, Collection players, boolean clear) { + + final List packets = new ArrayList<>(); + + for (int i = 0; i < 4; i++) { + int id = player.getEntityId(); + int slot = i+1; + net.minecraft.server.v1_8_R3.ItemStack item = clear ? CraftItemStack.asNMSCopy(new ItemStack(Material.AIR)) : CraftItemStack.asNMSCopy(player.getInventory().getArmorContents()[i]); + + + final PacketPlayOutEntityEquipment packet = new PacketPlayOutEntityEquipment(id, slot, item); + + packets.add(packet); + } + + players.stream().filter(it -> it.getUniqueId() != player.getUniqueId()).forEach(it -> packets.forEach(packet -> { + + try { + PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection; + playerConnection.sendPacket(packet); + } catch (Exception ex) { + ex.printStackTrace(); + } + + })); + + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/ItemCounter.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/ItemCounter.java new file mode 100644 index 0000000..49d01be --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/ItemCounter.java @@ -0,0 +1,130 @@ +package com.elevatemc.potpvp.ability.type; + +import com.elevatemc.potpvp.util.Color; +import lombok.Getter; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.Ability; +import com.elevatemc.potpvp.ability.listener.events.AbilityUseEvent; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.*; + +public class ItemCounter extends Ability { + @Getter + public static List cache = new ArrayList<>(); + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Material getMaterial() { + return Material.COMPASS; + } + + @Override + public String getDisplayName() { + return ChatColor.RED.toString() + ChatColor.BOLD + "Item Counter"; + } + + @Override + public List getLore() { + final List toReturn = new ArrayList<>(); + + toReturn.add(ChatColor.GRAY + "Hit an enemy to discover how many"); + toReturn.add(ChatColor.GRAY + "health potions and ability items they have!"); + + return toReturn; + } + + @Override + public long getCooldown() { + return 35_000L; + } + + @EventHandler(priority = EventPriority.MONITOR) + private void onDamage(EntityDamageByEntityEvent event) { + if (event.isCancelled() || !(event.getDamager() instanceof Player) || !(event.getEntity() instanceof Player)) { + return; + } + + final Player target = (Player) event.getEntity(); + final Player damager = (Player) event.getDamager(); + + if (damager.getItemInHand() == null || !this.isSimilar(damager.getItemInHand())) { + return; + } + + final AbilityUseEvent abilityUseEvent = new AbilityUseEvent(damager, target, damager.getLocation(), this, false); + PotPvPSI.getInstance().getServer().getPluginManager().callEvent(abilityUseEvent); + + if (abilityUseEvent.isCancelled()) { + return; + } + + final Map abilities = new HashMap<>(); + + int crapples = 0; + int godApples = 0; + + for (ItemStack content : target.getInventory().getContents()) { + + if (content == null || content.getType() == Material.AIR) { + continue; + } + + if (content.getType() == Material.GOLDEN_APPLE && content.getData().getData() == 0) { + crapples += content.getAmount(); + continue; + } + + if (content.getType() == Material.GOLDEN_APPLE && content.getData().getData() == 1) { + godApples += content.getAmount(); + continue; + } + + final Ability ability = PotPvPSI.getInstance().getAbilityHandler().getAbilities().values().stream().filter(it -> it.isSimilar(content)).findFirst().orElse(null); + + if (ability == null) { + continue; + } + + int amount = abilities.getOrDefault(ability, 0)+content.getAmount(); + + abilities.put(ability, amount); + } + + int amount = (int) Arrays.stream(target.getInventory().getContents()).filter(it -> it != null && it.getType() == Material.POTION && it.getDurability() == 16421).count(); + + damager.sendMessage(""); + damager.sendMessage(Color.translate(target.getName() + " &chas &f" + amount + " &chealth potions in their inventory.")); + damager.sendMessage(Color.translate(target.getName() + " &chas &f" + crapples + " &ccrapples in their inventory.")); + damager.sendMessage(Color.translate(target.getName() + " &chas &f" + godApples + " &cgod apples in their inventory.")); + if (!abilities.isEmpty()) { + damager.sendMessage(Color.translate("&6&lAbilities:")); + } + + for (Map.Entry abilityIntegerEntry : abilities.entrySet()) { + damager.sendMessage(Color.translate("&6- " + abilityIntegerEntry.getKey().getDisplayName() + "&7: &f" + abilityIntegerEntry.getValue())); + } + + damager.sendMessage(""); + + final ItemStack itemStack = damager.getItemInHand(); + + if (itemStack.getAmount() == 1) { + damager.setItemInHand(null); + } else { + itemStack.setAmount(itemStack.getAmount()-1); + } + + this.applyCooldown(damager); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/MedKit.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/MedKit.java new file mode 100644 index 0000000..88a6d65 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/MedKit.java @@ -0,0 +1,96 @@ +package com.elevatemc.potpvp.ability.type; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.Ability; +import com.elevatemc.potpvp.ability.listener.events.AbilityUseEvent; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.ArrayList; +import java.util.List; + +public class MedKit extends Ability { + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Material getMaterial() { + return Material.PAPER; + } + + @Override + public String getDisplayName() { + return ChatColor.GREEN.toString() + ChatColor.BOLD + "Med Kit"; + } + + @Override + public List getLore() { + final List toReturn = new ArrayList<>(); + + toReturn.add(ChatColor.GRAY + "Right Click to receive Resistance 3,"); + toReturn.add(ChatColor.GRAY + "Regeneration 3, and 4 Absorption"); + toReturn.add(ChatColor.GRAY + "Hearts for 5 seconds!"); + + return toReturn; + } + + @Override + public long getCooldown() { + return 90_000L; + } + + @EventHandler(priority = EventPriority.NORMAL) + public void onInteract(PlayerInteractEvent event) { + if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; + } + + final Player player = event.getPlayer(); + + final Location blockAt = player.getLocation(); + + if (!this.isSimilar(event.getItem())) { + return; + } + + final Block belowBlock = blockAt.getBlock().getRelative(BlockFace.DOWN); + + if (!player.isOnGround() && belowBlock.getType() == Material.AIR && belowBlock.getRelative(BlockFace.DOWN).getType() == Material.AIR && belowBlock.getRelative(BlockFace.DOWN).getRelative(BlockFace.DOWN).getType() == Material.AIR) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You may not use the " + this.getDisplayName() + ChatColor.RED + " in the air!"); + return; + } + + final AbilityUseEvent abilityUseEvent = new AbilityUseEvent(player, null, player.getLocation(), this, false); + PotPvPSI.getInstance().getServer().getPluginManager().callEvent(abilityUseEvent); + + if (abilityUseEvent.isCancelled()) { + return; + } + + event.setCancelled(true); + + if (player.getItemInHand().getAmount() == 1) { + player.setItemInHand(null); + } else { + player.getItemInHand().setAmount(player.getItemInHand().getAmount()-1); + } + + player.addPotionEffect(new PotionEffect(PotionEffectType.REGENERATION, 5*20, 2), true); + player.addPotionEffect(new PotionEffect(PotionEffectType.DAMAGE_RESISTANCE, 5*20, 2), true); + player.addPotionEffect(new PotionEffect(PotionEffectType.ABSORPTION, 30*20, 1), true); + + this.applyCooldown(player); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/MindStone.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/MindStone.java new file mode 100644 index 0000000..1de7485 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/MindStone.java @@ -0,0 +1,106 @@ +package com.elevatemc.potpvp.ability.type; + +import com.elevatemc.elib.util.ItemBuilder; +import com.elevatemc.potpvp.pvpclasses.PvPClassHandler; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.Ability; +import com.elevatemc.potpvp.ability.listener.events.AbilityUseEvent; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +public class MindStone extends Ability { + public MindStone() { + this.hassanStack = ItemBuilder.copyOf(hassanStack.clone()).data((byte)14).build(); + } + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Material getMaterial() { + return Material.INK_SACK; + } + + @Override + public String getDisplayName() { + return ChatColor.YELLOW.toString() + ChatColor.BOLD + "Mind Stone"; + } + + @Override + public List getLore() { + final List toReturn = new ArrayList<>(); + + toReturn.add(ChatColor.GRAY + "Hit a player to rotate their"); + toReturn.add(ChatColor.GRAY + "head 180 degrees and give them"); + toReturn.add(ChatColor.GRAY + "Blindness X, Slowness III and"); + toReturn.add(ChatColor.GRAY + "Nausea III for 8 seconds."); + + return toReturn; + } + + @Override + public long getCooldown() { + return TimeUnit.MINUTES.toMillis(2) + TimeUnit.SECONDS.toMillis(15); + } + + @EventHandler(priority = EventPriority.MONITOR) + private void onDamage(EntityDamageByEntityEvent event) { + if (event.isCancelled() || !(event.getDamager() instanceof Player) || !(event.getEntity() instanceof Player)) { + return; + } + + final Player target = (Player) event.getEntity(); + final Player damager = (Player) event.getDamager(); + + if (damager.getItemInHand() == null || !this.isSimilar(damager.getItemInHand())) { + return; + } + + final AbilityUseEvent abilityUseEvent = new AbilityUseEvent(damager, target, damager.getLocation(), this, false); + PotPvPSI.getInstance().getServer().getPluginManager().callEvent(abilityUseEvent); + + if (abilityUseEvent.isCancelled()) { + return; + } + + if (PvPClassHandler.getPvPClass(target) != null) { + damager.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You may not use a " + this.getDisplayName() + ChatColor.RED + " on a " + ChatColor.WHITE + Objects.requireNonNull(PvPClassHandler.getPvPClass(target)).getName() + ChatColor.RED + "."); + return; + } + + final Location location = target.getLocation(); + location.setYaw(location.getYaw()+180.0F); + + target.addPotionEffect(new PotionEffect(PotionEffectType.SLOW, 20*8, 2)); + target.addPotionEffect(new PotionEffect(PotionEffectType.CONFUSION, 20*8, 2)); + target.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, 20*8, 9)); + target.teleport(location); + + target.sendMessage(damager.getName() + ChatColor.RED + " has used the " + this.getDisplayName() + ChatColor.RED + " on you!"); + + final ItemStack itemStack = damager.getItemInHand(); + + if (itemStack.getAmount() == 1) { + damager.setItemInHand(null); + } else { + itemStack.setAmount(itemStack.getAmount()-1); + } + + this.applyCooldown(damager); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/NinjaStar.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/NinjaStar.java new file mode 100644 index 0000000..83cc90d --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/NinjaStar.java @@ -0,0 +1,233 @@ +package com.elevatemc.potpvp.ability.type; + +import com.elevatemc.elib.util.UUIDUtils; +import com.elevatemc.potpvp.util.PatchedPlayerUtils; +import lombok.AllArgsConstructor; +import lombok.Getter; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.Ability; +import com.elevatemc.potpvp.ability.listener.events.AbilityUseEvent; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.EnderPearl; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.entity.ProjectileLaunchEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +public class NinjaStar extends Ability { + @Getter + private Map cache = new HashMap<>(); + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Material getMaterial() { + return Material.NETHER_STAR; + } + + @Override + public String getDisplayName() { + return ChatColor.AQUA.toString() + ChatColor.BOLD + "Ninja Star"; + } + + @Override + public List getLore() { + final List toReturn = new ArrayList<>(); + + toReturn.add(ChatColor.GRAY + "Teleport to the last"); + toReturn.add(ChatColor.GRAY + "person who hit you within 30 seconds."); + return toReturn; + } + + @Override + public long getCooldown() { + return 90_000L; + } + + @EventHandler + private void onInteract(PlayerInteractEvent event) { + final Player player = event.getPlayer(); + + final long difference = TimeUnit.SECONDS.toMillis(30L); + + + if (event.getAction() != Action.RIGHT_CLICK_BLOCK && event.getAction() != Action.RIGHT_CLICK_AIR) { + return; + } + + if (player.getItemInHand() == null || !this.isSimilar(player.getItemInHand())) { + return; + } + + final AbilityUseEvent abilityUseEvent = new AbilityUseEvent(player, null, player.getLocation(), this, false); + PotPvPSI.getInstance().getServer().getPluginManager().callEvent(abilityUseEvent); + + if (abilityUseEvent.isCancelled()) { + return; + } + + if (!this.cache.containsKey(player.getUniqueId()) || (System.currentTimeMillis() - this.cache.get(player.getUniqueId()).getTime()) > difference) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "No player has hit you within the last 30 seconds."); + return; + } + final LastDamageEntry entry = cache.get(player.getUniqueId()); + + final Player target = PotPvPSI.getInstance().getServer().getPlayer(entry.getUuid()); + + if (target.isOnline()) { + + target.setMetadata("NINJASTAR", new FixedMetadataValue(PotPvPSI.getInstance(), true)); + target.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You may not use an enderpearl or timewarp for the next 5 seconds..."); + + PotPvPSI.getInstance().getServer().getScheduler().runTaskLater(PotPvPSI.getInstance(), () -> { + target.removeMetadata("NINJASTAR", PotPvPSI.getInstance()); + }, 20*5); + } + + new BukkitRunnable() { + private int seconds = 3; + + @Override + public void run() { + + if (!event.getPlayer().isOnline()) { + this.cancel(); + return; + } + + if (PotPvPSI.getInstance().getMatchHandler().getMatchPlaying(player) == null) { + this.cancel(); + return; + } + + final Player target = PotPvPSI.getInstance().getServer().getPlayer(entry.getUuid()); + + if (target == null && !target.isOnline()) { + this.cancel(); + return; + } + + + if (this.seconds < 1) { + + final Location location = (target != null && target.isOnline()) ? target.getLocation():entry.getLocation(); + + event.getPlayer().teleport(location); + + this.cancel(); + return; + } + + this.seconds--; + + event.getPlayer().sendMessage(ChatColor.YELLOW + "Teleporting to " + ChatColor.WHITE + UUIDUtils.name(entry.getUuid()) + ChatColor.YELLOW + " in " + ChatColor.RED + (this.seconds+1) + ChatColor.YELLOW + " second" + (this.seconds == 1 ? "":"s") + "..."); + + if (target != null && target.isOnline()) { + target.sendMessage(PatchedPlayerUtils.getFormattedName(player.getUniqueId()) + ChatColor.RED + " will teleport to you in " + ChatColor.WHITE + (this.seconds+1) + ChatColor.RED + " second" + (this.seconds == 1 ? "":"s") + "."); + } + } + + }.runTaskTimer(PotPvPSI.getInstance(),0L,20L); + + if (player.getItemInHand().getAmount() == 1) { + player.setItemInHand(null); + } else { + player.getItemInHand().setAmount(player.getItemInHand().getAmount()-1); + } + + this.applyCooldown(player); + } + + @EventHandler(priority = EventPriority.MONITOR) + private void onPearl(ProjectileLaunchEvent event) { + if (!(event.getEntity() instanceof EnderPearl)) { + return; + } + + if (!(event.getEntity().getShooter() instanceof Player)) { + return; + } + + final Player shooter = (Player) event.getEntity().getShooter(); + + if (shooter.hasMetadata("NINJASTAR")) { + event.setCancelled(true); + shooter.updateInventory(); + shooter.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You may not throw enderpearls whilst someone is using a " + this.getDisplayName() + ChatColor.RED + " on you!"); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + private void onEntityDamageByEntity(EntityDamageByEntityEvent event) { + + if (event.isCancelled()) { + return; + } + + if (!(event.getEntity() instanceof Player) || !(event.getDamager() instanceof Player)) { + return; + } + + this.cache.put(event.getEntity().getUniqueId(),new LastDamageEntry(System.currentTimeMillis(),event.getDamager().getUniqueId(),event.getDamager().getLocation())); + } + + @EventHandler(priority = EventPriority.MONITOR) + private void onEntityDamageByProjectile(EntityDamageByEntityEvent event) { + + if (event.isCancelled() || event.getDamager() instanceof EnderPearl) { + return; + } + + if (!(event.getEntity() instanceof Player) || !(event.getDamager() instanceof Projectile)) { + return; + } + + if (!(((Projectile) event.getDamager()).getShooter() instanceof Player)) { + return; + } + + final Player damager = (Player) ((Projectile) event.getDamager()).getShooter(); + + this.cache.put(event.getEntity().getUniqueId(),new LastDamageEntry(System.currentTimeMillis(),damager.getUniqueId(),damager.getLocation())); + } + + @EventHandler(priority = EventPriority.LOW) + private void onDeath(PlayerDeathEvent event) { + this.cache.remove(event.getEntity().getUniqueId()); + + final Optional> optionalLastDamageEntry = this.cache.entrySet().stream().filter(it -> it.getValue().getUuid().toString().equalsIgnoreCase(event.getEntity().getUniqueId().toString())).findFirst(); + + if (!optionalLastDamageEntry.isPresent()) { + return; + } + + this.cache.remove(optionalLastDamageEntry.get().getKey(), optionalLastDamageEntry.get().getValue()); + } + + @AllArgsConstructor + static class LastDamageEntry { + + @Getter + private long time; + @Getter + private UUID uuid; + @Getter + private Location location; + + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/PortableResistance.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/PortableResistance.java new file mode 100644 index 0000000..d399d5e --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/PortableResistance.java @@ -0,0 +1,88 @@ +package com.elevatemc.potpvp.ability.type; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.Ability; +import com.elevatemc.potpvp.ability.listener.events.AbilityUseEvent; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.ArrayList; +import java.util.List; + +public class PortableResistance extends Ability { + public static ItemStack itemStack; + + public PortableResistance() { + itemStack = this.hassanStack; + } + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Material getMaterial() { + return Material.IRON_INGOT; + } + + @Override + public String getDisplayName() { + return ChatColor.GOLD.toString() + ChatColor.BOLD + "Resistance"; + } + + @Override + public List getLore() { + final List toReturn = new ArrayList<>(); + + toReturn.add(ChatColor.GRAY + "Give yourself and your teammates"); + toReturn.add(ChatColor.GRAY + "around you Resistance 3 for 5 seconds"); + + return toReturn; + } + + @Override + public long getCooldown() { + return 90_000L; + } + + @EventHandler(priority = EventPriority.NORMAL) + public void onInteract(PlayerInteractEvent event) { + if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK && event.getAction() != Action.LEFT_CLICK_AIR && event.getAction() != Action.LEFT_CLICK_BLOCK) { + return; + } + + final Player player = event.getPlayer(); + + if (!this.isSimilar(event.getItem())) { + return; + } + + final AbilityUseEvent abilityUseEvent = new AbilityUseEvent(player, null, player.getLocation(), this, false); + PotPvPSI.getInstance().getServer().getPluginManager().callEvent(abilityUseEvent); + + if (abilityUseEvent.isCancelled()) { + return; + } + + event.setCancelled(true); + + if (player.getItemInHand().getAmount() == 1) { + player.setItemInHand(null); + } else { + player.getItemInHand().setAmount(player.getItemInHand().getAmount()-1); + } + + player.addPotionEffect(new PotionEffect(PotionEffectType.DAMAGE_RESISTANCE, 6*20, 2), true); + + this.applyCooldown(player); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/PortableStrength.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/PortableStrength.java new file mode 100644 index 0000000..1f28c24 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/PortableStrength.java @@ -0,0 +1,88 @@ +package com.elevatemc.potpvp.ability.type; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.Ability; +import com.elevatemc.potpvp.ability.listener.events.AbilityUseEvent; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.ArrayList; +import java.util.List; + +public class PortableStrength extends Ability { + public static ItemStack itemStack; + + public PortableStrength() { + itemStack = this.hassanStack; + } + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Material getMaterial() { + return Material.BLAZE_POWDER; + } + + @Override + public String getDisplayName() { + return ChatColor.GOLD.toString() + ChatColor.BOLD + "Strength"; + } + + @Override + public List getLore() { + final List toReturn = new ArrayList<>(); + + toReturn.add(ChatColor.GRAY + "Give yourself and your teammates"); + toReturn.add(ChatColor.GRAY + "around you Strength 2 for 5 seconds"); + + return toReturn; + } + + @Override + public long getCooldown() { + return 90_000L; + } + + @EventHandler(priority = EventPriority.NORMAL) + public void onInteract(PlayerInteractEvent event) { + if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK && event.getAction() != Action.LEFT_CLICK_AIR && event.getAction() != Action.LEFT_CLICK_BLOCK) { + return; + } + + final Player player = event.getPlayer(); + + if (!this.isSimilar(event.getItem())) { + return; + } + + final AbilityUseEvent abilityUseEvent = new AbilityUseEvent(player, null, player.getLocation(), this, false); + PotPvPSI.getInstance().getServer().getPluginManager().callEvent(abilityUseEvent); + + if (abilityUseEvent.isCancelled()) { + return; + } + + event.setCancelled(true); + + if (player.getItemInHand().getAmount() == 1) { + player.setItemInHand(null); + } else { + player.getItemInHand().setAmount(player.getItemInHand().getAmount()-1); + } + + player.addPotionEffect(new PotionEffect(PotionEffectType.INCREASE_DAMAGE, 6*20, 1), true); + + this.applyCooldown(player); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/PowerStone.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/PowerStone.java new file mode 100644 index 0000000..99559ba --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/PowerStone.java @@ -0,0 +1,176 @@ +package com.elevatemc.potpvp.ability.type; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.Ability; +import com.elevatemc.potpvp.ability.listener.events.AbilityUseEvent; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.entity.ThrownPotion; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.PotionSplashEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.Potion; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.potion.PotionType; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class PowerStone extends Ability { + public PowerStone() { + this.hassanStack.setDurability((byte)5); + } + + private List powerStone = new ArrayList<>(); + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Material getMaterial() { + return Material.INK_SACK; + } + + @Override + public String getDisplayName() { + return ChatColor.DARK_PURPLE.toString() + ChatColor.BOLD + "Power Stone"; + } + + @Override + public List getLore() { + final List toReturn = new ArrayList<>(); + + toReturn.add(ChatColor.GRAY + "Right Click to receive Strength 2, Resistance 3"); + toReturn.add(ChatColor.GRAY + "Regeneration 3 for 12 seconds. Within those"); + toReturn.add(ChatColor.GRAY + "12 seconds you may not use any sort of potions."); + + return toReturn; + } + + @Override + public long getCooldown() { + return 120_000L; + } + + @EventHandler(priority = EventPriority.NORMAL) + public void onInteract(PlayerInteractEvent event) { + if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; + } + + final Player player = event.getPlayer(); + + final Location blockAt = player.getLocation(); + + if (!this.isSimilar(event.getItem())) { + return; + } + + final AbilityUseEvent abilityUseEvent = new AbilityUseEvent(player, null, player.getLocation(), this, false); + PotPvPSI.getInstance().getServer().getPluginManager().callEvent(abilityUseEvent); + + if (abilityUseEvent.isCancelled()) { + return; + } + + final Block belowBlock = blockAt.getBlock().getRelative(BlockFace.DOWN); + + if (!player.isOnGround() && belowBlock.getType() == Material.AIR && belowBlock.getRelative(BlockFace.DOWN).getType() == Material.AIR && belowBlock.getRelative(BlockFace.DOWN).getRelative(BlockFace.DOWN).getType() == Material.AIR) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You may not use the " + this.getDisplayName() + ChatColor.RED + " in the air!"); + return; + } + + event.setCancelled(true); + + if (player.getItemInHand().getAmount() == 1) { + player.setItemInHand(null); + + } else { + player.getItemInHand().setAmount(player.getItemInHand().getAmount() - 1); + } + + player.addPotionEffect(new PotionEffect(PotionEffectType.REGENERATION, 20 * 12, 2), true); + player.addPotionEffect(new PotionEffect(PotionEffectType.DAMAGE_RESISTANCE, 20 * 12, 2), true); + player.addPotionEffect(new PotionEffect(PotionEffectType.INCREASE_DAMAGE, 20 * 12, 1), true); + + final UUID uuid = player.getUniqueId(); + powerStone.add(uuid); + + PotPvPSI.getInstance().getServer().getScheduler().runTaskLater(PotPvPSI.getInstance(), () -> { + player.sendMessage(""); + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Your " + this.getDisplayName() + ChatColor.RED + " has expired! You may now splash potions!"); + player.sendMessage(""); + powerStone.remove(uuid); + }, 20 * 12); + + this.applyCooldown(player); + } + + @EventHandler + private void onSplash(PlayerInteractEvent event) { + if (!event.getAction().name().contains("RIGHT")) { + return; + } + + final Player player = event.getPlayer(); + final ItemStack itemStack = event.getItem(); + + if (itemStack == null) { + return; + } + + if (itemStack.getType() != Material.POTION) { + return; + } + + if (itemStack.getDurability() == 0) { // Water bottle + return; + } + + final Potion potion = Potion.fromItemStack(itemStack); + + if (potion.getType() != PotionType.INSTANT_HEAL) { + return; + } + + if (!powerStone.contains(player.getUniqueId())) { + return; + } + + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You may not splash health potions whilst using " + this.getDisplayName() + ChatColor.RED + "."); + event.setCancelled(true); + } + + @EventHandler(priority = EventPriority.LOW) + private void onSplash(PotionSplashEvent event) { + final ThrownPotion thrownPotion = event.getPotion(); + + if (!(thrownPotion.getShooter() instanceof Player)) { + return; + } + + final Player shooter = (Player) event.getPotion().getShooter(); + + if (thrownPotion.getEffects().stream().noneMatch(it -> it.getType().getName().contains("HEAL"))) { + return; + } + + if (!powerStone.contains(shooter.getUniqueId())) { + return; + } + + shooter.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You may not splash health potions whilst using " + this.getDisplayName() + ChatColor.RED + "."); + event.setCancelled(true); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/SignalJammer.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/SignalJammer.java new file mode 100644 index 0000000..e36ec15 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/SignalJammer.java @@ -0,0 +1,206 @@ +package com.elevatemc.potpvp.ability.type; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.Ability; +import com.elevatemc.potpvp.ability.listener.events.AbilityUseEvent; +import com.elevatemc.potpvp.util.Color; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.metadata.FixedMetadataValue; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +public class SignalJammer extends Ability { + public static Map signalJammers = new HashMap<>(); + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Material getMaterial() { + return Material.COMMAND; + } + + @Override + public String getDisplayName() { + return ChatColor.GREEN.toString() + ChatColor.BOLD + "Signal Jammer"; + } + + @Override + public List getLore() { + final List toReturn = new ArrayList<>(); + + toReturn.add(ChatColor.GRAY + "Place this down and no one within"); + toReturn.add(ChatColor.GRAY + "16 blocks can use any partner/ability items!"); + + return toReturn; + } + + @Override + public long getCooldown() { + return TimeUnit.MINUTES.toMillis(1L) + TimeUnit.SECONDS.toMillis(30); + } + + @EventHandler(priority = EventPriority.LOW) + private void onPlace(BlockPlaceEvent event) { + final Player player = event.getPlayer(); + final Block block = event.getBlock(); + + if (event.getItemInHand() == null) { + return; + } + + if (block.getType() == Material.COMMAND && !player.isOp() && !this.isSimilar(event.getItemInHand())) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You may not place that block!"); + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.LOWEST) + private void onPlace(PlayerInteractEvent event) { + if (event.isCancelled() || event.getItem() == null || event.getClickedBlock() == null || !this.isSimilar(event.getItem()) || !event.getAction().name().contains("RIGHT")) { + return; + } + + final Player player = event.getPlayer(); + final Block clickedBlock = event.getClickedBlock(); + final Block block = clickedBlock.getRelative(BlockFace.UP); + + event.setCancelled(true); + + final AbilityUseEvent abilityUseEvent = new AbilityUseEvent(player, null, block.getLocation(), this, false); + PotPvPSI.getInstance().getServer().getPluginManager().callEvent(abilityUseEvent); + + if (abilityUseEvent.isCancelled()) { + player.updateInventory(); + return; + } + + if (clickedBlock.getType() == Material.STATIONARY_WATER || clickedBlock.getType() == Material.STATIONARY_LAVA || clickedBlock.getType() == Material.LAVA || clickedBlock.getType() == Material.WATER) { + player.updateInventory(); + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You may not place " + this.getDisplayName() + ChatColor.RED + " ontop of " + clickedBlock.getType().name().replace("_", "").toLowerCase()); + return; + } + + if (block.getType() != Material.AIR) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You can only place this on the ground!"); + return; + } + + final ItemStack itemStack = event.getItem(); + + if (itemStack.getAmount() == 1) { + player.setItemInHand(null); + } else { + itemStack.setAmount(itemStack.getAmount()-1); + } + player.updateInventory(); + + for (Player onlinePlayer : PotPvPSI.getInstance().getServer().getOnlinePlayers()) { + if (!onlinePlayer.getWorld().getName().equalsIgnoreCase(block.getWorld().getName())) { + continue; + } + + if (onlinePlayer.getLocation().distance(block.getLocation()) > 16) { + continue; + } + + onlinePlayer.sendMessage(""); + onlinePlayer.sendMessage(Color.translate(player.getName() + " &chas placed a " + this.getDisplayName() + "&c!")); + onlinePlayer.sendMessage(Color.translate("&7All players within a 16 block radius may not use any ability items!")); + onlinePlayer.sendMessage(""); + } + + if (block.getType().isSolid() && block.getType().isBlock()) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You may not place a " + this.getDisplayName() + ChatColor.RED + " here!"); + return; + } + + player.updateInventory(); + + signalJammers.put(block.getLocation(), player.getUniqueId()); + + block.setMetadata("SIGNAL_JAMMER", new FixedMetadataValue(PotPvPSI.getInstance(), true)); + block.setType(Material.COMMAND); + + PotPvPSI.getInstance().getServer().getScheduler().runTaskLater(PotPvPSI.getInstance(), () -> { + signalJammers.remove(block.getLocation()); + + block.setType(Material.AIR); + + if (player.isOnline()) { + player.sendMessage(""); + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Your " + this.getDisplayName() + ChatColor.RED + " has expired!"); + player.sendMessage(""); + } + }, 20*30); + + this.applyCooldown(player); + } + + @EventHandler(priority = EventPriority.LOW) + private void onInteract(PlayerInteractEvent event) { + final Player player = event.getPlayer(); + final Block block = event.getClickedBlock(); + + if (block == null || block.getType() != Material.COMMAND || !signalJammers.containsKey(block.getLocation())) { + return; + } + + if (event.getAction().name().contains("RIGHT")) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You may not right click the " + this.getDisplayName() + ChatColor.RED + "!"); + event.setCancelled(true); + return; + } + + int value = block.hasMetadata("SIGNAL_JAMMER") ? (block.getMetadata("SIGNAL_JAMMER").get(0).asInt() + 1) : 1; + + block.setMetadata("SIGNAL_JAMMER", new FixedMetadataValue(PotPvPSI.getInstance(), value)); + + if (value != 20) { + player.sendMessage(Color.translate("&cYou have to hit the &f" + this.getDisplayName() + " &c" + (20 - value) + " more time" + (20 - value == 1 ? "" : "s") + "!")); + return; + } + + block.setType(Material.AIR); + block.removeMetadata("SIGNAL_JAMMER", PotPvPSI.getInstance()); + + final Player owner = PotPvPSI.getInstance().getServer().getPlayer(signalJammers.remove(block.getLocation())); + + if (owner == null || !owner.isOnline()) { + return; + } + + owner.sendMessage(""); + owner.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Your " + this.getDisplayName() + ChatColor.RED + " has been broken by " + ChatColor.WHITE + player.getName() + ChatColor.RED + "!"); + owner.sendMessage(""); + } + + @EventHandler(priority = EventPriority.LOW) + private void onAbilityUse(AbilityUseEvent event) { + final Player player = event.getPlayer(); + final Ability ability = event.getAbility(); + final Location chosenLocation = event.getChosenLocation().clone(); + + if (signalJammers.keySet().stream().anyMatch(it -> player.getWorld().getName().equalsIgnoreCase(it.getWorld().getName()) && chosenLocation.distance(it) <= 16)) { + event.setCancelled(true); + + player.sendMessage(""); + player.sendMessage(Color.translate("&cYou may not use &f" + ability.getDisplayName() + " &cas you are within 16 blocks of a &f" + this.getDisplayName() + "&c!")); + player.sendMessage(""); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/SoulStone.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/SoulStone.java new file mode 100644 index 0000000..28aeda3 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/SoulStone.java @@ -0,0 +1,213 @@ +package com.elevatemc.potpvp.ability.type; + +import com.elevatemc.elib.util.UUIDUtils; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.Ability; +import com.elevatemc.potpvp.ability.listener.events.AbilityUseEvent; +import com.elevatemc.potpvp.util.Color; +import com.elevatemc.potpvp.util.PatchedPlayerUtils; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.EnderPearl; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +public class SoulStone extends Ability { + + @Getter + private Map cache = new HashMap<>(); + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Material getMaterial() { + return Material.GOLD_INGOT; + } + + @Override + public String getDisplayName() { + return ChatColor.YELLOW.toString() + ChatColor.BOLD + "Soul Stone"; + } + + @Override + public List getLore() { + final List toReturn = new ArrayList<>(); + + toReturn.add(ChatColor.GRAY + "Swap positions with the last"); + toReturn.add(ChatColor.GRAY + "person who hit you within 5 seconds."); + + return toReturn; + } + + @Override + public long getCooldown() { + return 120_000L; + } + + @EventHandler + private void onInteract(PlayerInteractEvent event) { + final Player player = event.getPlayer(); + + final long difference = TimeUnit.SECONDS.toMillis(5L); + + if (event.getAction() != Action.RIGHT_CLICK_BLOCK && event.getAction() != Action.RIGHT_CLICK_AIR) { + return; + } + + if (player.getItemInHand() == null || !this.isSimilar(player.getItemInHand())) { + return; + } + + final AbilityUseEvent abilityUseEvent = new AbilityUseEvent(player, null, player.getLocation(), this, false); + PotPvPSI.getInstance().getServer().getPluginManager().callEvent(abilityUseEvent); + + if (abilityUseEvent.isCancelled()) { + return; + } + + if (!this.cache.containsKey(player.getUniqueId()) || (System.currentTimeMillis() - this.cache.get(player.getUniqueId()).getTime()) > difference) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "No player has hit you within the last 5 seconds."); + return; + } + final LastDamageEntry entry = cache.get(player.getUniqueId()); + + final Player target = PotPvPSI.getInstance().getServer().getPlayer(entry.getUuid()); + + AntiBlockup.getCache().put(event.getPlayer().getUniqueId(), System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(10)); + + PotPvPSI.getInstance().getServer().getScheduler().runTaskLater(PotPvPSI.getInstance(), () -> { + AntiBlockup.cache.remove(event.getPlayer().getUniqueId()); + + event.getPlayer().sendMessage(""); + event.getPlayer().sendMessage(Color.translate("&cThe &f" + this.getDisplayName() + " &chas expired! You may now place blocks!")); + event.getPlayer().sendMessage(""); + }, 20*10); + + event.getPlayer().sendMessage(Color.translate("&cYou have been put on the &6&lAnti-Blockup &cfor 10 seconds as you used the &f" + getDisplayName() + "&c!")); + + new BukkitRunnable() { + private int seconds = 3; + + @Override + public void run() { + + if (!event.getPlayer().isOnline()) { + this.cancel(); + return; + } + final Player target = PotPvPSI.getInstance().getServer().getPlayer(entry.getUuid()); + + if (this.seconds < 1) { + + if (PotPvPSI.getInstance().getMatchHandler().getMatchPlaying(player) == null) { + this.cancel(); + return; + } + + final Location location = (target != null && target.isOnline()) ? target.getLocation():entry.getLocation(); + + if (target != null && target.isOnline()) { + target.teleport(event.getPlayer().getLocation()); + } + + event.getPlayer().teleport(location); + + this.cancel(); + return; + } + + this.seconds--; + + event.getPlayer().sendMessage(ChatColor.YELLOW + "Swapping positions with " + ChatColor.WHITE + UUIDUtils.name(entry.getUuid()) + ChatColor.YELLOW + " in " + ChatColor.RED + (this.seconds+1) + ChatColor.YELLOW + " second" + (this.seconds == 1 ? "":"s") + "..."); + + if (target != null && target.isOnline()) { + target.sendMessage(PatchedPlayerUtils.getFormattedName(player.getUniqueId()) + ChatColor.RED + " used " + getDisplayName() + ChatColor.RED + " and will swap positions with you in " + ChatColor.WHITE + (this.seconds+1) + ChatColor.RED + " second" + (this.seconds == 1 ? "":"s") + "."); + } + } + + }.runTaskTimer(PotPvPSI.getInstance(),0L,20L); + + if (player.getItemInHand().getAmount() == 1) { + player.setItemInHand(null); + } else { + player.getItemInHand().setAmount(player.getItemInHand().getAmount()-1); + } + + this.applyCooldown(player); + } + + @EventHandler(priority = EventPriority.MONITOR) + private void onEntityDamageByEntity(EntityDamageByEntityEvent event) { + + if (event.isCancelled()) { + return; + } + + if (!(event.getEntity() instanceof Player) || !(event.getDamager() instanceof Player)) { + return; + } + + this.cache.put(event.getEntity().getUniqueId(),new LastDamageEntry(System.currentTimeMillis(),event.getDamager().getUniqueId(),event.getDamager().getLocation())); + } + + @EventHandler(priority = EventPriority.MONITOR) + private void onEntityDamageByProjectile(EntityDamageByEntityEvent event) { + + if (event.isCancelled() || event.getDamager() instanceof EnderPearl) { + return; + } + + if (!(event.getEntity() instanceof Player) || !(event.getDamager() instanceof Projectile)) { + return; + } + + if (!(((Projectile) event.getDamager()).getShooter() instanceof Player)) { + return; + } + + final Player damager = (Player) ((Projectile) event.getDamager()).getShooter(); + + this.cache.put(event.getEntity().getUniqueId(),new LastDamageEntry(System.currentTimeMillis(),damager.getUniqueId(),damager.getLocation())); + } + + @EventHandler(priority = EventPriority.LOW) + private void onDeath(PlayerDeathEvent event) { + this.cache.remove(event.getEntity().getUniqueId()); + + final Optional> optionalLastDamageEntry = this.cache.entrySet().stream().filter(it -> it.getValue().getUuid().toString().equalsIgnoreCase(event.getEntity().getUniqueId().toString())).findFirst(); + + if (!optionalLastDamageEntry.isPresent()) { + return; + } + + this.cache.remove(optionalLastDamageEntry.get().getKey(), optionalLastDamageEntry.get().getValue()); + } + + @AllArgsConstructor + static class LastDamageEntry { + + @Getter + private long time; + @Getter + private UUID uuid; + @Getter + private Location location; + + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/Spider.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/Spider.java new file mode 100644 index 0000000..a5b3030 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/Spider.java @@ -0,0 +1,160 @@ +package com.elevatemc.potpvp.ability.type; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.Ability; +import com.elevatemc.potpvp.ability.listener.events.AbilityUseEvent; +import com.elevatemc.potpvp.util.PatchedPlayerUtils; +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.metadata.FixedMetadataValue; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +public class Spider extends Ability { + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Material getMaterial() { + return Material.SPIDER_EYE; + } + + @Override + public String getDisplayName() { + return ChatColor.DARK_PURPLE.toString() + ChatColor.BOLD + "Spider Ability"; + } + + @Override + public List getLore() { + final List toReturn = new ArrayList<>(); + + toReturn.add(ChatColor.GRAY + "Click to activate and for the"); + toReturn.add(ChatColor.GRAY + "next 15 seconds, all hits dealt"); + toReturn.add(ChatColor.GRAY + "have a 10% chance of putting a"); + toReturn.add(ChatColor.GRAY + "cobweb underneath the enemy."); + + return toReturn; + } + + @Override + public long getCooldown() { + return 90_000L; + } + + @EventHandler(priority = EventPriority.NORMAL) + public void onInteract(PlayerInteractEvent event) { + if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK && event.getAction() != Action.LEFT_CLICK_AIR && event.getAction() != Action.LEFT_CLICK_BLOCK) { + return; + } + + final Player player = event.getPlayer(); + + final Location blockAt = player.getLocation(); + + if (!this.isSimilar(event.getItem())) { + return; + } + + final AbilityUseEvent abilityUseEvent = new AbilityUseEvent(player, null, player.getLocation(), this, false); + PotPvPSI.getInstance().getServer().getPluginManager().callEvent(abilityUseEvent); + + if (abilityUseEvent.isCancelled()) { + return; + } + + event.setCancelled(true); + + if (player.getItemInHand().getAmount() == 1) { + player.setItemInHand(null); + } else { + player.getItemInHand().setAmount(player.getItemInHand().getAmount()-1); + } + + player.setMetadata("SPIDER", new FixedMetadataValue(PotPvPSI.getInstance(), true)); + + PotPvPSI.getInstance().getServer().getScheduler().runTaskLater(PotPvPSI.getInstance(), () -> player.removeMetadata("SPIDER", PotPvPSI.getInstance()), 20*15); + + this.applyCooldown(player); + } + + @EventHandler(priority = EventPriority.MONITOR) + private void onPlayerHit(EntityDamageByEntityEvent event) { + if (event.isCancelled() || !(event.getDamager() instanceof Player) || !(event.getEntity() instanceof Player)) { + return; + } + + final Player target = (Player) event.getEntity(); + final Player damager = (Player) event.getDamager(); + + if (!damager.hasMetadata("SPIDER")) { + return; + } + + if (ThreadLocalRandom.current().nextInt(100) > 10) { + return; + } + + final Block blockAt = target.getLocation().getBlock(); + + if (blockAt.getType() == Material.WEB) { + return; + } + + final Block block2 = blockAt.getRelative(BlockFace.SOUTH); + final Block block3 = blockAt.getRelative(BlockFace.WEST); + final Block block4 = block3.getRelative(BlockFace.SOUTH); + + if (blockAt.getType() == Material.AIR) { + blockAt.setType(Material.WEB); + } + + if (block2.getType() == Material.AIR) { + block2.setType(Material.WEB); + } + + if (block3.getType() == Material.AIR) { + block3.setType(Material.WEB); + } + + if (block4.getType() == Material.AIR) { + block4.setType(Material.WEB); + } + + PotPvPSI.getInstance().getServer().getScheduler().runTaskLater(PotPvPSI.getInstance(), () -> { + if (blockAt.getType() == Material.WEB) { + blockAt.setType(Material.AIR); + } + + if (block2.getType() == Material.WEB) { + block2.setType(Material.AIR); + } + + if (block3.getType() == Material.WEB) { + block3.setType(Material.AIR); + } + + if (block4.getType() == Material.WEB) { + block4.setType(Material.AIR); + } + }, 20*15); + + damager.playSound(damager.getLocation(), Sound.LEVEL_UP, 1, 1); + damager.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You have put " + PatchedPlayerUtils.getFormattedName(target.getUniqueId()) + ChatColor.RED + " in a cobweb!"); + damager.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + target.getName() + " has been put in a cobweb due to the " + this.getDisplayName() + "!"); + + target.playSound(target.getLocation(), Sound.ZOMBIE_WOODBREAK, 1, 1); + target.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You were placed in a cobweb due to the " + this.getDisplayName() + ChatColor.RED + "!"); + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/SwitchStick.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/SwitchStick.java new file mode 100644 index 0000000..1b2f48e --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/SwitchStick.java @@ -0,0 +1,92 @@ +package com.elevatemc.potpvp.ability.type; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.Ability; +import com.elevatemc.potpvp.ability.listener.events.AbilityUseEvent; +import com.elevatemc.potpvp.pvpclasses.PvPClassHandler; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +public class SwitchStick extends Ability { + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Material getMaterial() { + return Material.STICK; + } + + @Override + public String getDisplayName() { + return ChatColor.AQUA.toString() + ChatColor.BOLD + "Switch Stick"; + } + + @Override + public List getLore() { + final List toReturn = new ArrayList<>(); + + toReturn.add(ChatColor.GRAY + "Hit a player to turn their head 180 degrees."); + + return toReturn; + } + + @Override + public long getCooldown() { + return TimeUnit.MINUTES.toMillis(1L) + TimeUnit.SECONDS.toMillis(30); + } + + @EventHandler(priority = EventPriority.MONITOR) + private void onDamage(EntityDamageByEntityEvent event) { + if (event.isCancelled() || !(event.getDamager() instanceof Player) || !(event.getEntity() instanceof Player)) { + return; + } + + final Player target = (Player) event.getEntity(); + final Player damager = (Player) event.getDamager(); + + if (damager.getItemInHand() == null || !this.isSimilar(damager.getItemInHand())) { + return; + } + + if (PvPClassHandler.getPvPClass(target) != null) { + damager.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You may not use a " + this.getDisplayName() + ChatColor.RED + " on a " + ChatColor.WHITE + Objects.requireNonNull(PvPClassHandler.getPvPClass(target)).getName() + ChatColor.RED + "."); + return; + } + + final AbilityUseEvent abilityUseEvent = new AbilityUseEvent(damager, target, damager.getLocation(), this, false); + PotPvPSI.getInstance().getServer().getPluginManager().callEvent(abilityUseEvent); + + if (abilityUseEvent.isCancelled()) { + return; + } + + final Location location = target.getLocation(); + location.setYaw(location.getYaw()+180.0F); + target.teleport(location); + + target.sendMessage(damager.getName() + ChatColor.RED + " has used the " + this.getDisplayName() + ChatColor.RED + " on you!"); + + final ItemStack itemStack = damager.getItemInHand(); + + if (itemStack.getAmount() == 1) { + damager.setItemInHand(null); + } else { + itemStack.setAmount(itemStack.getAmount()-1); + } + + this.applyCooldown(damager); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/TimeStone.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/TimeStone.java new file mode 100644 index 0000000..c3999b3 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/TimeStone.java @@ -0,0 +1,87 @@ +package com.elevatemc.potpvp.ability.type; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.Ability; +import com.elevatemc.potpvp.ability.listener.events.AbilityUseEvent; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; + +import java.util.ArrayList; +import java.util.List; + +public class TimeStone extends Ability { + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Material getMaterial() { + return Material.EMERALD; + } + + @Override + public String getDisplayName() { + return ChatColor.GREEN.toString() + ChatColor.BOLD + "Time Stone"; + } + + @Override + public List getLore() { + final List toReturn = new ArrayList<>(); + + toReturn.add(ChatColor.GRAY + "Right Click to reset all of your"); + toReturn.add(ChatColor.GRAY + "ability/partner item cooldowns."); + + return toReturn; + } + + @Override + public long getCooldown() { + return 600_000L; + } + + @EventHandler(priority = EventPriority.NORMAL) + public void onInteract(PlayerInteractEvent event) { + if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; + } + + final Player player = event.getPlayer(); + + if (!this.isSimilar(event.getItem())) { + return; + } + + final AbilityUseEvent abilityUseEvent = new AbilityUseEvent(player, null, player.getLocation(), this, false); + PotPvPSI.getInstance().getServer().getPluginManager().callEvent(abilityUseEvent); + + if (abilityUseEvent.isCancelled()) { + return; + } + + event.setCancelled(true); + + if (player.getItemInHand().getAmount() == 1) { + player.setItemInHand(null); + } else { + player.getItemInHand().setAmount(player.getItemInHand().getAmount()-1); + } + + for (Ability value : PotPvPSI.getInstance().getAbilityHandler().getAbilities().values()) { + if (value.getName().equalsIgnoreCase("TimeStone")) { + continue; + } + + if (value.hasCooldown(player)) { + value.removeCooldown(player); + } + } + + this.applyCooldown(player); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/TimeWarp.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/TimeWarp.java new file mode 100644 index 0000000..bfed210 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/TimeWarp.java @@ -0,0 +1,203 @@ +package com.elevatemc.potpvp.ability.type; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.Ability; +import com.elevatemc.potpvp.ability.listener.events.AbilityUseEvent; +import com.elevatemc.spigot.event.PlayerPearlRefundEvent; +import org.bukkit.*; +import org.bukkit.entity.EnderPearl; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.ProjectileLaunchEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.*; + +public class TimeWarp extends Ability { + public static Map oldPearlLocations = new HashMap<>(); + public static Map pearlLocations = new HashMap<>(); + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Material getMaterial() { + return Material.WATCH; + } + + @Override + public String getDisplayName() { + return ChatColor.GOLD.toString() + ChatColor.BOLD + "Time Warp"; + } + + @Override + public List getLore() { + final List toReturn = new ArrayList<>(); + + toReturn.add(ChatColor.GRAY + "Right Click to go back to where you"); + toReturn.add(ChatColor.GRAY + "pearled from 15 seconds ago."); + return toReturn; + } + + @Override + public long getCooldown() { + return 90_000L; + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onInteract(PlayerInteractEvent event) { + if (!this.isSimilar(event.getItem()) || event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; + } + + final Player player = event.getPlayer(); + + event.setCancelled(true); + + final AbilityUseEvent abilityUseEvent = new AbilityUseEvent(player, null, player.getLocation(), this, false); + PotPvPSI.getInstance().getServer().getPluginManager().callEvent(abilityUseEvent); + + if (abilityUseEvent.isCancelled()) { + return; + } + + if (!pearlLocations.containsKey(player.getUniqueId())) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You have not thrown a pearl in the last 16 seconds..."); + return; + } + + if (player.hasMetadata("NINJASTAR")) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You may not use a " + this.getDisplayName() + ChatColor.RED + " whilst someone is using a Ninja Star on you!"); + return; + } + + if (player.getItemInHand().getAmount() == 1) { + player.setItemInHand(null); + } else { + player.getItemInHand().setAmount(player.getItemInHand().getAmount() - 1); + } + + final Location location = pearlLocations.remove(player.getUniqueId()).clone(); + + new BukkitRunnable() { + private int seconds = 4; + + @Override + public void run() { + this.seconds--; + + if (!event.getPlayer().isOnline()) { + this.cancel(); + return; + } + + if (!PotPvPSI.getInstance().getMatchHandler().isPlayingMatch(player)) { + return; + } + + if (player.hasMetadata("NINJASTAR")) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You may not use a " + getDisplayName() + ChatColor.RED + " whilst someone is using a Ninja Star on you!"); + this.cancel(); + refund(player); + return; + } + + if (this.seconds <= 0) { + player.teleport(location); + + this.cancel(); + return; + } + + event.getPlayer().sendMessage(ChatColor.YELLOW + "Teleporting in " + ChatColor.RED + this.seconds + ChatColor.YELLOW + " second" + (this.seconds == 1 ? "":"s") + "..."); + event.getPlayer().playSound(event.getPlayer().getLocation(), Sound.NOTE_PLING, 1, 1); + } + }.runTaskTimer(PotPvPSI.getInstance(),0L,20L); + + this.applyCooldown(player); + } + + public void refund(Player player) { + player.getInventory().addItem(this.hassanStack.clone()); + + this.removeCooldown(player); + } + + @EventHandler(priority = EventPriority.HIGHEST) + private void onLaunch(ProjectileLaunchEvent event) { + if (event.isCancelled() || !(event.getEntity() instanceof EnderPearl) || !(event.getEntity().getShooter() instanceof Player)) { + return; + } + + final Player shooter = (Player) event.getEntity().getShooter(); + final UUID shooterUUID = shooter.getUniqueId(); + + final Location location = shooter.getLocation(); + + pearlLocations.remove(shooterUUID); + if (pearlLocations.containsKey(shooterUUID)) { + oldPearlLocations.put(shooterUUID, pearlLocations.get(shooterUUID)); + } + pearlLocations.put(shooterUUID, shooter.getLocation()); + + PotPvPSI.getInstance().getServer().getScheduler().runTaskLater(PotPvPSI.getInstance(), () -> { + if (!pearlLocations.containsKey(shooter.getUniqueId())) { + return; + } + + final Location newLocation = pearlLocations.get(shooter.getUniqueId()); + + if (location.getX() != newLocation.getX() || location.getY() != newLocation.getY() || location.getZ() != newLocation.getZ()) { + return; + } + + pearlLocations.remove(shooter.getUniqueId()); + }, 20*16); + } + + @EventHandler(priority = EventPriority.LOW) + private void onPearl(PlayerInteractEvent event) { + final Player player = event.getPlayer(); + + if (event.getItem() == null || !event.getAction().name().contains("RIGHT")) { + return; + } + + if (event.getItem().getType() != Material.ENDER_PEARL) { + return; + } + + final Location location = player.getLocation().clone(); + + pearlLocations.remove(player.getUniqueId()); + pearlLocations.put(player.getUniqueId(), location); + + PotPvPSI.getInstance().getServer().getScheduler().runTaskLater(PotPvPSI.getInstance(), () -> { + if (!pearlLocations.containsKey(player.getUniqueId())) { + return; + } + + final Location newLocation = pearlLocations.get(player.getUniqueId()); + + if (location.getX() != newLocation.getX() || location.getY() != newLocation.getY() || location.getZ() != newLocation.getZ()) { + return; + } + + pearlLocations.remove(player.getUniqueId()); + }, 20*15); + } + + @EventHandler + private void onPearlRefund(PlayerPearlRefundEvent event) { + UUID uuid = event.getPlayer().getUniqueId(); + if (oldPearlLocations.containsKey(uuid)) { + oldPearlLocations.remove(uuid); + pearlLocations.put(uuid, oldPearlLocations.get(uuid)); + } + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/Vampire.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/Vampire.java new file mode 100644 index 0000000..54bc3bb --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/ability/type/Vampire.java @@ -0,0 +1,188 @@ +package com.elevatemc.potpvp.ability.type; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.Ability; +import com.elevatemc.potpvp.ability.listener.events.AbilityUseEvent; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Bat; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.ArrayList; +import java.util.List; + +public class Vampire extends Ability { + public Vampire() { + super(); + this.hassanStack.setDurability((byte) 65); + ItemMeta meta = this.hassanStack.getItemMeta(); + +// meta.addEnchant(glow, 1, true); + this.hassanStack.setItemMeta(meta); + } + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Material getMaterial() { + return Material.MONSTER_EGG; + } + + @Override + public String getDisplayName() { + return ChatColor.DARK_PURPLE.toString() + ChatColor.BOLD + "Vampire Effect"; + } + + @Override + public List getLore() { + final List toReturn = new ArrayList<>(); + + toReturn.add(ChatColor.GRAY + "Spawn 3 bats that give you the passive"); + toReturn.add(ChatColor.GRAY + "bard effects: strength, regeneration,"); + toReturn.add(ChatColor.GRAY + "and resistance. Each bat gives a"); + toReturn.add(ChatColor.GRAY + "different effect."); + + return toReturn; + } + + @Override + public long getCooldown() { + return 120_000L; + } + + @EventHandler(priority = EventPriority.LOWEST) + private void onInteract(PlayerInteractEvent event) { + final Player player = event.getPlayer(); + + if (!this.isSimilar(player.getItemInHand()) || event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; + } + + event.setCancelled(true); + + final AbilityUseEvent abilityUseEvent = new AbilityUseEvent(player, null, player.getLocation(), this, false); + PotPvPSI.getInstance().getServer().getPluginManager().callEvent(abilityUseEvent); + + if (abilityUseEvent.isCancelled()) { + return; + } + + if (player.getItemInHand().getAmount() == 1) { + player.setItemInHand(null); + } else { + player.getItemInHand().setAmount(player.getItemInHand().getAmount() - 1); + } + + player.updateInventory(); + + final Bat bat1 = (Bat) player.getWorld().spawnEntity(player.getLocation(), EntityType.BAT); + final Bat bat2 = (Bat) player.getWorld().spawnEntity(player.getLocation(), EntityType.BAT); + final Bat bat3 = (Bat) player.getWorld().spawnEntity(player.getLocation(), EntityType.BAT); + + bat1.setAwake(true); + bat1.setCustomNameVisible(true); + bat1.setCustomName(ChatColor.DARK_RED + "✖ " + ChatColor.RED + player.getName() + "'s Vampire #1"); + + bat2.setAwake(true); + bat2.setCustomNameVisible(true); + bat2.setCustomName(ChatColor.DARK_RED + "✖ " + ChatColor.RED + player.getName() + "'s Vampire #2"); + + bat3.setAwake(true); + bat3.setCustomNameVisible(true); + bat3.setCustomName(ChatColor.DARK_RED + "✖ " + ChatColor.RED + player.getName() + "'s Vampire #3"); + + PotPvPSI.getInstance().getServer().getScheduler().runTaskLater(PotPvPSI.getInstance(), () -> { + + if (!bat1.isDead()) { + bat1.remove(); + } + + if (!bat2.isDead()) { + bat2.remove(); + } + + if (!bat3.isDead()) { + bat3.remove(); + } + + if (player.isOnline()) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Your bats have despawned!"); + } + + }, 20*60*3); + + new BukkitRunnable() { + @Override + public void run() { + + if (bat1.isDead() && bat2.isDead() && bat3.isDead()) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "All your bats are now dead!"); + this.cancel(); + return; + } + + if (!bat1.isDead() && bat1.getLocation().distance(player.getLocation()) <= 15) { + bat1.setAwake(true); + player.addPotionEffect(new PotionEffect(PotionEffectType.DAMAGE_RESISTANCE, 20 * 6, 0), true); + } + + if (!bat2.isDead() && bat2.getLocation().distance(player.getLocation()) <= 15) { + bat2.setAwake(true); + player.addPotionEffect(new PotionEffect(PotionEffectType.REGENERATION, 20 * 6, 0), true); + } + + if (!bat3.isDead() && bat3.getLocation().distance(player.getLocation()) <= 15) { + bat3.setAwake(true); + player.addPotionEffect(new PotionEffect(PotionEffectType.INCREASE_DAMAGE, 20 * 6, 0), true); + } + } + }.runTaskTimer(PotPvPSI.getInstance(), 0, 40); + + this.applyCooldown(player); + } + + @EventHandler(priority = EventPriority.LOW) + private void onDeath(EntityDeathEvent event) { + if (!(event.getEntity() instanceof Bat)) { + return; + } + + final Bat bat = (Bat) event.getEntity(); + final String customName = bat.getCustomName(); + + final int id = Integer.parseInt(customName.charAt(customName.length() - 1) + ""); + + final String playersName = ChatColor.stripColor(customName).replace("'s Vampire #" + id, ""); + + final Player player = PotPvPSI.getInstance().getServer().getPlayer(playersName); + + if (player == null) { + return; + } + + switch (id) { + case 1: + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Your Resistance bat has died!"); + break; + case 2: + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Your Regeneration bat has died!"); + break; + case 3: + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Your Strength bat has died!"); + break; + } + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/Arena.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/Arena.java new file mode 100644 index 0000000..91473a2 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/Arena.java @@ -0,0 +1,277 @@ +package com.elevatemc.potpvp.arena; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.util.AngleUtils; +import com.elevatemc.elib.cuboid.Cuboid; +import com.elevatemc.elib.util.Callback; +import net.techcable.tacospigot.event.entity.ChunkSnapshot; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.Skull; +import org.bukkit.craftbukkit.v1_8_R3.util.LongHash; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Predicate; + +/** + * Represents a pasted instance of an {@link ArenaSchematic}. + * See {@link com.elevatemc.potpvp.arena} for a comparision of + * {@link Arena}s and {@link ArenaSchematic}s. + */ +public final class Arena { + + /** + * The name of the {@link ArenaSchematic} this Arena is + * copied from. + * @see com.elevatemc.potpvp.arena + */ + @Getter private String schematic; + + /** + * What number copy this arena is. + * @see com.elevatemc.potpvp.arena + */ + @Getter private int copy; + + /** + * Bounding box for this arena + */ + private Cuboid bounds; + + /** + * Team 1 spawn location for this arena + * For purposes of arena definition we ignore + * non-two-teamed matches. + */ + private Location team1Spawn; + + /** + * Team 2 spawn location for this arena + * For purposes of arena definition we ignore + * non-two-teamed matches. + */ + private Location team2Spawn; + + /** + * Spectator spawn location for this arena + */ + private Location spectatorSpawn; + + private List eventSpawns; + + /** + * If this arena is currently being used + * @see ArenaHandler#allocateUnusedArena(Predicate) + * @see ArenaHandler#releaseArena(Arena) + */ + // AccessLevel.NONE so arenas can only marked as in use + // or not in use by the appropriate methods in ArenaHandler + @Getter @Setter(AccessLevel.PACKAGE) private transient boolean inUse; + + private final transient Map chunkSnapshots = Maps.newHashMap(); + + public Arena() {} // for gson + + public Arena(String schematic, int copy, Cuboid bounds) { + this.schematic = Preconditions.checkNotNull(schematic); + this.copy = copy; + this.bounds = Preconditions.checkNotNull(bounds); + + scanLocations(); + } + + public Location getSpectatorSpawn() { + // if it's been defined in the actual map file or calculated before + if (spectatorSpawn != null) { + return spectatorSpawn; + } + + int xDiff = Math.abs(team1Spawn.getBlockX() - team2Spawn.getBlockX()); + int yDiff = Math.abs(team1Spawn.getBlockY() - team2Spawn.getBlockY()); + int zDiff = Math.abs(team1Spawn.getBlockZ() - team2Spawn.getBlockZ()); + + int newX = Math.min(team1Spawn.getBlockX(), team2Spawn.getBlockX()) + (xDiff / 2); + int newY = Math.min(team1Spawn.getBlockY(), team2Spawn.getBlockY()) + (yDiff / 2); + int newZ = Math.min(team1Spawn.getBlockZ(), team2Spawn.getBlockZ()) + (zDiff / 2); + + ArenaHandler arenaHandler = PotPvPSI.getInstance().getArenaHandler(); + spectatorSpawn = new Location(arenaHandler.getArenaWorld(), newX, newY, newZ); + + while (spectatorSpawn.getBlock().getType().isSolid()) { + spectatorSpawn = spectatorSpawn.add(0, 1, 0); + } + + return spectatorSpawn; + } + + private void scanLocations() { + // iterating the cuboid doesn't work because + // its iterator is broken :( + forEachBlock(block -> { + Material type = block.getType(); + + if (type != Material.SKULL) { + return; + } + + Skull skull = (Skull) block.getState(); + Block below = block.getRelative(BlockFace.DOWN); + + Location skullLocation = block.getLocation().clone().add(0.5, 1.5, 0.5); + skullLocation.setYaw(AngleUtils.faceToYaw(skull.getRotation()) + 90); + + switch (skull.getSkullType()) { + case SKELETON: + spectatorSpawn = skullLocation; + + block.setType(Material.AIR); + + if (below.getType() == Material.FENCE) { + below.setType(Material.AIR); + } + + break; + case PLAYER: + if (team1Spawn == null) { + team1Spawn = skullLocation; + } else { + team2Spawn = skullLocation; + } + + block.setType(Material.AIR); + + if (below.getType() == Material.FENCE) { + below.setType(Material.AIR); + } + + break; + case CREEPER: + block.setType(Material.AIR); + + if (below.getType() == Material.FENCE) { + below.setType(Material.AIR); + } + + if (eventSpawns == null) { + eventSpawns = new ArrayList<>(); + } + + if (!(eventSpawns.contains(skullLocation))) { + eventSpawns.add(skullLocation); + } + break; + case WITHER: + team1Spawn = skullLocation; + + block.setType(Material.AIR); + + if (below.getType() == Material.FENCE) { + below.setType(Material.AIR); + } + + break; + case ZOMBIE: + team2Spawn = skullLocation; + + block.setType(Material.AIR); + + if (below.getType() == Material.FENCE) { + below.setType(Material.AIR); + } + + break; + default: + break; + } + }); + + Preconditions.checkNotNull(team1Spawn, "Team 1 spawn (player skull) cannot be null."); + Preconditions.checkNotNull(team2Spawn, "Team 2 spawn (player skull) cannot be null."); + } + + private void forEachBlock(Callback callback) { + Location start = bounds.getLowerNE(); + Location end = bounds.getUpperSW(); + World world = bounds.getWorld(); + + for (int x = start.getBlockX(); x < end.getBlockX(); x++) { + for (int y = start.getBlockY(); y < end.getBlockY(); y++) { + for (int z = start.getBlockZ(); z < end.getBlockZ(); z++) { + callback.callback(world.getBlockAt(x, y, z)); + } + } + } + } + + public void takeSnapshot() { + synchronized (chunkSnapshots) { + forEachChunk(chunk -> chunkSnapshots.put(LongHash.toLong(chunk.getX(), chunk.getZ()), (ChunkSnapshot) chunk.takeSnapshot())); + } + } + + public void restore() { + synchronized (chunkSnapshots) { + World world = bounds.getWorld(); + chunkSnapshots.forEach((key, value) -> world.getChunkAt(LongHash.msw(key), LongHash.lsw(key)).restoreSnapshot((ChunkSnapshot) value)); + chunkSnapshots.clear(); + } + } + + private void forEachChunk(Callback callback) { + int lowerX = bounds.getLowerX() >> 4; + int lowerZ = bounds.getLowerZ() >> 4; + int upperX = bounds.getUpperX() >> 4; + int upperZ = bounds.getUpperZ() >> 4; + + World world = bounds.getWorld(); + + for (int x = lowerX; x <= upperX; x++) { + for (int z = lowerZ; z <= upperZ; z++) { + callback.callback(world.getChunkAt(x, z)); + } + } + } + + @Override + public boolean equals(Object o) { + if (o instanceof Arena) { + Arena a = (Arena) o; + return a.schematic.equals(schematic) && a.copy == copy; + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(schematic, copy); + } + + public Location getTeam1Spawn() { + return team1Spawn; + } + + public Location getTeam2Spawn() { + return team2Spawn; + } + + public List getEventSpawns() { + return eventSpawns; + } + + public Cuboid getBounds() { + return bounds; + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/ArenaGrid.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/ArenaGrid.java new file mode 100644 index 0000000..5096549 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/ArenaGrid.java @@ -0,0 +1,167 @@ +package com.elevatemc.potpvp.arena; + +import com.elevatemc.elib.util.Pair; +import com.sk89q.worldedit.CuboidClipboard; +import com.sk89q.worldedit.Vector; +import it.unimi.dsi.fastutil.longs.LongHash; +import lombok.Getter; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.elib.cuboid.Cuboid; +import net.minecraft.server.v1_8_R3.Chunk; +import net.minecraft.server.v1_8_R3.WorldServer; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_8_R3.CraftWorld; +import org.bukkit.scheduler.BukkitRunnable; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +/** + * Represents the grid on the world + * + * Z -------------> + * X (1,1) (1,2) + * | (2,1) (2,2) + * | (3,1) (3,2) + * | (4,1) (4,2) + * V + * + * X is per {@link ArenaSchematic} and is stored in {@link ArenaSchematic#gridIndex}. + * Z is per {@link Arena} and is the {@link Arena}'s {@link Arena#copy}. + * + * Each arena is allocated {@link #GRID_SPACING_Z} by {@link #GRID_SPACING_X} blocks + * + * @author Mazen Kotb + */ +public final class ArenaGrid { + + /** + * 'Starting' point of the grid. Expands (+, +) from this point. + */ + public static final Vector STARTING_POINT = new Vector(1_000, 80, 1_000); + + public static final int GRID_SPACING_X = 1500; + public static final int GRID_SPACING_Z = 1500; + + private static final long SCALE_TIME = 5; // seconds in between each arena scale + + @Getter private boolean busy = false; + + public void scaleCopies(ArenaSchematic schematic, int desiredCopies, Runnable callback) { + if (busy) { + throw new IllegalStateException("Grid is busy!"); + } + + busy = true; + + ArenaHandler arenaHandler = PotPvPSI.getInstance().getArenaHandler(); + int currentCopies = arenaHandler.countArenas(schematic); + + Runnable saveWrapper = () -> { + try { + arenaHandler.saveArenas(); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + + busy = false; + callback.run(); + }; + + if (currentCopies > desiredCopies) { + deleteArenas(schematic, currentCopies, currentCopies - desiredCopies, saveWrapper); + } else if (currentCopies < desiredCopies) { + createArenas(schematic, currentCopies, desiredCopies - currentCopies, saveWrapper); + } else { + saveWrapper.run(); + } + } + + private void createArenas(ArenaSchematic schematic, int currentCopies, int toCreate, Runnable callback) { + ArenaHandler arenaHandler = PotPvPSI.getInstance().getArenaHandler(); + + new BukkitRunnable() { + + int created = 0; + + @Override + public void run() { + int copy = currentCopies + created + 1; // arenas are 1-indexed, not 0 + int xStart = STARTING_POINT.getBlockX() + (GRID_SPACING_X * schematic.getGridIndex()); + int zStart = STARTING_POINT.getBlockZ() + (GRID_SPACING_Z * copy); + + try { + Arena created = createArena(schematic, xStart, zStart, copy); + arenaHandler.registerArena(created); + } catch (Exception ex) { + ex.printStackTrace(); + callback.run(); + cancel(); + } + + created++; + + if (created == toCreate) { + callback.run(); + cancel(); + } + } + + }.runTaskTimer(PotPvPSI.getInstance(), 8L, SCALE_TIME * 20L); + } + + private void deleteArenas(ArenaSchematic schematic, int currentCopies, int toDelete, Runnable callback) { + ArenaHandler arenaHandler = PotPvPSI.getInstance().getArenaHandler(); + + new BukkitRunnable() { + + int deleted = 0; + + @Override + public void run() { + int copy = currentCopies - deleted; + Arena existing = arenaHandler.getArena(schematic, copy); + + if (existing != null) { + WorldEditUtils.clear(existing.getBounds()); + arenaHandler.unregisterArena(existing); + } + + deleted++; + + if (deleted == toDelete) { + callback.run(); + cancel(); + } + } + + }.runTaskTimer(PotPvPSI.getInstance(), 8L, 8L); + } + + private Arena createArena(ArenaSchematic schematic, int xStart, int zStart, int copy) { + Vector pasteAt = new Vector(xStart, STARTING_POINT.getY(), zStart); + CuboidClipboard clipboard; + + try { + clipboard = WorldEditUtils.paste(schematic, pasteAt); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + + Location lowerCorner = WorldEditUtils.vectorToLocation(pasteAt); + Location upperCorner = WorldEditUtils.vectorToLocation(pasteAt.add(clipboard.getSize())); + + return new Arena( + schematic.getName(), + copy, + new Cuboid(lowerCorner, upperCorner) + ); + } + + public void free() { + busy = false; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/ArenaHandler.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/ArenaHandler.java new file mode 100644 index 0000000..22517ac --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/ArenaHandler.java @@ -0,0 +1,282 @@ +package com.elevatemc.potpvp.arena; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.google.common.base.Charsets; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import com.google.common.io.Files; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import lombok.Getter; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.event.ArenaAllocatedEvent; +import com.elevatemc.potpvp.arena.event.ArenaReleasedEvent; +import com.elevatemc.potpvp.arena.listener.ArenaItemResetListener; +import com.elevatemc.potpvp.util.AlphanumComparator; +import com.elevatemc.elib.eLib; +import org.bukkit.Bukkit; +import org.bukkit.World; +import com.google.gson.reflect.TypeToken; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.lang.reflect.Type; +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * Facilitates easy access to {@link ArenaSchematic}s and to {@link Arena}s + * based on their schematic+copy pair + */ +public final class ArenaHandler { + + public static final File WORLD_EDIT_SCHEMATICS_FOLDER = new File(JavaPlugin.getPlugin(WorldEditPlugin.class).getDataFolder(), "schematics"); + private static final String ARENA_INSTANCES_FILE_NAME = "arenaInstances.json"; + private static final String SCHEMATICS_FILE_NAME = "schematics.json"; + + // schematic -> (instance id -> Arena instance) + private final Map> arenaInstances = new HashMap<>(); + // schematic name -> ArenaSchematic instance + private Map schematics = new LinkedHashMap<>(); + @Getter private final ArenaGrid grid = new ArenaGrid(); + + public ArenaHandler() { + Bukkit.getPluginManager().registerEvents(new ArenaItemResetListener(), PotPvPSI.getInstance()); + + File worldFolder = getArenaWorld().getWorldFolder(); + + File arenaInstancesFile = new File(worldFolder, ARENA_INSTANCES_FILE_NAME); + File schematicsFile = new File(worldFolder, SCHEMATICS_FILE_NAME); + + try { + // parsed as a List and then inserted into Map> + if (arenaInstancesFile.exists()) { + try (Reader arenaInstancesReader = Files.newReader(arenaInstancesFile, Charsets.UTF_8)) { + Type arenaListType = new TypeToken>(){}.getType(); + List arenaList = PotPvPSI.getGson().fromJson(arenaInstancesReader, arenaListType); + + for (Arena arena : arenaList) { + // create inner Map for schematic if not present + arenaInstances.computeIfAbsent(arena.getSchematic(), i -> new HashMap<>()); + + // register this copy with the inner Map + arenaInstances.get(arena.getSchematic()).put(arena.getCopy(), arena); + } + } + } + + // parsed as a List and then inserted into Map + if (schematicsFile.exists()) { + try (Reader schematicsFileReader = Files.newReader(schematicsFile, Charsets.UTF_8)) { + Type schematicListType = new TypeToken>() {}.getType(); + List schematicList = PotPvPSI.getGson().fromJson(schematicsFileReader, schematicListType); + + for (ArenaSchematic schematic : schematicList) { + // Make sure every game mode is in the toggle list + for (GameMode gameMode : GameMode.getAll()) { + schematic.getEnabledGameModes().putIfAbsent(gameMode.getId(), false); + } + this.schematics.put(schematic.getName(), schematic); + } + } + sortSchematics(); + } + } catch (IOException ex) { + // just rethrow, can't recover from arenas failing to load + throw new RuntimeException(ex); + } + } + + public void sortSchematics() { + Comparator alphanumComparator = new AlphanumComparator(); + schematics = schematics.entrySet().stream() + .sorted((o1, o2) -> { + String firstName = o1.getValue().getDisplayName(); + String secondName = o2.getValue().getDisplayName(); + return alphanumComparator.compare(firstName, secondName); + }) + .collect(Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue, + (e1, e2) -> e1, LinkedHashMap::new)); + } + + public void saveSchematics() throws IOException { + Files.write( + PotPvPSI.getGson().toJson(schematics.values()), + new File(getArenaWorld().getWorldFolder(), SCHEMATICS_FILE_NAME), + Charsets.UTF_8 + ); + } + + public void saveArenas() throws IOException { + List allArenas = new ArrayList<>(); + + arenaInstances.forEach((schematic, copies) -> { + allArenas.addAll(copies.values()); + }); + + Files.write( + PotPvPSI.getGson().toJson(allArenas), + new File(getArenaWorld().getWorldFolder(), ARENA_INSTANCES_FILE_NAME), + Charsets.UTF_8 + ); + } + + public World getArenaWorld() { + return Bukkit.getWorld("arenas"); + } + + public void registerSchematic(ArenaSchematic schematic) { + // assign a grid index upon creation. currently this will not reuse + // lower grid indexes from deleted arenas. + int lastGridIndex = 0; + + for (ArenaSchematic otherSchematic : schematics.values()) { + lastGridIndex = Math.max(lastGridIndex, otherSchematic.getGridIndex()); + } + + schematic.setGridIndex(lastGridIndex + 1); + // Make sure every game mode is in the toggle list + for (GameMode gameMode : GameMode.getAll()) { + schematic.getEnabledGameModes().putIfAbsent(gameMode.getId(), false); + } + schematics.put(schematic.getName(), schematic); + sortSchematics(); + } + + public void unregisterSchematic(ArenaSchematic schematic) { + schematics.remove(schematic.getName()); + } + + void registerArena(Arena arena) { + Map copies = arenaInstances.get(arena.getSchematic()); + + if (copies == null) { + copies = new HashMap<>(); + arenaInstances.put(arena.getSchematic(), copies); + } + + copies.put(arena.getCopy(), arena); + } + + void unregisterArena(Arena arena) { + Map copies = arenaInstances.get(arena.getSchematic()); + + if (copies != null) { + copies.remove(arena.getCopy()); + } + } + + /** + * Finds an arena by its schematic and copy pair + * @param schematic ArenaSchematic to use when looking up arena + * @param copy copy of arena to look up + * @return Arena object existing for specified schematic and copy pair, if one exists + */ + public Arena getArena(ArenaSchematic schematic, int copy) { + Map arenaCopies = arenaInstances.get(schematic.getName()); + + if (arenaCopies != null) { + return arenaCopies.get(copy); + } else { + return null; + } + } + + /** + * Finds all arena instances for the given schematic + * @param schematic schematic to look up arenas for + * @return immutable set of all arenas for given schematic + */ + public Set getArenas(ArenaSchematic schematic) { + Map arenaCopies = arenaInstances.get(schematic.getName()); + + if (arenaCopies != null) { + return ImmutableSet.copyOf(arenaCopies.values()); + } else { + return ImmutableSet.of(); + } + } + + /** + * Counts the number of arena instances present for the given schematic + * @param schematic schematic to count arenas for + * @return number of copies present of the given schematic + */ + public int countArenas(ArenaSchematic schematic) { + Map arenaCopies = arenaInstances.get(schematic.getName()); + return arenaCopies != null ? arenaCopies.size() : 0; + } + + /** + * Finds all schematic instances registered + * @return immutable set of all schematics registered + */ + public Set getSchematics() { + return ImmutableSet.copyOf(schematics.values()); + } + + /** + * Finds an ArenaSchematic by its id + * @param schematicName schematic id to search with + * @return ArenaSchematic present for the given id, if one exists + */ + public ArenaSchematic getSchematic(String schematicName) { + return schematics.get(schematicName); + } + + /** + * Attempts to allocate an arena for use, using the Predicate provided to determine + * which arenas are eligible for use. Handles calling {@link com.elevatemc.potpvp.arena.event.ArenaAllocatedEvent} + * automatically. + * @param acceptableSchematicPredicate Predicate to use to determine if an {@link ArenaSchematic} + * is eligible for use. + * @return The arena which has been allocated for use, or null, if one was not found. + */ + public Optional allocateUnusedArena(Predicate acceptableSchematicPredicate) { + List acceptableArenas = new ArrayList<>(); + + for (ArenaSchematic schematic : schematics.values()) { + if (!acceptableSchematicPredicate.test(schematic)) { + continue; + } + + if (!arenaInstances.containsKey(schematic.getName())) { + continue; + } + + for (Arena arena : arenaInstances.get(schematic.getName()).values()) { + if (!arena.isInUse()) { + acceptableArenas.add(arena); + } + } + } + + if (acceptableArenas.isEmpty()) { + return Optional.empty(); + } + + Arena selected = acceptableArenas.get(PotPvPSI.RANDOM.nextInt(acceptableArenas.size())); + + selected.setInUse(true); + Bukkit.getPluginManager().callEvent(new ArenaAllocatedEvent(selected)); + + return Optional.of(selected); + } + + /** + * Releases (unallocates) an arena so that it may be used again. Handles calling + * {@link com.elevatemc.potpvp.arena.event.ArenaReleasedEvent} automatically. + * @param arena the arena to release + */ + public void releaseArena(Arena arena) { + Preconditions.checkArgument(arena.isInUse(), "Cannot release arena not in use."); + + arena.setInUse(false); + Bukkit.getPluginManager().callEvent(new ArenaReleasedEvent(arena)); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/ArenaSchematic.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/ArenaSchematic.java new file mode 100644 index 0000000..7eb8663 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/ArenaSchematic.java @@ -0,0 +1,122 @@ +package com.elevatemc.potpvp.arena; + +import com.elevatemc.potpvp.events.EventHandler; +import com.elevatemc.potpvp.events.event.GameEvent; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import com.sk89q.worldedit.Vector; +import lombok.Getter; +import lombok.Setter; +import org.bukkit.Material; + +import java.io.File; +import java.util.Map; + +/** + * Represents an arena schematic. See {@link com.elevatemc.potpvp.arena} + * for a comparision of {@link Arena}s and {@link ArenaSchematic}s. + */ +public final class ArenaSchematic { + + /** + * Name of this schematic (ex "Candyland") + */ + @Getter private String name; + + /** + * Display name of this Arena, will be used when communicating a GameMode + * to players. Ex: "Map 15", "Ice KOTH", ... + */ + @Getter @Setter private String displayName; + + /** + * If matches can be scheduled on an instance of this arena. + * Only impacts match scheduling, admin commands are (ignoring visual differences) nonchanged + */ + @Setter private boolean enabled = false; + + /** + * Material info which will be used when rendering this + * arena in selection menus and such. + */ + @Getter @Setter private Material icon; + + @Getter @Setter private boolean supportsRanked = true; + @Getter @Setter private boolean pearlsAllowed = true; + + @Getter @Setter private Map enabledGameModes = Maps.newHashMap(); + + @Getter @Setter private String eventName = null; + + /** + * Index on the X axis on the grid (and in calculations regarding model arenas) + * @see ArenaGrid + */ + @Getter @Setter private int gridIndex; + + public ArenaSchematic() {} // for gson + + public ArenaSchematic(String name, String displayName) { + this.name = Preconditions.checkNotNull(name, "name"); + this.displayName = Preconditions.checkNotNull(displayName, "displayName"); + this.icon = Material.GRASS; + } + + public File getSchematicFile() { + return new File(ArenaHandler.WORLD_EDIT_SCHEMATICS_FOLDER, name + ".schematic"); + } + + public Vector getModelArenaLocation() { + int xModifier = ArenaGrid.GRID_SPACING_X * gridIndex; + + return new Vector( + ArenaGrid.STARTING_POINT.getBlockX() - xModifier, + ArenaGrid.STARTING_POINT.getBlockY(), + ArenaGrid.STARTING_POINT.getBlockZ() + ); + } + + public void pasteModelArena() throws Exception { + Vector start = getModelArenaLocation(); + WorldEditUtils.paste(this, start); + } + + public void removeModelArena() throws Exception { + Vector start = getModelArenaLocation(); + Vector size = WorldEditUtils.readSchematicSize(this); + + WorldEditUtils.clear( + start, + start.add(size) + ); + } + + public GameEvent getEvent() { + if (eventName != null) { + for (GameEvent event : EventHandler.EVENTS) { + if (event.getName().equalsIgnoreCase(eventName)) { + return event; + } + } + + eventName = null; + } + + return null; + } + + @Override + public boolean equals(Object o) { + return o instanceof ArenaSchematic && ((ArenaSchematic) o).name.equals(name); + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + public boolean isEnabled() { + return enabled; + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/ArenaSchematicParameterType.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/ArenaSchematicParameterType.java new file mode 100644 index 0000000..5311614 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/ArenaSchematicParameterType.java @@ -0,0 +1,42 @@ +package com.elevatemc.potpvp.arena; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.elib.command.param.ParameterType; +import org.apache.commons.lang3.StringUtils; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public final class ArenaSchematicParameterType implements ParameterType { + + @Override + public ArenaSchematic transform(CommandSender sender, String source) { + ArenaHandler arenaHandler = PotPvPSI.getInstance().getArenaHandler(); + for (ArenaSchematic arenaSchematic : arenaHandler.getSchematics()) { + if (arenaSchematic.getName().equalsIgnoreCase(source)) { + return arenaSchematic; + } + } + + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "No arena with the name " + source + " found."); + return null; + } + + @Override + public List tabComplete(Player player, Set flags, String source) { + List completions = new ArrayList<>(); + + ArenaHandler arenaHandler = PotPvPSI.getInstance().getArenaHandler(); + for (ArenaSchematic arenaSchematic : arenaHandler.getSchematics()) { + if (StringUtils.startsWithIgnoreCase(arenaSchematic.getName(), source)) { + completions.add(arenaSchematic.getName()); + } + } + + return completions; + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/ArenaTeamfightCategory.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/ArenaTeamfightCategory.java new file mode 100644 index 0000000..d3747b6 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/ArenaTeamfightCategory.java @@ -0,0 +1,22 @@ +package com.elevatemc.potpvp.arena; + +import lombok.Getter; +import org.bukkit.Color; +import org.bukkit.Material; + +public enum ArenaTeamfightCategory { + CITADELS("Citadels", "In this category you can find a lot of citadels including HCTeams & Lunar citadels", Material.SMOOTH_BRICK), + NETHER("Nether Maps", "In this category you can all maps related to nether", Material.NETHER_FENCE), + KOTHS("KOTHs", "In this category you can find KOTHs", Material.LADDER), + OTHER("Other", "In this category you can find all the other maps", Material.GRASS); + + @Getter private final String name; + @Getter private final String description; + @Getter private final Material icon; + + ArenaTeamfightCategory(String name, String description, Material icon) { + this.name = name; + this.description = description; + this.icon = icon; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/WorldEditUtils.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/WorldEditUtils.java new file mode 100644 index 0000000..5e867b2 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/WorldEditUtils.java @@ -0,0 +1,100 @@ +package com.elevatemc.potpvp.arena; + +import com.sk89q.worldedit.*; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.bukkit.BukkitWorld; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.schematic.SchematicFormat; +import lombok.experimental.UtilityClass; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.elib.cuboid.Cuboid; +import org.bukkit.Location; +import org.bukkit.Material; + +import java.io.File; + +@UtilityClass +public final class WorldEditUtils { + + private static EditSession editSession; + private static com.sk89q.worldedit.world.World worldEditWorld; + + public static void primeWorldEditApi() { + if (editSession != null) { + return; + } + + EditSessionFactory esFactory = WorldEdit.getInstance().getEditSessionFactory(); + ArenaHandler arenaHandler = PotPvPSI.getInstance().getArenaHandler(); + + worldEditWorld = new BukkitWorld(arenaHandler.getArenaWorld()); + editSession = esFactory.getEditSession(worldEditWorld, Integer.MAX_VALUE); + } + + public static CuboidClipboard paste(ArenaSchematic schematic, Vector pasteAt) throws Exception { + primeWorldEditApi(); + + CuboidClipboard clipboard = SchematicFormat.MCEDIT.load(schematic.getSchematicFile()); + + // systems like the ArenaGrid assume that pastes will 'begin' directly at the Vector + // provided. to ensure we can do this, we manually clear any offset (distance from + // corner of schematic to player) to ensure our pastes aren't dependant on the + // location of the player when copied + clipboard.setOffset(new Vector(0, 0, 0)); + clipboard.paste(editSession, pasteAt, false); + + return clipboard; + } + + public static void save(ArenaSchematic schematic, Vector saveFrom) throws Exception { + primeWorldEditApi(); + + Vector schematicSize = readSchematicSize(schematic); + + CuboidClipboard newSchematic = new CuboidClipboard(schematicSize, saveFrom); + newSchematic.copy(editSession); + + SchematicFormat.MCEDIT.save(newSchematic, schematic.getSchematicFile()); + } + + public static void clear(Cuboid bounds) { + clear( + new Vector(bounds.getLowerX(), bounds.getLowerY(), bounds.getLowerZ()), + new Vector(bounds.getUpperX(), bounds.getUpperY(), bounds.getUpperZ()) + ); + } + + public static void clear(Vector lower, Vector upper) { + primeWorldEditApi(); + + BaseBlock air = new BaseBlock(Material.AIR.getId()); + Region region = new CuboidRegion(worldEditWorld, lower, upper); + + try { + editSession.setBlocks(region, air); + } catch (MaxChangedBlocksException ex) { + // our block change limit is Integer.MAX_VALUE, so will never + // have to worry about this happening + throw new RuntimeException(ex); + } + } + + public static Vector readSchematicSize(ArenaSchematic schematic) throws Exception { + File schematicFile = schematic.getSchematicFile(); + CuboidClipboard clipboard = SchematicFormat.MCEDIT.load(schematicFile); + + return clipboard.getSize(); + } + + public static Location vectorToLocation(Vector vector) { + ArenaHandler arenaHandler = PotPvPSI.getInstance().getArenaHandler(); + + return new Location( + arenaHandler.getArenaWorld(), + vector.getBlockX(), + vector.getBlockY(), + vector.getBlockZ() + ); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaCreateSchematicCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaCreateSchematicCommand.java new file mode 100644 index 0000000..d01e054 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaCreateSchematicCommand.java @@ -0,0 +1,44 @@ +package com.elevatemc.potpvp.arena.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.ArenaHandler; +import com.elevatemc.potpvp.arena.ArenaSchematic; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.io.File; + +public final class ArenaCreateSchematicCommand { + + @Command(names = { "arena createSchematic" }, permission = "op", description = "Create a new arena schematic") + public static void arenaCreateSchematic(Player sender, @Parameter(name="schematic") String schematicName, @Parameter(name="displayname", wildcard = true) String displayName) { + ArenaHandler arenaHandler = PotPvPSI.getInstance().getArenaHandler(); + + if (arenaHandler.getSchematic(schematicName) != null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Schematic " + schematicName + " already exists"); + return; + } + + ArenaSchematic schematic = new ArenaSchematic(schematicName, displayName); + File schemFile = schematic.getSchematicFile(); + + if (!schemFile.exists()) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "No file for " + schematicName + " found. (" + schemFile.getPath() + ")"); + return; + } + + arenaHandler.registerSchematic(schematic); + + try { + schematic.pasteModelArena(); + arenaHandler.saveSchematics(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + + sender.sendMessage(ChatColor.GREEN + "Schematic created."); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaFreeCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaFreeCommand.java new file mode 100644 index 0000000..75807e3 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaFreeCommand.java @@ -0,0 +1,16 @@ +package com.elevatemc.potpvp.arena.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.elib.command.Command; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class ArenaFreeCommand { + + @Command(names = { "arena free" }, permission = "op", description = "Free the arena grid") + public static void arenaFree(Player sender) { + PotPvPSI.getInstance().getArenaHandler().getGrid().free(); + sender.sendMessage(ChatColor.GREEN + "Arena grid has been freed."); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaListArenasCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaListArenasCommand.java new file mode 100644 index 0000000..b35efcb --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaListArenasCommand.java @@ -0,0 +1,29 @@ +package com.elevatemc.potpvp.arena.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.Arena; +import com.elevatemc.potpvp.arena.ArenaHandler; +import com.elevatemc.potpvp.arena.ArenaSchematic; +import com.elevatemc.potpvp.util.LocationUtils; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class ArenaListArenasCommand { + + @Command(names = { "arena listArenas" }, permission = "op", description = "List all the arena instances of the schematic") + public static void arenaListArenas(Player sender, @Parameter(name="schematic") ArenaSchematic schematic) { + ArenaHandler arenaHandler = PotPvPSI.getInstance().getArenaHandler(); + + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "------ " + ChatColor.WHITE + schematic.getName() + " Arenas" + ChatColor.RED + " ------"); + + for (Arena arena : arenaHandler.getArenas(schematic)) { + String locationStr = LocationUtils.locToStr(arena.getSpectatorSpawn()); + String occupiedStr = arena.isInUse() ? ChatColor.RED + "In Use" : ChatColor.GREEN + "Open"; + + sender.sendMessage(arena.getCopy() + ": " + ChatColor.GREEN + locationStr + ChatColor.GRAY + " - " + occupiedStr); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaManageCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaManageCommand.java new file mode 100644 index 0000000..2b6ba29 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaManageCommand.java @@ -0,0 +1,13 @@ +package com.elevatemc.potpvp.arena.command; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.potpvp.arena.menu.manageschematics.ManageSchematicsMenu; +import org.bukkit.entity.Player; + +public class ArenaManageCommand { + @Command(names = { "arena manage" }, permission = "op", description = "Manage the arena instances") + public static void arenaListArenas(Player sender) { + new ManageSchematicsMenu().openMenu(sender); + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaRepasteModel.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaRepasteModel.java new file mode 100644 index 0000000..980ed22 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaRepasteModel.java @@ -0,0 +1,22 @@ +package com.elevatemc.potpvp.arena.command; + +import com.elevatemc.potpvp.arena.ArenaSchematic; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public class ArenaRepasteModel { + + @Command(names = { "arena repastemodel" }, permission = "op", description = "Repaste the model arena") + public static void execute(Player player, @Parameter(name = "schematic") ArenaSchematic schematic) { + try { + schematic.pasteModelArena(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + + player.sendMessage(ChatColor.GREEN + "Repasted the model arena."); + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaRepasteSchematicCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaRepasteSchematicCommand.java new file mode 100644 index 0000000..2d0cc18 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaRepasteSchematicCommand.java @@ -0,0 +1,38 @@ +package com.elevatemc.potpvp.arena.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.ArenaGrid; +import com.elevatemc.potpvp.arena.ArenaHandler; +import com.elevatemc.potpvp.arena.ArenaSchematic; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class ArenaRepasteSchematicCommand { + + @Command(names = { "arena repasteSchematic" }, permission = "op", description = "Repaste all instances of a schematic") + public static void arenaRepasteSchematic(Player sender, @Parameter(name="schematic") ArenaSchematic schematic) { + ArenaHandler arenaHandler = PotPvPSI.getInstance().getArenaHandler(); + + int currentCopies = arenaHandler.countArenas(schematic); + + if (currentCopies == 0) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "No copies of " + schematic.getName() + " exist."); + return; + } + + ArenaGrid arenaGrid = arenaHandler.getGrid(); + + sender.sendMessage(ChatColor.GREEN + "Starting..."); + + arenaGrid.scaleCopies(schematic, 0, () -> { + sender.sendMessage(ChatColor.GREEN + "Removed old maps, creating new copies..."); + + arenaGrid.scaleCopies(schematic, currentCopies, () -> { + sender.sendMessage(ChatColor.GREEN + "Repasted " + currentCopies + " arenas using the newest " + schematic.getName() + " schematic."); + }); + }); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaScaleCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaScaleCommand.java new file mode 100644 index 0000000..e28f852 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaScaleCommand.java @@ -0,0 +1,38 @@ +package com.elevatemc.potpvp.arena.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.Arena; +import com.elevatemc.potpvp.arena.ArenaHandler; +import com.elevatemc.potpvp.arena.ArenaSchematic; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class ArenaScaleCommand { + + @Command(names = { "arena scale" }, permission = "op", description = "Scale an arena to a specific number") + public static void arenaScale(Player sender, @Parameter(name="schematic") ArenaSchematic schematic, @Parameter(name="count") int count) { + ArenaHandler arenaHandler = PotPvPSI.getInstance().getArenaHandler(); + + sender.sendMessage(ChatColor.GREEN + "Starting..."); + + arenaHandler.getGrid().scaleCopies(schematic, count, () -> sender.sendMessage(ChatColor.GREEN + "Scaled " + schematic.getName() + " to " + count + " copies.")); + } + + @Command(names = "arena rescaleall", permission = "op", description = "Rescale all the arenas") + public static void arenaRescaleAll(Player sender) { + PotPvPSI.getInstance().getArenaHandler().getSchematics().forEach(schematic -> { + ArenaHandler arenaHandler = PotPvPSI.getInstance().getArenaHandler(); + int totalCopies = 0; + + for (Arena arena : arenaHandler.getArenas(schematic)) { + totalCopies++; + } + + arenaScale(sender, schematic, 0); + arenaScale(sender, schematic, totalCopies); + }); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaSetDisplayName.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaSetDisplayName.java new file mode 100644 index 0000000..741be9c --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaSetDisplayName.java @@ -0,0 +1,20 @@ +package com.elevatemc.potpvp.arena.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.ArenaSchematic; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public class ArenaSetDisplayName { + + @Command(names = { "arena setdisplayname" }, permission = "op", description = "Sets an arena display name") + public static void execute(Player player, @Parameter(name = "schematic") ArenaSchematic schematic, @Parameter(name = "displayName", wildcard = true) String displayName) { + schematic.setDisplayName(displayName); + PotPvPSI.getInstance().getArenaHandler().sortSchematics(); + + player.sendMessage(ChatColor.GREEN + "You've updated this arena's display name."); + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaSetIconCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaSetIconCommand.java new file mode 100644 index 0000000..6cc7556 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaSetIconCommand.java @@ -0,0 +1,34 @@ +package com.elevatemc.potpvp.arena.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.ArenaSchematic; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.io.IOException; + +public final class ArenaSetIconCommand { + + @Command(names = { "arena seticon" }, permission = "op") + public static void arenaSetIcon(Player player, @Parameter(name="schematic") ArenaSchematic schematic) { + if (player.getItemInHand().getType() == Material.AIR) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Please hold an item in your hand."); + return; + } + + schematic.setIcon(player.getItemInHand().getType()); + + try { + PotPvPSI.getInstance().getArenaHandler().saveSchematics(); + } catch (IOException ex) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Failed to save " + schematic.getName() + ": " + ex.getMessage()); + ex.printStackTrace(); + } + + player.sendMessage(ChatColor.GREEN + "You've updated this arena's icon."); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaStupidCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaStupidCommand.java new file mode 100644 index 0000000..e0a31d2 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/command/ArenaStupidCommand.java @@ -0,0 +1,89 @@ +package com.elevatemc.potpvp.arena.command; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import com.elevatemc.elib.util.PlayerUtils; +import com.elevatemc.elib.util.TimeUtils; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.Arena; +import com.elevatemc.potpvp.arena.ArenaHandler; +import com.elevatemc.potpvp.arena.ArenaSchematic; +import com.elevatemc.potpvp.arena.WorldEditUtils; +import com.elevatemc.potpvp.util.Color; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; +import org.github.paperspigot.Title; + +import java.util.*; + +public final class ArenaStupidCommand { + @Command(names = { "arena stupid" }, permission = "op", description = "Stupid chunks - Recommended value for ticks is 20") + public static void arenaScale(Player sender, @Parameter(name="schematic") ArenaSchematic schematic, @Parameter(name="ticks") int ticks) { + ArenaHandler arenaHandler = PotPvPSI.getInstance().getArenaHandler(); + + Set arenas = arenaHandler.getArenas(schematic); + if (arenas.size() < 1) { + sender.sendMessage(ChatColor.RED + "There are no instances of this arena..."); + return; + } + List locations = new ArrayList<>(); + for (Arena arena : arenas) { + locations.add(arena.getTeam1Spawn()); + locations.add(arena.getTeam2Spawn()); + } + + processLocations(sender, locations, ticks); + } + + @Command(names = { "arena stupidall" }, permission = "op", description = "Stupid chunks - Recommended value for ticks is 20") + public static void arenaScale(Player sender, @Parameter(name="ticks") int ticks) { + ArenaHandler arenaHandler = PotPvPSI.getInstance().getArenaHandler(); + + List locations = new ArrayList<>(); + for (ArenaSchematic schem: arenaHandler.getSchematics()) { + for (Arena arena : arenaHandler.getArenas(schem)) { + locations.add(arena.getTeam1Spawn()); + locations.add(arena.getTeam2Spawn()); + } + } + + processLocations(sender, locations, ticks); + } + + private final static Title.Builder loadingLocations = Title.builder().stay(2147483647).title(Color.translate("&3Loading Locations")); + + public static void processLocations(Player player, List locations, long ticks) { + + double estimated = ((ticks / 20) * locations.size()) + (locations.size() * 0.3); + + player.sendMessage(Color.translate("&3Location Amount: &b" + locations.size() + " &3ETA: &b" + TimeUtils.formatIntoMMSS((int)estimated))); + + Iterator iterator = locations.iterator(); + + new BukkitRunnable() { + + int done = 1; + + @Override + public void run() { + if (!player.isOnline()) { + cancel(); + return; + } + + if (!iterator.hasNext()) { + player.sendMessage(Color.translate("&aFinished.")); + cancel(); + return; + } + Location location = iterator.next(); + player.teleport(location); + player.sendMessage(Color.translate("&3Location: " + "&b" + done + "/" + locations.size())); + done++; + } + + }.runTaskTimer(PotPvPSI.getInstance(), 20L, ticks); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/event/ArenaAllocatedEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/event/ArenaAllocatedEvent.java new file mode 100644 index 0000000..2d41dd0 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/event/ArenaAllocatedEvent.java @@ -0,0 +1,24 @@ +package com.elevatemc.potpvp.arena.event; + +import lombok.Getter; +import com.elevatemc.potpvp.arena.Arena; +import org.bukkit.event.HandlerList; + +/** + * Called when an {@link Arena} is allocated for use by a + * {@link com.elevatemc.potpvp.match.Match} + */ +public final class ArenaAllocatedEvent extends ArenaEvent { + + @Getter private static HandlerList handlerList = new HandlerList(); + + public ArenaAllocatedEvent(Arena arena) { + super(arena); + } + + @Override + public HandlerList getHandlers() { + return handlerList; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/event/ArenaEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/event/ArenaEvent.java new file mode 100644 index 0000000..a2658cb --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/event/ArenaEvent.java @@ -0,0 +1,22 @@ +package com.elevatemc.potpvp.arena.event; + +import com.google.common.base.Preconditions; +import lombok.Getter; +import com.elevatemc.potpvp.arena.Arena; +import org.bukkit.event.Event; + +/** + * Represents an event involving an {@link Arena} + */ +abstract class ArenaEvent extends Event { + + /** + * The match involved in this event + */ + @Getter private final Arena arena; + + ArenaEvent(Arena arena) { + this.arena = Preconditions.checkNotNull(arena, "arena"); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/event/ArenaReleasedEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/event/ArenaReleasedEvent.java new file mode 100644 index 0000000..e819465 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/event/ArenaReleasedEvent.java @@ -0,0 +1,24 @@ +package com.elevatemc.potpvp.arena.event; + +import lombok.Getter; +import com.elevatemc.potpvp.arena.Arena; +import org.bukkit.event.HandlerList; + +/** + * Called when an {@link Arena} is done being used by a + * {@link com.elevatemc.potpvp.match.Match} + */ +public final class ArenaReleasedEvent extends ArenaEvent { + + @Getter private static HandlerList handlerList = new HandlerList(); + + public ArenaReleasedEvent(Arena arena) { + super(arena); + } + + @Override + public HandlerList getHandlers() { + return handlerList; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/listener/ArenaItemResetListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/listener/ArenaItemResetListener.java new file mode 100644 index 0000000..0c2fc59 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/listener/ArenaItemResetListener.java @@ -0,0 +1,68 @@ +package com.elevatemc.potpvp.arena.listener; + +import com.elevatemc.elib.cuboid.Cuboid; +import com.elevatemc.potpvp.arena.event.ArenaReleasedEvent; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Item; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.world.ChunkUnloadEvent; + +import java.util.HashSet; +import java.util.Set; + +/** + * Removes projectiles & items from the chunk when it unloads in the arena world + */ +public final class ArenaItemResetListener implements Listener { + + @EventHandler + public void onChunkUnload(ChunkUnloadEvent event) { + Chunk chunk = event.getChunk(); + if (chunk.getWorld().getName().equals("arenas")) { + for (Entity entity : chunk.getEntities()) { + if (entity instanceof Item || entity instanceof Projectile) { + entity.remove(); + } + } + } + } + + @EventHandler + public void onArenaReleased(ArenaReleasedEvent event) { + Set coveredChunks = new HashSet<>(); + Cuboid bounds = event.getArena().getBounds(); + + Location minPoint = bounds.getLowerNE(); + Location maxPoint = bounds.getUpperSW(); + World world = minPoint.getWorld(); + + // definitely a better way to increment than += 1 but arenas + // are small enough this doesn't matter + for (int x = minPoint.getBlockX(); x <= maxPoint.getBlockX(); x++) { + for (int z = minPoint.getBlockZ(); z <= maxPoint.getBlockZ(); z++) { + // getChunkAt wants chunk x/z coords, not block coords + int xChunk = x >> 4; + int zChunk = z >> 4; + if (world.isChunkLoaded(xChunk, zChunk)) { + coveredChunks.add(world.getChunkAt(xChunk, zChunk)); + } + } + } + + coveredChunks.stream().forEach(chunk -> { + for (Entity entity : chunk.getEntities()) { + // if we remove all entities we might call .remove() + // on a player (breaks a lot of things) + if ((entity instanceof Item || entity instanceof Projectile) && bounds.contains(entity.getLocation())) { + entity.remove(); + } + } + }); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematic/CreateCopiesButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematic/CreateCopiesButton.java new file mode 100644 index 0000000..68d626f --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematic/CreateCopiesButton.java @@ -0,0 +1,70 @@ +package com.elevatemc.potpvp.arena.menu.manageschematic; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.ArenaHandler; +import com.elevatemc.potpvp.arena.ArenaSchematic; +import com.elevatemc.elib.menu.Button; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.List; + +final class CreateCopiesButton extends Button { + + private final ArenaSchematic schematic; + + CreateCopiesButton(ArenaSchematic schematic) { + this.schematic = Preconditions.checkNotNull(schematic, "schematic"); + } + + @Override + public String getName(Player player) { + return ChatColor.GREEN + "Create copies of " + schematic.getName() + ""; + } + + @Override + public List getDescription(Player player) { + return ImmutableList.of( + "", + ChatColor.GREEN.toString() + ChatColor.BOLD + "CLICK " + ChatColor.GREEN + "to create 1 new copy", + ChatColor.GREEN.toString() + ChatColor.BOLD + "SHIFT-CLICK " + ChatColor.GREEN + "to create 10 new copies", + "", + ChatColor.AQUA + "Scale directly to a desired quantity", + ChatColor.AQUA + "with /arena scale " + schematic.getName() + " " + ); + } + + @Override + public Material getMaterial(Player player) { + return Material.EMERALD_BLOCK; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + ArenaHandler arenaHandler = PotPvPSI.getInstance().getArenaHandler(); + int existing = arenaHandler.countArenas(schematic); + int create = clickType.isShiftClick() ? 10 : 1; + int desired = existing + create; + + if (arenaHandler.getGrid().isBusy()) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Grid is busy."); + return; + } + + try { + player.sendMessage(ChatColor.GREEN + "Starting..."); + + arenaHandler.getGrid().scaleCopies(schematic, desired, () -> { + player.sendMessage(ChatColor.GREEN + "Scaled " + schematic.getName() + " to " + desired + "."); + }); + } catch (Exception ex) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Failed to paste " + schematic.getName() + ": " + ex.getMessage()); + ex.printStackTrace(); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematic/EventNameButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematic/EventNameButton.java new file mode 100644 index 0000000..9c35a26 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematic/EventNameButton.java @@ -0,0 +1,85 @@ +package com.elevatemc.potpvp.arena.menu.manageschematic; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.ArenaSchematic; +import com.elevatemc.potpvp.util.Color; +import com.google.common.collect.ImmutableList; +import org.bukkit.Material; +import org.bukkit.conversations.*; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.List; + +public class EventNameButton extends Button { + + private final ArenaSchematic schematic; + + public EventNameButton(ArenaSchematic schematic) { + this.schematic = schematic; + } + + @Override + public String getName(Player var1) { + return Color.translate("&e&lEvent Name"); + } + + @Override + public List getDescription(Player var1) { + return Color.translate(ImmutableList.of( + "&eCurrent: &b" + schematic.getEventName(), + "", + "&7If this arena is meant to be used for an event", + "&7set the event id here", + "", + "&a&lLEFT-CLICK &ato set the event name", + "&c&lRIGHT-CLICK &cto remove the event name" + )); + } + + @Override + public Material getMaterial(Player var1) { + return Material.SIGN; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + if(clickType.isLeftClick()) { + ConversationFactory factory = new ConversationFactory(PotPvPSI.getInstance()).withModality(true).withPrefix(new NullConversationPrefix()).withFirstPrompt(new StringPrompt() { + + @Override + public String getPromptText(ConversationContext conversationContext) { + return Color.translate("&ePlease type the name of the event, or type &c\"cancel\" &eto cancel."); + } + + @Override + public Prompt acceptInput(ConversationContext cc, String name) { + if(name.equalsIgnoreCase("cancel")) { + return Prompt.END_OF_CONVERSATION; + } + + schematic.setEventName(name); + cc.getForWhom().sendRawMessage(Color.translate("&aSet the event name of " + schematic.getName() + " to " + schematic.getEventName())); + + (new BukkitRunnable() { + @Override + public void run() { + new ManageSchematicMenu(schematic).openMenu(player); + } + }).runTask(PotPvPSI.getInstance()); + + return Prompt.END_OF_CONVERSATION; + } + }).withLocalEcho(false).withEscapeSequence("/cancel").withTimeout(60).thatExcludesNonPlayersWithMessage("Player's only."); + + Conversation conversation = factory.buildConversation(player); + player.beginConversation(conversation); + }else if(clickType.isRightClick()) { + player.closeInventory(); + player.sendMessage(Color.translate("&aRemoved the event name.")); + schematic.setEventName(null); + } + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematic/ManageSchematicMenu.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematic/ManageSchematicMenu.java new file mode 100644 index 0000000..045c236 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematic/ManageSchematicMenu.java @@ -0,0 +1,82 @@ +package com.elevatemc.potpvp.arena.menu.manageschematic; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.ArenaSchematic; +import com.elevatemc.potpvp.arena.menu.manageschematics.ManageSchematicsMenu; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.util.menu.BooleanTraitButton; +import com.elevatemc.potpvp.util.menu.MenuBackButton; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import org.bukkit.ChatColor; +import org.bukkit.DyeColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +public final class ManageSchematicMenu extends Menu { + + private final ArenaSchematic schematic; + + public ManageSchematicMenu(ArenaSchematic schematic) { + setAutoUpdate(true); + + this.schematic = schematic; + } + + @Override + public String getTitle(Player player) { + return "Manage " + schematic.getName(); + } + + @Override + public Map getButtons(Player player) { + Map buttons = new HashMap<>(); + + buttons.put(0, new SchematicStatusButton(schematic)); + buttons.put(1, new ToggleEnabledButton(schematic)); + + buttons.put(3, new TeleportToModelButton(schematic)); + buttons.put(4, new SaveModelButton(schematic)); + + if (PotPvPSI.getInstance().getArenaHandler().getGrid().isBusy()) { + Button busyButton = Button.placeholder(Material.WOOL, DyeColor.SILVER.getWoolData(), ChatColor.GRAY.toString() + ChatColor.BOLD + "Grid is busy"); + + buttons.put(7, busyButton); + buttons.put(8, busyButton); + } else { + buttons.put(7, new CreateCopiesButton(schematic)); + buttons.put(8, new RemoveCopiesButton(schematic)); + } + + buttons.put(9, new MenuBackButton(p -> new ManageSchematicsMenu().openMenu(p))); + buttons.put(17, new EventNameButton(schematic)); + + Consumer save = schematic -> { + try { + PotPvPSI.getInstance().getArenaHandler().saveSchematics(); + } catch (Exception ex) { + ex.printStackTrace(); + } + }; + + int i = 18; + buttons.put(i++, new BooleanTraitButton<>(schematic, "Supports Ranked", ArenaSchematic::setSupportsRanked, ArenaSchematic::isSupportsRanked, save)); + buttons.put(i++, new BooleanTraitButton<>(schematic, "Pearls Allowed", ArenaSchematic::setPearlsAllowed, ArenaSchematic::isPearlsAllowed, save)); + i = 36; + for (GameMode gameMode : GameMode.getAll()) { + buttons.put(i, new BooleanTraitButton<>(schematic, gameMode.getName(), (schem, bool) -> { + schem.getEnabledGameModes().put(gameMode.getId(), bool); + }, (schem) -> { + return schem.getEnabledGameModes().get(gameMode.getId()); + }, save)); + i++; + } + + return buttons; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematic/RemoveCopiesButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematic/RemoveCopiesButton.java new file mode 100644 index 0000000..b480215 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematic/RemoveCopiesButton.java @@ -0,0 +1,65 @@ +package com.elevatemc.potpvp.arena.menu.manageschematic; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.ArenaHandler; +import com.elevatemc.potpvp.arena.ArenaSchematic; +import com.elevatemc.elib.menu.Button; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.List; + +final class RemoveCopiesButton extends Button { + + private final ArenaSchematic schematic; + + RemoveCopiesButton(ArenaSchematic schematic) { + this.schematic = Preconditions.checkNotNull(schematic, "schematic"); + } + + @Override + public String getName(Player player) { + return ChatColor.RED + "Remove copies of " + schematic.getName() + ""; + } + + @Override + public List getDescription(Player player) { + return ImmutableList.of( + "", + ChatColor.RED.toString() + ChatColor.BOLD + "CLICK " + ChatColor.RED + "to remove 1 copy", + ChatColor.RED.toString() + ChatColor.BOLD + "SHIFT-CLICK " + ChatColor.RED + "to remove 10 copies", + "", + ChatColor.AQUA + "Scale directly to a desired quantity", + ChatColor.AQUA + "with /arena scale " + schematic.getName() + " " + ); + } + + @Override + public Material getMaterial(Player player) { + return Material.REDSTONE_BLOCK; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + ArenaHandler arenaHandler = PotPvPSI.getInstance().getArenaHandler(); + int existing = arenaHandler.countArenas(schematic); + int remove = clickType.isShiftClick() ? 10 : 1; + int desired = Math.max(existing - remove, 0); + + if (arenaHandler.getGrid().isBusy()) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Grid is busy."); + return; + } + + player.sendMessage(ChatColor.GREEN + "Starting..."); + + arenaHandler.getGrid().scaleCopies(schematic, desired, () -> { + player.sendMessage(ChatColor.GREEN + "Scaled " + schematic.getName() + " to " + desired + "."); + }); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematic/SaveModelButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematic/SaveModelButton.java new file mode 100644 index 0000000..eb219bc --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematic/SaveModelButton.java @@ -0,0 +1,53 @@ +package com.elevatemc.potpvp.arena.menu.manageschematic; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.elevatemc.potpvp.arena.ArenaSchematic; +import com.elevatemc.potpvp.arena.WorldEditUtils; +import com.elevatemc.elib.menu.Button; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.List; + +final class SaveModelButton extends Button { + + private final ArenaSchematic schematic; + + SaveModelButton(ArenaSchematic schematic) { + this.schematic = Preconditions.checkNotNull(schematic, "schematic"); + } + + @Override + public String getName(Player player) { + return ChatColor.GOLD + "Save model"; + } + + @Override + public List getDescription(Player player) { + return ImmutableList.of( + "", + ChatColor.YELLOW + "Click to save the model arena" + ); + } + + @Override + public Material getMaterial(Player player) { + return Material.PISTON_BASE; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + player.closeInventory(); + + try { + WorldEditUtils.save(schematic, schematic.getModelArenaLocation()); + } catch (Exception ex) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Failed to save " + schematic.getName() + ": " + ex.getMessage()); + ex.printStackTrace(); + } + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematic/SchematicStatusButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematic/SchematicStatusButton.java new file mode 100644 index 0000000..a843c8c --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematic/SchematicStatusButton.java @@ -0,0 +1,63 @@ +package com.elevatemc.potpvp.arena.menu.manageschematic; + +import com.google.common.base.Preconditions; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.Arena; +import com.elevatemc.potpvp.arena.ArenaHandler; +import com.elevatemc.potpvp.arena.ArenaSchematic; +import com.elevatemc.elib.menu.Button; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; + +final class SchematicStatusButton extends Button { + + private final ArenaSchematic schematic; + + SchematicStatusButton(ArenaSchematic schematic) { + this.schematic = Preconditions.checkNotNull(schematic, "schematic"); + } + + @Override + public String getName(Player player) { + return ChatColor.YELLOW + schematic.getName() + " Status"; + } + + @Override + public List getDescription(Player player) { + ArenaHandler arenaHandler = PotPvPSI.getInstance().getArenaHandler(); + int totalCopies = 0; + int inUseCopies = 0; + + for (Arena arena : arenaHandler.getArenas(schematic)) { + totalCopies++; + + if (arena.isInUse()) { + inUseCopies++; + } + } + + List description = new ArrayList<>(); + + description.add(""); + description.add(ChatColor.GREEN + "Copies: " + ChatColor.WHITE + totalCopies); + description.add(ChatColor.GREEN + "Copies in use: " + ChatColor.WHITE + inUseCopies); + + return description; + } + + @Override + public int getAmount(Player player) { + ArenaHandler arenaHandler = PotPvPSI.getInstance().getArenaHandler(); + return arenaHandler.getArenas(schematic).size(); + } + + @Override + public Material getMaterial(Player player) { + return Material.NAME_TAG; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematic/TeleportToModelButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematic/TeleportToModelButton.java new file mode 100644 index 0000000..d53a041 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematic/TeleportToModelButton.java @@ -0,0 +1,65 @@ +package com.elevatemc.potpvp.arena.menu.manageschematic; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.sk89q.worldedit.Vector; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.ArenaHandler; +import com.elevatemc.potpvp.arena.ArenaSchematic; +import com.elevatemc.elib.menu.Button; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.List; + +final class TeleportToModelButton extends Button { + + private final ArenaSchematic schematic; + + TeleportToModelButton(ArenaSchematic schematic) { + this.schematic = Preconditions.checkNotNull(schematic, "schematic"); + } + + @Override + public String getName(Player player) { + return ChatColor.GOLD + "Teleport to model"; + } + + @Override + public List getDescription(Player player) { + return ImmutableList.of( + "", + ChatColor.YELLOW + "Click to teleport to the model arena, which", + ChatColor.YELLOW + "will allow you to make edits to the schematic." + ); + } + + @Override + public Material getMaterial(Player player) { + return Material.BREWING_STAND_ITEM; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + player.closeInventory(); + + ArenaHandler arenaHandler = PotPvPSI.getInstance().getArenaHandler(); + Vector arenaStart = schematic.getModelArenaLocation(); + + // we add 50 so players don't spawn in the very botton corner of + // the schematic. perhaps later we should apply the same logic we + // do for spectators to center the player + player.teleport(new Location( + arenaHandler.getArenaWorld(), + arenaStart.getX() + 50, + arenaStart.getY() + 50, + arenaStart.getZ() + 50 + )); + + player.sendMessage(ChatColor.GREEN + "Teleporting to " + schematic.getName()); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematic/ToggleEnabledButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematic/ToggleEnabledButton.java new file mode 100644 index 0000000..7e7b0be --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematic/ToggleEnabledButton.java @@ -0,0 +1,70 @@ +package com.elevatemc.potpvp.arena.menu.manageschematic; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.ArenaSchematic; +import com.elevatemc.elib.menu.Button; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.io.IOException; +import java.util.List; + +final class ToggleEnabledButton extends Button { + + private final ArenaSchematic schematic; + + ToggleEnabledButton(ArenaSchematic schematic) { + this.schematic = Preconditions.checkNotNull(schematic, "schematic"); + } + + @Override + public String getName(Player player) { + if (schematic.isEnabled()) { + return ChatColor.RED + "Disable " + schematic.getName(); + } else { + return ChatColor.GREEN + "Enable " + schematic.getName(); + } + } + + @Override + public List getDescription(Player player) { + if (schematic.isEnabled()) { + return ImmutableList.of( + "", + ChatColor.YELLOW + "Click to disable " + schematic.getName() + ", which will prevent matches", + ChatColor.YELLOW + "being scheduled on these arenas. Admin", + ChatColor.YELLOW + "commands will not be impacted." + ); + } else { + return ImmutableList.of( + "", + ChatColor.YELLOW + "Click to enable " + schematic.getName() + ", which will allow matches", + ChatColor.YELLOW + "to be scheduled on these arenas." + ); + } + } + + @Override + public Material getMaterial(Player player) { + // we invert our normal logic here to show the 'potential' state (similiar to + // video player play/pause buttons) + return schematic.isEnabled() ? Material.REDSTONE_BLOCK : Material.EMERALD_BLOCK; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + schematic.setEnabled(!schematic.isEnabled()); + + try { + PotPvPSI.getInstance().getArenaHandler().saveSchematics(); + } catch (IOException ex) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Failed to save " + schematic.getName() + ": " + ex.getMessage()); + ex.printStackTrace(); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematics/ManageSchematicButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematics/ManageSchematicButton.java new file mode 100644 index 0000000..d746b4a --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematics/ManageSchematicButton.java @@ -0,0 +1,71 @@ +package com.elevatemc.potpvp.arena.menu.manageschematics; + +import com.google.common.base.Preconditions; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.Arena; +import com.elevatemc.potpvp.arena.ArenaHandler; +import com.elevatemc.potpvp.arena.ArenaSchematic; +import com.elevatemc.potpvp.arena.menu.manageschematic.ManageSchematicMenu; +import com.elevatemc.elib.menu.Button; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.ArrayList; +import java.util.List; + +final class ManageSchematicButton extends Button { + + private final ArenaSchematic schematic; + + ManageSchematicButton(ArenaSchematic schematic) { + this.schematic = Preconditions.checkNotNull(schematic, "schematic"); + } + + @Override + public String getName(Player player) { + return ChatColor.RED + schematic.getName(); + } + + @Override + public List getDescription(Player player) { + ArenaHandler arenaHandler = PotPvPSI.getInstance().getArenaHandler(); + int totalCopies = 0; + int inUseCopies = 0; + + for (Arena arena : arenaHandler.getArenas(schematic)) { + totalCopies++; + + if (arena.isInUse()) { + inUseCopies++; + } + } + + List description = new ArrayList<>(); + + description.add(ChatColor.DARK_AQUA + "Display Name: "+ ChatColor.WHITE + schematic.getDisplayName()); + description.add(ChatColor.DARK_AQUA + "Enabled: " + ChatColor.WHITE + (schematic.isEnabled() ? ChatColor.GREEN + "Yes" : ChatColor.RED + "No")); + description.add(ChatColor.DARK_AQUA + "Copies: " + ChatColor.WHITE + inUseCopies + "/" + totalCopies); + + return description; + } + + @Override + public int getAmount(Player player) { + ArenaHandler arenaHandler = PotPvPSI.getInstance().getArenaHandler(); + return arenaHandler.getArenas(schematic).size(); + } + + @Override + public Material getMaterial(Player player) { + return schematic.getIcon(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + player.closeInventory(); + new ManageSchematicMenu(schematic).openMenu(player); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematics/ManageSchematicsMenu.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematics/ManageSchematicsMenu.java new file mode 100644 index 0000000..5aa9f08 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/manageschematics/ManageSchematicsMenu.java @@ -0,0 +1,38 @@ +package com.elevatemc.potpvp.arena.menu.manageschematics; + +import com.elevatemc.elib.eLib; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.ArenaHandler; +import com.elevatemc.potpvp.arena.ArenaSchematic; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; + +public final class ManageSchematicsMenu extends Menu { + + public ManageSchematicsMenu() { + setAutoUpdate(true); + } + + @Override + public String getTitle(Player player) { + return "Manage schematics"; + } + + @Override + public Map getButtons(Player player) { + ArenaHandler arenaHandler = PotPvPSI.getInstance().getArenaHandler(); + Map buttons = new HashMap<>(); + int index = 0; + + for (ArenaSchematic schematic : arenaHandler.getSchematics()) { + buttons.put(index++, new ManageSchematicButton(schematic)); + } + + return buttons; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/select/ArenaButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/select/ArenaButton.java new file mode 100644 index 0000000..19f33aa --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/select/ArenaButton.java @@ -0,0 +1,42 @@ +package com.elevatemc.potpvp.arena.menu.select; + +import com.google.common.collect.ImmutableList; +import lombok.AllArgsConstructor; +import com.elevatemc.potpvp.arena.ArenaSchematic; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.util.Callback; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.List; + +@AllArgsConstructor +public class ArenaButton extends Button { + + private ArenaSchematic arenaSchematic; + private final Callback callback; + + @Override + public String getName(Player player) { + return ChatColor.GREEN + arenaSchematic.getDisplayName(); + } + + @Override + public List getDescription(Player player) { + return ImmutableList.of( + ChatColor.GRAY + "Click here to select this map" + ); + } + + @Override + public Material getMaterial(Player player) { + return arenaSchematic.getIcon(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + callback.callback(arenaSchematic.getName()); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/select/SelectArenaMenu.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/select/SelectArenaMenu.java new file mode 100644 index 0000000..1b9e5e9 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/select/SelectArenaMenu.java @@ -0,0 +1,120 @@ +package com.elevatemc.potpvp.arena.menu.select; + +import com.elevatemc.potpvp.arena.ArenaTeamfightCategory; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.ArenaSchematic; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import com.elevatemc.elib.util.Callback; +import com.google.common.collect.ImmutableList; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.*; +import java.util.stream.Collectors; + +public class SelectArenaMenu extends Menu { + + private final GameMode gameMode; + private final Callback callback; + private final ArenaTeamfightCategory arenaTeamfightCategory; + List possibleSchematics = new ArrayList<>(); + + public SelectArenaMenu(GameMode gameMode, Callback callback) { + this(gameMode, null, callback); + } + + public SelectArenaMenu(GameMode gameMode, ArenaTeamfightCategory arenaTeamfightCategory, Callback callback) { + this.callback = Preconditions.checkNotNull(callback, "callback"); + this.gameMode = Preconditions.checkNotNull(gameMode, "gameMode"); + this.arenaTeamfightCategory = arenaTeamfightCategory; + + for (ArenaSchematic schematic : PotPvPSI.getInstance().getArenaHandler().getSchematics()) { + if (isValidArena(schematic)) { + possibleSchematics.add(schematic); + } + } + + setPlaceholder(true); + } + + private boolean isValidArena (ArenaSchematic schematic) { + return schematic.isEnabled() && schematic.getEnabledGameModes().get(gameMode.getId()); + } + + @Override + public String getTitle(Player player) { + return ChatColor.BLUE + "Select an arena"; + } + + @Override + public Map getButtons(Player arg0) { + Map buttons = Maps.newHashMap(); + + int i = 9; + if (arenaTeamfightCategory == null) { + buttons.put(0, new Button() { + @Override + public String getName(Player player) { + return ChatColor.GREEN + "Random Arena"; + } + + @Override + public List getDescription(Player player) { + return ImmutableList.of( + ChatColor.GRAY + "Click this to duel a random arena" + ); + } + + @Override + public Material getMaterial(Player player) { + return Material.NETHER_STAR; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + callback.callback(null); + } + }); + for (ArenaSchematic schematic : possibleSchematics) { + if ((i + 1) % 9 == 0) i++; + if (i % 9 == 0) i++; + buttons.put(i++, new ArenaButton(schematic, callback)); + } + } else { + for (ArenaSchematic schematic : getFilteredSchematics()) { + if ((i + 1) % 9 == 0) i++; + if (i % 9 == 0) i++; + buttons.put(i++, new ArenaButton(schematic, callback)); + } + } + + return buttons; + } + + @Override + public int size(Player player) { + Map buttons = getButtons(player); + return ((int)Math.ceil(((float)buttons.size() / 7)) + 2) * 9; + } + + private List getFilteredSchematics() { + switch (arenaTeamfightCategory) { + case CITADELS: + return possibleSchematics.stream().filter(schematic -> schematic.getName().startsWith("Citadel")).collect(Collectors.toList()); + case NETHER: + return possibleSchematics.stream().filter(schematic -> schematic.getName().startsWith("Nether")).collect(Collectors.toList()); + case KOTHS: + return possibleSchematics.stream().filter(schematic -> schematic.getName().startsWith("KOTH")).collect(Collectors.toList()); + default: + return possibleSchematics.stream().filter(schematic -> !schematic.getName().startsWith("Citadel") && !schematic.getName().startsWith("KOTH") && !schematic.getName().startsWith("Nether")).collect(Collectors.toList()); + } + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/select/teamfight/ArenaTeamfightCategoryButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/select/teamfight/ArenaTeamfightCategoryButton.java new file mode 100644 index 0000000..3c1eabd --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/select/teamfight/ArenaTeamfightCategoryButton.java @@ -0,0 +1,42 @@ +package com.elevatemc.potpvp.arena.menu.select.teamfight; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.util.Callback; +import com.elevatemc.potpvp.arena.ArenaTeamfightCategory; +import com.google.common.collect.ImmutableList; +import lombok.AllArgsConstructor; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.List; + +@AllArgsConstructor +public class ArenaTeamfightCategoryButton extends Button { + + private ArenaTeamfightCategory arenaTeamfightCategory; + private Callback callback; + + @Override + public String getName(Player player) { + return ChatColor.DARK_AQUA + arenaTeamfightCategory.getName(); + } + + @Override + public List getDescription(Player player) { + return ImmutableList.of( + ChatColor.DARK_AQUA + "❘ " + ChatColor.WHITE + arenaTeamfightCategory.getDescription() + ); + } + + @Override + public Material getMaterial(Player player) { + return arenaTeamfightCategory.getIcon(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + callback.callback(arenaTeamfightCategory); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/select/teamfight/SelectArenaTeamfightCategoryMenu.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/select/teamfight/SelectArenaTeamfightCategoryMenu.java new file mode 100644 index 0000000..3946cdc --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/arena/menu/select/teamfight/SelectArenaTeamfightCategoryMenu.java @@ -0,0 +1,73 @@ +package com.elevatemc.potpvp.arena.menu.select.teamfight; + +import com.elevatemc.potpvp.arena.ArenaTeamfightCategory; +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import com.elevatemc.potpvp.arena.ArenaSchematic; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import com.elevatemc.elib.util.Callback; +import com.google.common.collect.ImmutableList; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.*; + +public class SelectArenaTeamfightCategoryMenu extends Menu { + private final Callback callback; + List possibleSchematics = new ArrayList<>(); + + public SelectArenaTeamfightCategoryMenu(Callback callback) { + this.callback = Preconditions.checkNotNull(callback, "callback"); + setPlaceholder(true); + } + + @Override + public String getTitle(Player player) { + return ChatColor.BLUE + "Select a category"; + } + + @Override + public Map getButtons(Player arg0) { + Map buttons = Maps.newHashMap(); + + buttons.put(getSlot(0, 2), new Button() { + @Override + public String getName(Player player) { + return ChatColor.GREEN + "View All"; + } + + @Override + public List getDescription(Player player) { + return ImmutableList.of( + ChatColor.DARK_AQUA + "❘ " + ChatColor.WHITE + "Click this to view all the teamfight maps" + ); + } + + @Override + public Material getMaterial(Player player) { + return Material.BRICK; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + callback.callback(null); + } + }); + + buttons.put(getSlot(1, 1), new ArenaTeamfightCategoryButton(ArenaTeamfightCategory.CITADELS, callback)); + buttons.put(getSlot(3, 1), new ArenaTeamfightCategoryButton(ArenaTeamfightCategory.NETHER, callback)); + buttons.put(getSlot(5, 1), new ArenaTeamfightCategoryButton(ArenaTeamfightCategory.KOTHS, callback)); + buttons.put(getSlot(7, 1), new ArenaTeamfightCategoryButton(ArenaTeamfightCategory.OTHER, callback)); + + return buttons; + } + + + @Override + public int size(Player player) { + return 9*3; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/AntidoteCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/AntidoteCommand.java new file mode 100644 index 0000000..905e09f --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/AntidoteCommand.java @@ -0,0 +1,14 @@ +package com.elevatemc.potpvp.command; + +import com.elevatemc.potpvp.kit.KitItems; +import com.elevatemc.elib.command.Command; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public class AntidoteCommand { + @Command(names = {"antidote"}, permission = "op") + public static void antidote(Player sender) { + sender.getInventory().addItem(KitItems.ANTIDOTE_ITEM); + sender.sendMessage(ChatColor.GREEN + "You received an antidote!"); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/EditPotionModifyCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/EditPotionModifyCommand.java new file mode 100644 index 0000000..8ad03ff --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/EditPotionModifyCommand.java @@ -0,0 +1,38 @@ +package com.elevatemc.potpvp.command; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +public final class EditPotionModifyCommand { + + @Command(names = {"editPotion modify"}, permission = "op") + public static void editPotionModify(Player sender, @Parameter(name="effect") String effect, @Parameter(name="seconds") int seconds, @Parameter(name="amplifier") int amplifier) { + PotionEffectType effectType = PotionEffectType.getByName(effect.toUpperCase()); + ItemStack hand = sender.getItemInHand(); + + if (hand == null || hand.getType() != Material.POTION) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Please hold a potion."); + return; + } + + if (effectType == null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Could not parse " + effect); + return; + } + + PotionMeta meta = (PotionMeta) hand.getItemMeta(); + meta.addCustomEffect(new PotionEffect(effectType, seconds * 20, amplifier), true); + hand.setItemMeta(meta); + + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Modified effect " + effectType.getName() + ": Level " + amplifier + " for " + seconds + " seconds."); + sender.updateInventory(); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/EloConvertCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/EloConvertCommand.java new file mode 100644 index 0000000..c9b4532 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/EloConvertCommand.java @@ -0,0 +1,41 @@ +package com.elevatemc.potpvp.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.elo.repository.EloRepository; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.elib.command.Command; +import com.google.common.collect.ImmutableSet; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; + +import java.io.IOException; +import java.util.Map; + +public final class EloConvertCommand { + + @Command(names = {"eloconvert"}, permission = "op") + public static void eloconvert(Player sender) { + OfflinePlayer[] offlinePlayers = Bukkit.getOfflinePlayers(); + + EloRepository repository = PotPvPSI.getInstance().getEloHandler().getEloRepository(); + + for (int i = 0; i < offlinePlayers.length; i++) { + OfflinePlayer target = offlinePlayers[i]; + + if (i % 100 == 0) { + sender.sendMessage(ChatColor.GREEN + "Converting: " + i + "/" + offlinePlayers.length); + } + + try { + Map map = repository.loadElo(ImmutableSet.of(target.getUniqueId())); + repository.saveElo(ImmutableSet.of(target.getUniqueId()), map); + } catch (IOException e) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "An error occured."); + e.printStackTrace(); + } + } + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/EntityCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/EntityCommand.java new file mode 100644 index 0000000..56c6419 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/EntityCommand.java @@ -0,0 +1,31 @@ +package com.elevatemc.potpvp.command; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import com.elevatemc.potpvp.util.Color; +import org.bukkit.Bukkit; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; + +public class EntityCommand { + + @Command(names = {"ec clearall items", "ec clearall", "ec clear items", "items clear"}, permission = "op", description = "Clear all items") + public static void clearItems(Player player, @Parameter(name = "radius", wildcard = true, defaultValue = "0") double radius) { + final List entities = new ArrayList<>(); + if(radius == 0) { + entities.addAll(player.getWorld().getEntities()); + } else { + entities.addAll(player.getNearbyEntities(radius, radius, radius)); + } + entities.stream().filter(entity -> entity.getType() == EntityType.DROPPED_ITEM).forEach(Entity::remove); + + player.sendMessage(Color.translate("&aCleared " + entities.size() + " entities.")); + Bukkit.getOnlinePlayers().stream().filter(Player::isOp).forEach(op -> { + op.sendMessage(Color.translate("&c[A] &a" + entities.size() + " entities &3have been cleared.")); + }); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/HCTRankedCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/HCTRankedCommand.java new file mode 100644 index 0000000..00f4788 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/HCTRankedCommand.java @@ -0,0 +1,28 @@ +package com.elevatemc.potpvp.command; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.potpvp.util.Color; +import org.bukkit.entity.Player; + +public class HCTRankedCommand { + + @Command(names = {"hctranked"}, permission = "") + public static void hctRanked(Player player) { + player.sendMessage(Color.translate("&b&lHCT Ranked Discord: &ehttps://discord.gg/HCTRanked")); + } + + @Command(names = {"website"}, permission = "") + public static void website(Player player) { + player.sendMessage(Color.translate("&b&lWebsite: &ehttps://elevatemc.com")); + } + + @Command(names = {"tele", "telegram"}, permission = "") + public static void telegram(Player player) { + player.sendMessage(Color.translate("&b&lTelegram: &ehttps://telegram.com/ElevateMC")); + } + + @Command(names = {"store", "shop"}, permission = "") + public static void store(Player player) { + player.sendMessage(Color.translate("&b&lStore: &ehttps://store.elevatemc.com")); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/HelpCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/HelpCommand.java new file mode 100644 index 0000000..d5a518d --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/HelpCommand.java @@ -0,0 +1,12 @@ +package com.elevatemc.potpvp.command; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.potpvp.lobby.menu.HelpMenu; +import org.bukkit.entity.Player; + +public class HelpCommand { + @Command(names = {"help", "?", "halp", "helpme"}, permission = "") + public static void help(Player sender) { + new HelpMenu().openMenu(sender); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/KillCommands.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/KillCommands.java new file mode 100644 index 0000000..fa9c2dd --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/KillCommands.java @@ -0,0 +1,15 @@ +package com.elevatemc.potpvp.command; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class KillCommands { + + @Command(names = {"kill"}, permission = "core.adminteam") + public static void kill(Player sender, @Parameter(name="target") Player target) { + target.setHealth(0); + sender.sendMessage(target.getDisplayName() + ChatColor.RED + " has been killed."); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/MatchListCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/MatchListCommand.java new file mode 100644 index 0000000..10fa3d7 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/MatchListCommand.java @@ -0,0 +1,21 @@ +package com.elevatemc.potpvp.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.elib.command.Command; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class MatchListCommand { + + @Command(names = { "match list" }, permission = "op") + public static void matchList(Player sender) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + for (Match match : matchHandler.getHostedMatches()) { + sender.sendMessage(ChatColor.GOLD + match.getSimpleDescription(true)); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/MatchStatusCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/MatchStatusCommand.java new file mode 100644 index 0000000..18d4e4c --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/MatchStatusCommand.java @@ -0,0 +1,29 @@ +package com.elevatemc.potpvp.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public final class MatchStatusCommand { + + @Command(names = { "match status" }, permission = "op") + public static void matchStatus(CommandSender sender, @Parameter(name = "target") Player target) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match match = matchHandler.getMatchPlayingOrSpectating(target); + + if (match == null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + target.getName() + " is in the lobby right now."); + return; + } + + for (String line : PotPvPSI.getGson().toJson(match).split("\n")) { + sender.sendMessage(" " + ChatColor.GRAY + line); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/ModCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/ModCommand.java new file mode 100644 index 0000000..78cd77f --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/ModCommand.java @@ -0,0 +1,28 @@ +package com.elevatemc.potpvp.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.util.VisibilityUtils; +import com.elevatemc.elib.command.Command; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.metadata.FixedMetadataValue; + +public final class ModCommand { + + @Command(names = {"mod", "h", "staff", "silent"}, permission = "core.staffteam") + public static void silent(Player sender) { + if (sender.hasMetadata("modmode")) { + sender.removeMetadata("modmode", PotPvPSI.getInstance()); + sender.removeMetadata("invisible", PotPvPSI.getInstance()); + + sender.sendMessage(ChatColor.GOLD + "Mod Mode" + ChatColor.GRAY + ": " + ChatColor.RED + "Disabled"); + + } else { + sender.setMetadata("modmode", new FixedMetadataValue(PotPvPSI.getInstance(), true)); + sender.setMetadata("invisible", new FixedMetadataValue(PotPvPSI.getInstance(), true)); + + sender.sendMessage(ChatColor.GOLD + "Mod Mode" + ChatColor.GRAY + ": " + ChatColor.GREEN + "Enabled"); + } + VisibilityUtils.updateVisibility(sender); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/PStatusCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/PStatusCommand.java new file mode 100644 index 0000000..65e44c7 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/PStatusCommand.java @@ -0,0 +1,63 @@ +package com.elevatemc.potpvp.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.follow.FollowHandler; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.party.PartyHandler; +import com.elevatemc.potpvp.queue.QueueHandler; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class PStatusCommand { + + @Command(names = {"pstatus"}, permission = "op") + public static void pStatus(Player sender, @Parameter(name="target", defaultValue = "self") Player target) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + QueueHandler queueHandler = PotPvPSI.getInstance().getQueueHandler(); + PartyHandler partyHandler = PotPvPSI.getInstance().getPartyHandler(); + FollowHandler followHandler = PotPvPSI.getInstance().getFollowHandler(); + + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + target.getName() + ":"); + sender.sendMessage("In match: " + matchHandler.isPlayingMatch(target)); + sender.sendMessage("In match (NC): " + noCacheIsPlayingMatch(target)); + sender.sendMessage("In Event: " + matchHandler.isPlayingEvent(target)); + sender.sendMessage("Spectating match: " + matchHandler.isSpectatingMatch(target)); + sender.sendMessage("Spectating match (NC): " + noCacheIsSpectatingMatch(target)); + sender.sendMessage("In or spectating match: " + matchHandler.isPlayingOrSpectatingMatch(target)); + sender.sendMessage("In or spectating match (NC): " + noCacheIsPlayingOrSpectatingMatch(target)); + sender.sendMessage("In queue: " + queueHandler.isQueued(target.getUniqueId())); + sender.sendMessage("In party: " + partyHandler.hasParty(target)); + sender.sendMessage("Following: " + followHandler.getFollowing(target).isPresent()); + } + + private static boolean noCacheIsPlayingMatch(Player target) { + for (Match match : PotPvPSI.getInstance().getMatchHandler().getHostedMatches()) { + for (MatchTeam team : match.getTeams()) { + if (team.isAlive(target.getUniqueId())) { + return true; + } + } + } + + return false; + } + + private static boolean noCacheIsSpectatingMatch(Player target) { + for (Match match : PotPvPSI.getInstance().getMatchHandler().getHostedMatches()) { + if (match.isSpectator(target.getUniqueId())) { + return true; + } + } + + return false; + } + + private static boolean noCacheIsPlayingOrSpectatingMatch(Player target) { + return noCacheIsPlayingMatch(target) || noCacheIsSpectatingMatch(target); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/PingCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/PingCommand.java new file mode 100644 index 0000000..687fbb6 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/PingCommand.java @@ -0,0 +1,22 @@ +package com.elevatemc.potpvp.command; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import com.elevatemc.elib.util.PlayerUtils; +import com.elevatemc.potpvp.util.Color; +import com.elevatemc.potpvp.util.PatchedPlayerUtils; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public class PingCommand { + @Command(names = {"ping"}, permission = "") + public static void ping(Player sender, @Parameter(name = "player", defaultValue = "self") Player player) { + int ping = PlayerUtils.getPing(player); + if(sender.getUniqueId().equals(player.getUniqueId())) { + sender.sendMessage(Color.translate("&6Ping: &f" + ping)); + return; + } + + sender.sendMessage(PatchedPlayerUtils.getFormattedName(player.getUniqueId()) + "'s" + ChatColor.GOLD + " ping: " + ChatColor.WHITE + ping); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/SetSpawnCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/SetSpawnCommand.java new file mode 100644 index 0000000..76d49da --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/SetSpawnCommand.java @@ -0,0 +1,33 @@ +package com.elevatemc.potpvp.command; + +import com.elevatemc.elib.command.Command; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +/** + * /setspawn command, updates spawn location + * (spawn location is used when teleporting players to the lobby) + * + * {@link org.bukkit.World#setSpawnLocation(int, int, int, float, float)} + * is a custom method provided by PowerSpigot which stores yaw/pitch along + * with x/y/z. See net.frozenorb:mspigot-api in pom.xml + */ +public final class SetSpawnCommand { + + @Command(names = {"setspawn"}, permission = "op") + public static void setSpawn(Player sender) { + Location loc = sender.getLocation(); + + sender.getWorld().setSpawnLocation( + loc.getBlockX() + 0.5, + loc.getBlockY(), + loc.getBlockZ() + 0.5, + loc.getYaw(), + loc.getPitch() + ); + + sender.sendMessage(ChatColor.YELLOW + "Spawn point updated!"); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/StatsResetCommands.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/StatsResetCommands.java new file mode 100644 index 0000000..849df89 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/StatsResetCommands.java @@ -0,0 +1,101 @@ +package com.elevatemc.potpvp.command; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import com.elevatemc.elib.menu.menus.ConfirmMenu; +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.redis.RedisCommand; +import com.elevatemc.elib.util.Callback; +import com.elevatemc.elib.util.UUIDUtils; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import redis.clients.jedis.Jedis; + +import java.util.UUID; + +public class StatsResetCommands { + private static String REDIS_PREFIX = "PotPvP:statsResetToken:"; + + @Command(names = { "statsreset addtoken" }, permission = "op", async = true) + public static void addToken(CommandSender sender, @Parameter(name = "player") String playerName, @Parameter(name = "amount") int amount) { + UUID uuid = UUIDUtils.uuid(playerName); + + if (uuid == null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Unable to locate '" + playerName + "'."); + return; + } + + addTokens(uuid, amount); + sender.sendMessage(ChatColor.GREEN + "Added " + amount + " token" + (amount == 1 ? "" : "s") + " to " + UUIDUtils.name(uuid) + "."); + } + + @Command(names = {"statsreset"}, permission = "", async = true) + public static void reset(Player sender) { + int tokens = getTokens(sender.getUniqueId()); + if (tokens <= 0) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You need at least one token to reset your stats."); + return; + } + + Bukkit.getScheduler().runTask(PotPvPSI.getInstance(), () -> { + new ConfirmMenu("Stats reset", new Callback() { + + @Override + public void callback(Boolean reset) { + if (!reset) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Stats reset aborted."); + return; + } + + Bukkit.getScheduler().runTaskAsynchronously(PotPvPSI.getInstance(), () -> { + PotPvPSI.getInstance().getEloHandler().resetElo(sender.getUniqueId()); + removeTokens(sender.getUniqueId(), 1); + int tokens = getTokens(sender.getUniqueId()); + sender.sendMessage(ChatColor.GREEN + "Reset your stats! Used one token. " + tokens + " token" + (tokens == 1 ? "" : "s") + " left."); + }); + } + + }).openMenu(sender); + }); + } + + private static int getTokens(UUID player) { + return eLib.getInstance().runBackboneRedisCommand(new RedisCommand() { + + @Override + public Integer execute(Jedis redis) { + return Integer.valueOf(Objects.firstNonNull(redis.get(REDIS_PREFIX + player.toString()), "0")); + } + + }); + } + + private static void addTokens(UUID player, int amountBy) { + eLib.getInstance().runBackboneRedisCommand(new RedisCommand() { + + @Override + public Object execute(Jedis redis) { + redis.incrBy(REDIS_PREFIX + player.toString(), amountBy); + return null; + } + + }); + } + + public static void removeTokens(UUID player, int amountBy) { + eLib.getInstance().runBackboneRedisCommand(new RedisCommand() { + + @Override + public Object execute(Jedis redis) { + redis.decrBy(REDIS_PREFIX + player.toString(), amountBy); + return null; + } + + }); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/UnbreakableCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/UnbreakableCommand.java new file mode 100644 index 0000000..c980d7f --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/UnbreakableCommand.java @@ -0,0 +1,24 @@ +package com.elevatemc.potpvp.command; + +import com.elevatemc.elib.command.Command; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +public class UnbreakableCommand { + @Command(names = {"unbreakable"}, permission = "op") + public static void unbreakable(Player sender) { + ItemStack hand = sender.getItemInHand(); + if (hand == null || hand.getType().equals(Material.AIR)) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You must hold an item to make it unbreakable!"); + return; + } + + ItemMeta meta = hand.getItemMeta(); + meta.spigot().setUnbreakable(true); + hand.setItemMeta(meta); + sender.sendMessage(ChatColor.GREEN + "The item is now unbreakable!"); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/UpdateInventoryCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/UpdateInventoryCommand.java new file mode 100644 index 0000000..42b77ca --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/UpdateInventoryCommand.java @@ -0,0 +1,22 @@ +package com.elevatemc.potpvp.command; + +import com.elevatemc.potpvp.util.InventoryUtils; +import com.elevatemc.elib.command.Command; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +/** + * /updateInventory command, typically only used for debugging inventory + * issues. Available to all players to enforce the constraint that + * {@link com.elevatemc.potpvp.util.InventoryUtils#resetInventoryDelayed(Player)} + * can always be called at any time. + */ +public final class UpdateInventoryCommand { + + @Command(names = {"updateinventory", "updateinv", "upinv", "ui"}, permission = "") + public static void updateInventory(Player sender) { + InventoryUtils.resetInventoryDelayed(sender); + sender.sendMessage(ChatColor.GREEN + "Updated your inventory."); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/UpdateVisibilityCommands.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/UpdateVisibilityCommands.java new file mode 100644 index 0000000..30f7b2b --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/UpdateVisibilityCommands.java @@ -0,0 +1,22 @@ +package com.elevatemc.potpvp.command; + +import com.elevatemc.potpvp.util.VisibilityUtils; +import com.elevatemc.elib.command.Command; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class UpdateVisibilityCommands { + + @Command(names = {"updatevisibility", "updatevis", "upvis", "uv"}, permission = "") + public static void updateVisibility(Player sender) { + VisibilityUtils.updateVisibility(sender); + sender.sendMessage(ChatColor.GREEN + "Updated your visibility."); + } + + @Command(names = {"updatevisibilityFlicker", "updatevisFlicker", "upvisFlicker", "uvf"}, permission = "uvf") + public static void updateVisibilityFlicker(Player sender) { + VisibilityUtils.updateVisibilityFlicker(sender); + sender.sendMessage(ChatColor.GREEN + "Updated your visibility (flicker mode)."); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/VDebugCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/VDebugCommand.java new file mode 100644 index 0000000..cb2e906 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/VDebugCommand.java @@ -0,0 +1,65 @@ +package com.elevatemc.potpvp.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import net.minecraft.server.v1_8_R3.EntityTracker; +import net.minecraft.server.v1_8_R3.EntityTrackerEntry; +import net.minecraft.server.v1_8_R3.PacketPlayOutPlayerInfo; +import net.minecraft.server.v1_8_R3.WorldServer; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import org.bukkit.entity.Player; + +public final class VDebugCommand { + + @Command(names = {"weirdcommand"}, permission = "op") + public static void vdebug(Player sender, @Parameter(name="a") Player a, @Parameter(name="b") Player b, @Parameter(name="modify", defaultValue = "0") int modify) { + CraftPlayer aCraft = (CraftPlayer) a; + CraftPlayer bCraft = (CraftPlayer) b; + EntityTracker tracker = ((WorldServer) aCraft.getHandle().world).tracker; + EntityTrackerEntry aTracker = tracker.trackedEntities.get(aCraft.getHandle().getId()); + EntityTrackerEntry bTracker = tracker.trackedEntities.get(bCraft.getHandle().getId()); + + if (modify == 1) { + a.showPlayer(b); + b.showPlayer(a); + + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Performed soft modify."); + return; + } else if (modify == 2) { + aCraft.getHandle().playerConnection.sendPacket(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, bCraft.getHandle())); + bCraft.getHandle().playerConnection.sendPacket(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, aCraft.getHandle())); + + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Performed hard modify."); + return; + } else if (modify == 3) { + a.hidePlayer(b); + b.hidePlayer(a); + + Bukkit.getScheduler().runTaskLater(PotPvPSI.getInstance(), () -> { + a.showPlayer(b); + b.showPlayer(a); + }, 10L); + + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Performed flicker modify."); + return; + } + + sender.sendMessage(ChatColor.AQUA.toString() + ChatColor.UNDERLINE + a.getName() + " <-> " + b.getName() + ":"); + sender.sendMessage(""); + sender.sendMessage(ChatColor.BLUE + "a Validation: " + ChatColor.WHITE + (aCraft.getHandle().playerConnection != null && !a.equals(b))); + sender.sendMessage(ChatColor.BLUE + "b Validation: " + ChatColor.WHITE + (bCraft.getHandle().playerConnection != null && !b.equals(a))); + + sender.sendMessage(ChatColor.BLUE + "a.canSee(b): " + ChatColor.WHITE + a.canSee(b)); + sender.sendMessage(ChatColor.BLUE + "b.canSee(a): " + ChatColor.WHITE + b.canSee(a)); + + sender.sendMessage(ChatColor.BLUE + "a Tracker Entry: " + ChatColor.WHITE + aTracker); + sender.sendMessage(ChatColor.BLUE + "b Tracker Entry: " + ChatColor.WHITE + bTracker); + + sender.sendMessage(ChatColor.BLUE + "aTracker.trackedPlayers.contains(b): " + ChatColor.WHITE + aTracker.trackedPlayers.contains(bCraft.getHandle())); + sender.sendMessage(ChatColor.BLUE + "bTracker.trackedPlayers.contains(a): " + ChatColor.WHITE + bTracker.trackedPlayers.contains(aCraft.getHandle())); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/WorldCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/WorldCommand.java new file mode 100644 index 0000000..e3d475c --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/WorldCommand.java @@ -0,0 +1,51 @@ +package com.elevatemc.potpvp.command; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.generator.ChunkGenerator; +import org.jetbrains.annotations.NotNull; + +public class WorldCommand { + + @Command(names = {"world tp"}, permission = "op", description = "Teleport to a world") + public static void worldTp(@NotNull Player player, @Parameter(name = "world") World world) { + player.sendMessage(ChatColor.GOLD + "Teleporting you to " + ChatColor.WHITE + world.getName()); + player.teleport(world.getSpawnLocation()); + } + + @Command(names = {"world list"}, permission = "op", description = "Get a list of all worlds") + public static void worldList(Player player) { + for (World world : Bukkit.getWorlds()) { + String name = world.getName(); + Location spawn = world.getSpawnLocation(); + + ChunkGenerator generator = world.getGenerator(); + String generatorName; + if (generator == null) { // Edge cases when it fails to load a generator + generatorName = "null"; + } else { + generatorName = generator.getClass().getSimpleName(); + } + + String type = world.getWorldType().toString(); + int entities = world.getEntities().size(); + int players = world.getPlayers().size(); + int loadedChunks = world.getLoadedChunks().length; + + player.sendMessage(ChatColor.GRAY.toString() + net.md_5.bungee.api.ChatColor.STRIKETHROUGH + "-----------------------------------------------------"); + player.sendMessage(ChatColor.GOLD + "Name: " + ChatColor.WHITE + name); + player.sendMessage(ChatColor.GOLD + "Spawn: " + ChatColor.WHITE + spawn.getBlockX() + " " + spawn.getBlockY() + " " + spawn.getBlockZ()); + player.sendMessage(ChatColor.GOLD + "Generator: " + ChatColor.WHITE + generatorName); + player.sendMessage(ChatColor.GOLD + "Type: " + ChatColor.WHITE + type); + player.sendMessage(ChatColor.GOLD + "Entities: " + ChatColor.WHITE + entities); + player.sendMessage(ChatColor.GOLD + "Players: " + ChatColor.WHITE + players); + player.sendMessage(ChatColor.GOLD + "Loaded chunks: " + ChatColor.WHITE + loadedChunks); + } + player.sendMessage(ChatColor.GRAY.toString() + net.md_5.bungee.api.ChatColor.STRIKETHROUGH + "-----------------------------------------------------"); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/package-info.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/package-info.java new file mode 100644 index 0000000..5b66093 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/command/package-info.java @@ -0,0 +1,8 @@ +/** + * Commands that do not belong to another specific package + * + * Commands should be, by default, placed in packages specific to + * their function (ex /party commands in party package), however some + * commands (ex /help, /setspawn, etc) don't map cleanly to a package + */ +package com.elevatemc.potpvp.command; \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/cosmetic/Cosmetic.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/cosmetic/Cosmetic.java new file mode 100644 index 0000000..83ffadd --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/cosmetic/Cosmetic.java @@ -0,0 +1,31 @@ +package com.elevatemc.potpvp.cosmetic; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.cosmetic.type.CosmeticType; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; + +@Getter @RequiredArgsConstructor +public abstract class Cosmetic implements Listener { + + private final String name; + + public void register() { + Bukkit.getPluginManager().registerEvents(this, PotPvPSI.getInstance()); + } + + public void unregister() { + HandlerList.unregisterAll(this); + } + + public abstract void onEnable(Player player); + public abstract void onDisable(Player player); + + public CosmeticType getType() { + return CosmeticType.MISC; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/cosmetic/CosmeticHandler.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/cosmetic/CosmeticHandler.java new file mode 100644 index 0000000..d70145a --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/cosmetic/CosmeticHandler.java @@ -0,0 +1,61 @@ +package com.elevatemc.potpvp.cosmetic; + +import com.elevatemc.potpvp.cosmetic.impl.WoolTrailCosmetic; +import com.elevatemc.potpvp.cosmetic.type.CosmeticType; +import com.elevatemc.potpvp.util.Color; +import lombok.Getter; +import org.bukkit.entity.Player; + +import java.util.*; + +@Getter +public class CosmeticHandler { + + private final List cosmetics; + private final Map> activeCosmetics; + + public CosmeticHandler() { + this.cosmetics = new ArrayList<>(); + this.activeCosmetics = new HashMap<>(); + registerCosmetics(); + } + + private void registerCosmetics() { + cosmetics.addAll(Arrays.asList( + new WoolTrailCosmetic() + )); + cosmetics.forEach(Cosmetic::register); + } + + public void activateCosmetic(Player player, Cosmetic cosmetic) { + final List activeCosmetics = new ArrayList<>(); + activeCosmetics.addAll(this.activeCosmetics.getOrDefault(player.getUniqueId(), Collections.emptyList())); + if(activeCosmetics.stream().anyMatch(curCos -> curCos.getType() == cosmetic.getType())) { + player.sendMessage(Color.translate("&cYou already have a cosmetic with that type enabled! Disable before equipping a new cosmetic!")); + return; + } + activeCosmetics.add(cosmetic); + cosmetic.onEnable(player); + this.activeCosmetics.put(player.getUniqueId(), activeCosmetics); + } + + public void deactivateCosmetics(Player player) { + this.activeCosmetics.remove(player.getUniqueId()).forEach(cosmetic -> { + cosmetic.onDisable(player); + }); + } + + public void deactivateCosmetic(Player player, Cosmetic cosmetic) { + final List activeCosmetics = new ArrayList<>(); + activeCosmetics.addAll(this.activeCosmetics.getOrDefault(player.getUniqueId(), Collections.emptyList())); + if(activeCosmetics.isEmpty()) return; + activeCosmetics.remove(cosmetic); + cosmetic.onDisable(player); + this.activeCosmetics.put(player.getUniqueId(), activeCosmetics); + } + + public boolean isEnabled(Player player, CosmeticType type) { + final List activeCosmetics = new ArrayList<>(this.activeCosmetics.getOrDefault(player.getUniqueId(), Collections.emptyList())); + return activeCosmetics.stream().anyMatch(cosmetic -> cosmetic.getType() == type); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/cosmetic/command/CosmeticCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/cosmetic/command/CosmeticCommand.java new file mode 100644 index 0000000..588aadf --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/cosmetic/command/CosmeticCommand.java @@ -0,0 +1,35 @@ +package com.elevatemc.potpvp.cosmetic.command; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.cosmetic.Cosmetic; +import com.elevatemc.potpvp.cosmetic.CosmeticHandler; +import org.bukkit.entity.Player; + +import java.util.stream.Collectors; + +public class CosmeticCommand { + + private static final CosmeticHandler cosmeticHandler = PotPvPSI.getInstance().getCosmeticHandler(); + + @Command(names = {"cosmetic list"}, permission = "op", description = "") + public static void list(Player player) { + player.sendMessage(cosmeticHandler.getCosmetics().stream().map(Cosmetic::getName).collect(Collectors.joining(", "))); + } + + @Command(names = {"cosmetic equip"}, permission = "op", description = "") + public static void equip(Player player, @Parameter(name = "cosmetic") Cosmetic cosmetic) { + cosmeticHandler.activateCosmetic(player, cosmetic); + } + + @Command(names = {"cosmetic unequip-all"}, permission = "op", description = "") + public static void unequipAll(Player player) { + cosmeticHandler.deactivateCosmetics(player); + } + + @Command(names = {"cosmetic unequip"}, permission = "op", description = "") + public static void unequip(Player player, @Parameter(name = "cosmetic") Cosmetic cosmetic) { + cosmeticHandler.deactivateCosmetic(player, cosmetic); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/cosmetic/command/type/CosmeticParameterType.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/cosmetic/command/type/CosmeticParameterType.java new file mode 100644 index 0000000..09fa625 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/cosmetic/command/type/CosmeticParameterType.java @@ -0,0 +1,35 @@ +package com.elevatemc.potpvp.cosmetic.command.type; + +import com.elevatemc.elib.command.param.ParameterType; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.cosmetic.Cosmetic; +import com.elevatemc.potpvp.cosmetic.CosmeticHandler; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class CosmeticParameterType implements ParameterType { + + private final CosmeticHandler cosmeticHandler = PotPvPSI.getInstance().getCosmeticHandler(); + + @Override + public Cosmetic transform(CommandSender sender, String s) { + if(s.equalsIgnoreCase("all")) return null; + return cosmeticHandler.getCosmetics() + .stream() + .filter(cosmetic -> cosmetic.getName().equalsIgnoreCase(s)) + .findAny() + .orElse(null); + } + + @Override + public List tabComplete(Player player, Set set, String s) { + return cosmeticHandler.getCosmetics() + .stream() + .map(Cosmetic::getName) + .collect(Collectors.toList()); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/cosmetic/impl/WoolTrailCosmetic.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/cosmetic/impl/WoolTrailCosmetic.java new file mode 100644 index 0000000..cfe2960 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/cosmetic/impl/WoolTrailCosmetic.java @@ -0,0 +1,124 @@ +package com.elevatemc.potpvp.cosmetic.impl; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.cosmetic.Cosmetic; +import com.elevatemc.potpvp.cosmetic.type.CosmeticType; +import com.elevatemc.potpvp.util.Color; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.material.MaterialData; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.*; + +public class WoolTrailCosmetic extends Cosmetic { + + private final List blockRestores; + + public WoolTrailCosmetic() { + super("WOOL_TRAIL"); + this.blockRestores = new ArrayList<>(); + new BlockRestoreTask().runTaskTimer(PotPvPSI.getInstance(), 0L, 20L); + } + + @Override + public void register() { + super.register(); + } + + @Override + public void onEnable(Player player) { + player.sendMessage(Color.translate("&eYou have &aenabled ðe &fWool Trail &ecosmetic!")); + } + + @Override + public void onDisable(Player player) { + player.sendMessage(Color.translate("&eYou have &cdisabled ðe &fWool Trail &ecosmetic!")); + } + + @EventHandler + public void onMove(PlayerMoveEvent event) { + final Player player = event.getPlayer(); + if(!PotPvPSI.getInstance().getCosmeticHandler().isEnabled(player, getType())) return; + if(event.getTo().equals(event.getFrom())) return; + final Location location = event.getTo(); + final ItemStack wool = getWool(player); + + // set 3x3 block radius below players feet + final Location belowLocation = location.clone().subtract(0, 1, 0); + for(int x=-1; x<=1; x++) { + for(int z=-1; z<=1; z++) { + final Block block = belowLocation.getBlock().getRelative(x, 0, z); + if(block.getType() == wool.getType()) continue; + this.blockRestores.add(new BlockRestore( + block.getLocation(), + block.getType(), + block.getState().getData() + )); + + block.setType(wool.getType()); + block.getState().setData(wool.getData()); + } + } + } + + public ItemStack getWool(Player player) { + final Optional profileOptional = PotPvPSI.getInstance().getPrime().getModuleHandler().getModule(ProfileHandler.class).getProfile(player.getUniqueId()); + if(!profileOptional.isPresent()) return new ItemStack(Material.WOOL); + + final Profile profile = profileOptional.get(); + return profile.getHighestActiveGrant().getRank().getWool(); + } + + @Override + public CosmeticType getType() { + return CosmeticType.TRAIL; + } + + private class BlockRestoreTask extends BukkitRunnable { + + @Override + public void run() { + Iterator itr = blockRestores.iterator(); + while(itr.hasNext()) { + final BlockRestore blockRestore = itr.next(); + if(System.currentTimeMillis() >= blockRestore.getExpireAt()) { + final Block block = blockRestore.getBlockLocation().getWorld().getBlockAt(blockRestore.getBlockLocation()); + block.setType(blockRestore.getPreviousBlock()); + block.getState().setData(blockRestore.getPreviousData()); + itr.remove(); + } + } + } + } + + @Getter @RequiredArgsConstructor + private static class BlockRestore { + private final Location blockLocation; + private final Material previousBlock; + private final MaterialData previousData; + private final long expireAt = System.currentTimeMillis() + 1500; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BlockRestore that = (BlockRestore) o; + return com.google.common.base.Objects.equal(blockLocation, that.blockLocation); + } + + @Override + public int hashCode() { + return com.google.common.base.Objects.hashCode(blockLocation); + } + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/cosmetic/type/CosmeticType.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/cosmetic/type/CosmeticType.java new file mode 100644 index 0000000..20b4b05 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/cosmetic/type/CosmeticType.java @@ -0,0 +1,7 @@ +package com.elevatemc.potpvp.cosmetic.type; + +public enum CosmeticType { + TRAIL, + PARTICLE, + MISC; +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/DeathMessageHandler.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/DeathMessageHandler.java new file mode 100644 index 0000000..620ddb2 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/DeathMessageHandler.java @@ -0,0 +1,45 @@ +package com.elevatemc.potpvp.deathmessage; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.deathmessage.listeners.DamageListener; +import com.elevatemc.potpvp.deathmessage.objects.Damage; +import com.elevatemc.potpvp.deathmessage.trackers.*; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class DeathMessageHandler { + private final Map> damage = new HashMap<>(); + + public DeathMessageHandler () { + Bukkit.getPluginManager().registerEvents(new DamageListener(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new GeneralTracker(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new PVPTracker(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new EntityTracker(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new FallTracker(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new ArrowTracker(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new VoidTracker(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new BurnTracker(), PotPvPSI.getInstance()); + } + + public List getDamage(Player player) { + return damage.get(player.getName()); + } + + public void addDamage(Player player, Damage addedDamage) { + if (!damage.containsKey(player.getName())) + damage.put(player.getName(), new ArrayList<>()); + List damageList = damage.get(player.getName()); + while (damageList.size() > 30) + damageList.remove(0); + damageList.add(addedDamage); + } + + public void clearDamage(Player player) { + damage.remove(player.getName()); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/event/CustomPlayerDamageEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/event/CustomPlayerDamageEvent.java new file mode 100644 index 0000000..89e9604 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/event/CustomPlayerDamageEvent.java @@ -0,0 +1,49 @@ +package com.elevatemc.potpvp.deathmessage.event; + +import com.elevatemc.potpvp.deathmessage.objects.Damage; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityDamageEvent; + +import java.beans.ConstructorProperties; + +public class CustomPlayerDamageEvent extends Event { + private static final HandlerList handlerList = new HandlerList(); + private final EntityDamageEvent cause; + private Damage trackerDamage; + + @ConstructorProperties({"cause", "trackerDamage"}) + public CustomPlayerDamageEvent(EntityDamageEvent cause, Damage trackerDamage) { + this.cause = cause; + this.trackerDamage = trackerDamage; + } + + public static HandlerList getHandlerList() { + return handlerList; + } + + public EntityDamageEvent getCause() { + return this.cause; + } + + public Damage getTrackerDamage() { + return this.trackerDamage; + } + + public void setTrackerDamage(Damage trackerDamage) { + this.trackerDamage = trackerDamage; + } + + public Player getPlayer() { + return (Player) this.cause.getEntity(); + } + + public double getDamage() { + return this.cause.getDamage(); + } + + public HandlerList getHandlers() { + return handlerList; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/event/PlayerKilledEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/event/PlayerKilledEvent.java new file mode 100644 index 0000000..c6ea450 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/event/PlayerKilledEvent.java @@ -0,0 +1,35 @@ +package com.elevatemc.potpvp.deathmessage.event; + +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import java.beans.ConstructorProperties; + +public class PlayerKilledEvent extends Event { + private static final HandlerList handlerList = new HandlerList(); + private final Player killer; + private final Player victim; + + @ConstructorProperties({"killer", "victim"}) + public PlayerKilledEvent(Player killer, Player victim) { + this.killer = killer; + this.victim = victim; + } + + public static HandlerList getHandlerList() { + return handlerList; + } + + public Player getKiller() { + return this.killer; + } + + public Player getVictim() { + return this.victim; + } + + public HandlerList getHandlers() { + return handlerList; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/listeners/DamageListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/listeners/DamageListener.java new file mode 100644 index 0000000..db23871 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/listeners/DamageListener.java @@ -0,0 +1,31 @@ +package com.elevatemc.potpvp.deathmessage.listeners; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.deathmessage.DeathMessageHandler; +import com.elevatemc.potpvp.deathmessage.event.CustomPlayerDamageEvent; +import com.elevatemc.potpvp.deathmessage.util.UnknownDamage; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +public class DamageListener implements Listener { + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onEntityDamage(EntityDamageEvent event) { + if (event.getEntity() instanceof Player) { + Player player = (Player) event.getEntity(); + CustomPlayerDamageEvent customEvent = new CustomPlayerDamageEvent(event, new UnknownDamage(player.getName(), event.getDamage())); + Bukkit.getPluginManager().callEvent(customEvent); + PotPvPSI.getInstance().getDeathMessageHandler().addDamage(player, customEvent.getTrackerDamage()); + } + } + + @EventHandler + public void onQuit(PlayerQuitEvent event) { + PotPvPSI.getInstance().getDeathMessageHandler().clearDamage(event.getPlayer()); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/objects/Damage.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/objects/Damage.java new file mode 100644 index 0000000..90dcb99 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/objects/Damage.java @@ -0,0 +1,47 @@ +package com.elevatemc.potpvp.deathmessage.objects; + +import com.elevatemc.potpvp.nametag.PotPvPNametagProvider; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public abstract class Damage { + private final String damaged; + + private final double damage; + + private final long time; + + public Damage(String damaged, double damage) { + this.damaged = damaged; + this.damage = damage; + this.time = System.currentTimeMillis(); + } + + public String getDamaged() { + return this.damaged; + } + + public double getDamage() { + return this.damage; + } + + public long getTime() { + return this.time; + } + + public String wrapName(String playerName, Player viewer) { + Player player = Bukkit.getPlayer(playerName); + ChatColor color = ChatColor.GRAY; + if (player != null) { + color = PotPvPNametagProvider.getNameColor(player, viewer); + } + return color.toString() + playerName + ChatColor.GRAY; + } + + public long getTimeDifference() { + return System.currentTimeMillis() - this.time; + } + + public abstract String getDeathMessage(Player viewer); +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/objects/MobDamage.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/objects/MobDamage.java new file mode 100644 index 0000000..00037df --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/objects/MobDamage.java @@ -0,0 +1,17 @@ +package com.elevatemc.potpvp.deathmessage.objects; + +import com.elevatemc.potpvp.deathmessage.objects.Damage; +import org.bukkit.entity.EntityType; + +public abstract class MobDamage extends Damage { + private final EntityType mobType; + + public MobDamage(String damaged, double damage, EntityType mobType) { + super(damaged, damage); + this.mobType = mobType; + } + + public EntityType getMobType() { + return this.mobType; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/objects/PlayerDamage.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/objects/PlayerDamage.java new file mode 100644 index 0000000..4e016aa --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/objects/PlayerDamage.java @@ -0,0 +1,14 @@ +package com.elevatemc.potpvp.deathmessage.objects; + +public abstract class PlayerDamage extends Damage { + private final String damager; + + public PlayerDamage(String damaged, double damage, String damager) { + super(damaged, damage); + this.damager = damager; + } + + public String getDamager() { + return this.damager; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/trackers/ArrowTracker.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/trackers/ArrowTracker.java new file mode 100644 index 0000000..26d3577 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/trackers/ArrowTracker.java @@ -0,0 +1,94 @@ +package com.elevatemc.potpvp.deathmessage.trackers; + +import com.elevatemc.elib.util.EntityUtils; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.deathmessage.event.CustomPlayerDamageEvent; +import com.elevatemc.potpvp.deathmessage.objects.Damage; +import com.elevatemc.potpvp.deathmessage.objects.MobDamage; +import com.elevatemc.potpvp.deathmessage.objects.PlayerDamage; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityShootBowEvent; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.metadata.MetadataValue; + +public class ArrowTracker implements Listener { + @EventHandler + public void onEntityShootBow(EntityShootBowEvent event) { + if (event.getEntity() instanceof Player) + event.getProjectile().setMetadata("ShotFromDistance", new FixedMetadataValue(PotPvPSI.getInstance(), event.getProjectile().getLocation())); + } + + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onCustomPlayerDamage(CustomPlayerDamageEvent event) { + if (event.getCause() instanceof EntityDamageByEntityEvent) { + EntityDamageByEntityEvent entityDamageByEntityEvent = (EntityDamageByEntityEvent) event.getCause(); + if (entityDamageByEntityEvent.getDamager() instanceof Arrow) { + Arrow arrow = (Arrow) entityDamageByEntityEvent.getDamager(); + if (arrow.getShooter() instanceof Player) { + Player shooter = (Player) arrow.getShooter(); + for (MetadataValue value : arrow.getMetadata("ShotFromDistance")) { + Location shotFrom = (Location) value.value(); + double distance = shotFrom.distance(event.getPlayer().getLocation()); + event.setTrackerDamage(new ArrowDamageByPlayer(event.getPlayer().getName(), event.getDamage(), shooter.getName(), shotFrom, distance)); + } + } else if (arrow.getShooter() instanceof Entity) { + event.setTrackerDamage(new ArrowDamageByMob(event.getPlayer().getName(), event.getDamage(), (Entity) arrow.getShooter())); + } else { + event.setTrackerDamage(new ArrowDamage(event.getPlayer().getName(), event.getDamage())); + } + } + } + } + + public static class ArrowDamage extends Damage { + public ArrowDamage(String damaged, double damage) { + super(damaged, damage); + } + + public String getDeathMessage(Player viewer) { + return wrapName(getDamaged(), viewer) + " was shot."; + } + } + + public static class ArrowDamageByPlayer extends PlayerDamage { + private final Location shotFrom; + + private final double distance; + + public ArrowDamageByPlayer(String damaged, double damage, String damager, Location shotFrom, double distance) { + super(damaged, damage, damager); + this.shotFrom = shotFrom; + this.distance = distance; + } + + public Location getShotFrom() { + return this.shotFrom; + } + + public double getDistance() { + return this.distance; + } + + public String getDeathMessage(Player viewer) { + return wrapName(getDamaged(), viewer) + " was shot by " + wrapName(getDamager(), viewer) + " from " + ChatColor.BLUE + (int) this.distance + " blocks" + ChatColor.GRAY + "."; + } + } + + public static class ArrowDamageByMob extends MobDamage { + public ArrowDamageByMob(String damaged, double damage, Entity damager) { + super(damaged, damage, damager.getType()); + } + + public String getDeathMessage(Player viewer) { + return wrapName(getDamaged(), viewer) + " was shot by a " + ChatColor.RED + EntityUtils.getName(getMobType()) + ChatColor.GRAY + "."; + } + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/trackers/BurnTracker.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/trackers/BurnTracker.java new file mode 100644 index 0000000..10b42cf --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/trackers/BurnTracker.java @@ -0,0 +1,60 @@ +package com.elevatemc.potpvp.deathmessage.trackers; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.deathmessage.DeathMessageHandler; +import com.elevatemc.potpvp.deathmessage.event.CustomPlayerDamageEvent; +import com.elevatemc.potpvp.deathmessage.objects.Damage; +import com.elevatemc.potpvp.deathmessage.objects.PlayerDamage; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageEvent; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class BurnTracker implements Listener { + @EventHandler(priority = EventPriority.LOW) + public void onCustomPlayerDamage(CustomPlayerDamageEvent event) { + if (event.getCause().getCause() != EntityDamageEvent.DamageCause.FIRE_TICK && event.getCause().getCause() != EntityDamageEvent.DamageCause.LAVA) + return; + List record = PotPvPSI.getInstance().getDeathMessageHandler().getDamage(event.getPlayer()); + Damage knocker = null; + long knockerTime = 0L; + if (record != null) + for (Damage damage : record) { + if (damage instanceof BurnDamage || damage instanceof BurnDamageByPlayer) + continue; + if (damage instanceof PlayerDamage && (knocker == null || damage.getTime() > knockerTime)) { + knocker = damage; + knockerTime = damage.getTime(); + } + } + if (knocker != null && knockerTime + TimeUnit.MINUTES.toMillis(1L) > System.currentTimeMillis()) { + event.setTrackerDamage(new BurnDamageByPlayer(event.getPlayer().getName(), event.getDamage(), ((PlayerDamage) knocker).getDamager())); + } else { + event.setTrackerDamage(new BurnDamage(event.getPlayer().getName(), event.getDamage())); + } + } + + public static class BurnDamage extends Damage { + public BurnDamage(String damaged, double damage) { + super(damaged, damage); + } + + public String getDeathMessage(Player viewer) { + return wrapName(getDamaged(), viewer) + " burned to death"; + } + } + + public static class BurnDamageByPlayer extends PlayerDamage { + public BurnDamageByPlayer(String damaged, double damage, String damager) { + super(damaged, damage, damager); + } + + public String getDeathMessage(Player viewer) { + return wrapName(getDamaged(), viewer) + " burned to death thanks to " + wrapName(getDamager(), viewer) + "."; + } + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/trackers/EntityTracker.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/trackers/EntityTracker.java new file mode 100644 index 0000000..67861a0 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/trackers/EntityTracker.java @@ -0,0 +1,33 @@ +package com.elevatemc.potpvp.deathmessage.trackers; + +import com.elevatemc.elib.util.EntityUtils; +import com.elevatemc.potpvp.deathmessage.event.CustomPlayerDamageEvent; +import com.elevatemc.potpvp.deathmessage.objects.MobDamage; +import org.bukkit.ChatColor; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; + +public class EntityTracker implements Listener { + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onCustomPlayerDamage(CustomPlayerDamageEvent event) { + if (event.getCause() instanceof EntityDamageByEntityEvent) { + EntityDamageByEntityEvent e = (EntityDamageByEntityEvent) event.getCause(); + if (!(e.getDamager() instanceof org.bukkit.entity.Player) && !(e.getDamager() instanceof org.bukkit.entity.Arrow)) + event.setTrackerDamage(new EntityDamage(event.getPlayer().getName(), event.getDamage(), e.getDamager())); + } + } + + public static class EntityDamage extends MobDamage { + public EntityDamage(String damaged, double damage, Entity entity) { + super(damaged, damage, entity.getType()); + } + + public String getDeathMessage(Player viewer) { + return wrapName(getDamaged(), viewer) + " was slain by a " + ChatColor.RED + EntityUtils.getName(getMobType()) + ChatColor.GRAY + "."; + } + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/trackers/FallTracker.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/trackers/FallTracker.java new file mode 100644 index 0000000..38fa21d --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/trackers/FallTracker.java @@ -0,0 +1,60 @@ +package com.elevatemc.potpvp.deathmessage.trackers; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.deathmessage.DeathMessageHandler; +import com.elevatemc.potpvp.deathmessage.event.CustomPlayerDamageEvent; +import com.elevatemc.potpvp.deathmessage.objects.Damage; +import com.elevatemc.potpvp.deathmessage.objects.PlayerDamage; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageEvent; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class FallTracker implements Listener { + @EventHandler(priority = EventPriority.LOW) + public void onCustomPlayerDamage(CustomPlayerDamageEvent event) { + if (event.getCause().getCause() != EntityDamageEvent.DamageCause.FALL) + return; + List record = PotPvPSI.getInstance().getDeathMessageHandler().getDamage(event.getPlayer()); + Damage knocker = null; + long knockerTime = 0L; + if (record != null) + for (Damage damage : record) { + if (damage instanceof FallDamage || damage instanceof FallDamageByPlayer) + continue; + if (damage instanceof PlayerDamage && (knocker == null || damage.getTime() > knockerTime)) { + knocker = damage; + knockerTime = damage.getTime(); + } + } + if (knocker != null && knockerTime + TimeUnit.MINUTES.toMillis(1L) > System.currentTimeMillis()) { + event.setTrackerDamage(new FallDamageByPlayer(event.getPlayer().getName(), event.getDamage(), ((PlayerDamage) knocker).getDamager())); + } else { + event.setTrackerDamage(new FallDamage(event.getPlayer().getName(), event.getDamage())); + } + } + + public static class FallDamage extends Damage { + public FallDamage(String damaged, double damage) { + super(damaged, damage); + } + + public String getDeathMessage(Player viewer) { + return wrapName(getDamaged(), viewer) + " hit the ground too hard."; + } + } + + public static class FallDamageByPlayer extends PlayerDamage { + public FallDamageByPlayer(String damaged, double damage, String damager) { + super(damaged, damage, damager); + } + + public String getDeathMessage(Player viewer) { + return wrapName(getDamaged(), viewer) + " hit the ground too hard thanks to " + wrapName(getDamager(), viewer) + "."; + } + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/trackers/GeneralTracker.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/trackers/GeneralTracker.java new file mode 100644 index 0000000..c6e5e8d --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/trackers/GeneralTracker.java @@ -0,0 +1,47 @@ +package com.elevatemc.potpvp.deathmessage.trackers; + +import com.elevatemc.potpvp.deathmessage.event.CustomPlayerDamageEvent; +import com.elevatemc.potpvp.deathmessage.objects.Damage; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; + +public class GeneralTracker implements Listener { + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onCustomPlayerDamage(CustomPlayerDamageEvent event) { + switch (event.getCause().getCause()) { + case SUFFOCATION: + event.setTrackerDamage(new GeneralDamage(event.getPlayer().getName(), event.getDamage(), "suffocated")); + break; + case DROWNING: + event.setTrackerDamage(new GeneralDamage(event.getPlayer().getName(), event.getDamage(), "drowned")); + break; + case STARVATION: + event.setTrackerDamage(new GeneralDamage(event.getPlayer().getName(), event.getDamage(), "starved to death")); + break; + case LIGHTNING: + event.setTrackerDamage(new GeneralDamage(event.getPlayer().getName(), event.getDamage(), "was struck by lightning")); + break; + case POISON: + event.setTrackerDamage(new GeneralDamage(event.getPlayer().getName(), event.getDamage(), "was poisoned")); + break; + case WITHER: + event.setTrackerDamage(new GeneralDamage(event.getPlayer().getName(), event.getDamage(), "withered away")); + break; + } + } + + public static class GeneralDamage extends Damage { + private final String message; + + public GeneralDamage(String damaged, double damage, String message) { + super(damaged, damage); + this.message = message; + } + + public String getDeathMessage(Player viewer) { + return wrapName(getDamaged(), viewer) + " " + this.message + "."; + } + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/trackers/PVPTracker.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/trackers/PVPTracker.java new file mode 100644 index 0000000..deb3abf --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/trackers/PVPTracker.java @@ -0,0 +1,46 @@ +package com.elevatemc.potpvp.deathmessage.trackers; + +import com.elevatemc.potpvp.deathmessage.event.CustomPlayerDamageEvent; +import com.elevatemc.potpvp.deathmessage.objects.PlayerDamage; +import com.elevatemc.potpvp.deathmessage.util.MobUtil; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; + +public class PVPTracker implements Listener { + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onCustomPlayerDamage(CustomPlayerDamageEvent event) { + if (event.getCause() instanceof EntityDamageByEntityEvent) { + EntityDamageByEntityEvent e = (EntityDamageByEntityEvent) event.getCause(); + if (e.getDamager() instanceof Player) { + Player damager = (Player) e.getDamager(); + Player damaged = event.getPlayer(); + event.setTrackerDamage(new PVPDamage(damaged.getName(), event.getDamage(), damager.getName(), damager.getItemInHand())); + } + } + } + + public static class PVPDamage extends PlayerDamage { + private String itemString; + + public PVPDamage(String damaged, double damage, String damager, ItemStack itemStack) { + super(damaged, damage, damager); + this.itemString = "Error"; + if (itemStack.getType() == Material.AIR) { + this.itemString = "their fists"; + } else { + this.itemString = MobUtil.getItemName(itemStack); + } + } + + public String getDeathMessage(Player viewer) { + return wrapName(getDamaged(), viewer) + " was slain by " + wrapName(getDamager(), viewer) + "."; + } + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/trackers/VoidTracker.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/trackers/VoidTracker.java new file mode 100644 index 0000000..8dfccde --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/trackers/VoidTracker.java @@ -0,0 +1,60 @@ +package com.elevatemc.potpvp.deathmessage.trackers; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.deathmessage.DeathMessageHandler; +import com.elevatemc.potpvp.deathmessage.event.CustomPlayerDamageEvent; +import com.elevatemc.potpvp.deathmessage.objects.Damage; +import com.elevatemc.potpvp.deathmessage.objects.PlayerDamage; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageEvent; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class VoidTracker implements Listener { + @EventHandler(priority = EventPriority.LOW) + public void onCustomPlayerDamage(CustomPlayerDamageEvent event) { + if (event.getCause().getCause() != EntityDamageEvent.DamageCause.VOID) + return; + List record = PotPvPSI.getInstance().getDeathMessageHandler().getDamage(event.getPlayer()); + Damage knocker = null; + long knockerTime = 0L; + if (record != null) + for (Damage damage : record) { + if (damage instanceof VoidDamage || damage instanceof VoidDamageByPlayer) + continue; + if (damage instanceof PlayerDamage && (knocker == null || damage.getTime() > knockerTime)) { + knocker = damage; + knockerTime = damage.getTime(); + } + } + if (knocker != null && knockerTime + TimeUnit.MINUTES.toMillis(1L) > System.currentTimeMillis()) { + event.setTrackerDamage(new VoidDamageByPlayer(event.getPlayer().getName(), event.getDamage(), ((PlayerDamage) knocker).getDamager())); + } else { + event.setTrackerDamage(new VoidDamage(event.getPlayer().getName(), event.getDamage())); + } + } + + public static class VoidDamage extends Damage { + public VoidDamage(String damaged, double damage) { + super(damaged, damage); + } + + public String getDeathMessage(Player viewer) { + return wrapName(getDamaged(), viewer) + " fell into the void."; + } + } + + public static class VoidDamageByPlayer extends PlayerDamage { + public VoidDamageByPlayer(String damaged, double damage, String damager) { + super(damaged, damage, damager); + } + + public String getDeathMessage(Player viewer) { + return wrapName(getDamaged(), viewer) + " fell into the void thanks to " + wrapName(getDamager(), viewer) + "."; + } + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/util/MobUtil.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/util/MobUtil.java new file mode 100644 index 0000000..c903570 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/util/MobUtil.java @@ -0,0 +1,13 @@ +package com.elevatemc.potpvp.deathmessage.util; + +import org.apache.commons.lang.WordUtils; +import org.bukkit.ChatColor; +import org.bukkit.inventory.ItemStack; + +public class MobUtil { + public static String getItemName(ItemStack itemStack) { + if (itemStack.getItemMeta().hasDisplayName()) + return ChatColor.stripColor(itemStack.getItemMeta().getDisplayName()); + return WordUtils.capitalizeFully(itemStack.getType().name().replace('_', ' ')); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/util/UnknownDamage.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/util/UnknownDamage.java new file mode 100644 index 0000000..8ac2695 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/deathmessage/util/UnknownDamage.java @@ -0,0 +1,14 @@ +package com.elevatemc.potpvp.deathmessage.util; + +import com.elevatemc.potpvp.deathmessage.objects.Damage; +import org.bukkit.entity.Player; + +public class UnknownDamage extends Damage { + public UnknownDamage(String damaged, double damage) { + super(damaged, damage); + } + + public String getDeathMessage(Player viewer) { + return wrapName(getDamaged(), viewer) + " died."; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/duel/DuelHandler.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/duel/DuelHandler.java new file mode 100644 index 0000000..9c2bccb --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/duel/DuelHandler.java @@ -0,0 +1,90 @@ +package com.elevatemc.potpvp.duel; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.duel.listener.DuelListener; +import com.elevatemc.potpvp.party.Party; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public final class DuelHandler { + + public static final int DUEL_INVITE_TIMEOUT_SECONDS = 30; + + // this does mean lookups are O(n), but unlike matches or parties + // there are isn't enough volume + frequency to become an issue + private Set activeInvites = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + public DuelHandler() { + Bukkit.getPluginManager().registerEvents(new DuelListener(), PotPvPSI.getInstance()); + Bukkit.getScheduler().runTaskTimerAsynchronously(PotPvPSI.getInstance(), () -> activeInvites.removeIf(DuelInvite::isExpired), 20, 20); + } + + public void insertInvite(DuelInvite invite) { + activeInvites.add(invite); + } + + public void removeInvite(DuelInvite invite) { + activeInvites.remove(invite); + } + + public void removeInvitesTo(Player player) { + activeInvites.removeIf(i -> + i instanceof PlayerDuelInvite && + ((PlayerDuelInvite) i).getTarget().equals(player.getUniqueId()) + ); + } + + public void removeInvitesFrom(Player player) { + activeInvites.removeIf(i -> + i instanceof PlayerDuelInvite && + ((PlayerDuelInvite) i).getSender().equals(player.getUniqueId()) + ); + } + + public void removeInvitesTo(Party party) { + activeInvites.removeIf(i -> + i instanceof PartyDuelInvite && + ((PartyDuelInvite) i).getTarget() == party + ); + } + + public void removeInvitesFrom(Party party) { + activeInvites.removeIf(i -> + i instanceof PartyDuelInvite && + ((PartyDuelInvite) i).getSender() == party + ); + } + + public PartyDuelInvite findInvite(Party sender, Party target) { + for (DuelInvite invite : activeInvites) { + if (invite instanceof PartyDuelInvite) { + PartyDuelInvite partyInvite = (PartyDuelInvite) invite; + + if (partyInvite.getSender() == sender && partyInvite.getTarget() == target) { + return partyInvite; + } + } + } + + return null; + } + + public PlayerDuelInvite findInvite(Player sender, Player target) { + for (DuelInvite invite : activeInvites) { + if (invite instanceof PlayerDuelInvite) { + PlayerDuelInvite playerInvite = (PlayerDuelInvite) invite; + + if (playerInvite.getSender().equals(sender.getUniqueId()) && playerInvite.getTarget().equals(target.getUniqueId())) { + return playerInvite; + } + } + } + + return null; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/duel/DuelInvite.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/duel/DuelInvite.java new file mode 100644 index 0000000..f3064a5 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/duel/DuelInvite.java @@ -0,0 +1,31 @@ +package com.elevatemc.potpvp.duel; + +import com.google.common.base.Preconditions; +import lombok.Getter; +import com.elevatemc.potpvp.gamemode.GameMode; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; + +public abstract class DuelInvite { + + @Getter private final T sender; + @Getter private final T target; + @Getter private final GameMode gameMode; + @Getter private final String arenaName; + @Getter private final Instant timeSent; + + public DuelInvite(T sender, T target, GameMode gameMode, String arenaName) { + this.sender = Preconditions.checkNotNull(sender, "sender"); + this.target = Preconditions.checkNotNull(target, "target"); + this.gameMode = Preconditions.checkNotNull(gameMode, "gameMode"); + this.arenaName = arenaName; // Arena can be null -> random arena + this.timeSent = Instant.now(); + } + + public boolean isExpired() { + long sentAgo = ChronoUnit.SECONDS.between(timeSent, Instant.now()); + return sentAgo > DuelHandler.DUEL_INVITE_TIMEOUT_SECONDS; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/duel/PartyDuelInvite.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/duel/PartyDuelInvite.java new file mode 100644 index 0000000..6a3a3d1 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/duel/PartyDuelInvite.java @@ -0,0 +1,12 @@ +package com.elevatemc.potpvp.duel; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.party.Party; + +public final class PartyDuelInvite extends DuelInvite { + + public PartyDuelInvite(Party sender, Party target, GameMode gameModes, String arenaName) { + super(sender, target, gameModes, arenaName); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/duel/PlayerDuelInvite.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/duel/PlayerDuelInvite.java new file mode 100644 index 0000000..c754dcc --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/duel/PlayerDuelInvite.java @@ -0,0 +1,14 @@ +package com.elevatemc.potpvp.duel; + +import com.elevatemc.potpvp.gamemode.GameMode; +import org.bukkit.entity.Player; + +import java.util.UUID; + +public final class PlayerDuelInvite extends DuelInvite { + + public PlayerDuelInvite(Player sender, Player target, GameMode gameMode, String arenaName) { + super(sender.getUniqueId(), target.getUniqueId(), gameMode, arenaName); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/duel/command/AcceptCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/duel/command/AcceptCommand.java new file mode 100644 index 0000000..a3f7b62 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/duel/command/AcceptCommand.java @@ -0,0 +1,121 @@ +package com.elevatemc.potpvp.duel.command; + +import com.google.common.collect.ImmutableList; +import com.elevatemc.potpvp.PotPvPLang; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.duel.DuelHandler; +import com.elevatemc.potpvp.duel.DuelInvite; +import com.elevatemc.potpvp.duel.PartyDuelInvite; +import com.elevatemc.potpvp.duel.PlayerDuelInvite; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.potpvp.party.PartyHandler; +import com.elevatemc.potpvp.validation.PotPvPValidation; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import com.elevatemc.elib.util.UUIDUtils; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class AcceptCommand { + + @Command(names = {"accept"}, permission = "") + public static void accept(Player sender, @Parameter(name = "player") Player target) { + if (sender == target) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You cannot accept duel requests from yourself."); + return; + } + + PartyHandler partyHandler = PotPvPSI.getInstance().getPartyHandler(); + DuelHandler duelHandler = PotPvPSI.getInstance().getDuelHandler(); + + Party senderParty = partyHandler.getParty(sender); + Party targetParty = partyHandler.getParty(target); + + if (senderParty != null && targetParty != null) { + // party accepting from party (legal) + PartyDuelInvite invite = duelHandler.findInvite(targetParty, senderParty); + + if (invite != null) { + acceptParty(sender, senderParty, targetParty, invite); + } else { + // we grab the leader's name as the member targeted might not be the leader + String leaderName = UUIDUtils.name(targetParty.getLeader()); + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Your party doesn't have a duel invite from " + leaderName + "'s party."); + } + } else if (senderParty == null && targetParty == null) { + // player accepting from player (legal) + PlayerDuelInvite invite = duelHandler.findInvite(target, sender); + + if (invite != null) { + acceptPlayer(sender, target, invite); + } else { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You don't have a duel invite from " + target.getName() + "."); + } + } else if (senderParty == null) { + // player accepting from party (illegal) + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You don't have a duel invite from " + target.getName() + "."); + } else { + // party accepting from player (illegal) + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Your party doesn't have a duel invite from " + target.getName() + "'s party."); + } + } + + private static void acceptParty(Player sender, Party senderParty, Party targetParty, DuelInvite invite) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + DuelHandler duelHandler = PotPvPSI.getInstance().getDuelHandler(); + + if (!senderParty.isLeader(sender.getUniqueId())) { + sender.sendMessage(PotPvPLang.NOT_LEADER_OF_PARTY); + return; + } + + if (!PotPvPValidation.canAcceptDuel(senderParty, targetParty, sender)) { + return; + } + + Match match = matchHandler.startMatch( + ImmutableList.of(new MatchTeam(senderParty.getMembers()), new MatchTeam(targetParty.getMembers())), + invite.getGameMode(), + invite.getArenaName(), + false, + true // see Match#allowRematches + ); + + if (match != null) { + // only remove invite if successful + duelHandler.removeInvite(invite); + } else { + senderParty.message(PotPvPLang.ERROR_WHILE_STARTING_MATCH); + targetParty.message(PotPvPLang.ERROR_WHILE_STARTING_MATCH); + } + } + + private static void acceptPlayer(Player sender, Player target, DuelInvite invite) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + DuelHandler duelHandler = PotPvPSI.getInstance().getDuelHandler(); + + if (!PotPvPValidation.canAcceptDuel(sender, target)) { + return; + } + + Match match = matchHandler.startMatch( + ImmutableList.of(new MatchTeam(sender.getUniqueId()), new MatchTeam(target.getUniqueId())), + invite.getGameMode(), + invite.getArenaName(), + false, + true // see Match#allowRematches + ); + + if (match != null) { + // only remove invite if successful + duelHandler.removeInvite(invite); + } else { + sender.sendMessage(PotPvPLang.ERROR_WHILE_STARTING_MATCH); + target.sendMessage(PotPvPLang.ERROR_WHILE_STARTING_MATCH); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/duel/command/DuelCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/duel/command/DuelCommand.java new file mode 100644 index 0000000..b48e89c --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/duel/command/DuelCommand.java @@ -0,0 +1,210 @@ +package com.elevatemc.potpvp.duel.command; + +import com.elevatemc.potpvp.PotPvPLang; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.menu.select.SelectArenaMenu; +import com.elevatemc.potpvp.arena.menu.select.teamfight.SelectArenaTeamfightCategoryMenu; +import com.elevatemc.potpvp.duel.DuelHandler; +import com.elevatemc.potpvp.duel.DuelInvite; +import com.elevatemc.potpvp.duel.PartyDuelInvite; +import com.elevatemc.potpvp.duel.PlayerDuelInvite; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.elevatemc.potpvp.gamemode.menu.select.SelectGameModeMenu; +import com.elevatemc.potpvp.lobby.LobbyHandler; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.potpvp.party.PartyHandler; +import com.elevatemc.potpvp.validation.PotPvPValidation; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import com.elevatemc.elib.util.PlayerUtils; +import com.elevatemc.elib.util.UUIDUtils; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.TextComponent; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class DuelCommand { + + @Command(names = {"duel"}, permission = "") + public static void duel(Player sender, @Parameter(name = "player") Player target) { + if (sender == target) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You cannot duel yourself."); + return; + } + + PartyHandler partyHandler = PotPvPSI.getInstance().getPartyHandler(); + LobbyHandler lobbyHandler = PotPvPSI.getInstance().getLobbyHandler(); + + Party senderParty = partyHandler.getParty(sender); + Party targetParty = partyHandler.getParty(target); + + if (senderParty != null && targetParty != null) { + // party dueling party (legal) + if (!PotPvPValidation.canSendDuel(senderParty, targetParty, sender)) { + return; + } + + new SelectGameModeMenu(gameMode -> { + if (gameMode.equals(GameModes.TEAMFIGHT) || gameMode.equals(GameModes.TEAMFIGHT_DEBUFF)) { + new SelectArenaTeamfightCategoryMenu(category -> { + new SelectArenaMenu(gameMode, category, arenaName -> { + sender.closeInventory(); + + // reassign these fields so that any party changes + // (kicks, etc) are reflectednow + Party newSenderParty = partyHandler.getParty(sender); + Party newTargetParty = partyHandler.getParty(target); + + if (newSenderParty != null && newTargetParty != null) { + if (newSenderParty.isLeader(sender.getUniqueId())) { + duel(sender, newSenderParty, newTargetParty, gameMode, arenaName); + } else { + sender.sendMessage(PotPvPLang.NOT_LEADER_OF_PARTY); + } + } + }).openMenu(sender); + }).openMenu(sender); + } else { + new SelectArenaMenu(gameMode, arenaName -> { + sender.closeInventory(); + + // reassign these fields so that any party changes + // (kicks, etc) are reflectednow + Party newSenderParty = partyHandler.getParty(sender); + Party newTargetParty = partyHandler.getParty(target); + + if (newSenderParty != null && newTargetParty != null) { + if (newSenderParty.isLeader(sender.getUniqueId())) { + duel(sender, newSenderParty, newTargetParty, gameMode, arenaName); + } else { + sender.sendMessage(PotPvPLang.NOT_LEADER_OF_PARTY); + } + } + }).openMenu(sender); + } + + }, "Select gamemode").openMenu(sender); + } else if (senderParty == null && targetParty == null) { + // player dueling player (legal) + if (!PotPvPValidation.canSendDuel(sender, target)) { + return; + } + + if (target.hasPermission("core.media") && System.currentTimeMillis() - lobbyHandler.getLastLobbyTime(target) < 3_000) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Please wait a few moments before executing this command again."); + return; + } + + new SelectGameModeMenu(gameMode -> { + if (gameMode.equals(GameModes.TEAMFIGHT) || gameMode.equals(GameModes.TEAMFIGHT_DEBUFF)) { + new SelectArenaTeamfightCategoryMenu(category -> { + new SelectArenaMenu(gameMode, category, arenaName -> { + sender.closeInventory(); + duel(sender, target, gameMode, arenaName); + }).openMenu(sender); + }).openMenu(sender); + } else { + new SelectArenaMenu(gameMode, arenaName -> { + sender.closeInventory(); + duel(sender, target, gameMode, arenaName); + }).openMenu(sender); + } + }, "Select gamemode").openMenu(sender); + } else if (senderParty == null) { + // player dueling party (illegal) + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You must create a party to duel " + target.getName() + "'s party."); + } else { + // party dueling player (illegal) + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You must leave your party to duel " + target.getName() + "."); + } + } + + public static void duel(Player sender, Player target, GameMode gameMode, String arenaName) { + if (!PotPvPValidation.canSendDuel(sender, target)) { + return; + } + + DuelHandler duelHandler = PotPvPSI.getInstance().getDuelHandler(); + DuelInvite autoAcceptInvite = duelHandler.findInvite(target, sender); + + // if two players duel each other for the same thing automatically + // accept it to make their life a bit easier. + if (autoAcceptInvite != null && autoAcceptInvite.getGameMode() == gameMode && autoAcceptInvite.getArenaName() == arenaName) { + AcceptCommand.accept(sender, target); + return; + } + + DuelInvite alreadySentInvite = duelHandler.findInvite(sender, target); + + if (alreadySentInvite != null) { + if (alreadySentInvite.getGameMode() == gameMode) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You have already invited " + ChatColor.RED + target.getName() + ChatColor.RED + " to a " + gameMode.getName() + ChatColor.RED + " duel."); + return; + } else { + // if an invite was already sent (with a different kit type) + // just delete it (so /accept will accept the 'latest' invite) + duelHandler.removeInvite(alreadySentInvite); + } + } + + target.sendMessage(ChatColor.DARK_AQUA + "⚔ " + sender.getName() + ChatColor.GRAY + " (" + PlayerUtils.getPing(sender) + "ms)" + ChatColor.AQUA + " has sent you a " + ChatColor.DARK_AQUA + gameMode.getName() + (arenaName != null ? (" (" + PotPvPSI.getInstance().getArenaHandler().getSchematic(arenaName).getDisplayName() + ")") : "") + ChatColor.AQUA + " duel."); + target.spigot().sendMessage(createInviteNotification(sender.getName())); + sender.sendMessage(ChatColor.DARK_AQUA + "⚔ " + ChatColor.AQUA + "You have sent a " + ChatColor.DARK_AQUA + gameMode.getName() + ChatColor.AQUA + " duel invite to " + ChatColor.DARK_AQUA + target.getName() + ChatColor.AQUA + "."); + duelHandler.insertInvite(new PlayerDuelInvite(sender, target, gameMode, arenaName)); + } + + public static void duel(Player sender, Party senderParty, Party targetParty, GameMode gameMode, String arenaName) { + if (!PotPvPValidation.canSendDuel(senderParty, targetParty, sender)) { + return; + } + + DuelHandler duelHandler = PotPvPSI.getInstance().getDuelHandler(); + DuelInvite autoAcceptInvite = duelHandler.findInvite(targetParty, senderParty); + String targetPartyLeader = UUIDUtils.name(targetParty.getLeader()); + + // if two players duel each other for the same thing automatically + // accept it to make their life a bit easier. + if (autoAcceptInvite != null && autoAcceptInvite.getGameMode() == gameMode && autoAcceptInvite.getArenaName().equals(arenaName)) { + AcceptCommand.accept(sender, Bukkit.getPlayer(targetParty.getLeader())); + return; + } + + DuelInvite alreadySentInvite = duelHandler.findInvite(senderParty, targetParty); + + if (alreadySentInvite != null) { + if (alreadySentInvite.getGameMode() == gameMode) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You have already invited " + ChatColor.DARK_AQUA + targetPartyLeader + "'s party" + ChatColor.YELLOW + " to a " + ChatColor.DARK_AQUA + gameMode.getName() + ChatColor.AQUA + " duel."); + return; + } else { + // if an invite was already sent (with a different kit type) + // just delete it (so /accept will accept the 'latest' invite) + duelHandler.removeInvite(alreadySentInvite); + } + } + + targetParty.message(ChatColor.DARK_AQUA + "⚔ " + sender.getName() + "'s Party (" + senderParty.getMembers().size() + ")" + ChatColor.AQUA + " has sent you a " + ChatColor.DARK_AQUA + gameMode.getName() + (arenaName != null ? (" (" + PotPvPSI.getInstance().getArenaHandler().getSchematic(arenaName).getDisplayName() + ")") : "") + ChatColor.AQUA + " duel."); + Bukkit.getPlayer(targetParty.getLeader()).spigot().sendMessage(createInviteNotification(sender.getName())); + + sender.sendMessage(ChatColor.DARK_AQUA + "⚔ " + ChatColor.AQUA + "You have sent a " + ChatColor.DARK_AQUA + gameMode.getName() + ChatColor.AQUA + " duel invite to " + ChatColor.DARK_AQUA + targetPartyLeader + "'s party" + ChatColor.AQUA + "."); + duelHandler.insertInvite(new PartyDuelInvite(senderParty, targetParty, gameMode, arenaName)); + } + + private static TextComponent[] createInviteNotification(String sender) { + TextComponent firstPart = new TextComponent("[Click Here To Accept]"); + + firstPart.setColor(net.md_5.bungee.api.ChatColor.DARK_AQUA); + + ClickEvent.Action runCommand = ClickEvent.Action.RUN_COMMAND; + HoverEvent.Action showText = HoverEvent.Action.SHOW_TEXT; + + firstPart.setClickEvent(new ClickEvent(runCommand, "/accept " + sender)); + firstPart.setHoverEvent(new HoverEvent(showText, new BaseComponent[] { new TextComponent(ChatColor.GREEN + "Click here to accept this duel") })); + + return new TextComponent[] { firstPart }; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/duel/listener/DuelListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/duel/listener/DuelListener.java new file mode 100644 index 0000000..d93c5bb --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/duel/listener/DuelListener.java @@ -0,0 +1,51 @@ +package com.elevatemc.potpvp.duel.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.duel.DuelHandler; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.match.event.MatchCountdownStartEvent; +import com.elevatemc.potpvp.match.event.MatchSpectatorJoinEvent; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.potpvp.party.event.PartyDisbandEvent; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +import java.util.UUID; + +public final class DuelListener implements Listener { + + @EventHandler + public void onMatchSpectatorJoin(MatchSpectatorJoinEvent event) { + DuelHandler duelHandler = PotPvPSI.getInstance().getDuelHandler(); + Player player = event.getSpectator(); + + duelHandler.removeInvitesTo(player); + duelHandler.removeInvitesFrom(player); + } + + @EventHandler + public void onPartyDisband(PartyDisbandEvent event) { + DuelHandler duelHandler = PotPvPSI.getInstance().getDuelHandler(); + Party party = event.getParty(); + + duelHandler.removeInvitesTo(party); + duelHandler.removeInvitesFrom(party); + } + + @EventHandler + public void onMatchCountdownStart(MatchCountdownStartEvent event) { + DuelHandler duelHandler = PotPvPSI.getInstance().getDuelHandler(); + + for (MatchTeam team : event.getMatch().getTeams()) { + for (UUID member : team.getAllMembers()) { + Player memberPlayer = Bukkit.getPlayer(member); + + duelHandler.removeInvitesTo(memberPlayer); + duelHandler.removeInvitesFrom(memberPlayer); + } + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/elo/EloCalculator.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/elo/EloCalculator.java new file mode 100644 index 0000000..af5ac6e --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/elo/EloCalculator.java @@ -0,0 +1,63 @@ +package com.elevatemc.potpvp.elo; + +import lombok.Getter; + +public final class EloCalculator { + + private final double kPower; + private final int minEloGain; + private final int maxEloGain; + private final int minEloLoss; + private final int maxEloLoss; + + public EloCalculator(double kPower, int minEloGain, int maxEloGain, int minEloLoss, int maxEloLoss) { + this.kPower = kPower; + this.minEloGain = minEloGain; + this.maxEloGain = maxEloGain; + this.minEloLoss = minEloLoss; + this.maxEloLoss = maxEloLoss; + } + + public Result calculate(int winnerElo, int loserElo) { + double winnerQ = Math.pow(10, ((double) winnerElo) / 300D); + double loserQ = Math.pow(10, ((double) loserElo) / 300D); + + double winnerE = winnerQ / (winnerQ + loserQ); + double loserE = loserQ / (winnerQ + loserQ); + + int winnerGain = (int) (kPower * (1 - winnerE)); + int loserGain = (int) (kPower * (0 - loserE)); + + winnerGain = Math.min(winnerGain, maxEloGain); + winnerGain = Math.max(winnerGain, minEloGain); + + // loserGain will be negative so pay close attention here + loserGain = Math.min(loserGain, -minEloLoss); + loserGain = Math.max(loserGain, -maxEloLoss); + + return new Result(winnerElo, winnerGain, loserElo, loserGain); + } + + public static class Result { + + @Getter private final int winnerOld; + @Getter private final int winnerGain; + @Getter private final int winnerNew; + + @Getter private final int loserOld; + @Getter private final int loserGain; + @Getter private final int loserNew; + + Result(int winnerOld, int winnerGain, int loserOld, int loserGain) { + this.winnerOld = winnerOld; + this.winnerGain = winnerGain; + this.winnerNew = winnerOld + winnerGain; + + this.loserOld = loserOld; + this.loserGain = loserGain; + this.loserNew = loserOld + loserGain; + } + + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/elo/EloHandler.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/elo/EloHandler.java new file mode 100644 index 0000000..4099f5b --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/elo/EloHandler.java @@ -0,0 +1,124 @@ +package com.elevatemc.potpvp.elo; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import lombok.Getter; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.elo.listener.EloLoadListener; +import com.elevatemc.potpvp.elo.listener.EloUpdateListener; +import com.elevatemc.potpvp.elo.repository.EloRepository; +import com.elevatemc.potpvp.elo.repository.MongoEloRepository; +import com.elevatemc.elib.util.UUIDUtils; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.io.IOException; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public final class EloHandler { + + public static final int DEFAULT_ELO = 1000; + + private final Map, Map> eloData = new ConcurrentHashMap<>(); + @Getter private final EloRepository eloRepository; + + public EloHandler() { + Bukkit.getPluginManager().registerEvents(new EloLoadListener(this), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new EloUpdateListener(this, new EloCalculator( + 35, // k power + 7, + 25, + 7, + 25 + )), PotPvPSI.getInstance()); + + eloRepository = new MongoEloRepository(); + } + + public int getElo(Player player, GameMode gameMode) { + return getElo(ImmutableSet.of(player.getUniqueId()), gameMode); + } + + public void setElo(Player player, GameMode gameMode, int newElo) { + setElo(ImmutableSet.of(player.getUniqueId()), gameMode, newElo); + } + + public int getElo(Set playerUuids, GameMode gameMode) { + Map partyElo = eloData.getOrDefault(playerUuids, ImmutableMap.of()); + return partyElo.getOrDefault(gameMode, DEFAULT_ELO); + } + + public int getGlobalElo(UUID uuid) { + Map eloValues = eloData.getOrDefault(ImmutableSet.of(uuid), ImmutableMap.of()); + if (eloValues.isEmpty()) return EloHandler.DEFAULT_ELO; + int[] wrapper = new int[2]; + GameMode.getAll().stream().filter(GameMode::getSupportsCompetitive).forEach(gameMode -> { + wrapper[0] = wrapper[0] + 1; + wrapper[1] = wrapper[1] + eloValues.getOrDefault(gameMode, EloHandler.DEFAULT_ELO); + }); + + return wrapper[1] / wrapper[0]; + } + + public void setElo(Set playerUuids, GameMode gameMode, int newElo) { + Map partyElo = eloData.computeIfAbsent(playerUuids, i -> new ConcurrentHashMap<>()); + partyElo.put(gameMode, newElo); + + Bukkit.getScheduler().runTaskAsynchronously(PotPvPSI.getInstance(), () -> { + try { + eloRepository.saveElo(playerUuids, partyElo); + } catch (IOException ex) { + // just log, nothing else to do. + ex.printStackTrace(); + } + }); + } + + public void loadElo(Set playerUuids) { + Map partyElo; + + try { + partyElo = new ConcurrentHashMap<>(eloRepository.loadElo(playerUuids)); + } catch (IOException ex) { + // just print + return an empty map, this will cause us + // to fall back to default values. + ex.printStackTrace(); + partyElo = new ConcurrentHashMap<>(); + } + + eloData.put(playerUuids, partyElo); + } + + public void unloadElo(Set playerUuids) { + eloData.remove(playerUuids); + } + + public Map topElo(GameMode type) { + Map topElo; + + try { + topElo = eloRepository.topElo(type); + } catch (IOException ex) { + ex.printStackTrace(); + topElo = ImmutableMap.of(); + } + + return topElo; + } + + public void resetElo(final UUID player) { + Bukkit.getLogger().info("Resetting elo of " + UUIDUtils.name(player) + "."); + Bukkit.getScheduler().runTaskAsynchronously(PotPvPSI.getInstance(), () -> { + unloadElo(ImmutableSet.of(player)); + try { + eloRepository.saveElo(ImmutableSet.of(player), ImmutableMap.of()); + } catch (IOException e) { + e.printStackTrace(); + } + }); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/elo/command/EloCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/elo/command/EloCommand.java new file mode 100644 index 0000000..9e5e8cc --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/elo/command/EloCommand.java @@ -0,0 +1,13 @@ +package com.elevatemc.potpvp.elo.command; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import com.elevatemc.potpvp.lobby.menu.StatisticsMenu; +import org.bukkit.entity.Player; + +public class EloCommand { + @Command(names = {"elo"}, permission = "") + public static void eloCommand(Player sender, @Parameter(name = "target", defaultValue = "self")Player target) { + new StatisticsMenu(target).openMenu(sender); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/elo/command/EloSetCommands.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/elo/command/EloSetCommands.java new file mode 100644 index 0000000..0c4a7b9 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/elo/command/EloSetCommands.java @@ -0,0 +1,39 @@ +package com.elevatemc.potpvp.elo.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.elo.EloHandler; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.potpvp.party.PartyHandler; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import com.elevatemc.elib.util.UUIDUtils; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class EloSetCommands { + + @Command(names = {"elo setSolo"}, permission = "op") + public static void eloSetSolo(Player sender, @Parameter(name="target") Player target, @Parameter(name="gamemode") GameMode gameMode, @Parameter(name="new elo") int newElo) { + EloHandler eloHandler = PotPvPSI.getInstance().getEloHandler(); + eloHandler.setElo(target, gameMode, newElo); + sender.sendMessage(ChatColor.YELLOW + "Set " + target.getName() + "'s " + gameMode.getName() + " elo to " + newElo + "."); + } + + @Command(names = {"elo setTeam"}, permission = "op") + public static void eloSetTeam(Player sender, @Parameter(name="target") Player target, @Parameter(name="gamemode") GameMode gameMode, @Parameter(name="new elo") int newElo) { + PartyHandler partyHandler = PotPvPSI.getInstance().getPartyHandler(); + EloHandler eloHandler = PotPvPSI.getInstance().getEloHandler(); + + Party targetParty = partyHandler.getParty(target); + + if (targetParty == null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + target.getName() + " is not in a party."); + return; + } + + eloHandler.setElo(targetParty.getMembers(), gameMode, newElo); + sender.sendMessage(ChatColor.YELLOW + "Set " + gameMode.getName() + " elo of " + UUIDUtils.name(targetParty.getLeader()) + "'s party to " + newElo + "."); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/elo/listener/EloLoadListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/elo/listener/EloLoadListener.java new file mode 100644 index 0000000..f385014 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/elo/listener/EloLoadListener.java @@ -0,0 +1,70 @@ +package com.elevatemc.potpvp.elo.listener; + +import com.google.common.collect.ImmutableSet; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.elo.EloHandler; +import com.elevatemc.potpvp.party.event.PartyCreateEvent; +import com.elevatemc.potpvp.party.event.PartyMemberJoinEvent; +import com.elevatemc.potpvp.party.event.PartyMemberKickEvent; +import com.elevatemc.potpvp.party.event.PartyMemberLeaveEvent; +import org.bukkit.Bukkit; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerPreLoginEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.Set; +import java.util.UUID; + +// TODO: Remove old data! +public final class EloLoadListener implements Listener { + + private final EloHandler eloHandler; + + public EloLoadListener(EloHandler eloHandler) { + this.eloHandler = eloHandler; + } + + @EventHandler(priority = EventPriority.LOWEST) // LOWEST runs first + public void onAsyncPlayerPreLogin(AsyncPlayerPreLoginEvent event) { + Set playerSet = ImmutableSet.of(event.getUniqueId()); + eloHandler.loadElo(playerSet); + } + + @EventHandler(priority = EventPriority.MONITOR) // MONITOR runs last + public void onPlayerQuit(PlayerQuitEvent event) { + Set playerSet = ImmutableSet.of(event.getPlayer().getUniqueId()); + eloHandler.unloadElo(playerSet); + } + + @EventHandler + public void onPartyCreate(PartyCreateEvent event) { + Bukkit.getScheduler().runTaskAsynchronously(PotPvPSI.getInstance(), () -> { + eloHandler.loadElo(event.getParty().getMembers()); + }); + } + + @EventHandler + public void onPartyMemberJoin(PartyMemberJoinEvent event) { + Bukkit.getScheduler().runTaskAsynchronously(PotPvPSI.getInstance(), () -> { + eloHandler.loadElo(event.getParty().getMembers()); + }); + } + + @EventHandler + public void onPartyMemberKick(PartyMemberKickEvent event) { + Bukkit.getScheduler().runTaskAsynchronously(PotPvPSI.getInstance(), () -> { + eloHandler.loadElo(event.getParty().getMembers()); + }); + } + + @EventHandler + public void onPartyMemberLeave(PartyMemberLeaveEvent event) { + Bukkit.getScheduler().runTaskAsynchronously(PotPvPSI.getInstance(), () -> { + // calling load will overwrite the existing data for the party (which we want) + eloHandler.loadElo(event.getParty().getMembers()); + }); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/elo/listener/EloUpdateListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/elo/listener/EloUpdateListener.java new file mode 100644 index 0000000..e8360e4 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/elo/listener/EloUpdateListener.java @@ -0,0 +1,97 @@ +package com.elevatemc.potpvp.elo.listener; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.google.common.base.Joiner; +import com.elevatemc.potpvp.elo.EloCalculator; +import com.elevatemc.potpvp.elo.EloHandler; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.match.event.MatchEndEvent; +import com.elevatemc.potpvp.match.event.MatchTerminateEvent; +import com.elevatemc.potpvp.util.PatchedPlayerUtils; +import com.elevatemc.elib.util.UUIDUtils; +import org.bukkit.ChatColor; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +import java.util.List; + +public final class EloUpdateListener implements Listener { + + private static final String ELO_CHANGE_MESSAGE = ChatColor.translateAlternateColorCodes('&', "&eElo Changes: &a%s +%d (%d) &c%s -%d (%d)"); + private static final int COINS_ON_WIN = 10; + + private final EloHandler eloHandler; + private final EloCalculator eloCalculator; + + public EloUpdateListener(EloHandler eloHandler, EloCalculator eloCalculator) { + this.eloHandler = eloHandler; + this.eloCalculator = eloCalculator; + } + + // we actually save elo when the match first ends but only + // send messages when it terminates (when players go back to + // the lobby) + @EventHandler + public void onMatchEnd(MatchEndEvent event) { + Match match = event.getMatch(); + GameMode gameMode = match.getGameMode(); + List teams = match.getTeams(); + + if (!match.isRanked() || teams.size() != 2 || match.getWinner() == null) { + return; + } + + MatchTeam winnerTeam = match.getWinner(); + MatchTeam loserTeam = teams.get(0) == winnerTeam ? teams.get(1) : teams.get(0); + + final int winningElo = eloHandler.getElo(winnerTeam.getAllMembers(), gameMode); + final int losingElo = eloHandler.getElo(loserTeam.getAllMembers(), gameMode); + + EloCalculator.Result result = eloCalculator.calculate( + winningElo, + losingElo + ); + + // Clan handler access elo change + final int deltaWin = Math.abs(result.getWinnerNew() - winningElo); + final int deltaLose = Math.abs(losingElo - result.getLoserNew()); + + final int winningPointsEarned = deltaWin / 10; + final int losingPointsLost = deltaLose / 10; + eloHandler.setElo(winnerTeam.getAllMembers(), gameMode, result.getWinnerNew()); + eloHandler.setElo(loserTeam.getAllMembers(), gameMode, result.getLoserNew()); + + match.setEloChange(result); + } + + // see comment on onMatchEnd method + @EventHandler + public void onMatchTerminate(MatchTerminateEvent event) { + Match match = event.getMatch(); + EloCalculator.Result result = match.getEloChange(); + + if (result == null) { + return; + } + + List teams = match.getTeams(); + MatchTeam winnerTeam = match.getWinner(); + MatchTeam loserTeam = teams.get(0) == winnerTeam ? teams.get(1) : teams.get(0); + + String winnerStr; + String loserStr; + + if (winnerTeam.getAllMembers().size() == 1 && loserTeam.getAllMembers().size() == 1) { + winnerStr = UUIDUtils.name(winnerTeam.getFirstMember()); + loserStr = UUIDUtils.name(loserTeam.getFirstMember()); + } else { + winnerStr = Joiner.on(", ").join(PatchedPlayerUtils.mapToNames(winnerTeam.getAllMembers())); + loserStr = Joiner.on(", ").join(PatchedPlayerUtils.mapToNames(loserTeam.getAllMembers())); + } + + // we negate loser gain to convert negative gain to positive (which we prefix with - in the string) + match.messageAll(String.format(ELO_CHANGE_MESSAGE, winnerStr, result.getWinnerGain(), result.getWinnerNew(), loserStr, -result.getLoserGain(), result.getLoserNew())); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/elo/repository/EloRepository.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/elo/repository/EloRepository.java new file mode 100644 index 0000000..2ae5623 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/elo/repository/EloRepository.java @@ -0,0 +1,16 @@ +package com.elevatemc.potpvp.elo.repository; + +import com.elevatemc.potpvp.gamemode.GameMode; + +import java.io.IOException; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +public interface EloRepository { + + Map loadElo(Set playerUuids) throws IOException; + void saveElo(Set playerUuids, Map elo) throws IOException; + + Map topElo(GameMode gameMode) throws IOException; +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/elo/repository/MongoEloRepository.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/elo/repository/MongoEloRepository.java new file mode 100644 index 0000000..8bc3c6a --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/elo/repository/MongoEloRepository.java @@ -0,0 +1,145 @@ +package com.elevatemc.potpvp.elo.repository; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.mongodb.MongoException; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.Sorts; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.elo.EloHandler; +import com.elevatemc.potpvp.util.MongoUtils; +import com.elevatemc.potpvp.util.PatchedPlayerUtils; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.util.UUIDUtils; +import com.google.common.collect.ImmutableSet; +import org.bson.Document; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.io.IOException; +import java.util.*; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public final class MongoEloRepository implements EloRepository { + + private static final String MONGO_COLLECTION_NAME = "elo"; + + private static final Map> cachedFormattedElo = Maps.newHashMap(); + private static MongoEloRepository instance; + + public MongoEloRepository() { + instance = this; + MongoUtils.getCollection(MONGO_COLLECTION_NAME).createIndex(new Document("players", 1)); + + Bukkit.getScheduler().runTaskTimerAsynchronously(PotPvPSI.getInstance(), this::refreshFormattedElo, 5 * 30, 5 * 30); + } + + @Override + public Map loadElo(Set playerUuids) throws IOException { + MongoCollection partyEloCollection = MongoUtils.getCollection(MONGO_COLLECTION_NAME); + Set playerUuidStrings = playerUuids.stream().map(UUID::toString).collect(Collectors.toSet()); + + try { + Document eloDocument = partyEloCollection.find(new Document("players", playerUuidStrings)).first(); + + if (eloDocument == null) { + return ImmutableMap.of(); + } + + Map parsedElo = new HashMap<>(); + final Document finalEloDocument = eloDocument; + + GameMode.getAll().forEach((gameMode) -> { + Integer elo = finalEloDocument.getInteger(gameMode.getId()); + if (elo != null) { + parsedElo.put(gameMode, elo); + } else { + parsedElo.put(gameMode, EloHandler.DEFAULT_ELO); + } + }); + + return ImmutableMap.copyOf(parsedElo); + } catch (MongoException ex) { + throw new IOException(ex); + } + } + + @Override + public void saveElo(Set playerUuids, Map elo) throws IOException { + Document document = new Document(); + elo.forEach((kit, value) -> document.put(kit.getId(), value)); + int[] wrapper = new int[2]; + + GameMode.getAll().forEach(gameMode -> document.putIfAbsent(gameMode.getId(), EloHandler.DEFAULT_ELO)); + + GameMode.getAll().stream().filter(GameMode::getSupportsCompetitive).forEach(gameMode -> { + wrapper[0] = wrapper[0] + 1; + wrapper[1] = wrapper[1] + elo.getOrDefault(gameMode, EloHandler.DEFAULT_ELO); + }); + + document.put("GLOBAL", wrapper[1] / wrapper[0]); + if (playerUuids.size() == 1) { + document.put("lastUsername", UUIDUtils.name(playerUuids.iterator().next())); + } + + try { + MongoUtils.getCollection(MONGO_COLLECTION_NAME).updateOne(new Document("players", playerUuids.stream().map(UUID::toString).collect(Collectors.toSet())), new Document("$set", document), MongoUtils.UPSERT_OPTIONS // creates document if it doesn't exist + ); + } catch (MongoException ex) { + throw new IOException(ex); + } + } + + @Command(names = { "recalcGlobalElo" }, permission = "op") + public static void recalcGlobalElo(Player sender) { + List documents = MongoUtils.getCollection(MONGO_COLLECTION_NAME).find().into(new ArrayList<>()); + sender.sendMessage(ChatColor.GREEN + "Recalculating " + documents.size() + " players global elo..."); + final int[] wrapper = new int[2]; + documents.forEach(document -> { + try { + UUID uuid = UUID.fromString((String) document.get("players", ArrayList.class).get(0)); + instance.saveElo(ImmutableSet.of(uuid), instance.loadElo(ImmutableSet.of(uuid))); + wrapper[0]++; + if (wrapper[0] % 100 == 0) { + sender.sendMessage(ChatColor.GREEN + "Finished " + wrapper[0] + " out of " + documents.size() +" players..."); + } + } catch (Exception e) { + e.printStackTrace(); + wrapper[1]++; + } + }); + } + + @Override + public Map topElo(GameMode type) throws IOException { + return cachedFormattedElo.getOrDefault(type == null ? "GLOBAL" : type.getId(), ImmutableMap.of()); + } + + private void refreshFormattedElo() { + GameMode.getAll().stream().filter(GameMode::getSupportsCompetitive).forEach(type -> { + Map topElo = Maps.newLinkedHashMap(); + mapTop10(type.getId(), topElo); + cachedFormattedElo.put(type.getId(), topElo); + }); + + Map topGlobal = Maps.newLinkedHashMap(); + mapTop10("GLOBAL", topGlobal); + cachedFormattedElo.put("GLOBAL", topGlobal); + } + + public void mapTop10(String gameModeName, Map toInsert) { + try { + MongoUtils.getCollection(MONGO_COLLECTION_NAME).find().sort(Sorts.descending(gameModeName)).limit(10).forEach((Consumer) document -> { + Object eloNumber = document.get(gameModeName); + int elo = eloNumber instanceof Number ? ((Number) eloNumber).intValue() : EloHandler.DEFAULT_ELO; + toInsert.put(PatchedPlayerUtils.getFormattedName(UUID.fromString((String) document.get("players", ArrayList.class).get(0))), elo); + }); + + } catch (Exception exception) { + exception.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/EventHandler.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/EventHandler.java new file mode 100644 index 0000000..2d5caf2 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/EventHandler.java @@ -0,0 +1,37 @@ +package com.elevatemc.potpvp.events; + +import com.elevatemc.elib.util.ItemBuilder; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.events.event.GameEvent; +import com.elevatemc.potpvp.events.event.impl.brackets.BracketsGameEvent; +import com.elevatemc.potpvp.events.event.impl.lms.LastManStandingGameEvent; +import com.elevatemc.potpvp.events.event.impl.sumo.SumoGameEvent; +import com.elevatemc.potpvp.events.game.GameQueue; +import com.elevatemc.potpvp.util.Color; +import com.google.common.collect.ImmutableList; +import lombok.Getter; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +@Getter +public class EventHandler { + + private final GameQueue gameQueue; + + public EventHandler(PotPvPSI plugin) { + this.gameQueue = new GameQueue(); + gameQueue.run(plugin); + } + + public static final List EVENTS = ImmutableList.of( + new LastManStandingGameEvent(), + new BracketsGameEvent(), + new SumoGameEvent() + ); + + public static ItemStack getLeaveItem() { + return ItemBuilder.of(Material.INK_SACK).data((short)1).name(Color.translate("&cLeave Event")).build(); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/EventItems.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/EventItems.java new file mode 100644 index 0000000..7b88943 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/EventItems.java @@ -0,0 +1,18 @@ +package com.elevatemc.potpvp.events; + +import com.elevatemc.elib.util.ItemBuilder; +import lombok.experimental.UtilityClass; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import static org.bukkit.ChatColor.AQUA; +import static org.bukkit.ChatColor.GRAY; + +@UtilityClass +public final class EventItems { + + public ItemStack getEventItem() { + return ItemBuilder.of(Material.DIAMOND).name(GRAY + "• " + AQUA + "Join an Event" + GRAY + " •").build(); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/EventListeners.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/EventListeners.java new file mode 100644 index 0000000..873e1f5 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/EventListeners.java @@ -0,0 +1,148 @@ +package com.elevatemc.potpvp.events; + +import com.elevatemc.elib.eLib; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.events.bukkit.event.GameStateChangeEvent; +import com.elevatemc.potpvp.events.bukkit.event.PlayerGameInteractionEvent; +import com.elevatemc.potpvp.events.bukkit.event.PlayerJoinGameEvent; +import com.elevatemc.potpvp.events.bukkit.event.PlayerQuitGameEvent; +import com.elevatemc.potpvp.events.game.Game; +import com.elevatemc.potpvp.events.game.GameQueue; +import com.elevatemc.potpvp.events.game.GameState; +import com.elevatemc.potpvp.events.menu.EventsMenu; +import com.elevatemc.potpvp.events.util.team.GameTeam; +import com.elevatemc.potpvp.events.util.team.GameTeamEventLogic; +import com.elevatemc.potpvp.events.util.team.GameTeamSizeParameter; +import com.elevatemc.potpvp.lobby.LobbyUtils; +import com.elevatemc.potpvp.util.Color; +import com.elevatemc.potpvp.util.PatchedPlayerUtils; +import com.elevatemc.potpvp.util.VisibilityUtils; +import com.google.common.collect.ImmutableList; +import org.bukkit.Bukkit; +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.player.PlayerInteractAtEntityEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.bukkit.event.player.PlayerInteractEvent; + +public class EventListeners implements Listener { + + @EventHandler + public void onPlayerInteractEvent(PlayerInteractEvent event) { + Player player = event.getPlayer(); + if(event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) return; + + if (event.getItem() != null && event.getItem().equals(EventItems.getEventItem()) && PotPvPSI.getInstance().getLobbyHandler().isInLobby(player)) { + new EventsMenu().openMenu(player); + } + + } + + @EventHandler + public void onGameStateChangeEvent(GameStateChangeEvent event) { + Game game = event.getGame(); + + if (event.getTo() == GameState.ENDED) { + if(game.getArena().isInUse()) PotPvPSI.getInstance().getArenaHandler().releaseArena(game.getArena()); + for (Player player : game.getPlayers()) { + eLib.getInstance().getNameTagHandler().reloadPlayer(player); + eLib.getInstance().getNameTagHandler().reloadOthersFor(player); + VisibilityUtils.updateVisibility(player); + PotPvPSI.getInstance().getLobbyHandler().returnToLobby(player); + LobbyUtils.resetInventory(player); + } + } + } + + @EventHandler + public void onPlayerJoinGameEvent(PlayerJoinGameEvent event) { + eLib.getInstance().getNameTagHandler().reloadPlayer(event.getPlayer()); + eLib.getInstance().getNameTagHandler().reloadOthersFor(event.getPlayer()); + for (Player player : event.getGame().getPlayers()) { + VisibilityUtils.updateVisibility(player); + } + } + + @EventHandler + public void onPlayerQuitGameEvent(PlayerQuitGameEvent event) { + eLib.getInstance().getNameTagHandler().reloadPlayer(event.getPlayer()); + eLib.getInstance().getNameTagHandler().reloadOthersFor(event.getPlayer()); + PotPvPSI.getInstance().getLobbyHandler().returnToLobby(event.getPlayer()); + } + + @EventHandler + public void onPlayerGameInteractionEvent(PlayerGameInteractionEvent event) { + eLib.getInstance().getNameTagHandler().reloadPlayer(event.getPlayer()); + eLib.getInstance().getNameTagHandler().reloadOthersFor(event.getPlayer()); + VisibilityUtils.updateVisibility(event.getPlayer()); + } + + // PlayerInteractAtEntityEvent - Working on 1.8 + + @EventHandler + public void onInteractPlayerEvent(PlayerInteractEntityEvent event) { + final Player sender = event.getPlayer(); + if(!(event.getRightClicked() instanceof Player)) return; + final Player receiver = (Player) event.getRightClicked(); + + final Game game = GameQueue.INSTANCE.getCurrentGame(sender); + if(game == null) return; // Player is not in game + if(game.getState() != GameState.STARTING) return; // Game is not in starting state + if(!game.getPlayers().contains(receiver)) return; // Receiver is not in game somehow + + if(!(game.getLogic() instanceof GameTeamEventLogic)) return; // Does not use the team based event layout + if(game.getParameter(GameTeamSizeParameter.Duos.class) == null) return; // Solo + final GameTeamEventLogic logic = (GameTeamEventLogic) game.getLogic(); + + /* + Invites Map + key - sender + value - receiver + */ + + // check to see if the players are already on the same team, if they are ignore + if(logic.get(sender) != null && logic.get(sender).getPlayers().contains(receiver)) return; + + // check to see if the sender had a request from the receiver + if(logic.getInvites().containsKey(receiver.getUniqueId()) && logic.getInvites().get(receiver.getUniqueId()).equals(sender.getUniqueId())) { + // if one of the players was already in a team, disband that team and create this new one + removeExistingTeam(sender, logic); + removeExistingTeam(receiver, logic); + + // create their team + final GameTeam team = new GameTeam(ImmutableList.of(sender, receiver)); + logic.getParticipants().add(team); + + // send message letting them know that they are now on the same team + sender.sendMessage(Color.translate("&d" + PatchedPlayerUtils.getFormattedName(receiver.getUniqueId()) + " &eis now on your team.")); + receiver.sendMessage(Color.translate("&d" + PatchedPlayerUtils.getFormattedName(sender.getUniqueId()) + " &eis now on your team.")); + eLib.getInstance().getNameTagHandler().reloadPlayer(sender); + eLib.getInstance().getNameTagHandler().reloadOthersFor(receiver); + return; + } + + // if they didn't have a request, send the request + logic.getInvites().put(sender.getUniqueId(), receiver.getUniqueId()); + + // send messages to both parties letting them know about the request + sender.sendMessage(Color.translate("&eYou have sent a team request to &d" + PatchedPlayerUtils.getFormattedName(receiver.getUniqueId()) + "&e.")); + receiver.sendMessage(Color.translate("&eYou have received a team request from &d" + PatchedPlayerUtils.getFormattedName(sender.getUniqueId()) + "&e.")); + + } + + private void removeExistingTeam(Player receiver, GameTeamEventLogic logic) { + if(logic.get(receiver) != null) { + final GameTeam team = logic.get(receiver); + logic.getParticipants().remove(team); + team.getOthers(receiver).forEach(other -> { + other.sendMessage(Color.translate("&d" + PatchedPlayerUtils.getFormattedName(receiver.getUniqueId()) + " &chas left your team.")); + eLib.getInstance().getNameTagHandler().reloadPlayer(other); + eLib.getInstance().getNameTagHandler().reloadOthersFor(other); + }); + eLib.getInstance().getNameTagHandler().reloadPlayer(receiver); + eLib.getInstance().getNameTagHandler().reloadOthersFor(receiver); + } + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/EventTask.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/EventTask.java new file mode 100644 index 0000000..97f293f --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/EventTask.java @@ -0,0 +1,36 @@ +package com.elevatemc.potpvp.events; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.events.game.Game; +import com.elevatemc.potpvp.events.game.GameQueue; +import com.elevatemc.potpvp.lobby.LobbyHandler; +import com.elevatemc.potpvp.lobby.LobbyUtils; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +public class EventTask extends BukkitRunnable { + + @Override + public void run() { + for (Player player : Bukkit.getOnlinePlayers()) { + LobbyHandler handler = PotPvPSI.getInstance().getLobbyHandler(); + + if (handler.isInLobby(player)) { + if(!GameQueue.INSTANCE.getGames().isEmpty() || !GameQueue.INSTANCE.getRunningGames().isEmpty()) { + if (!player.getInventory().contains(EventItems.getEventItem()) && !PotPvPSI.getInstance().getPartyHandler().hasParty(player)) { + LobbyUtils.resetInventory(player); + } + } + + } else { + Game game = GameQueue.INSTANCE.getCurrentGame(player); + if (game != null && game.getPlayers().contains(player) && player.getInventory().contains(EventItems.getEventItem())) { + player.getInventory().remove(Material.DIAMOND); + } + } + + } + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/bukkit/event/GameStateChangeEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/bukkit/event/GameStateChangeEvent.java new file mode 100644 index 0000000..4f80b2e --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/bukkit/event/GameStateChangeEvent.java @@ -0,0 +1,31 @@ +package com.elevatemc.potpvp.events.bukkit.event; + +import com.elevatemc.potpvp.events.game.Game; +import com.elevatemc.potpvp.events.game.GameState; +import lombok.Getter; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +@Getter +public class GameStateChangeEvent extends Event { + + private static final HandlerList handlers = new HandlerList(); + + private final Game game; + private final GameState to; + + public GameStateChangeEvent(Game game, GameState to) { + this.game = game; + this.to = to; + game.setState(to); + } + + + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/bukkit/event/PlayerGameInteractionEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/bukkit/event/PlayerGameInteractionEvent.java new file mode 100644 index 0000000..98a25da --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/bukkit/event/PlayerGameInteractionEvent.java @@ -0,0 +1,26 @@ +package com.elevatemc.potpvp.events.bukkit.event; + +import com.elevatemc.potpvp.events.game.Game; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +@Getter +@RequiredArgsConstructor +public class PlayerGameInteractionEvent extends Event { + + private static final HandlerList handlers = new HandlerList(); + + private final Player player; + private final Game game; + + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/bukkit/event/PlayerJoinGameEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/bukkit/event/PlayerJoinGameEvent.java new file mode 100644 index 0000000..d6505a9 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/bukkit/event/PlayerJoinGameEvent.java @@ -0,0 +1,26 @@ +package com.elevatemc.potpvp.events.bukkit.event; + +import com.elevatemc.potpvp.events.game.Game; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +@Getter +@RequiredArgsConstructor +public class PlayerJoinGameEvent extends Event { + + private static final HandlerList handlers = new HandlerList(); + + private final Player player; + private final Game game; + + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/bukkit/event/PlayerQuitGameEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/bukkit/event/PlayerQuitGameEvent.java new file mode 100644 index 0000000..5d6fbcb --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/bukkit/event/PlayerQuitGameEvent.java @@ -0,0 +1,34 @@ +package com.elevatemc.potpvp.events.bukkit.event; + +import com.elevatemc.elib.util.TaskUtil; +import com.elevatemc.potpvp.events.game.Game; +import lombok.Getter; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +@Getter +public class PlayerQuitGameEvent extends Event { + + private static final HandlerList handlers = new HandlerList(); + + private final Player player; + private final Game game; + + public PlayerQuitGameEvent(Player player, Game game) { + this.player = player; + this.game = game; + + TaskUtil.runTaskLater(() -> { + game.getPlayers().remove(player); + game.getSpectators().remove(player); + }, 2); + } + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/command/ForceEndCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/command/ForceEndCommand.java new file mode 100644 index 0000000..1cbf1cb --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/command/ForceEndCommand.java @@ -0,0 +1,23 @@ +package com.elevatemc.potpvp.events.command; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.potpvp.events.game.Game; +import com.elevatemc.potpvp.events.game.GameQueue; +import org.bukkit.entity.Player; + +public class ForceEndCommand { + + @Command(names = { "event forceend", "forceend"}, permission = "op") + public static void host(Player sender) { + Game game = GameQueue.INSTANCE.getCurrentGame(sender); + + if (game == null) { + sender.sendMessage("You're not in a game"); + return; + } + + game.end(); + game.sendMessage("&cThe game has been forcefully ended."); + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/command/HostCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/command/HostCommand.java new file mode 100644 index 0000000..b0be126 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/command/HostCommand.java @@ -0,0 +1,34 @@ +package com.elevatemc.potpvp.events.command; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.potpvp.events.game.Game; +import com.elevatemc.potpvp.events.game.GameQueue; +import com.elevatemc.potpvp.events.game.GameState; +import com.elevatemc.potpvp.events.menu.HostMenu; +import com.elevatemc.potpvp.util.Color; +import org.bukkit.entity.Player; + +public class HostCommand { + + @Command(names = { "host"}, permission = "potpvp.host") + public static void host(Player sender) { + new HostMenu().openMenu(sender); + } + + @Command(names = {"forcestart"}, permission = "op") + public static void forcestart(Player player) { + final Game game = GameQueue.INSTANCE.getCurrentGame(player); + if(game == null) { + player.sendMessage(Color.translate("&cYou are not in an event.")); + return; + } + + if(game.getState() != GameState.STARTING) { + player.sendMessage(Color.translate("&cThis event cannot be force started")); + return; + } + + game.setStartingAt(System.currentTimeMillis()); + player.sendMessage(Color.translate("&aSuccessfully updated the events start time to now.")); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/GameEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/GameEvent.java new file mode 100644 index 0000000..91f52c1 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/GameEvent.java @@ -0,0 +1,28 @@ +package com.elevatemc.potpvp.events.event; + +import com.elevatemc.potpvp.events.game.Game; +import com.elevatemc.potpvp.events.parameter.GameParameter; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +public interface GameEvent { + + String getName(); + String getPermission(); + String getDescription(); + ItemStack getIcon(); + boolean canStart(Game game); + GameEventLogic getLogic(Game game); + String getNameTag(Game game, Player player, Player viewer); + List getScoreboardScores(Player player, Game game); + List getListeners(); + List getParameters(); + List getLobbyItems(); + default int getMaxInstances() { + return 1; + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/GameEventLogic.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/GameEventLogic.java new file mode 100644 index 0000000..e0bb8aa --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/GameEventLogic.java @@ -0,0 +1,5 @@ +package com.elevatemc.potpvp.events.event; + +public interface GameEventLogic { + void start(); +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/impl/brackets/BracketsGameEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/impl/brackets/BracketsGameEvent.java new file mode 100644 index 0000000..fcd406e --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/impl/brackets/BracketsGameEvent.java @@ -0,0 +1,144 @@ +package com.elevatemc.potpvp.events.event.impl.brackets; + +import com.elevatemc.elib.util.TimeUtils; +import com.elevatemc.potpvp.PotPvPLang; +import com.elevatemc.potpvp.events.event.GameEvent; +import com.elevatemc.potpvp.events.event.GameEventLogic; +import com.elevatemc.potpvp.events.event.impl.sumo.SumoGameEventLogic; +import com.elevatemc.potpvp.events.game.Game; +import com.elevatemc.potpvp.events.game.GameState; +import com.elevatemc.potpvp.events.parameter.GameParameter; +import com.elevatemc.potpvp.events.util.team.GameTeam; +import com.elevatemc.potpvp.events.util.team.GameTeamSizeParameter; +import com.google.common.collect.ImmutableList; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; + +public class BracketsGameEvent implements GameEvent { + + private static final String NAME = "Brackets"; + private static final String PERMISSION = "potpvp.host.brackets"; + private static final String DESCRIPTION = "Compete against other players in brackets."; + + @Override + public String getName() { + return NAME; + } + + @Override + public String getPermission() { + return PERMISSION; + } + + @Override + public String getDescription() { + return DESCRIPTION; + } + + @Override + public ItemStack getIcon() { + return new ItemStack(Material.IRON_SWORD); + } + + @Override + public boolean canStart(Game game) { + if(game.getParameter(GameTeamSizeParameter.Duos.class) != null) { + return game.getPlayers().size() >= 4; + } + + return game.getPlayers().size() >= 2; + } + + @Override + public GameEventLogic getLogic(Game game) { + return new SumoGameEventLogic(game); + } + + @Override + public String getNameTag(Game game, Player player, Player viewer) { + if(!(game.getLogic() instanceof BracketsGameEventLogic)) return ""; + final BracketsGameEventLogic logic = (BracketsGameEventLogic) game.getLogic(); + + if((logic.getInvites().containsKey(player.getUniqueId()) && logic.getInvites().get(player.getUniqueId()).equals(viewer.getUniqueId())) || (logic.getInvites().containsKey(viewer.getUniqueId()) && logic.getInvites().get(viewer.getUniqueId()).equals(player.getUniqueId()))) { + return ChatColor.YELLOW.toString(); + } + + final GameTeam participant = logic.get(player); + if(participant != null && participant.getPlayers().contains(viewer)) { + return ChatColor.GREEN.toString(); + } + + if(participant == null && game.getState() != GameState.STARTING) { + return ChatColor.GRAY.toString(); + } + + return ChatColor.RED.toString(); + } + + @Override + public List getScoreboardScores(Player player, Game game) { + final List lines = new ArrayList<>(); + final SumoGameEventLogic logic = (SumoGameEventLogic) game.getLogic(); + String name = NAME; + + if(game.getParameter(GameTeamSizeParameter.Duos.class) != null) { + name = "2v2 " + name; + } + + lines.add("&cEvent &7(" + name + ")"); + if(game.getState() == GameState.STARTING) { + lines.add("&6 " + PotPvPLang.LEFT_ARROW_NAKED + " &fStarting: &7" + (TimeUtils.formatIntoDetailedString((int) ((game.getStartingAt() + 500 - System.currentTimeMillis()) / 1000)))); + } + + lines.add("&6 " + PotPvPLang.LEFT_ARROW_NAKED + " &fPlayers: &7" + logic.getPlayersLeft() + "/" + game.getMaxPlayers()); + + + if(game.getState() == GameState.RUNNING) { + lines.add("&6 " + PotPvPLang.LEFT_ARROW_NAKED + " &fRound: &7" + logic.getRound()); + if(game.getParameter(GameTeamSizeParameter.Duos.class) == null) { + final GameTeam fighter = logic.getNextParticipant(null); + final GameTeam opponent = logic.getNextParticipant(fighter); + + if(opponent != null && fighter != null) { + lines.add(fighter.getName() + " &7vs " + opponent.getName()); + } + } else { + // TODO: Duos lines + final GameTeam fighter = logic.getNextParticipant(null); + final GameTeam opponent = logic.getNextParticipant(fighter); + + if(opponent != null && fighter != null) { + lines.add(fighter.getName()); + lines.add("&7vs"); + lines.add(opponent.getName()); + } + } + } + + return lines; + } + + @Override + public List getListeners() { + return ImmutableList.of(new BracketsGameEventListener()); + } + + @Override + public List getParameters() { + return ImmutableList.of( + new GameTeamSizeParameter(), + new BracketsGameKitParameter() + ); + } + + @Override + public List getLobbyItems() { + return ImmutableList.of(); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/impl/brackets/BracketsGameEventListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/impl/brackets/BracketsGameEventListener.java new file mode 100644 index 0000000..be957d3 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/impl/brackets/BracketsGameEventListener.java @@ -0,0 +1,227 @@ +package com.elevatemc.potpvp.events.event.impl.brackets; + +import com.elevatemc.elib.util.TaskUtil; +import com.elevatemc.potpvp.events.bukkit.event.PlayerGameInteractionEvent; +import com.elevatemc.potpvp.events.bukkit.event.PlayerQuitGameEvent; +import com.elevatemc.potpvp.events.event.impl.lms.LastManStandingGameEvent; +import com.elevatemc.potpvp.events.event.impl.lms.LastManStandingGameEventLogic; +import com.elevatemc.potpvp.events.game.Game; +import com.elevatemc.potpvp.events.game.GameQueue; +import com.elevatemc.potpvp.events.game.GameState; +import com.elevatemc.potpvp.events.util.team.GameTeam; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.FoodLevelChangeEvent; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.util.Vector; + +import java.util.List; + +public class BracketsGameEventListener implements Listener { + + @EventHandler + public void onPlayerMoveEvent(PlayerMoveEvent event) { + if(event.getFrom().getBlockX() != event.getTo().getBlockX() || event.getFrom().getBlockY() != event.getTo().getBlockY() || event.getFrom().getBlockZ() != event.getTo().getBlockZ()) { + final Game game = GameQueue.INSTANCE.getCurrentGame(event.getPlayer()); + if(game == null) return; + + if(!(game.getLogic() instanceof BracketsGameEventLogic)) return; + final BracketsGameEventLogic logic = (BracketsGameEventLogic) game.getLogic(); + + final GameTeam participant = logic.get(event.getPlayer()); + if(participant == null) return; + + if(participant.isStarting()) { + if(event.getFrom().getBlockX() != event.getTo().getBlockX() || event.getFrom().getBlockZ() != event.getTo().getBlockZ()) { + event.getPlayer().teleport(event.getFrom()); + event.getPlayer().setVelocity(new Vector(0, -1, 0)); + } + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onEntityDamageByEntityEvent(EntityDamageByEntityEvent event) { + if(event.getEntity() instanceof Player) { + final Player player = (Player) event.getEntity(); + final Game game = GameQueue.INSTANCE.getCurrentGame(player); + if(game == null) return; + if(!(game.getLogic() instanceof BracketsGameEventLogic)) return; + final BracketsGameEventLogic logic = (BracketsGameEventLogic) game.getLogic(); + + final GameTeam participant = logic.get(player); + + if(game.getSpectators().contains(player)) { + event.setCancelled(true); + return; + } + + if(event.getDamager() instanceof Player && game.getSpectators().contains((Player) event.getDamager())) { + event.setCancelled(true); + return; + } + + if(game.getPlayers().contains(player)) { + Player opponent = null; + + if(event.getDamager() instanceof Player) { + opponent = (Player) event.getDamager(); + } + + if(event.getDamager() instanceof Projectile && ((Projectile) event.getDamager()).getShooter() instanceof Player) { + opponent = (Player)((Projectile) event.getDamager()).getShooter(); + } + + if(participant != null && opponent != null) { + if(participant.getPlayers().contains(opponent)) { + event.setCancelled(true); + return; + } + + if(participant.isFighting()) { + final GameTeam opponentParticipating = logic.get(opponent); + if(opponentParticipating != null && opponentParticipating.isFighting()) { + event.setCancelled(false); + return; + } + } + } + + event.setCancelled(true); + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onEntityDamageEvent(EntityDamageEvent event) { + if(event.getEntity() instanceof Player) { + final Player player = (Player) event.getEntity(); + final Game game = GameQueue.INSTANCE.getCurrentGame(player); + if(game == null) return; + if(!(game.getLogic() instanceof BracketsGameEventLogic)) return; + final BracketsGameEventLogic logic = (BracketsGameEventLogic) game.getLogic(); + + if(game.getPlayers().contains(player) && game.getState() != GameState.STARTING) { + final GameTeam participant = logic.get(player); + + if(participant != null) { + if(participant.isFighting() && !participant.hasDied(player)) { + event.setCancelled(false); + return; + } + } + + event.setCancelled(true); + } + } + } + + @EventHandler + public void onPlayerQuitEvent(PlayerQuitEvent event) { + final Game game = GameQueue.INSTANCE.getCurrentGame(event.getPlayer()); + if(game == null) return; + if(!(game.getLogic() instanceof BracketsGameEventLogic)) return; + final BracketsGameEventLogic logic = (BracketsGameEventLogic) game.getLogic(); + + final GameTeam participant = logic.get(event.getPlayer()); + if(participant == null) return; + + if(participant.isFighting() || participant.isStarting()) { + participant.died(event.getPlayer()); + + logic.check(); + } else { + if(participant.getPlayers().size() == 1 || game.getState() == GameState.STARTING) { + logic.getParticipants().remove(participant); + } else { + final List newPlayers = participant.getPlayers(); + newPlayers.remove(event.getPlayer()); + participant.setPlayers(newPlayers); + } + } + } + + @EventHandler + public void onPlayerQuitGameEvent(PlayerQuitGameEvent event) { + final Game game = GameQueue.INSTANCE.getCurrentGame(event.getPlayer()); + if(game == null) return; + if(!(game.getLogic() instanceof BracketsGameEventLogic)) return; + final BracketsGameEventLogic logic = (BracketsGameEventLogic) game.getLogic(); + + final GameTeam participant = logic.get(event.getPlayer()); + if(participant == null) return; + + if(participant.isFighting() || participant.isStarting()) { + participant.died(event.getPlayer()); + + logic.check(); + } else { + if(participant.getPlayers().size() == 1 || game.getState() == GameState.STARTING) { + logic.getParticipants().remove(participant); + } else { + final List newPlayers = participant.getPlayers(); + newPlayers.remove(event.getPlayer()); + participant.setPlayers(newPlayers); + } + } + } + + @EventHandler + public void onPlayerDeathEvent(PlayerDeathEvent event) { + final Game game = GameQueue.INSTANCE.getCurrentGame(event.getEntity()); + if(game == null) return; + if(!(game.getLogic() instanceof BracketsGameEventLogic)) return; + final BracketsGameEventLogic logic = (BracketsGameEventLogic) game.getLogic(); + + final GameTeam participant = logic.get(event.getEntity()); + if(participant == null) return; + + event.getDrops().clear(); + + if(participant.isFighting()) { + participant.died(event.getEntity()); + + if(participant.isFinished()) { + event.getEntity().setHealth(event.getEntity().getMaxHealth()); + logic.check(); + + participant.getPlayers().forEach(player -> TaskUtil.runTaskLater(() -> game.reset(player), 2)); + game.getPlayers().forEach(player -> game.getPlayers().forEach(otherPlayer -> { + if(player.getUniqueId().equals(otherPlayer.getUniqueId())) return; + player.teleport(game.getArena().getSpectatorSpawn()); + player.showPlayer(otherPlayer); + })); + } else { + TaskUtil.runTaskLater(() -> { + event.getEntity().spigot().respawn(); + event.getEntity().teleport(game.getArena().getSpectatorSpawn()); + Bukkit.getPluginManager().callEvent(new PlayerGameInteractionEvent(event.getEntity(), game)); + }, 2); + } + } + } + + @EventHandler + public void onFoodLevelChangeEvent(FoodLevelChangeEvent event) { + if(event.getEntity() instanceof Player) { + final Player player = (Player) event.getEntity(); + final Game game = GameQueue.INSTANCE.getCurrentGame(player); + if(game == null) return; + if(!(game.getLogic() instanceof BracketsGameEventLogic)) return; + final BracketsGameEventLogic logic = (BracketsGameEventLogic) game.getLogic(); + + final GameTeam participant = logic.get(player); + if(participant == null) return; + + event.setFoodLevel(20); + } + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/impl/brackets/BracketsGameEventLogic.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/impl/brackets/BracketsGameEventLogic.java new file mode 100644 index 0000000..81bde51 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/impl/brackets/BracketsGameEventLogic.java @@ -0,0 +1,206 @@ +package com.elevatemc.potpvp.events.event.impl.brackets; + +import com.elevatemc.elib.util.TaskUtil; +import com.elevatemc.potpvp.events.bukkit.event.PlayerGameInteractionEvent; +import com.elevatemc.potpvp.events.game.Game; +import com.elevatemc.potpvp.events.game.GameState; +import com.elevatemc.potpvp.events.parameter.GameParameterOption; +import com.elevatemc.potpvp.events.util.GameEventCountdown; +import com.elevatemc.potpvp.events.util.team.GameTeam; +import com.elevatemc.potpvp.events.util.team.GameTeamEventLogic; +import com.elevatemc.potpvp.util.VisibilityUtils; +import com.google.common.collect.ImmutableList; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.util.Vector; + +import java.util.stream.Collectors; + +public class BracketsGameEventLogic extends GameTeamEventLogic { + + public BracketsGameEventLogic(Game game) { + super(game); + } + + @Override + public void start() { + super.start(); + + next(); + } + + public void check() { + final GameTeam winner = getWinner(); + if(winner == null) return; + final GameTeam loser = getLoser(); + if(loser == null) return; + + winner.reset(); + winner.setRound(winner.getRound() + 1); + winner.setFighting(false); + + participants.remove(loser); + + for(Player player : winner.getPlayers()) { + game.reset(player); + } + + for(Player player : loser.getPlayers()) { + game.getSpectators().add(player); + game.reset(player); + } + + if(getNextParticipant(winner) == null) { + broadcastWinner(winner); + + TaskUtil.runTaskLater(game::end, 40); + game.end(); + + return; + } + + game.sendMessage("", winner.getName() + " &ebeat " + loser.getName() + "&e!", ""); + next(); + } + + private void broadcastWinner(GameTeam winner) { + Bukkit.getOnlinePlayers().forEach(player -> player.sendMessage(new String[]{"", + ChatColor.GRAY + "███████", + ChatColor.GRAY + "█" + ChatColor.GOLD + "█████" + ChatColor.GRAY + "█" + " " + ChatColor.GOLD + "[" + game.getEvent().getName() + " Event Winner]", + ChatColor.GRAY + "█" + ChatColor.GOLD + "█" + ChatColor.GRAY + "█████" + " ", + ChatColor.GRAY + "█" + ChatColor.GOLD + "████" + ChatColor.GRAY + "██" + " " + winner.getName() + ChatColor.GRAY + " has won the event!", + ChatColor.GRAY + "█" + ChatColor.GOLD + "█" + ChatColor.GRAY + "█████" + " ", + ChatColor.GRAY + "█" + ChatColor.GOLD + "█████" + ChatColor.GRAY + "█" + " " + ChatColor.GRAY + ChatColor.ITALIC + "Event Type: (" + game.getParameters().stream().map(GameParameterOption::getDisplayName).collect(Collectors.joining(", ")) + ")", + ChatColor.GRAY + "███████", + ""} + )); + } + + private void next() { + final GameTeam fighter = getNextParticipant(null); + final GameTeam opponent = getNextParticipant(fighter); + + if(fighter != opponent && fighter != null && opponent != null) { + if(fighter.getRound() != opponent.getRound()) { + fighter.setRound(Math.max(fighter.getRound(), opponent.getRound())); + opponent.setRound(fighter.getRound()); + } + + game.sendMessage("", "&e&lNext Matchup:", fighter.getName() + "&e vs. " + opponent.getName() + "&e!", ""); + + fighter.setStarting(true); + opponent.setStarting(true); + + new GameEventCountdown(5, () -> { + fighter.setStarting(false); + opponent.setStarting(false); + + fighter.setFighting(true); + opponent.setFighting(true); + + fighter.showTeamsPlayers(opponent); + opponent.showTeamsPlayers(fighter); + + fighter.getPlayers().forEach(player -> player.removePotionEffect(PotionEffectType.INVISIBILITY)); + opponent.getPlayers().forEach(player -> player.removePotionEffect(PotionEffectType.INVISIBILITY)); + }, ImmutableList.of(fighter, opponent)); + + if(game.getEvent() instanceof BracketsGameEvent && game.getParameter(BracketsGameKitParameter.BracketsGameKitOption.class) == null) { + game.end(true); + game.sendMessage("&cFailed to start round. Kit parameter is not set up properly."); + return; + } + + final BracketsGameKitParameter.BracketsGameKitOption kit = (BracketsGameKitParameter.BracketsGameKitOption) game.getParameter(BracketsGameKitParameter.BracketsGameKitOption.class); + + for(int i=0; i OPTIONS = ImmutableList.of( + new BracketsGameKitOption(GameModeKit.byId("NO_DEBUFF")), + new BracketsGameKitOption(GameModeKit.byId("VANILLA")), + new BracketsGameKitOption(GameModeKit.byId("SOUP")) + ); + + @Override + public String getDisplayName() { + return DISPLAY_NAME; + } + + @Override + public List getOptions() { + return OPTIONS; + } + + public static final class BracketsGameKitOption implements GameParameterOption { + + private final GameModeKit kit; + + public BracketsGameKitOption(GameModeKit kit) { + this.kit = kit; + } + + @Override + public String getDisplayName() { + return kit.getDisplayName(); + } + + @Override + public ItemStack getIcon() { + final ItemStack icon = new ItemStack(kit.getIcon().getItemType()); + + icon.setData(kit.getIcon()); + + return icon; + } + + private ItemStack[] getItems() { + return kit.getDefaultInventory(); + } + + private ItemStack[] getArmor() { + return kit.getDefaultArmor(); + } + + public void apply(Player player) { + player.setHealth(player.getMaxHealth()); + player.setFoodLevel(20); + player.getInventory().setArmorContents(getArmor()); + final ItemStack[] items = getItems(); + + ItemStack filler = items[7]; + if(filler != null && filler.getType() != Material.POTION && filler.getType() != Material.MUSHROOM_SOUP) { + filler = new ItemStack(Material.AIR); + } + +// Bukkit.broadcastMessage(player.getName() + " items size is " + items.length); +// int potCount = 0, nonSplash = 0; + +// for(int i=0; i= 4; + } + + return game.getPlayers().size() >= 2; + } + + @Override + public GameEventLogic getLogic(Game game) { + return new LastManStandingGameEventLogic(game); + } + + @Override + public String getNameTag(Game game, Player player, Player viewer) { + if(game.getLogic() instanceof GameTeamEventLogic) { + final GameTeamEventLogic logic = (GameTeamEventLogic) game.getLogic(); + + if((logic.getInvites().containsKey(player.getUniqueId()) && logic.getInvites().get(player.getUniqueId()).equals(viewer.getUniqueId())) || (logic.getInvites().containsKey(viewer.getUniqueId()) && logic.getInvites().get(viewer.getUniqueId()).equals(player.getUniqueId()))) { + return ChatColor.YELLOW.toString(); + } + + final GameTeam team = logic.get(player); + // both players are on the same team, and its a team based gamemode, therefore they need green colors to denote that they are on the same team + if(team != null && team.getPlayers().contains(viewer)) { + return ChatColor.GREEN.toString(); + } + } + + // player is not on the same team, or its not a team gamemode, so the player is an enemy + return ChatColor.RED.toString(); + } + + @Override + public List getScoreboardScores(Player player, Game game) { + final List lines = new ArrayList<>(); + final LastManStandingGameEventLogic logic = (LastManStandingGameEventLogic) game.getLogic(); + String name = NAME; + + if(game.getParameter(GameTeamSizeParameter.Duos.class) != null) { + name = "2v2 " + name; + } + + lines.add("&cEvent &7(" + name + ")"); + if(game.getState() == GameState.STARTING) { + lines.add("&6 " + PotPvPLang.LEFT_ARROW_NAKED + " &fStarting: &7" + (TimeUtils.formatIntoDetailedString((int)((game.getStartingAt() + 500 - System.currentTimeMillis()) / 1000)))); + } else { + lines.add("&6 " + PotPvPLang.LEFT_ARROW_NAKED + " &fTime: &7" + TimeUtils.formatIntoDetailedString((int)((System.currentTimeMillis() - game.getStartingAt()) / 1000))); + } + lines.add("&6 " + PotPvPLang.LEFT_ARROW_NAKED + " &fPlayers: &7" + logic.getPlayersLeft() + "/" + game.getMaxPlayers()); + + if(game.getState() == GameState.RUNNING) { + lines.add("&6 " + PotPvPLang.LEFT_ARROW_NAKED + " &fKills: &7" + player.getStatistic(Statistic.PLAYER_KILLS)); + } + + return lines; + } + + @Override + public List getListeners() { + return ImmutableList.of(new LastManStandingGameEventListeners()); + } + + @Override + public List getParameters() { + return ImmutableList.of( + new GameTeamSizeParameter(), + new LastManStandingGameEventKitParameter() + ); + } + + @Override + public List getLobbyItems() { + return ImmutableList.of(); + } + + @Override + public int getMaxInstances() { + return 5; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/impl/lms/LastManStandingGameEventKitParameter.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/impl/lms/LastManStandingGameEventKitParameter.java new file mode 100644 index 0000000..cc5b296 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/impl/lms/LastManStandingGameEventKitParameter.java @@ -0,0 +1,74 @@ +package com.elevatemc.potpvp.events.event.impl.lms; + +import com.elevatemc.potpvp.events.parameter.GameParameter; +import com.elevatemc.potpvp.events.parameter.GameParameterOption; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import com.google.common.collect.ImmutableList; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +public class LastManStandingGameEventKitParameter implements GameParameter { + + private static final String DISPLAY_NAME = "Kit"; + private static final List OPTIONS = ImmutableList.of( + new LastManStandingKitOption(GameModeKit.byId("NO_DEBUFF")), + new LastManStandingKitOption(GameModeKit.byId("SOUP")), + new LastManStandingKitOption(GameModeKit.byId("VANILLA")) +// new LastManStandingKitOption(GameModeKit.byId("AXE")), +// new LastManStandingKitOption(GameModeKit.byId("CLASSIC")) + ); + + @Override + public String getDisplayName() { + return DISPLAY_NAME; + } + + @Override + public List getOptions() { + return OPTIONS; + } + + public static final class LastManStandingKitOption implements GameParameterOption { + + private GameModeKit kit; + + public LastManStandingKitOption(GameModeKit kit) { + this.kit = kit; + } + + @Override + public String getDisplayName() { + return kit.getDisplayName(); + } + + @Override + public ItemStack getIcon() { + final ItemStack icon = new ItemStack(kit.getIcon().getItemType()); + + icon.setData(kit.getIcon()); + + return icon; + } + + private ItemStack[] getItems() { + return kit.getDefaultInventory(); + } + + private ItemStack[] getArmor() { + return kit.getDefaultArmor(); + } + + public void apply(Player player) { + player.setHealth(player.getMaxHealth()); + player.setFoodLevel(20); + player.getInventory().setArmorContents(getArmor()); + player.getInventory().setContents(getItems()); + + player.updateInventory(); + } + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/impl/lms/LastManStandingGameEventListeners.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/impl/lms/LastManStandingGameEventListeners.java new file mode 100644 index 0000000..2beadeb --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/impl/lms/LastManStandingGameEventListeners.java @@ -0,0 +1,217 @@ +package com.elevatemc.potpvp.events.event.impl.lms; + +import com.elevatemc.elib.util.TaskUtil; +import com.elevatemc.potpvp.events.bukkit.event.PlayerGameInteractionEvent; +import com.elevatemc.potpvp.events.bukkit.event.PlayerQuitGameEvent; +import com.elevatemc.potpvp.events.game.Game; +import com.elevatemc.potpvp.events.game.GameQueue; +import com.elevatemc.potpvp.events.util.team.GameTeam; +import com.elevatemc.potpvp.util.Color; +import com.elevatemc.potpvp.util.PatchedPlayerUtils; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.EnderPearl; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; + +public class LastManStandingGameEventListeners implements Listener { + + @EventHandler + public void onPlayerMoveEvent(PlayerMoveEvent event) { + if(event.getFrom().getBlockX() != event.getTo().getBlockX() || event.getFrom().getBlockY() != event.getTo().getBlockY() || event.getFrom().getBlockZ() != event.getTo().getBlockZ()) { + final Game game = GameQueue.INSTANCE.getCurrentGame(event.getPlayer()); + if(game == null) return; + + if(!(game.getLogic() instanceof LastManStandingGameEventLogic)) return; + final LastManStandingGameEventLogic logic = (LastManStandingGameEventLogic) game.getLogic(); + + if(game.getSpectators().contains(event.getPlayer()) && event.getTo().getBlockY() <= 0) { + event.getPlayer().teleport(game.getArena().getTeam1Spawn()); + return; + } + + final GameTeam participant = logic.get(event.getPlayer()); + if(participant == null) return; + + if(participant.isStarting()) { + if(event.getFrom().getBlockX() != event.getTo().getBlockX() || event.getFrom().getBlockZ() != event.getTo().getBlockZ()) { + event.getPlayer().teleport(event.getFrom()); + event.getPlayer().setVelocity(new Vector(0, -1, 0)); + } + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onEntityDamageEvent(EntityDamageEvent event) { + if(event.getEntity() instanceof Player) { + final Player player = (Player) event.getEntity(); + final Game game = GameQueue.INSTANCE.getCurrentGame(player); + if(game == null) return; + if(!(game.getLogic() instanceof LastManStandingGameEventLogic)) return; + + final GameTeam participant = ((LastManStandingGameEventLogic) game.getLogic()).get(player); + if(participant == null) return; + + if(participant.isStarting()) { + event.setCancelled(true); + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onEntityDamageByEntityEvent(EntityDamageByEntityEvent event) { + if(event.getEntity() instanceof Player) { + final Player player = (Player) event.getEntity(); + final Game game = GameQueue.INSTANCE.getCurrentGame(player); + if(game == null) return; + if(!(game.getLogic() instanceof LastManStandingGameEventLogic)) return; + + final GameTeam participant = ((LastManStandingGameEventLogic) game.getLogic()).get(player); + if(participant == null) return; + + if(game.getPlayers().contains(player)) { + boolean allowed = event.getDamager() instanceof Player; + + if(event.getDamager() instanceof Projectile) { + allowed = ((Projectile) event.getDamager()).getShooter() instanceof Player; + } + + if(allowed) { + if(event.getDamager() instanceof EnderPearl) { + event.setCancelled(true); + return; + } + + if(participant.getPlayers().contains((Player)event.getDamager())) { + event.setCancelled(true); + return; + } + + if(event.getDamager() instanceof Projectile && participant.getPlayers().contains(((Projectile) event.getDamager()).getShooter())) { + event.setCancelled(true); + return; + } + + if(event.getDamager() instanceof Projectile && game.getSpectators().contains(((Projectile) event.getDamager()).getShooter())) { + event.setCancelled(true); + return; + } + + if(event.getDamager() instanceof Player && game.getSpectators().contains((Player) event.getDamager())) { + event.setCancelled(true); + return; + } + + if(participant.isFighting()) { + event.setCancelled(false); + return; + } + } + + event.setCancelled(true); + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onPlayerDropItemEvent(PlayerDropItemEvent event) { + final Game game = GameQueue.INSTANCE.getCurrentGame(event.getPlayer()); + if(game == null) return; + if(!(game.getLogic() instanceof LastManStandingGameEventLogic)) return; + final GameTeam participant = ((LastManStandingGameEventLogic) game.getLogic()).get(event.getPlayer()); + + if(!(participant.hasDied(event.getPlayer())) && game.getEvent() instanceof LastManStandingGameEvent) { + event.setCancelled(false); + event.getItemDrop().remove(); + } + } + + @EventHandler + public void onInventoryClickEvent(InventoryClickEvent event) { + if(event.getWhoClicked() instanceof Player && event.getClick() == ClickType.DROP && event.getInventory() == event.getWhoClicked().getInventory()) { + final Player player = (Player) event.getWhoClicked(); + final Game game = GameQueue.INSTANCE.getCurrentGame(player); + if(game == null) return; + if(!(game.getLogic() instanceof LastManStandingGameEventLogic)) return; + final GameTeam participant = ((LastManStandingGameEventLogic) game.getLogic()).get(player); + if(participant == null) return; + + if(!(participant.hasDied(player)) && game.getEvent() instanceof LastManStandingGameEvent) { + final ItemStack item = event.getCurrentItem(); + + if(item != null) { + if(item.getAmount() > 1) { + item.setAmount(item.getAmount()-1); + } else { + event.getInventory().setItem(event.getRawSlot(), new ItemStack(Material.AIR)); + } + } + } + } + } + + @EventHandler + public void onPlayerQuitEvent(PlayerQuitEvent event) { + final Game game = GameQueue.INSTANCE.getCurrentGame(event.getPlayer()); + if(game == null) return; + if(!(game.getLogic() instanceof LastManStandingGameEventLogic)) return; + final GameTeam participant = ((LastManStandingGameEventLogic) game.getLogic()).get(event.getPlayer()); + if(participant == null) return; + + participant.died(event.getPlayer()); + Bukkit.getPluginManager().callEvent(new PlayerQuitGameEvent(event.getPlayer(), game)); + ((LastManStandingGameEventLogic) game.getLogic()).check(); + } + + @EventHandler + public void onPlayerDeathEvent(PlayerDeathEvent event) { + final Game game = GameQueue.INSTANCE.getCurrentGame(event.getEntity()); + if(game == null) return; + if(!(game.getLogic() instanceof LastManStandingGameEventLogic)) return; + final GameTeam participant = ((LastManStandingGameEventLogic) game.getLogic()).get(event.getEntity()); + if(participant == null) return; + + Location location = event.getEntity().getLocation(); + if(location.getBlockY() < 0) { + location = game.getArena().getTeam1Spawn(); + } + + if(game.getEvent() instanceof LastManStandingGameEvent) { + event.getDrops().removeIf(item -> item != null && item.getType() != Material.POTION); + } + + if(event.getEntity().getKiller() != null) { + event.getEntity().getKiller().setHealth(event.getEntity().getKiller().getMaxHealth()); + game.sendMessage("", Color.translate("&d" + PatchedPlayerUtils.getFormattedName(event.getEntity().getUniqueId()) + " &7was killed by &d" + PatchedPlayerUtils.getFormattedName(event.getEntity().getKiller().getUniqueId()) + "&7."), ""); + } else { + game.sendMessage("", Color.translate("&d" + PatchedPlayerUtils.getFormattedName(event.getEntity().getUniqueId()) + " &7died."), ""); + } + + participant.died(event.getEntity()); + + final Location finalLocation = location; + TaskUtil.runTaskLater(() -> { + game.getSpectators().add(event.getEntity()); + event.getEntity().spigot().respawn(); + event.getEntity().teleport(finalLocation); + game.reset(event.getEntity()); + Bukkit.getPluginManager().callEvent(new PlayerGameInteractionEvent(event.getEntity(), game)); + ((LastManStandingGameEventLogic) game.getLogic()).check(); + }, 2); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/impl/lms/LastManStandingGameEventLogic.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/impl/lms/LastManStandingGameEventLogic.java new file mode 100644 index 0000000..3de2ce0 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/impl/lms/LastManStandingGameEventLogic.java @@ -0,0 +1,115 @@ +package com.elevatemc.potpvp.events.event.impl.lms; + +import com.elevatemc.potpvp.events.game.Game; +import com.elevatemc.potpvp.events.game.GameState; +import com.elevatemc.potpvp.events.parameter.GameParameterOption; +import com.elevatemc.potpvp.events.util.GameEventCountdown; +import com.elevatemc.potpvp.events.util.team.GameTeam; +import com.elevatemc.potpvp.events.util.team.GameTeamEventLogic; +import com.elevatemc.potpvp.util.PatchedPlayerUtils; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Statistic; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +public class LastManStandingGameEventLogic extends GameTeamEventLogic { + + public LastManStandingGameEventLogic(Game game) { + super(game); + } + + @Override + public void start() { + super.start(); + + final AtomicInteger index = new AtomicInteger(); + + participants.forEach(team -> { + team.setStarting(true); + team.getPlayers().forEach(player -> { + player.getInventory().clear(); + player.setStatistic(Statistic.PLAYER_KILLS, 0); + player.teleport(game.getArena().getEventSpawns().get(index.get())); + }); + + index.getAndIncrement(); + }); + + if(game.getParameter(LastManStandingGameEventKitParameter.LastManStandingKitOption.class) == null) { + game.end(true); + game.sendMessage("&cFailed to start match. Kit parameter is not set up properly."); + return; + } + + final LastManStandingGameEventKitParameter.LastManStandingKitOption kit = (LastManStandingGameEventKitParameter.LastManStandingKitOption) game.getParameter(LastManStandingGameEventKitParameter.LastManStandingKitOption.class); + + new GameEventCountdown(5, () -> { + for(GameTeam participant : participants) { + participant.setStarting(false); + participant.setFighting(true); + + participant.getPlayers().forEach(((LastManStandingGameEventKitParameter.LastManStandingKitOption) kit)::apply); + } + }, new ArrayList<>(participants)); + } + + public void check() { + final List alive = new ArrayList<>(); + + for(GameTeam team : participants) { + if(!team.isFinished()) { + alive.add(team); + } + } + + if(alive.size() == 1) { + final GameTeam winner = alive.get(0); + broadcastWinner(winner); + end(); + } else if(alive.size() == 0) { + end(); + } + } + + public void end() { + game.end(); + + for(Player player : game.getPlayers()) { + player.setStatistic(Statistic.PLAYER_KILLS, 0); + } + } + + public int getPlayersLeft() { + if(game.getState() == GameState.STARTING) return game.getPlayers().size(); + + int toReturn = 0; + + for(GameTeam participant : participants) { + for(Player player : participant.getPlayers()) { + if(!(participant.hasDied(player))) { + toReturn++; + } + } + } + + return toReturn; + } + + public void broadcastWinner(GameTeam winner) { + Bukkit.getOnlinePlayers().forEach(player -> player.sendMessage(new String[]{"", + ChatColor.GRAY + "███████", + ChatColor.GRAY + "█" + ChatColor.GOLD + "█████" + ChatColor.GRAY + "█" + " " + ChatColor.GOLD + "[" + game.getEvent().getName() + " Event Winner]", + ChatColor.GRAY + "█" + ChatColor.GOLD + "█" + ChatColor.GRAY + "█████" + " ", + ChatColor.GRAY + "█" + ChatColor.GOLD + "████" + ChatColor.GRAY + "██" + " " + winner.getName() + ChatColor.GRAY + " has won the event!", + ChatColor.GRAY + "█" + ChatColor.GOLD + "█" + ChatColor.GRAY + "█████" + " ", + ChatColor.GRAY + "█" + ChatColor.GOLD + "█████" + ChatColor.GRAY + "█" + " " + ChatColor.GRAY + ChatColor.ITALIC + "Event Type: (" + game.getParameters().stream().map(GameParameterOption::getDisplayName).collect(Collectors.joining(", ")) + ")", + ChatColor.GRAY + "███████", + ""} + )); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/impl/sumo/SumoGameEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/impl/sumo/SumoGameEvent.java new file mode 100644 index 0000000..f998182 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/impl/sumo/SumoGameEvent.java @@ -0,0 +1,161 @@ +package com.elevatemc.potpvp.events.event.impl.sumo; + +import com.elevatemc.elib.util.TimeUtils; +import com.elevatemc.potpvp.PotPvPLang; +import com.elevatemc.potpvp.events.event.GameEvent; +import com.elevatemc.potpvp.events.event.GameEventLogic; +import com.elevatemc.potpvp.events.event.impl.brackets.BracketsGameEventLogic; +import com.elevatemc.potpvp.events.game.Game; +import com.elevatemc.potpvp.events.game.GameState; +import com.elevatemc.potpvp.events.parameter.GameParameter; +import com.elevatemc.potpvp.events.util.team.GameTeam; +import com.elevatemc.potpvp.events.util.team.GameTeamSizeParameter; +import com.elevatemc.potpvp.util.Color; +import com.google.common.collect.ImmutableList; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BookMeta; + +import java.util.ArrayList; +import java.util.List; + +public class SumoGameEvent implements GameEvent { + + private static final String NAME = "Sumo"; + private static final String DESCRIPTION = "Knock people off the sumo platform."; + private static final String PERMISSION = "potpvp.host.sumo"; + private static final ItemStack tutorialBook = new ItemStack(Material.WRITTEN_BOOK); + +// public SumoGameEvent() { +// final BookMeta meta = (BookMeta) tutorialBook; +// +// meta.setDisplayName(Color.translate("&3How to play Sumo")); +// meta.setAuthor("ElevateMC"); +// meta.setTitle("How to play Sumo"); +// meta.setPages(Color.translate("&lSUMO TUTORIAL\n&r" + "\nIn Sumo, you'll be placed on a platform" + "\n with an opponent and whoever knocks the other" + " person off first wins the match." + "\n\n\n" + "&rIf you're in a Duo game, you can team up with other people " + "&cby right-clicking them with nothing in your hand.")); +// +// tutorialBook.setItemMeta(meta); +// } + + @Override + public String getName() { + return NAME; + } + + @Override + public String getPermission() { + return PERMISSION; + } + + @Override + public String getDescription() { + return DESCRIPTION; + } + + @Override + public ItemStack getIcon() { + return new ItemStack(Material.LEASH); + } + + @Override + public boolean canStart(Game game) { + if(game.getParameter(GameTeamSizeParameter.Duos.class) != null) { + return game.getPlayers().size() >= 4; + } + + return game.getPlayers().size() >= 2; + } + + @Override + public GameEventLogic getLogic(Game game) { + return new SumoGameEventLogic(game); + } + + @Override + public String getNameTag(Game game, Player player, Player viewer) { + if(!(game.getLogic() instanceof BracketsGameEventLogic)) return ""; + final BracketsGameEventLogic logic = (BracketsGameEventLogic) game.getLogic(); + + if((logic.getInvites().containsKey(player.getUniqueId()) && logic.getInvites().get(player.getUniqueId()).equals(viewer.getUniqueId())) || (logic.getInvites().containsKey(viewer.getUniqueId()) && logic.getInvites().get(viewer.getUniqueId()).equals(player.getUniqueId()))) { + return ChatColor.YELLOW.toString(); + } + + final GameTeam participant = logic.get(player); + if(participant != null && participant.getPlayers().contains(viewer)) { + return ChatColor.GREEN.toString(); + } + + if(participant == null && game.getState() != GameState.STARTING) { + return ChatColor.GRAY.toString(); + } + + return ChatColor.RED.toString(); + } + + @Override + public List getScoreboardScores(Player player, Game game) { + final List lines = new ArrayList<>(); + final SumoGameEventLogic logic = (SumoGameEventLogic) game.getLogic(); + String name = NAME; + + if(game.getParameter(GameTeamSizeParameter.Duos.class) != null) { + name = "2v2 " + name; + } + + lines.add("&cEvent &7(" + name + ")"); + if(game.getState() == GameState.STARTING) { + lines.add("&6 " + PotPvPLang.LEFT_ARROW_NAKED + " &fStarting: &7" + (TimeUtils.formatIntoDetailedString((int) ((game.getStartingAt() + 500 - System.currentTimeMillis()) / 1000)))); + } + + lines.add("&6 " + PotPvPLang.LEFT_ARROW_NAKED + " &fPlayers: &7" + logic.getPlayersLeft() + "/" + game.getMaxPlayers()); + + + if(game.getState() == GameState.RUNNING) { + lines.add("&6 " + PotPvPLang.LEFT_ARROW_NAKED + " &fRound: &7" + logic.getRound()); + if(game.getParameter(GameTeamSizeParameter.Duos.class) == null) { + final GameTeam fighter = logic.getNextParticipant(null); + final GameTeam opponent = logic.getNextParticipant(fighter); + + if(opponent != null && fighter != null) { + lines.add(fighter.getName() + " &7vs " + opponent.getName()); + } + } else { + // TODO: Duos lines + final GameTeam fighter = logic.getNextParticipant(null); + final GameTeam opponent = logic.getNextParticipant(fighter); + + if(opponent != null && fighter != null) { + lines.add(fighter.getName()); + lines.add("&7vs"); + lines.add(opponent.getName()); + } + } + } + + return lines; + } + + @Override + public List getListeners() { + return ImmutableList.of( + new SumoGameEventListeners() + ); + } + + @Override + public List getParameters() { + return ImmutableList.of( + new GameTeamSizeParameter() + ); + } + + @Override + public List getLobbyItems() { + return ImmutableList.of( +// tutorialBook + ); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/impl/sumo/SumoGameEventListeners.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/impl/sumo/SumoGameEventListeners.java new file mode 100644 index 0000000..a3be4f7 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/impl/sumo/SumoGameEventListeners.java @@ -0,0 +1,93 @@ +package com.elevatemc.potpvp.events.event.impl.sumo; + +import com.elevatemc.elib.util.TaskUtil; +import com.elevatemc.potpvp.events.event.impl.lms.LastManStandingGameEventLogic; +import com.elevatemc.potpvp.events.game.Game; +import com.elevatemc.potpvp.events.game.GameQueue; +import com.elevatemc.potpvp.events.game.GameState; +import com.elevatemc.potpvp.events.util.team.GameTeam; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.FoodLevelChangeEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.util.Vector; + +public class SumoGameEventListeners implements Listener { + + @EventHandler + public void onPlayerMoveEvent(PlayerMoveEvent event) { + if(event.getFrom().getBlockX() != event.getTo().getBlockX() || event.getFrom().getBlockY() != event.getTo().getBlockY() || event.getFrom().getBlockZ() != event.getTo().getBlockZ()) { + final Game game = GameQueue.INSTANCE.getCurrentGame(event.getPlayer()); + if(game == null) return; + + if(!(game.getLogic() instanceof SumoGameEventLogic)) return; + final SumoGameEventLogic logic = (SumoGameEventLogic) game.getLogic(); + + final GameTeam participant = logic.get(event.getPlayer()); + if(participant == null) return; + + if(event.getTo().getBlockY() + 5 < game.getFirstSpawnLocations().get(0).getBlockY() && participant.isFighting()) { + participant.died(event.getPlayer()); + + if(participant.isFinished()) { + logic.check(); + + participant.getPlayers().forEach(player -> TaskUtil.runTaskLater(() -> game.reset(player), 2)); + game.getPlayers().forEach(player -> game.getPlayers().forEach(otherPlayer -> { + if(player.getUniqueId().equals(otherPlayer.getUniqueId())) return; + player.teleport(game.getArena().getSpectatorSpawn()); + player.showPlayer(otherPlayer); + })); + } else { + game.addSpectator(event.getPlayer()); + } + } + } + } + + @EventHandler + public void onPlayerDamage(EntityDamageEvent event) { + if(!(event.getEntity() instanceof Player)) return; + final Player player = (Player) event.getEntity(); + final Game game = GameQueue.INSTANCE.getCurrentGame(player); + if(game == null) return; + + if(!(game.getLogic() instanceof SumoGameEventLogic)) return; + final SumoGameEventLogic logic = (SumoGameEventLogic) game.getLogic(); + + if(!(game.getEvent() instanceof SumoGameEvent)) return; + + if(game.getPlayers().contains(player) && game.getState() != GameState.STARTING) { + final GameTeam participant = logic.get(player); + + if(participant != null) { + if(participant.isFighting() && !participant.hasDied(player)) { + event.setDamage(0.0D); + event.setCancelled(false); + return; + } + } + + event.setCancelled(true); + } + } + + @EventHandler + public void onFoodLevelChange(FoodLevelChangeEvent event) { + if(!(event.getEntity() instanceof Player)) return; + final Player player = (Player) event.getEntity(); + final Game game = GameQueue.INSTANCE.getCurrentGame(player); + if(game == null) return; + + if(!(game.getLogic() instanceof SumoGameEventLogic)) return; + final SumoGameEventLogic logic = (SumoGameEventLogic) game.getLogic(); + + if(!(game.getEvent() instanceof SumoGameEvent)) return; + + if(game.getPlayers().contains(player)) { + event.setFoodLevel(20); + } + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/impl/sumo/SumoGameEventLogic.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/impl/sumo/SumoGameEventLogic.java new file mode 100644 index 0000000..c1bd6fc --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/event/impl/sumo/SumoGameEventLogic.java @@ -0,0 +1,10 @@ +package com.elevatemc.potpvp.events.event.impl.sumo; + +import com.elevatemc.potpvp.events.event.impl.brackets.BracketsGameEventLogic; +import com.elevatemc.potpvp.events.game.Game; + +public class SumoGameEventLogic extends BracketsGameEventLogic { + public SumoGameEventLogic(Game game) { + super(game); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/game/Game.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/game/Game.java new file mode 100644 index 0000000..4ca1d2c --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/game/Game.java @@ -0,0 +1,193 @@ +package com.elevatemc.potpvp.events.game; + +import com.elevatemc.elib.util.TaskUtil; +import com.elevatemc.potpvp.arena.Arena; +import com.elevatemc.potpvp.events.EventHandler; +import com.elevatemc.potpvp.events.bukkit.event.GameStateChangeEvent; +import com.elevatemc.potpvp.events.bukkit.event.PlayerJoinGameEvent; +import com.elevatemc.potpvp.events.event.GameEvent; +import com.elevatemc.potpvp.events.event.GameEventLogic; +import com.elevatemc.potpvp.events.event.impl.brackets.BracketsGameEventLogic; +import com.elevatemc.potpvp.events.event.impl.lms.LastManStandingGameEventLogic; +import com.elevatemc.potpvp.events.event.impl.sumo.SumoGameEventLogic; +import com.elevatemc.potpvp.events.parameter.GameParameterOption; +import com.elevatemc.potpvp.events.util.team.GameTeam; +import com.elevatemc.potpvp.events.util.team.GameTeamSizeParameter; +import com.elevatemc.potpvp.util.Color; +import com.elevatemc.potpvp.util.PatchedPlayerUtils; +import com.google.common.collect.ImmutableList; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.util.Vector; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Data +public class Game { + + private final GameEvent event; + private final Player host; + private final List parameters; + + private GameState state = GameState.QUEUED; + private long startingAt = 0L; + private final Set players = new HashSet<>(); + private final GameEventLogic logic; + private final Set spectators = new HashSet<>(); + private Arena arena; + + public Game(GameEvent event, Player host, List parameters) { + this.event = event; + this.host = host; + this.parameters = parameters; + this.logic = event.getLogic(this); + } + + public void addSpectator(Player player) { + if(state == GameState.ENDED) return; + + spectators.add(player); + players.add(player); + + sendMessage("&d" + PatchedPlayerUtils.getFormattedName(player.getUniqueId()) + " &7is now spectating."); + + reset(player); + + // look at if statement once im done + player.teleport(arena.getSpectatorSpawn()); + } + + public void add(Player player) { + final Game other = GameQueue.INSTANCE.getCurrentGame(player); + if(other != null) return; + if(state != GameState.STARTING) return; + + players.add(player); + sendMessage(Color.translate("&d" + PatchedPlayerUtils.getFormattedName(player.getUniqueId()) + " &7joined the event.")); + reset(player); + Bukkit.getPluginManager().callEvent(new PlayerJoinGameEvent(player, this)); + } + + private void resetSpectator(Player player) { + player.getInventory().clear(); + player.getInventory().setHeldItemSlot(0); + player.getInventory().setArmorContents(null); + player.setGameMode(GameMode.SURVIVAL); + player.getInventory().addItem(event.getLobbyItems().toArray(new ItemStack[0])); + player.getInventory().setItem(8, EventHandler.getLeaveItem()); + player.setHealth(player.getMaxHealth()); + player.setFoodLevel(20); + + player.getActivePotionEffects().forEach(effect -> player.removePotionEffect(effect.getType())); + + // make it so alive players cannot see the spectators + players.forEach(p -> p.hidePlayer(player)); + + // show game players to spectator + players.forEach(player::showPlayer); + + player.updateInventory(); + } + + public void reset(Player player) { + if(spectators.contains(player)) { + resetSpectator(player); + return; + } + + player.teleport(arena.getSpectatorSpawn().clone().add(0.0D, -1.0D, 0.0D)); + + PatchedPlayerUtils.resetInventory(player, GameMode.SURVIVAL, false); + player.getInventory().setHeldItemSlot(0); + player.getInventory().addItem(event.getLobbyItems().toArray(new ItemStack[0])); + player.getInventory().setItem(8, EventHandler.getLeaveItem()); + player.getActivePotionEffects().forEach(effect -> player.removePotionEffect(effect.getType())); + } + + public void start() { + if(!event.canStart(this)) { + end(true); + sendMessage("&cCould not start the match. Sending you back to the lobby."); + return; + } + + arena.takeSnapshot(); + logic.start(); + + Bukkit.getPluginManager().callEvent(new GameStateChangeEvent(this, GameState.RUNNING)); + } + + public void end(boolean... failed) { + if(failed.length == 0) arena.restore(); + Bukkit.getPluginManager().callEvent(new GameStateChangeEvent(this, GameState.ENDED)); + } + + public List getFirstSpawnLocations() { + if(getParameter(GameTeamSizeParameter.Duos.class) != null) { + final Vector direction = arena.getTeam1Spawn().getDirection(); + return ImmutableList.of( + arena.getTeam1Spawn().add(direction.clone().setX(-direction.getZ()).setZ(direction.getX())), + arena.getTeam1Spawn().add(direction.clone().setX(direction.getZ()).setZ(-direction.getX())) + ); + } else { + return ImmutableList.of(arena.getTeam1Spawn()); + } + } + + public List getSecondSpawnLocations() { + if(getParameter(GameTeamSizeParameter.Duos.class) != null) { + final Vector direction = arena.getTeam2Spawn().getDirection(); + return ImmutableList.of( + arena.getTeam2Spawn().add(direction.clone().setX(-direction.getZ()).setZ(direction.getX())), + arena.getTeam2Spawn().add(direction.clone().setX(direction.getZ()).setZ(-direction.getX())) + ); + } else { + return ImmutableList.of(arena.getTeam2Spawn()); + } + } + + public void sendMessage(String... message) { + for(String msg : message) { + players.forEach(player -> player.sendMessage(Color.translate(msg))); + } + } + + public GameParameterOption getParameter(Class clazz) { + for(GameParameterOption parameter : parameters) { + if(parameter.getClass() == clazz || clazz.isAssignableFrom(parameter.getClass())) { + return (GameParameterOption) clazz.cast(parameter); + } + } + + return null; + } + + public int getMaxPlayers() { + if(logic instanceof LastManStandingGameEventLogic) { + if(getParameter(GameTeamSizeParameter.Duos.class) != null) { + return arena.getEventSpawns().size() * 2; + } else { + return arena.getEventSpawns().size(); + } + } + + if(logic instanceof SumoGameEventLogic) { + if(getParameter(GameTeamSizeParameter.Duos.class) != null) { + return 32; + } else { + return 16; + } + } + + return -1; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/game/GameListeners.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/game/GameListeners.java new file mode 100644 index 0000000..567e92f --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/game/GameListeners.java @@ -0,0 +1,119 @@ +package com.elevatemc.potpvp.events.game; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.events.bukkit.event.PlayerQuitGameEvent; +import com.elevatemc.potpvp.util.Color; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.FoodLevelChangeEvent; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerPickupItemEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.Iterator; + +public class GameListeners implements Listener { + + private static final PotPvPSI plugin = PotPvPSI.getInstance(); + private static final GameQueue gameQueue = plugin.getEventHandler().getGameQueue(); + + @EventHandler + public void onPlayerDamageEvent(EntityDamageEvent event) { + if(event.getEntity() instanceof Player) { + final Game game = gameQueue.getCurrentGame((Player) event.getEntity()); + + if(game != null && game.getState() == GameState.STARTING && game.getPlayers().contains((Player) event.getEntity())) { + event.setCancelled(true); + } + } + } + + @EventHandler + public void onEntityDamageByEntityEvent(EntityDamageByEntityEvent event) { + if(event.getDamager() instanceof Player) { + final Game game = gameQueue.getCurrentGame((Player) event.getDamager()); + if(game != null && game.getSpectators().contains((Player) event.getDamager())) { + event.setCancelled(true); + } + } + } + + @EventHandler + public void onPlayerQuitEvent(PlayerQuitEvent event) { + final Game game = gameQueue.getCurrentGame(event.getPlayer()); + + if(game != null && game.getPlayers().contains(event.getPlayer())) { + Bukkit.getPluginManager().callEvent(new PlayerQuitGameEvent(event.getPlayer(), game)); + } + + final Iterator iterator = gameQueue.getGames().iterator(); + while(iterator.hasNext()) { + final Game other = iterator.next(); + if(other.getHost() == event.getPlayer() && other.getState() == GameState.QUEUED) { + iterator.remove(); + } + } + } + + @EventHandler + public void onPlayerDropItemEvent(PlayerDropItemEvent event) { + final Game game = gameQueue.getCurrentGame(event.getPlayer()); + + if(game != null && game.getPlayers().contains(event.getPlayer())) { + event.setCancelled(true); + } + } + + @EventHandler + public void onPlayerInventoryClickEvent(InventoryClickEvent event) { + final Game game = gameQueue.getCurrentGame((Player)event.getWhoClicked()); + + if(game != null && game.getState() == GameState.STARTING && game.getPlayers().contains((Player) event.getWhoClicked())) { + event.setCancelled(true); + } + + if(game != null && game.getSpectators().contains((Player) event.getWhoClicked())) { + event.setCancelled(true); + } + } + + @EventHandler + public void onPlayerPickupItemEvent(PlayerPickupItemEvent event) { + final Game game = gameQueue.getCurrentGame(event.getPlayer()); + + if(game != null && game.getSpectators().contains(event.getPlayer())) { + event.setCancelled(true); + } + } + + @EventHandler + public void onFoodLevelChangeEvent(FoodLevelChangeEvent event) { + final Game game = gameQueue.getCurrentGame((Player)event.getEntity()); + if(game != null && game.getState() == GameState.STARTING && game.getSpectators().contains((Player)event.getEntity())) { + event.setFoodLevel(20); + } + } + + @EventHandler + public void onPlayerInteractEvent(PlayerInteractEvent event) { + final Game game = gameQueue.getCurrentGame(event.getPlayer()); + + if(game == null) return; + + if(event.getItem() != null && event.getItem().isSimilar(com.elevatemc.potpvp.events.EventHandler.getLeaveItem())) { + Bukkit.getPluginManager().callEvent(new PlayerQuitGameEvent(event.getPlayer(), game)); + event.getPlayer().sendMessage(Color.translate("&cYou left the " + game.getEvent().getName() + " event.")); + return; + } + + if(game.getSpectators().contains(event.getPlayer())) { + event.setCancelled(true); + } + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/game/GameQueue.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/game/GameQueue.java new file mode 100644 index 0000000..9ad6e4e --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/game/GameQueue.java @@ -0,0 +1,103 @@ +package com.elevatemc.potpvp.events.game; + +import com.elevatemc.elib.util.TaskUtil; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.events.task.GameStartTask; +import lombok.Getter; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +@Getter +public class GameQueue { + + private final List runningGames = new ArrayList<>(); + private final LinkedList games = new LinkedList<>(); + + public static GameQueue INSTANCE; + + public GameQueue() { + INSTANCE = this; + } + + public void run(PotPvPSI plugin) { + TaskUtil.runTaskTimer(() -> check(plugin), 20, 20); + } + + private void check(PotPvPSI plugin) { + final Game game = games.peek(); + if(game != null) { + if(game.getState() == GameState.QUEUED) { + int count = 0; + boolean cancelled = false; + + for(Game other : runningGames) { + if(other.getState() == GameState.STARTING || other.getState() == GameState.ENDED) { + cancelled = true; + break; + } + + if(other.getEvent() == game.getEvent()) { + count++; + } + } + + if(count >= game.getEvent().getMaxInstances()) { + cancelled = true; + } + + if(!game.getHost().isOnline()) { + games.remove(); + cancelled = true; + } + + if(!cancelled) { + games.remove(); + runningGames.add(game); + new GameStartTask(plugin, game); + } + } + } + + final Iterator iterator = runningGames.iterator(); + while(iterator.hasNext()) { + final Game runningGame = iterator.next(); + + if(runningGame.getState() == GameState.ENDED) { + iterator.remove(); + continue; + } + + int onlinePlayers = 0; + for(Player player : runningGame.getPlayers()) { + if(player.isOnline()) onlinePlayers++; + } + + if(runningGame.getState() != GameState.STARTING && (runningGame.getPlayers().isEmpty() || onlinePlayers == 0)) { + iterator.remove(); + game.end(); + continue; + } + } + } + + public void add(Game game) { + games.add(game); + } + + public int size() { + return games.size(); + } + + public List getCurrentGames() { + return runningGames; + } + + public Game getCurrentGame(Player player) { + return runningGames.stream().filter(game -> game.getPlayers().contains(player)).findFirst().orElse(null); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/game/GameState.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/game/GameState.java new file mode 100644 index 0000000..93e73b1 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/game/GameState.java @@ -0,0 +1,8 @@ +package com.elevatemc.potpvp.events.game; + +public enum GameState { + QUEUED, + STARTING, + RUNNING, + ENDED +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/menu/EventsMenu.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/menu/EventsMenu.java new file mode 100644 index 0000000..a5eeb5a --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/menu/EventsMenu.java @@ -0,0 +1,96 @@ +package com.elevatemc.potpvp.events.menu; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import com.elevatemc.potpvp.events.game.Game; +import com.elevatemc.potpvp.events.game.GameQueue; +import com.elevatemc.potpvp.events.game.GameState; +import com.elevatemc.potpvp.util.Color; +import com.google.common.collect.Maps; +import org.apache.commons.lang.StringUtils; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public class EventsMenu extends Menu { + + public EventsMenu() { + setAutoUpdate(true); + } + + @Override + public String getTitle(Player player) { + return Color.translate("&3Join an event"); + } + + @Override + public Map getButtons(Player player) { + final Map buttons = Maps.newHashMap(); + + for (Game game : GameQueue.INSTANCE.getCurrentGames()) { + buttons.put(buttons.size(), new Button() { + @Override + public String getName(Player player) { + return ChatColor.AQUA + game.getEvent().getName() + " Event"; + } + + @Override + public List getDescription(Player player) { + + List lines = new ArrayList<>(); + + for (String line : Arrays.asList( + "&7&m-------------------------", + "&bPlayers&7: &f" + game.getPlayers().size() + (game.getMaxPlayers() == -1 ? "" : "&7/" + game.getMaxPlayers()), + "&bState&7: &f" + StringUtils.capitalize(game.getState().name().toLowerCase()), + "&bHosted By&7: &f" + game.getHost().getDisplayName(), + " ", + (game.getState() == GameState.STARTING ? "&aClick here to join." : "&7Click here to spectate."), + "&7&m-------------------------")) { + lines.add(ChatColor.translateAlternateColorCodes('&', line)); + } + + return lines; + } + + @Override + public Material getMaterial(Player player) { + return game.getEvent().getIcon().getType(); + } + + @Override + public byte getDamageValue(Player player) { + return (byte) game.getEvent().getIcon().getDurability(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + player.closeInventory(); + if (game.getState() == GameState.STARTING) { + if (game.getMaxPlayers() > 0 && game.getPlayers().size() >= game.getMaxPlayers()) { + player.sendMessage(ChatColor.RED + "This event is currently full! Sorry!"); + return; + } + game.add(player); + } else { + game.addSpectator(player); + } + } + + }); + } + + if (buttons.isEmpty()) { + player.closeInventory(); + } + + return buttons; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/menu/HostEventButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/menu/HostEventButton.java new file mode 100644 index 0000000..f9685d3 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/menu/HostEventButton.java @@ -0,0 +1,74 @@ +package com.elevatemc.potpvp.events.menu; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.potpvp.events.event.GameEvent; +import com.elevatemc.potpvp.events.game.Game; +import com.elevatemc.potpvp.events.game.GameQueue; +import com.elevatemc.potpvp.events.menu.parameter.HostParametersMenu; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class HostEventButton extends Button { + + private final GameEvent event; + + public HostEventButton(GameEvent event) { + this.event = event; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + if (player.hasPermission(event.getPermission())) { + if (event.getParameters().isEmpty()) { + + for (Game game : GameQueue.INSTANCE.getGames()) { + if (game.getHost().equals(player)) { + player.sendMessage(ChatColor.RED + "You've already queued an event!"); + player.closeInventory(); + return; + } + } + + if (GameQueue.INSTANCE.size() > 9) { + player.sendMessage(ChatColor.RED + "The game queue is currently full! Try again later."); + } else { + GameQueue.INSTANCE.add(new Game(event, player, new ArrayList<>())); + player.sendMessage(ChatColor.GREEN + "You've added a " + event.getName().toLowerCase() + " event to the queue."); + } + + player.closeInventory(); + } else { + new HostParametersMenu(event).openMenu(player); + } + } + } + + @Override + public String getName(Player player) { + if (player.hasPermission(event.getPermission())) { + return ChatColor.GREEN + event.getName(); + } + return ChatColor.RED + event.getName(); + } + + @Override + public List getDescription(Player player) { + return Collections.singletonList(ChatColor.GRAY + event.getDescription()); + } + + @Override + public Material getMaterial(Player player) { + return event.getIcon().getType(); + } + + @Override + public byte getDamageValue(Player player) { + return (byte) event.getIcon().getDurability(); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/menu/HostMenu.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/menu/HostMenu.java new file mode 100644 index 0000000..a9420ce --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/menu/HostMenu.java @@ -0,0 +1,30 @@ +package com.elevatemc.potpvp.events.menu; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import com.elevatemc.potpvp.events.EventHandler; +import com.elevatemc.potpvp.events.event.GameEvent; +import com.elevatemc.potpvp.util.Color; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; + +public class HostMenu extends Menu { + + @Override + public String getTitle(Player player) { + return Color.translate("&3Host an event"); + } + + @Override + public Map getButtons(Player player) { + Map toReturn = new HashMap<>(); + + for (GameEvent event : EventHandler.EVENTS) { + toReturn.put(toReturn.size(), new HostEventButton(event)); + } + + return toReturn; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/menu/parameter/HostParameterButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/menu/parameter/HostParameterButton.java new file mode 100644 index 0000000..5876663 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/menu/parameter/HostParameterButton.java @@ -0,0 +1,74 @@ +package com.elevatemc.potpvp.events.menu.parameter; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.potpvp.events.parameter.GameParameter; +import com.elevatemc.potpvp.events.parameter.GameParameterOption; +import com.elevatemc.potpvp.util.Color; +import lombok.Getter; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.ArrayList; +import java.util.List; + +public class HostParameterButton extends Button { + + private final GameParameter parameter; + @Getter private GameParameterOption selectedOption; + + public HostParameterButton(GameParameter parameter) { + this.parameter = parameter; + this.selectedOption = parameter.getOptions().get(0); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + int index = parameter.getOptions().indexOf(selectedOption); + + if (index + 1 == parameter.getOptions().size()) { + index = 0; + } else { + index++; + } + + selectedOption = parameter.getOptions().get(index); + } + + @Override + public String getName(Player player) { + return Color.translate("&3" + parameter.getDisplayName()); + } + + @Override + public List getDescription(Player player) { + List toReturn = new ArrayList<>(); + + for (GameParameterOption option : parameter.getOptions()) { + if (option.equals(selectedOption)) { + toReturn.add(ChatColor.GREEN + "» " + ChatColor.GRAY + option.getDisplayName()); + } else { + toReturn.add(ChatColor.GRAY + option.getDisplayName()); + } + } + + return toReturn; + } + + @Override + public Material getMaterial(Player player) { + return selectedOption.getIcon().getType(); + } + + @Override + public int getAmount(Player player) { + return selectedOption.getIcon().getAmount(); + } + + @Override + public byte getDamageValue(Player player) { + return (byte) selectedOption.getIcon().getDurability(); + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/menu/parameter/HostParametersMenu.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/menu/parameter/HostParametersMenu.java new file mode 100644 index 0000000..cf72899 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/menu/parameter/HostParametersMenu.java @@ -0,0 +1,93 @@ +package com.elevatemc.potpvp.events.menu.parameter; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import com.elevatemc.potpvp.events.event.GameEvent; +import com.elevatemc.potpvp.events.game.Game; +import com.elevatemc.potpvp.events.game.GameQueue; +import com.elevatemc.potpvp.events.parameter.GameParameter; +import com.elevatemc.potpvp.events.parameter.GameParameterOption; +import com.elevatemc.potpvp.util.Color; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.*; + +public class HostParametersMenu extends Menu { + + private final GameEvent event; + private final List buttons = new ArrayList<>(); + + public HostParametersMenu(GameEvent event) { + setUpdateAfterClick(true); + setPlaceholder(true); + + for (GameParameter parameter : event.getParameters()) { + buttons.add(new HostParameterButton(parameter)); + } + + this.event = event; + } + + @Override + public String getTitle(Player player) { + return Color.translate("&3" + event.getName() + " options"); + } + + @Override + public Map getButtons(Player player) { + Map toReturn = new HashMap<>(); + + for (HostParameterButton button : buttons) { + toReturn.put(toReturn.size(), button); + } + + toReturn.put(8, new Button() { // todo change although i doubt one event would ever have more than 8 parameters lol + @Override + public String getName(Player player) { + return ChatColor.GREEN + "Start " + event.getName(); + } + + @Override + public List getDescription(Player player) { + return Collections.singletonList(ChatColor.GRAY + "Click to start the event."); + } + + @Override + public Material getMaterial(Player player) { + return Material.EMERALD; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + for (Game game : GameQueue.INSTANCE.getGames()) { + if (game.getHost().equals(player)) { + player.sendMessage(ChatColor.RED + "You've already queued an event!"); + player.closeInventory(); + return; + } + } + + if (GameQueue.INSTANCE.size() > 9) { + player.sendMessage(ChatColor.RED + "The game queue is currently full! Try again later."); + } else { + List options = new ArrayList<>(); + + for (HostParameterButton hostParameterButton : buttons) { + options.add(hostParameterButton.getSelectedOption()); + } + + GameQueue.INSTANCE.add(new Game(event, player, options)); + player.sendMessage(ChatColor.GREEN + "You've added a " + event.getName().toLowerCase() + " event to the queue."); + } + + player.closeInventory(); + } + }); + + return toReturn; + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/parameter/GameParameter.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/parameter/GameParameter.java new file mode 100644 index 0000000..50182d1 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/parameter/GameParameter.java @@ -0,0 +1,8 @@ +package com.elevatemc.potpvp.events.parameter; + +import java.util.List; + +public interface GameParameter { + String getDisplayName(); + List getOptions(); +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/parameter/GameParameterOption.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/parameter/GameParameterOption.java new file mode 100644 index 0000000..68f584d --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/parameter/GameParameterOption.java @@ -0,0 +1,8 @@ +package com.elevatemc.potpvp.events.parameter; + +import org.bukkit.inventory.ItemStack; + +public interface GameParameterOption { + String getDisplayName(); + ItemStack getIcon(); +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/task/GameStartTask.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/task/GameStartTask.java new file mode 100644 index 0000000..cae305a --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/task/GameStartTask.java @@ -0,0 +1,74 @@ +package com.elevatemc.potpvp.events.task; + +import com.elevatemc.elib.util.TaskUtil; +import com.elevatemc.elib.util.TimeUtils; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.Arena; +import com.elevatemc.potpvp.events.bukkit.event.GameStateChangeEvent; +import com.elevatemc.potpvp.events.game.Game; +import com.elevatemc.potpvp.events.game.GameState; +import com.elevatemc.potpvp.events.parameter.GameParameterOption; +import com.elevatemc.potpvp.util.Color; +import com.elevatemc.potpvp.util.PatchedPlayerUtils; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +public class GameStartTask { + + private final long startsAt = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(45) - 5; + private final int interval = 15; + + private final PotPvPSI plugin; + private final Game game; + + public GameStartTask(PotPvPSI plugin, Game game) { + this.plugin = plugin; + this.game = game; + + final Optional arena = plugin.getArenaHandler().allocateUnusedArena(schem -> schem.getEvent() == game.getEvent() && schem.isEnabled()); + if(arena.isPresent()) { + Bukkit.getPluginManager().callEvent(new GameStateChangeEvent(game, GameState.STARTING)); + game.setStartingAt(startsAt); + game.setArena(arena.get()); + new Task(game).runTaskTimer(plugin, 0, interval * 20L); + } else { + Bukkit.getPluginManager().callEvent(new GameStateChangeEvent(game, GameState.ENDED)); + Bukkit.getOnlinePlayers().stream().filter(Player::isOp).forEach(player -> { + player.sendMessage(Color.translate("&cFailed to start " + game.getEvent().getName() + " due to lack of an arena.")); + }); + } + } + + private final class Task extends BukkitRunnable { + private final Game game; + + public Task(Game game) { + this.game = game; + } + + @Override + public void run() { + if(startsAt <= System.currentTimeMillis() || game.getPlayers().size() == game.getMaxPlayers()) { + TaskUtil.runSync(game::start); + cancel(); + return; + } + + Bukkit.getOnlinePlayers().forEach(player -> player.sendMessage(new String[]{"", + ChatColor.GRAY + "███████", + ChatColor.GRAY + "█" + ChatColor.DARK_AQUA + "█████" + ChatColor.GRAY + "█" + " " + ChatColor.DARK_AQUA + "[" + game.getEvent().getName() + " Event]", + ChatColor.GRAY + "█" + ChatColor.DARK_AQUA + "█" + ChatColor.GRAY + "█████" + " " + PatchedPlayerUtils.getFormattedName(game.getHost().getUniqueId()) + ChatColor.GRAY + " is hosting an event!", + ChatColor.GRAY + "█" + ChatColor.DARK_AQUA + "████" + ChatColor.GRAY + "██" + " " + ChatColor.GRAY + "Starts in " + ChatColor.AQUA + (TimeUtils.formatIntoDetailedString((int)((startsAt + 500 - System.currentTimeMillis()) / 1000))), + ChatColor.GRAY + "█" + ChatColor.DARK_AQUA + "█" + ChatColor.GRAY + "█████" + " " + ChatColor.GRAY + "Join with the " + ChatColor.AQUA + "diamond " + ChatColor.GRAY + "in your hotbar.", + ChatColor.GRAY + "█" + ChatColor.DARK_AQUA + "█████" + ChatColor.GRAY + "█" + " " + ChatColor.GRAY + ChatColor.ITALIC + "Event Type: (" + game.getParameters().stream().map(GameParameterOption::getDisplayName).collect(Collectors.joining(", ")) + ")", + ChatColor.GRAY + "███████", + ""})); + } + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/util/GameEventCountdown.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/util/GameEventCountdown.java new file mode 100644 index 0000000..6eadf15 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/util/GameEventCountdown.java @@ -0,0 +1,54 @@ +package com.elevatemc.potpvp.events.util; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.events.util.team.GameTeam; +import com.elevatemc.potpvp.util.Color; +import org.bukkit.Bukkit; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.List; + +public class GameEventCountdown { + + private int duration; + private final Runnable runnable; + private final List participants; + + public GameEventCountdown(int duration, Runnable runnable, List participants) { + this.duration = duration; + this.runnable = runnable; + this.participants = participants; + new Countdown().runTaskTimerAsynchronously(PotPvPSI.getInstance(), 0L, 20L); + } + + private final class Countdown extends BukkitRunnable { + + @Override + public void run() { + if(duration == -1) { + cancel(); + return; + } + + for(GameTeam participant : participants) { + for(Player player : participant.getPlayers()) { + if(duration > 0) { + player.sendMessage(Color.translate("&e" + duration + "...")); + player.playSound(player.getLocation(), Sound.NOTE_PLING, 1F, 1F); + } else { + player.sendMessage(Color.translate("&aMatch started.")); + player.playSound(player.getLocation(), Sound.NOTE_PLING, 1F, 2F); + } + } + } + + if(duration == 0) { + runnable.run(); + } + + duration--; + } + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/util/team/GameTeam.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/util/team/GameTeam.java new file mode 100644 index 0000000..9935cac --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/util/team/GameTeam.java @@ -0,0 +1,81 @@ +package com.elevatemc.potpvp.events.util.team; + +import com.elevatemc.elib.util.PlayerUtils; +import com.elevatemc.potpvp.util.PatchedPlayerUtils; +import lombok.Data; +import org.apache.commons.lang.StringUtils; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +@Data +public class GameTeam { + + private List players; + + private List died = new ArrayList<>(); + private int round = 0; + private int kills = 0; + private boolean fighting = false; + private boolean starting = false; + + public GameTeam(List players) { + this.players = players; + } + + public boolean isFinished() { + return died.size() == players.size(); + } + + public void died(Player player) { + if(!died.contains(player)) { + died.add(player); + } + } + + public boolean hasDied(Player player) { + return died.contains(player); + } + + public void reset() { + died.clear(); + fighting = false; + starting = false; + } + + public String getName() { + return StringUtils.join(players.stream().map(player -> PatchedPlayerUtils.getFormattedName(player.getUniqueId())).collect(Collectors.toList()), ChatColor.YELLOW + " + "); + } + + public int getPing() { + return PlayerUtils.getPing(players.get(0)); + } + + public List getOthers(Player player) { + return getPlayers() + .stream() + .filter(other -> !other.getUniqueId().equals(player.getUniqueId())) + .collect(Collectors.toList()); + } + + public void showTeamsPlayers(GameTeam team) { + getPlayers().forEach(thisPlayer -> team.getPlayers().forEach(thisPlayer::showPlayer)); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GameTeam gameTeam = (GameTeam) o; + return round == gameTeam.round && kills == gameTeam.kills && fighting == gameTeam.fighting && starting == gameTeam.starting && Objects.equals(players, gameTeam.players) && Objects.equals(died, gameTeam.died); + } + + @Override + public int hashCode() { + return Objects.hash(players); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/util/team/GameTeamEventLogic.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/util/team/GameTeamEventLogic.java new file mode 100644 index 0000000..3bc7d4a --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/util/team/GameTeamEventLogic.java @@ -0,0 +1,89 @@ +package com.elevatemc.potpvp.events.util.team; + +import com.elevatemc.potpvp.events.event.GameEventLogic; +import com.elevatemc.potpvp.events.event.impl.lms.LastManStandingGameEvent; +import com.elevatemc.potpvp.events.game.Game; +import com.elevatemc.potpvp.util.Color; +import com.elevatemc.potpvp.util.PatchedPlayerUtils; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import lombok.Getter; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +@Getter +public class GameTeamEventLogic implements GameEventLogic { + + protected final Game game; + + protected Map invites = Maps.newHashMap(); + protected Set participants = Sets.newHashSet(); + + public GameTeamEventLogic(Game game) { + this.game = game; + } + + @Override + public void start() { + if(game.getParameter(GameTeamSizeParameter.class) != null) { + generateTeams(); + } else { + for(Player player : game.getPlayers()) { + participants.add(new GameTeam(ImmutableList.of(player))); + } + } + + invites.clear(); + } + + private void generateTeams() { + for(Player player : game.getPlayers()) { + if(!contains(player)) { + for(Player other : game.getPlayers()) { + if(player != other && !contains(other)) { + player.sendMessage(Color.translate("&eYou were automatically put into a team with &d" + PatchedPlayerUtils.getFormattedName(other.getUniqueId()) + "&e.")); + other.sendMessage(Color.translate("&eYou were automatically put into a team with &d" + PatchedPlayerUtils.getFormattedName(player.getUniqueId()) + "&e.")); + + final GameTeam team = new GameTeam(ImmutableList.of(player, other)); + participants.add(team); + + continue; + } + } + + // TODO: lms condition + if(game.getEvent() instanceof LastManStandingGameEvent) { + final GameTeam team = new GameTeam(ImmutableList.of(player)); + participants.add(team); + } else { + player.sendMessage(Color.translate("&cWe couldn't find a player for you to team up with, so you were sent to the lobby.")); + game.addSpectator(player); + } + } + } + } + + public GameTeam get(Player player) { + for(GameTeam participant : participants) { + if(participant.getPlayers().contains(player)) { + return participant; + } + } + + return null; + } + + public boolean contains(Player player) { + for(GameTeam participant : participants) { + if(participant.getPlayers().contains(player)) { + return true; + } + } + return false; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/util/team/GameTeamSizeParameter.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/util/team/GameTeamSizeParameter.java new file mode 100644 index 0000000..033be60 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/events/util/team/GameTeamSizeParameter.java @@ -0,0 +1,60 @@ +package com.elevatemc.potpvp.events.util.team; + +import com.elevatemc.potpvp.events.parameter.GameParameter; +import com.elevatemc.potpvp.events.parameter.GameParameterOption; +import com.google.common.collect.ImmutableList; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +public class GameTeamSizeParameter implements GameParameter { + + private static final String DISPLAY_NAME = "Team Size"; + private static final List OPTIONS = ImmutableList.of( + new Singles() +// new Duos() + ); + + @Override + public String getDisplayName() { + return DISPLAY_NAME; + } + + @Override + public List getOptions() { + return OPTIONS; + } + + public static final class Singles implements GameParameterOption { + + private static final String DISPLAY_NAME = "1v1"; + private static final ItemStack ICON = new ItemStack(Material.DIAMOND_HELMET); + + @Override + public String getDisplayName() { + return DISPLAY_NAME; + } + + @Override + public ItemStack getIcon() { + return ICON; + } + } + + public static final class Duos implements GameParameterOption { + + private static final String DISPLAY_NAME = "2v2"; + private static final ItemStack ICON = new ItemStack(Material.DIAMOND_HELMET, 2); + + @Override + public String getDisplayName() { + return DISPLAY_NAME; + } + + @Override + public ItemStack getIcon() { + return ICON; + } + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/follow/FollowHandler.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/follow/FollowHandler.java new file mode 100644 index 0000000..6ed6b46 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/follow/FollowHandler.java @@ -0,0 +1,74 @@ +package com.elevatemc.potpvp.follow; + + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.follow.listener.FollowGeneralListener; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.match.MatchState; +import com.elevatemc.potpvp.util.InventoryUtils; +import com.elevatemc.potpvp.util.VisibilityUtils; +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.util.UUIDUtils; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public final class FollowHandler { + + // (follower -> target) + private final Map followingData = new ConcurrentHashMap<>(); + + public FollowHandler() { + Bukkit.getPluginManager().registerEvents(new FollowGeneralListener(this), PotPvPSI.getInstance()); + } + + public Optional getFollowing(Player player) { + return Optional.ofNullable(followingData.get(player.getUniqueId())); + } + + public void startFollowing(Player player, Player target) { + followingData.put(player.getUniqueId(), target.getUniqueId()); + player.sendMessage(ChatColor.YELLOW + "You are now following " + ChatColor.GOLD + target.getName() + ChatColor.YELLOW + "."); + + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match targetMatch = matchHandler.getMatchPlayingOrSpectating(target); + + if (targetMatch != null && targetMatch.getState() != MatchState.ENDING) { + targetMatch.addSpectator(player, target); + } else { + InventoryUtils.resetInventoryDelayed(player); + VisibilityUtils.updateVisibility(player); + eLib.getInstance().getNameTagHandler().reloadOthersFor(player); + + player.teleport(target); + } + } + + public void stopFollowing(Player player) { + UUID prevTarget = followingData.remove(player.getUniqueId()); + + if (prevTarget != null) { + player.sendMessage(ChatColor.YELLOW + "You are no longer following " + ChatColor.GOLD + UUIDUtils.name(prevTarget) + ChatColor.YELLOW + "."); + InventoryUtils.resetInventoryDelayed(player); + VisibilityUtils.updateVisibility(player); + eLib.getInstance().getNameTagHandler().reloadOthersFor(player); + } + } + + public Set getFollowers(Player player) { + Set followers = new HashSet<>(); + + followingData.forEach((follower, followed) -> { + if (followed == player.getUniqueId()) { + followers.add(follower); + } + }); + + return followers; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/follow/command/FollowCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/follow/command/FollowCommand.java new file mode 100644 index 0000000..0d62a65 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/follow/command/FollowCommand.java @@ -0,0 +1,46 @@ +package com.elevatemc.potpvp.follow.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.follow.FollowHandler; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.setting.Setting; +import com.elevatemc.potpvp.setting.SettingHandler; +import com.elevatemc.potpvp.validation.PotPvPValidation; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class FollowCommand { + + @Command(names={"follow"}, permission="") + public static void follow(Player sender, @Parameter(name="target") Player target) { + if (!PotPvPValidation.canFollowSomeone(sender)) { + return; + } + + FollowHandler followHandler = PotPvPSI.getInstance().getFollowHandler(); + SettingHandler settingHandler = PotPvPSI.getInstance().getSettingHandler(); + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + if (sender == target) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You cannot follow yourself."); + return; + } else if (!settingHandler.getSetting(target, Setting.ALLOW_SPECTATORS)) { + if (sender.isOp()) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Bypassing " + target.getName() + "'s no spectators preference."); + } else { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + target.getName() + " has their spectators setting turned off right now."); + return; + } + } + + followHandler.getFollowing(sender).ifPresent(fo -> UnfollowCommand.unfollow(sender)); + + if (matchHandler.isSpectatingMatch(sender)) { + matchHandler.getMatchSpectating(sender).removeSpectator(sender); + } + + followHandler.startFollowing(sender, target); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/follow/command/SilentFollowCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/follow/command/SilentFollowCommand.java new file mode 100644 index 0000000..d1216b4 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/follow/command/SilentFollowCommand.java @@ -0,0 +1,23 @@ +package com.elevatemc.potpvp.follow.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.command.LeaveCommand; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import org.bukkit.entity.Player; +import org.bukkit.metadata.FixedMetadataValue; + +public class SilentFollowCommand { + + @Command(names = {"sfollow", "sf", "silentfollow"}, permission = "core.staffteam") + public static void silentfollow(Player sender, @Parameter(name = "target") Player target) { + sender.setMetadata("modmode", new FixedMetadataValue(PotPvPSI.getInstance(), true)); + sender.setMetadata("invisible", new FixedMetadataValue(PotPvPSI.getInstance(), true)); + + if (PotPvPSI.getInstance().getPartyHandler().hasParty(sender)) { + LeaveCommand.leave(sender); + } + + FollowCommand.follow(sender, target); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/follow/command/UnfollowCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/follow/command/UnfollowCommand.java new file mode 100644 index 0000000..75eb47b --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/follow/command/UnfollowCommand.java @@ -0,0 +1,31 @@ +package com.elevatemc.potpvp.follow.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.follow.FollowHandler; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.elib.command.Command; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class UnfollowCommand { + + @Command(names={"unfollow"}, permission="") + public static void unfollow(Player sender) { + FollowHandler followHandler = PotPvPSI.getInstance().getFollowHandler(); + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + if (!followHandler.getFollowing(sender).isPresent()) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You're not following anybody."); + return; + } + + Match spectating = matchHandler.getMatchSpectating(sender); + + if (spectating != null) { + spectating.removeSpectator(sender); + } + + followHandler.stopFollowing(sender); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/follow/listener/FollowGeneralListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/follow/listener/FollowGeneralListener.java new file mode 100644 index 0000000..aa10e25 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/follow/listener/FollowGeneralListener.java @@ -0,0 +1,75 @@ +package com.elevatemc.potpvp.follow.listener; + +import com.elevatemc.potpvp.follow.FollowHandler; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.match.event.MatchCountdownStartEvent; +import com.elevatemc.potpvp.match.event.MatchSpectatorLeaveEvent; +import com.elevatemc.potpvp.setting.Setting; +import com.elevatemc.potpvp.setting.event.SettingUpdateEvent; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.UUID; + +public final class FollowGeneralListener implements Listener { + + private final FollowHandler followHandler; + + public FollowGeneralListener(FollowHandler followHandler) { + this.followHandler = followHandler; + } + + @EventHandler + public void onMatchStart(MatchCountdownStartEvent event) { + Match match = event.getMatch(); + + for (MatchTeam team : match.getTeams()) { + for (UUID member : team.getAllMembers()) { + Player memberBukkit = Bukkit.getPlayer(member); + + for (UUID follower : followHandler.getFollowers(memberBukkit)) { + match.addSpectator(Bukkit.getPlayer(follower), memberBukkit); + } + } + } + } + @EventHandler + public void onMatchSpectatorLeave(MatchSpectatorLeaveEvent event) { + // leaving while spectating a match counts as typing /unfollow + followHandler.stopFollowing(event.getSpectator()); + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + // can't follow an offline player + for (UUID follower : followHandler.getFollowers(event.getPlayer())) { + followHandler.stopFollowing(Bukkit.getPlayer(follower)); + } + + // garbage collects players who leave + followHandler.stopFollowing(event.getPlayer()); + } + + @EventHandler + public void onSettingUpdate(SettingUpdateEvent event) { + if (event.getSetting() != Setting.ALLOW_SPECTATORS || event.isEnabled()) { + return; + } + + // can't follow a player who doesn't allow spectators + for (UUID follower : followHandler.getFollowers(event.getPlayer())) { + Player followerPlayer = Bukkit.getPlayer(follower); + + if (followerPlayer.isOp() || followerPlayer.hasMetadata("modmode")) { + continue; + } + + followHandler.stopFollowing(followerPlayer); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/GameMode.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/GameMode.java new file mode 100644 index 0000000..a1d857a --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/GameMode.java @@ -0,0 +1,34 @@ +package com.elevatemc.potpvp.gamemode; + +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import lombok.Getter; +import org.bukkit.material.MaterialData; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public abstract class GameMode { + public abstract String getName(); + public abstract String getDescription(); + public abstract MaterialData getIcon(); + public abstract HealingMethod getHealingMethod(); + public abstract List getKits(); + public abstract boolean getBuildingAllowed(); + public abstract boolean getHealthShown(); + public abstract boolean getHardcoreHealing(); + public abstract boolean getPearlDamage(); + public abstract boolean getSupportsCompetitive(); + + public String getId() { + return getName().toUpperCase().replaceAll(" ", "_"); + } + + public boolean isVoidTeleport() { + return true; + } + + @Getter + private static final ArrayList all = new ArrayList<>(); +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/GameModes.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/GameModes.java new file mode 100644 index 0000000..ecc1198 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/GameModes.java @@ -0,0 +1,73 @@ +package com.elevatemc.potpvp.gamemode; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.ArenaSchematic; +import com.elevatemc.potpvp.gamemodes.archer.Archer; +import com.elevatemc.potpvp.gamemodes.bedfight.BedFight; +import com.elevatemc.potpvp.gamemodes.boxing.Boxing; +import com.elevatemc.potpvp.gamemodes.bridges.Bridges; +import com.elevatemc.potpvp.gamemodes.builduhc.BuildUHC; +import com.elevatemc.potpvp.gamemodes.combo.Combo; +import com.elevatemc.potpvp.gamemodes.debuff.Debuff; +import com.elevatemc.potpvp.gamemodes.gapple.Gapple; +import com.elevatemc.potpvp.gamemodes.invaded.Invaded; +import com.elevatemc.potpvp.gamemodes.nodebuff.NoDebuff; +import com.elevatemc.potpvp.gamemodes.pearlfight.PearlFight; +import com.elevatemc.potpvp.gamemodes.skywars.Skywars; +import com.elevatemc.potpvp.gamemodes.sotw.SOTW; +import com.elevatemc.potpvp.gamemodes.soup.SoupPVP; +import com.elevatemc.potpvp.gamemodes.spleef.Spleef; +import com.elevatemc.potpvp.gamemodes.sumo.Sumo; +import com.elevatemc.potpvp.gamemodes.teamfight.Teamfight; +import com.elevatemc.potpvp.gamemodes.teamfight.TeamfightDebuff; +import com.elevatemc.potpvp.gamemodes.trapping.Trapping; +import com.elevatemc.potpvp.gamemodes.vanilla.Vanilla; + +public class GameModes { + public static final NoDebuff NO_DEBUFF = new NoDebuff(); + public static final Debuff DEBUFF = new Debuff(); + public static final Archer ARCHER = new Archer(); + public static final Boxing BOXING = new Boxing(); + public static final BedFight BED_FIGHT = new BedFight(); + public static final Gapple GAPPLE = new Gapple(); + public static final Invaded INVADED = new Invaded(); + public static final Skywars SKYWARS = new Skywars(); + public static final SOTW SOTW = new SOTW(); + public static final BuildUHC BUILD_UHC = new BuildUHC(); + + public static final SoupPVP SOUP_PVP = new SoupPVP(); + public static final Combo COMBO = new Combo(); + public static final Spleef SPLEEF = new Spleef(); + public static final Sumo SUMO = new Sumo(); + public static final Teamfight TEAMFIGHT = new Teamfight(); + public static final TeamfightDebuff TEAMFIGHT_DEBUFF = new TeamfightDebuff(); + public static final Trapping TRAPPING = new Trapping(); + public static final Vanilla VANILLA = new Vanilla(); + public static final Bridges BRIDGES = new Bridges(); + public static final PearlFight PEARL_FIGHT = new PearlFight(); + + public static void loadGameModes() { + GameMode.getAll().add(NO_DEBUFF); + GameMode.getAll().add(DEBUFF); + GameMode.getAll().add(TRAPPING); + GameMode.getAll().add(PEARL_FIGHT); + GameMode.getAll().add(BED_FIGHT); + GameMode.getAll().add(BOXING); +// GameMode.getAll().add(BRIDGES); + GameMode.getAll().add(SUMO); + GameMode.getAll().add(INVADED); + GameMode.getAll().add(GAPPLE); + GameMode.getAll().add(VANILLA); + GameMode.getAll().add(SOTW); + GameMode.getAll().add(SKYWARS); + GameMode.getAll().add(SPLEEF); + GameMode.getAll().add(TEAMFIGHT); + GameMode.getAll().add(TEAMFIGHT_DEBUFF); + GameMode.getAll().add(BUILD_UHC); +// GameMode.getAll().add(COMBO); + GameMode.getAll().add(SOUP_PVP); + + GameMode.getAll().forEach(PotPvPSI.getInstance().getQueueHandler()::addQueues); + System.out.println("All game-modes were loaded!"); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/HealingMethod.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/HealingMethod.java new file mode 100644 index 0000000..d135add --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/HealingMethod.java @@ -0,0 +1,67 @@ +package com.elevatemc.potpvp.gamemode; + +import lombok.Getter; +import com.elevatemc.potpvp.util.ItemUtils; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import java.util.function.Function; + +public enum HealingMethod { + + POTIONS( + "pot", "pots", // short singular/plural + "health potion", "health potions", // long singular/plural + Material.POTION, + (short) 16421, // data for splash healing II pot + i -> ItemUtils.countStacksMatching(i, ItemUtils.INSTANT_HEAL_POTION_PREDICATE) + ), + GOLDEN_APPLE( + "gap", "gaps", // short singular/plural + "golden apple", "golden apples", // long singular/plural + Material.GOLDEN_APPLE, + (short) 1, // data for enchanted golden apple + items -> { + int count = 0; + + for (ItemStack item : items) { + if (item != null && item.getType() == Material.GOLDEN_APPLE && item.getData().getData() == (byte) 1) { + count += Math.max(1, item.getAmount()); + } + } + + return count; + } + ), + SOUP( + "soup", "soup", // short singular/plural + "soup", "soup", // long singular/plural + Material.MUSHROOM_SOUP, + (short) 0, + i -> ItemUtils.countStacksMatching(i, ItemUtils.SOUP_PREDICATE) + ); + + @Getter private final String shortSingular; + @Getter private final String shortPlural; + @Getter private final String longSingular; + @Getter private final String longPlural; + + @Getter private final Material iconType; + @Getter private final short iconDurability; + private final Function countFunction; + + HealingMethod(String shortSingular, String shortPlural, String longSingular, String longPlural, Material iconType, short iconDurability, Function countFunction) { + this.shortSingular = shortSingular; + this.shortPlural = shortPlural; + this.longSingular = longSingular; + this.longPlural = longPlural; + this.iconType = iconType; + this.iconDurability = iconDurability; + this.countFunction = countFunction; + } + + public int count(ItemStack[] items) { + return countFunction.apply(items); + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/kit/GameModeKit.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/kit/GameModeKit.java new file mode 100644 index 0000000..eca66c0 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/kit/GameModeKit.java @@ -0,0 +1,93 @@ +package com.elevatemc.potpvp.gamemode.kit; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.util.MongoUtils; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.Filters; +import lombok.Getter; +import lombok.Setter; +import org.bson.Document; +import org.bukkit.Bukkit; +import com.google.gson.annotations.SerializedName; +import org.bukkit.inventory.ItemStack; +import org.bukkit.material.MaterialData; + +import java.util.HashSet; +import java.util.Set; + +public class GameModeKit { + private static final String MONGO_COLLECTION_NAME = "defaultKits"; + + @Getter + private static final Set allKits = new HashSet<>(); + + public static GameModeKit fetchById(String id) { + Document document = MongoUtils.getCollection(MONGO_COLLECTION_NAME).find(Filters.eq("_id", id)).first(); + if (document != null) { + return PotPvPSI.getPLAIN_GSON().fromJson(document.toJson(), GameModeKit.class); + } else { + return null; + } + } + + public static GameModeKit createFromGameMode(GameMode gameMode) { + return new GameModeKit(gameMode.getName().toUpperCase().replaceAll(" ", "_"), gameMode.getName(), gameMode.getIcon()); + } + + public static GameModeKit byId(String id) { + for (GameModeKit kit : allKits) { + if (kit.getId().equalsIgnoreCase(id)) { + return kit; + } + } + + return null; + } + + @SerializedName("_id") private String id; + + @Getter private final String displayName; + + @Getter private final MaterialData icon; + + @Getter @Setter + private ItemStack[] defaultArmor = new ItemStack[0]; + + @Getter @Setter + private ItemStack[] defaultInventory = new ItemStack[0]; + + @Getter @Setter private ItemStack[] editorItems = new ItemStack[0]; + + public GameModeKit(String id, String displayName, MaterialData icon) { + GameModeKit saved = fetchById(id); + this.id = id; + this.displayName = displayName; + this.icon = icon; + if (saved != null) { + this.defaultArmor = saved.defaultArmor; + this.defaultInventory = saved.defaultInventory; + this.editorItems = saved.editorItems; + } else { + this.saveAsync(); + } + allKits.add(this); + } + + public String getId() { + return id.toUpperCase(); + } + + public void saveAsync() { + Bukkit.getScheduler().runTaskAsynchronously(PotPvPSI.getInstance(), () -> { + MongoCollection collection = MongoUtils.getCollection(MONGO_COLLECTION_NAME); + Document gameModeDoc = Document.parse(PotPvPSI.getPLAIN_GSON().toJson(this)); + gameModeDoc.remove("_id"); // upserts with an _id field is weird. + + Document query = new Document("_id", id); + Document kitUpdate = new Document("$set", gameModeDoc); + + collection.updateOne(query, kitUpdate, MongoUtils.UPSERT_OPTIONS); + }); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/kit/GameModeKitJsonAdapter.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/kit/GameModeKitJsonAdapter.java new file mode 100644 index 0000000..e30cce4 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/kit/GameModeKitJsonAdapter.java @@ -0,0 +1,20 @@ +package com.elevatemc.potpvp.gamemode.kit; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; + +public final class GameModeKitJsonAdapter extends TypeAdapter { + + @Override + public void write(JsonWriter writer, GameModeKit kit) throws IOException { + writer.value(kit.getId()); + } + + @Override + public GameModeKit read(JsonReader reader) throws IOException { + return GameModeKit.byId(reader.nextString()); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/kit/GameModeKitParameterType.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/kit/GameModeKitParameterType.java new file mode 100644 index 0000000..d803751 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/kit/GameModeKitParameterType.java @@ -0,0 +1,41 @@ +package com.elevatemc.potpvp.gamemode.kit; + +import com.elevatemc.elib.command.param.ParameterType; +import com.elevatemc.potpvp.gamemode.GameMode; +import org.apache.commons.lang3.StringUtils; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public final class GameModeKitParameterType implements ParameterType { + + @Override + public GameModeKit transform(CommandSender sender, String source) { + for (GameModeKit kit : GameModeKit.getAllKits()) { + if (kit.getId().equalsIgnoreCase(source)) { + return kit; + } + } + + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "No kit with the name " + source + " found."); + return null; + } + + @Override + public List tabComplete(Player player, Set flags, String source) { + List completions = new ArrayList<>(); + + for (GameModeKit gameMode : GameModeKit.getAllKits()) { + if (StringUtils.startsWithIgnoreCase(gameMode.getId(), source)) { + completions.add(gameMode.getId()); + } + } + + return completions; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/kit/command/KitLoadDefaultCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/kit/command/KitLoadDefaultCommand.java new file mode 100644 index 0000000..650f994 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/kit/command/KitLoadDefaultCommand.java @@ -0,0 +1,20 @@ +package com.elevatemc.potpvp.gamemode.kit.command; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class KitLoadDefaultCommand { + + @Command(names = "kit loaddefault", permission = "op") + public static void kitLoadDefault(Player sender, @Parameter(name="gamemode kit") GameModeKit kit) { + sender.getInventory().setArmorContents(kit.getDefaultArmor()); + sender.getInventory().setContents(kit.getDefaultInventory()); + sender.updateInventory(); + + sender.sendMessage(ChatColor.YELLOW + "Loaded default armor/inventory for " + kit.getId() + "."); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/kit/command/KitSaveDefaultCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/kit/command/KitSaveDefaultCommand.java new file mode 100644 index 0000000..268155a --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/kit/command/KitSaveDefaultCommand.java @@ -0,0 +1,20 @@ +package com.elevatemc.potpvp.gamemode.kit.command; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class KitSaveDefaultCommand { + + @Command(names = "kit savedefault", permission = "op") + public static void kitSaveDefault(Player sender, @Parameter(name="gamemode kit") GameModeKit kit) { + kit.setDefaultArmor(sender.getInventory().getArmorContents()); + kit.setDefaultInventory(sender.getInventory().getContents()); + kit.saveAsync(); + + sender.sendMessage(ChatColor.YELLOW + "Saved default armor/inventory for " + kit.getId() + "."); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/kit/command/KitSetEditorItemsCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/kit/command/KitSetEditorItemsCommand.java new file mode 100644 index 0000000..d368577 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/kit/command/KitSetEditorItemsCommand.java @@ -0,0 +1,29 @@ +package com.elevatemc.potpvp.gamemode.kit.command; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; + +public final class KitSetEditorItemsCommand { + + @Command(names = "kit seteditoritems", permission = "op") + public static void kitSaveDefault(Player sender, @Parameter(name="gamemode kit") GameModeKit kit) { + ArrayList editorItems = new ArrayList<>(); + for (ItemStack item : sender.getInventory().getContents()) { + if (item != null && !item.getType().equals(Material.AIR)) { + editorItems.add(item); + } + } + kit.setEditorItems(editorItems.toArray(new ItemStack[0])); + kit.saveAsync(); + + sender.sendMessage(ChatColor.YELLOW + "Saved default editor items for " + kit.getId() + "."); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/menu/editing/EditGameModeButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/menu/editing/EditGameModeButton.java new file mode 100644 index 0000000..36454bf --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/menu/editing/EditGameModeButton.java @@ -0,0 +1,67 @@ +package com.elevatemc.potpvp.gamemode.menu.editing; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.elevatemc.potpvp.gamemode.menu.extra.SelectNoDebuffOrDebuff; +import com.google.common.base.Preconditions; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.util.Callback; +import org.apache.commons.lang.StringEscapeUtils; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.Collections; +import java.util.List; + +public class EditGameModeButton extends Button { + + private final GameMode gameMode; + private final Callback callback; + + public EditGameModeButton(GameMode gameMode, Callback callback) { + this.gameMode = Preconditions.checkNotNull(gameMode, "gameMode"); + this.callback = Preconditions.checkNotNull(callback, "callback"); + } + + @Override + public String getName(Player player) { + return ChatColor.DARK_AQUA + gameMode.getName(); + } + + @Override + public List getDescription(Player player) { + if (gameMode.getKits().size() > 1) { + return Collections.singletonList(ChatColor.DARK_AQUA + "❘" + ChatColor.WHITE + " Click here to edit one of the " + gameMode.getName() + " kits"); + } else { + return Collections.singletonList(ChatColor.DARK_AQUA + "❘" + ChatColor.WHITE + " Click here to edit the " + gameMode.getName() + " kit"); + } + } + + @Override + public Material getMaterial(Player player) { + return gameMode.getIcon().getItemType(); + } + + @Override + public byte getDamageValue(Player player) { + return gameMode.getIcon().getData(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + if (gameMode.equals(GameModes.TEAMFIGHT)) { + new SelectNoDebuffOrDebuff(isDebuff -> { + if (isDebuff) { + callback.callback(gameMode); + } else { + callback.callback(gameMode); + } + }).openMenu(player); + } else { + callback.callback(gameMode); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/menu/editing/EditGameModeKitMenu.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/menu/editing/EditGameModeKitMenu.java new file mode 100644 index 0000000..746cfa2 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/menu/editing/EditGameModeKitMenu.java @@ -0,0 +1,54 @@ +package com.elevatemc.potpvp.gamemode.menu.editing; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import com.elevatemc.elib.util.Callback; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import com.google.common.base.Preconditions; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; + +public class EditGameModeKitMenu extends Menu { + + private final GameMode gameMode; + private final Callback callback; + + public EditGameModeKitMenu(Callback callback, GameMode gameMode) { + this.gameMode = gameMode; + this.callback = Preconditions.checkNotNull(callback, "callback"); + + setPlaceholder(true); + } + + @Override + public String getTitle(Player player) { + return ChatColor.BLUE + "Kit Editor"; + } + + @Override + public Map getButtons(Player p0) { + Map buttons = new HashMap<>(); + + int index = 9; + + + for (GameModeKit gameModeKit : gameMode.getKits()) { + index++; + if ((index + 1) % 9 == 0) index++; + if (index % 9 == 0) index++; + + buttons.put(index, new GameModeKitButton(gameModeKit, callback)); + } + + return buttons; + } + + @Override + public int size(Player player) { + return ((int)Math.ceil(((float)getButtons(player).size() / 7)) + 2) * 9; + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/menu/editing/EditGameModeMenu.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/menu/editing/EditGameModeMenu.java new file mode 100644 index 0000000..fae49c0 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/menu/editing/EditGameModeMenu.java @@ -0,0 +1,57 @@ +package com.elevatemc.potpvp.gamemode.menu.editing; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.google.common.base.Preconditions; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import com.elevatemc.elib.util.Callback; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; + +public final class EditGameModeMenu extends Menu { + + private final Callback callback; + + public EditGameModeMenu(Callback callback) { + this.callback = Preconditions.checkNotNull(callback, "callback"); + + setPlaceholder(true); + } + + @Override + public String getTitle(Player player) { + return ChatColor.BLUE + "Kit Editor"; + } + + @Override + public Map getButtons(Player player) { + Map buttons = new HashMap<>(); + + int index = 9; + + + for (GameMode gameMode : GameMode.getAll()) { + if (gameMode.getKits().isEmpty() || gameMode.equals(GameModes.TEAMFIGHT_DEBUFF)) { + continue; + } + + index++; + if ((index + 1) % 9 == 0) index++; + if (index % 9 == 0) index++; + + buttons.put(index, new EditGameModeButton(gameMode, callback)); + } + + return buttons; + } + + @Override + public int size(Player player) { + Map buttons = getButtons(player); + return ((int)Math.ceil(((float)buttons.size() / 7)) + 2) * 9; + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/menu/editing/GameModeKitButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/menu/editing/GameModeKitButton.java new file mode 100644 index 0000000..782a43f --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/menu/editing/GameModeKitButton.java @@ -0,0 +1,47 @@ +package com.elevatemc.potpvp.gamemode.menu.editing; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.util.Callback; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.Collections; +import java.util.List; + +public class GameModeKitButton extends Button { + private final GameModeKit kit; + private final Callback callback; + + public GameModeKitButton(GameModeKit kit, Callback callback) { + this.kit = kit; + this.callback = callback; + } + + @Override + public String getName(Player player) { + return ChatColor.AQUA + kit.getDisplayName(); + } + + @Override + public List getDescription(Player paramPlayer) { + return Collections.singletonList(ChatColor.GRAY + "Click here to edit this kit"); + } + + @Override + public Material getMaterial(Player player) { + return kit.getIcon().getItemType(); + } + + @Override + public byte getDamageValue(Player player) { + return kit.getIcon().getData(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + callback.callback(kit); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/menu/extra/SelectNoDebuffOrDebuff.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/menu/extra/SelectNoDebuffOrDebuff.java new file mode 100644 index 0000000..1b698c3 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/menu/extra/SelectNoDebuffOrDebuff.java @@ -0,0 +1,104 @@ +package com.elevatemc.potpvp.gamemode.menu.extra; + +import com.google.common.base.Preconditions; +import com.elevatemc.potpvp.util.Color; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import com.elevatemc.elib.util.Callback; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; + +import java.util.*; + +public final class SelectNoDebuffOrDebuff extends Menu { + + private final Callback callback; + + public SelectNoDebuffOrDebuff(Callback callback) { + + this.callback = Preconditions.checkNotNull(callback, "callback"); + setPlaceholder(true); + } + + @Override + public Map getButtons(Player player) { + Map buttons = new HashMap<>(); + + buttons.put(getSlot(2, 1), new Button() { + @Override + public String getName(Player player) { + return Color.translate("&bNo Debuff"); + } + + @Override + public List getDescription(Player player) { + return Collections.singletonList(ChatColor.GRAY + "Select this for the regular teamfight kit."); + } + + @Override + public Material getMaterial(Player player) { + return Material.POTION; + } + + @Override + public ItemStack getButtonItem(Player player) { + ItemStack superItem = super.getButtonItem(player); + + superItem.setDurability((short) 16421); + + return superItem; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + callback.callback(false); + } + }); + + buttons.put(getSlot(6, 1), new Button() { + @Override + public String getName(Player player) { + return Color.translate("&bDebuff"); + } + + @Override + public List getDescription(Player player) { + return Collections.singletonList(ChatColor.GRAY + "Select this for the debuff teamfight kit."); + } + + @Override + public Material getMaterial(Player player) { + return Material.POTION; + } + + @Override + public ItemStack getButtonItem(Player player) { + ItemStack superItem = super.getButtonItem(player); + + superItem.setDurability((short) 16388); + + return superItem; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + callback.callback(true); + } + }); + + return buttons; + } + + @Override + public int size(Player buttons) { + return 9 * 3; + } + + @Override + public String getTitle(Player player) { + return ChatColor.BLUE + "Select No Debuff or Debuff"; + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/menu/queue/QueueGameModeButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/menu/queue/QueueGameModeButton.java new file mode 100644 index 0000000..75a6d17 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/menu/queue/QueueGameModeButton.java @@ -0,0 +1,80 @@ +package com.elevatemc.potpvp.gamemode.menu.queue; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.elevatemc.potpvp.gamemode.menu.extra.SelectNoDebuffOrDebuff; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.queue.QueueHandler; +import com.google.common.base.Preconditions; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.util.Callback; +import com.google.common.collect.ImmutableList; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.Arrays; +import java.util.List; + +public final class QueueGameModeButton extends Button { + + private final GameMode gameMode; + private final Callback callback; + private final boolean competitive; + + public QueueGameModeButton(GameMode gameMode, boolean competitive, Callback callback) { + this.gameMode = Preconditions.checkNotNull(gameMode, "gameMode"); + this.competitive = Preconditions.checkNotNull(competitive, "competitive"); + this.callback = Preconditions.checkNotNull(callback, "callback"); + } + + @Override + public String getName(Player player) { + return ChatColor.DARK_AQUA + gameMode.getName(); + } + + @Override + public List getDescription(Player player) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + QueueHandler queueHandler = PotPvPSI.getInstance().getQueueHandler(); + int inFightsRanked = matchHandler.countPlayersPlayingMatches(m -> m.getGameMode() == gameMode && m.isRanked()); + int inQueueRanked = queueHandler.countPlayersQueued(gameMode, true); + + int inFightsUnranked = matchHandler.countPlayersPlayingMatches(m -> m.getGameMode() == gameMode && !m.isRanked()); + int inQueueUnranked = queueHandler.countPlayersQueued(gameMode, false); + + return ImmutableList.of( + ChatColor.DARK_AQUA + "┃ " + ChatColor.WHITE + "In Fights: " + ChatColor.DARK_AQUA + (competitive ? inFightsRanked : inFightsUnranked), + ChatColor.DARK_AQUA + "┃ " + ChatColor.WHITE + "In Queue: " + ChatColor.DARK_AQUA + (competitive ? inQueueRanked : inQueueUnranked), + "", + ChatColor.AQUA + "⇨ " + ChatColor.WHITE + "Click this to join the " + gameMode.getName() + " queue"); + + } + + @Override + public Material getMaterial(Player player) { + return gameMode.getIcon().getItemType(); + } + + @Override + public byte getDamageValue(Player player) { + return gameMode.getIcon().getData(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + if (gameMode.equals(GameModes.TEAMFIGHT)) { + new SelectNoDebuffOrDebuff(isDebuff -> { + if (isDebuff) { + callback.callback(gameMode); + } else { + callback.callback(gameMode); + } + }).openMenu(player); + } else { + callback.callback(gameMode); + } + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/menu/queue/QueueGameModeMenu.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/menu/queue/QueueGameModeMenu.java new file mode 100644 index 0000000..2bb6f7d --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/menu/queue/QueueGameModeMenu.java @@ -0,0 +1,60 @@ +package com.elevatemc.potpvp.gamemode.menu.queue; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.google.common.base.Preconditions; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import com.elevatemc.elib.util.Callback; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; + +public final class QueueGameModeMenu extends Menu { + + private final Callback callback; + private final boolean competitive; + + public QueueGameModeMenu(Callback callback, boolean competitive) { + this.callback = Preconditions.checkNotNull(callback, "callback"); + this.competitive = competitive; + + setPlaceholder(true); + setAutoUpdate(true); + } + + @Override + public String getTitle(Player player) { + return ChatColor.BLUE + "Play " + (competitive ? "Competitive" : "Casual"); + } + + @Override + public Map getButtons(Player player) { + Map buttons = new HashMap<>(); + + int index = 9; + + + for (GameMode gameMode : GameMode.getAll()) { + if (gameMode.equals(GameModes.TEAMFIGHT)) { + continue; + } + + index++; + if ((index + 1) % 9 == 0) index++; + if (index % 9 == 0) index++; + + buttons.put(index, new QueueGameModeButton(gameMode, competitive, callback)); + } + + return buttons; + } + + @Override + public int size(Player player) { + Map buttons = getButtons(player); + return ((int)Math.ceil(((float)buttons.size() / 7)) + 2) * 9; + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/menu/select/SelectGameModeButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/menu/select/SelectGameModeButton.java new file mode 100644 index 0000000..1c5dfc3 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/menu/select/SelectGameModeButton.java @@ -0,0 +1,62 @@ +package com.elevatemc.potpvp.gamemode.menu.select; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.elevatemc.potpvp.gamemode.menu.extra.SelectNoDebuffOrDebuff; +import com.google.common.base.Preconditions; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.util.Callback; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.Collections; +import java.util.List; + +final class SelectGameModeButton extends Button { + + private final GameMode gameMode; + private final Callback callback; + + SelectGameModeButton(GameMode gameMode, Callback callback) { + this.gameMode = Preconditions.checkNotNull(gameMode, "gameMode"); + this.callback = Preconditions.checkNotNull(callback, "callback"); + } + + @Override + public String getName(Player player) { + return ChatColor.DARK_AQUA + gameMode.getName(); + } + + @Override + public List getDescription(Player player) { + return Collections.singletonList(ChatColor.GRAY + gameMode.getDescription()); + } + + @Override + public Material getMaterial(Player player) { + return gameMode.getIcon().getItemType(); + } + + @Override + public byte getDamageValue(Player player) { + return gameMode.getIcon().getData(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + if (gameMode.equals(GameModes.TEAMFIGHT)) { + new SelectNoDebuffOrDebuff(isDebuff -> { + if (isDebuff) { + callback.callback(gameMode); + } else { + callback.callback(GameModes.TEAMFIGHT_DEBUFF); + } + }).openMenu(player); + } else { + callback.callback(gameMode); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/menu/select/SelectGameModeMenu.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/menu/select/SelectGameModeMenu.java new file mode 100644 index 0000000..f716600 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemode/menu/select/SelectGameModeMenu.java @@ -0,0 +1,70 @@ +package com.elevatemc.potpvp.gamemode.menu.select; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.elevatemc.potpvp.gamemodes.teamfight.TeamfightDebuff; +import com.google.common.base.Preconditions; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import com.elevatemc.elib.util.Callback; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; + +public final class SelectGameModeMenu extends Menu { + + private final String title; + private final Callback callback; + + public SelectGameModeMenu(Callback callback, String title) { + this.title = title; + this.callback = Preconditions.checkNotNull(callback, "callback"); + + setPlaceholder(true); + } + + @Override + public String getTitle(Player player) { + return ChatColor.BLUE + title; + } + + @Override + public Map getButtons(Player player) { + Map buttons = new HashMap<>(); + + int index = 9; + + + for (GameMode gameMode : GameMode.getAll()) { + if (gameMode.equals(GameModes.TEAMFIGHT)) { + continue; + } + + index++; + if ((index + 1) % 9 == 0) index++; + if (index % 9 == 0) index++; + + buttons.put(index, new SelectGameModeButton(gameMode, callback)); + } + + Party party = PotPvPSI.getInstance().getPartyHandler().getParty(player); + if (party != null) { + index++; + if ((index + 1) % 9 == 0) index++; + if (index % 9 == 0) index++; + buttons.put(index, new SelectGameModeButton(GameModes.TEAMFIGHT, callback)); + } + + return buttons; + } + + @Override + public int size(Player player) { + Map buttons = getButtons(player); + return ((int)Math.ceil(((float)buttons.size() / 7)) + 2) * 9; + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/archer/Archer.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/archer/Archer.java new file mode 100644 index 0000000..1e36145 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/archer/Archer.java @@ -0,0 +1,64 @@ +package com.elevatemc.potpvp.gamemodes.archer; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.HealingMethod; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import org.bukkit.Material; +import org.bukkit.material.MaterialData; + +import java.util.Collections; +import java.util.List; + +public class Archer extends GameMode { + private final GameModeKit kit = GameModeKit.createFromGameMode(this); + + @Override + public String getName() { + return "Archer"; + } + + @Override + public String getDescription() { + return null; + } + + @Override + public MaterialData getIcon() { + return new MaterialData(Material.BOW); + } + + @Override + public HealingMethod getHealingMethod() { + return null; + } + + @Override + public List getKits() { + return Collections.singletonList(kit); + } + + @Override + public boolean getBuildingAllowed() { + return false; + } + + @Override + public boolean getHealthShown() { + return true; + } + + @Override + public boolean getHardcoreHealing() { + return false; + } + + @Override + public boolean getPearlDamage() { + return false; + } + + @Override + public boolean getSupportsCompetitive() { + return true; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/bedfight/BedFight.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/bedfight/BedFight.java new file mode 100644 index 0000000..327f89f --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/bedfight/BedFight.java @@ -0,0 +1,71 @@ +package com.elevatemc.potpvp.gamemodes.bedfight; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.HealingMethod; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import com.elevatemc.potpvp.gamemodes.bedfight.listener.BedFightListener; +import com.elevatemc.potpvp.gamemodes.pearlfight.listener.MatchPearlfightListener; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.material.MaterialData; + +import java.util.Collections; +import java.util.List; + +public class BedFight extends GameMode { + private GameModeKit kit = GameModeKit.createFromGameMode(this); + + public BedFight() { + Bukkit.getPluginManager().registerEvents(new BedFightListener(), PotPvPSI.getInstance()); + } + @Override + public String getName() { + return "Bed Fight"; + } + + @Override + public String getDescription() { + return "TODO WRITE DESCRIPTION"; // TODO: Write description + } + + @Override + public MaterialData getIcon() { + return new MaterialData(Material.BED); + } + + @Override + public HealingMethod getHealingMethod() { + return null; + } + + @Override + public List getKits() { + return Collections.singletonList(kit); + } + + @Override + public boolean getBuildingAllowed() { + return true; + } + + @Override + public boolean getHealthShown() { + return false; + } + + @Override + public boolean getHardcoreHealing() { + return false; + } + + @Override + public boolean getPearlDamage() { + return false; + } + + @Override + public boolean getSupportsCompetitive() { + return true; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/bedfight/listener/BedFightListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/bedfight/listener/BedFightListener.java new file mode 100644 index 0000000..f3be14c --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/bedfight/listener/BedFightListener.java @@ -0,0 +1,109 @@ +package com.elevatemc.potpvp.gamemodes.bedfight.listener; + + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.spigot.event.BlockDropItemsEvent; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.FoodLevelChangeEvent; +import org.bukkit.event.player.PlayerPickupItemEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +public final class BedFightListener implements Listener { + private List blocksPlaced = new ArrayList<>(); + private List allowBreak = Arrays.asList(Material.WOOD, Material.ENDER_STONE, Material.WOOL); + + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onBlockBreak(BlockBreakEvent event) { + System.out.println("Block break"); + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match match = matchHandler.getMatchPlaying(event.getPlayer()); + + if (match == null || !match.getGameMode().equals(GameModes.BED_FIGHT)) { + return; + } + + System.out.println("Broke bedfight item"); + + final Block block = event.getBlock(); + + if (event.getBlock().getType().name().contains("BED")) { + System.out.println("Broke bedfight bed"); + + final Block belowBlock = block.getRelative(BlockFace.DOWN); + + if (!belowBlock.getType().equals(Material.STAINED_CLAY)) { + System.out.println("Broke bedfight not clay thouhg"); + return; + } + + System.out.println("Broke bedfight yeeee"); + + byte data = belowBlock.getData(); + + boolean blue = data == DyeColor.BLUE.getWoolData(); + + for (Player allPlayer : match.findAllPlayers()) { + allPlayer.sendMessage(""); + allPlayer.sendMessage(ChatColor.translateAlternateColorCodes('&', "&3&lBED DETECTION > " + (blue ? "&9Blue Bed" : "&cRed Bed") + " &ewas destroyed by " + (blue ? "&9" : "&c") + event.getPlayer().getName() + "&e!")); + allPlayer.sendMessage(""); + + allPlayer.playSound(allPlayer.getLocation(), Sound.WITHER_DEATH, 1.0F, 1.0F); + } + return; + } + + if (allowBreak.stream().noneMatch(it -> block.getType().equals(it))) { + event.setCancelled(true); + } + } + + @EventHandler + private void onPickup(PlayerPickupItemEvent event) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match match = matchHandler.getMatchPlaying(event.getPlayer()); + + if (match == null || !match.getGameMode().equals(GameModes.BED_FIGHT)) { + return; + } + + if (event.getItem().getItemStack().getType().equals(Material.BED)) { + event.setCancelled(true); + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onPlace(BlockPlaceEvent event) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match match = matchHandler.getMatchPlaying(event.getPlayer()); + + if (match == null || !match.getGameMode().equals(GameModes.BED_FIGHT)) { + return; + } + + if (!event.getBlock().getType().equals(Material.WOOL)) { + event.setCancelled(true); + return; + } + + blocksPlaced.add(event.getBlockPlaced().getLocation()); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/boxing/Boxing.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/boxing/Boxing.java new file mode 100644 index 0000000..6ca8ac0 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/boxing/Boxing.java @@ -0,0 +1,71 @@ +package com.elevatemc.potpvp.gamemodes.boxing; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.HealingMethod; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import com.elevatemc.potpvp.gamemodes.boxing.listener.MatchBoxingListener; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.material.MaterialData; + +import javax.swing.*; +import java.util.Collections; +import java.util.List; + +public class Boxing extends GameMode { + private final GameModeKit kit = GameModeKit.createFromGameMode(this); + public Boxing() { + Bukkit.getPluginManager().registerEvents(new MatchBoxingListener(), PotPvPSI.getInstance()); + } + + @Override + public String getName() { + return "Boxing"; + } + + @Override + public String getDescription() { + return "TODO WRITE DESCRIPTION"; // TODO: Write description + } + + @Override + public MaterialData getIcon() { + return new MaterialData(Material.GOLD_SWORD); + } + + @Override + public HealingMethod getHealingMethod() { + return null; + } + + @Override + public List getKits() { + return Collections.singletonList(kit); + } + + @Override + public boolean getBuildingAllowed() { + return false; + } + + @Override + public boolean getHealthShown() { + return false; + } + + @Override + public boolean getHardcoreHealing() { + return false; + } + + @Override + public boolean getPearlDamage() { + return false; + } + + @Override + public boolean getSupportsCompetitive() { + return true; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/boxing/listener/MatchBoxingListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/boxing/listener/MatchBoxingListener.java new file mode 100644 index 0000000..f705a95 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/boxing/listener/MatchBoxingListener.java @@ -0,0 +1,99 @@ +package com.elevatemc.potpvp.gamemodes.boxing.listener; + +import com.elevatemc.elib.util.ItemBuilder; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.match.event.MatchCountdownStartEvent; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.FoodLevelChangeEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.List; +import java.util.UUID; + +public final class MatchBoxingListener implements Listener { + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onEntityDamageByEntity(EntityDamageByEntityEvent event) { + if (event.getEntity() instanceof Player && event.getDamager() instanceof Player) { + Player damager = (Player) event.getEntity(); + Match match = PotPvPSI.getInstance().getMatchHandler().getMatchPlaying(damager); + if (match != null && match.getGameMode().equals(GameModes.BOXING)) { + List teams = match.getTeams(); + if (teams.size() > 2) { + int won = -1; + for (MatchTeam team : teams) { + if (team.getHits() == 100) { + won = teams.indexOf(team); + } + } + if (won != -1) { + for (MatchTeam team : teams) { + if (teams.indexOf(team) != won) { + for (UUID alive : team.getAliveMembers()) { + Bukkit.getPlayer(alive).setHealth(0); + } + } + } + } + } else { + MatchTeam firstTeam = teams.get(0); + MatchTeam secondTeam = teams.get(1); + if (firstTeam.getHits() == 100) { + for (UUID alive : secondTeam.getAliveMembers()) { + Bukkit.getPlayer(alive).setHealth(0); + } + } else if (secondTeam.getHits() == 100) { + for (UUID alive : firstTeam.getAliveMembers()) { + Bukkit.getPlayer(alive).setHealth(0); + } + } + } + } + } + } + + @EventHandler + public void onFoodLevelChange(FoodLevelChangeEvent event) { + if (event.getEntityType() != EntityType.PLAYER) { + return; + } + + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match match = matchHandler.getMatchPlaying((Player) event.getEntity()); + + if (match != null && match.getGameMode().equals(GameModes.BOXING)) { + event.setFoodLevel(20); + } + } + + + @EventHandler + public void onDamage(EntityDamageEvent event) { + if (event.getEntityType() != EntityType.PLAYER) { + return; + } + + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match match = matchHandler.getMatchPlaying((Player) event.getEntity()); + + if (match != null && match.getGameMode().equals(GameModes.BOXING)) { + event.setDamage(0); + } + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/bridges/Bridges.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/bridges/Bridges.java new file mode 100644 index 0000000..31aa54c --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/bridges/Bridges.java @@ -0,0 +1,64 @@ +package com.elevatemc.potpvp.gamemodes.bridges; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.HealingMethod; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import org.bukkit.Material; +import org.bukkit.material.MaterialData; + +import java.util.Collections; +import java.util.List; + +public class Bridges extends GameMode { + private final GameModeKit kit = GameModeKit.createFromGameMode(this); + + @Override + public String getName() { + return "Bridges"; + } + + @Override + public String getDescription() { + return null; + } + + @Override + public MaterialData getIcon() { + return new MaterialData(Material.CLAY, (byte) 5); + } + + @Override + public HealingMethod getHealingMethod() { + return null; + } + + @Override + public List getKits() { + return Collections.singletonList(kit); + } + + @Override + public boolean getBuildingAllowed() { + return true; + } + + @Override + public boolean getHealthShown() { + return false; + } + + @Override + public boolean getHardcoreHealing() { + return false; + } + + @Override + public boolean getPearlDamage() { + return false; + } + + @Override + public boolean getSupportsCompetitive() { + return true; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/builduhc/BuildUHC.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/builduhc/BuildUHC.java new file mode 100644 index 0000000..2e4273d --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/builduhc/BuildUHC.java @@ -0,0 +1,73 @@ +package com.elevatemc.potpvp.gamemodes.builduhc; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.HealingMethod; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import com.elevatemc.potpvp.gamemodes.builduhc.listener.GoldenHeadListener; +import com.elevatemc.potpvp.gamemodes.builduhc.listener.MatchRodListener; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.material.MaterialData; + +import java.util.Collections; +import java.util.List; + +public class BuildUHC extends GameMode { + private final GameModeKit kit = GameModeKit.createFromGameMode(this); + + public BuildUHC() { + Bukkit.getPluginManager().registerEvents(new MatchRodListener(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new GoldenHeadListener(), PotPvPSI.getInstance()); + + } + @Override + public String getName() { + return "Build UHC"; + } + + @Override + public String getDescription() { + return "TODO WRITE DESCRIPTION"; // TODO: Write description + } + + @Override + public MaterialData getIcon() { + return new MaterialData(Material.LAVA_BUCKET); + } + + @Override + public HealingMethod getHealingMethod() { + return HealingMethod.GOLDEN_APPLE; + } + + @Override + public List getKits() { + return Collections.singletonList(kit); + } + + @Override + public boolean getBuildingAllowed() { + return true; + } + + @Override + public boolean getHealthShown() { + return false; + } + + @Override + public boolean getHardcoreHealing() { + return false; + } + + @Override + public boolean getPearlDamage() { + return false; + } + + @Override + public boolean getSupportsCompetitive() { + return true; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/builduhc/listener/GoldenHeadListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/builduhc/listener/GoldenHeadListener.java new file mode 100644 index 0000000..f85a1f0 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/builduhc/listener/GoldenHeadListener.java @@ -0,0 +1,35 @@ +package com.elevatemc.potpvp.gamemodes.builduhc.listener; + +import com.elevatemc.elib.util.ItemBuilder; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerItemConsumeEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +public final class GoldenHeadListener implements Listener { + + private static final int HEALING_POINTS = 8; // half hearts, so 4 hearts + private static final ItemStack GOLDEN_HEAD = ItemBuilder.of(Material.GOLDEN_APPLE) + .name("&6&lGolden Head") + .build(); + + @EventHandler + public void onItemConsume(PlayerItemConsumeEvent event) { + Player player = event.getPlayer(); + + ItemStack item = event.getItem(); + + if (matches(item)) { + player.addPotionEffect(new PotionEffect(PotionEffectType.REGENERATION, HEALING_POINTS * 25, 1), true); + } + } + + private boolean matches(ItemStack item) { + return GOLDEN_HEAD.isSimilar(item); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/builduhc/listener/MatchRodListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/builduhc/listener/MatchRodListener.java new file mode 100644 index 0000000..d56ad28 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/builduhc/listener/MatchRodListener.java @@ -0,0 +1,33 @@ +package com.elevatemc.potpvp.gamemodes.builduhc.listener; + +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.FishHook; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.player.PlayerItemDamageEvent; + +/** + * Makes sure fishing rods don't do any damage to armor. + */ +public final class MatchRodListener implements Listener { + + @EventHandler + public void onItemDamage(PlayerItemDamageEvent event) { + Player player = event.getPlayer(); + + // dirty armor check + if (!Enchantment.PROTECTION_ENVIRONMENTAL.canEnchantItem(event.getItem())) { + return; + } + + // if their last damage cause is by a fishing hook, don't allow any damage. + if (player.getLastDamageCause() != null && player.getLastDamageCause() instanceof EntityDamageByEntityEvent) { + if (((EntityDamageByEntityEvent) player.getLastDamageCause()).getDamager() instanceof FishHook) { + event.setCancelled(true); + } + } + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/combo/Combo.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/combo/Combo.java new file mode 100644 index 0000000..81a99d4 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/combo/Combo.java @@ -0,0 +1,71 @@ +package com.elevatemc.potpvp.gamemodes.combo; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.HealingMethod; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import com.elevatemc.potpvp.gamemodes.combo.listener.MatchComboListener; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.material.MaterialData; + +import java.util.Collections; +import java.util.List; + +public class Combo extends GameMode { + private final GameModeKit kit = GameModeKit.createFromGameMode(this); + + public Combo() { + Bukkit.getPluginManager().registerEvents(new MatchComboListener(), PotPvPSI.getInstance()); + } + @Override + public String getName() { + return "Combo"; + } + + @Override + public String getDescription() { + return "TODO WRITE DESCRIPTION"; // TODO: Write description + } + + @Override + public MaterialData getIcon() { + return new MaterialData(Material.RAW_FISH, (byte) 3); + } + + @Override + public HealingMethod getHealingMethod() { + return HealingMethod.GOLDEN_APPLE; + } + + @Override + public List getKits() { + return Collections.singletonList(kit); + } + + @Override + public boolean getBuildingAllowed() { + return false; + } + + @Override + public boolean getHealthShown() { + return false; + } + + @Override + public boolean getHardcoreHealing() { + return false; + } + + @Override + public boolean getPearlDamage() { + return true; + } + + @Override + public boolean getSupportsCompetitive() { + return true; + } +} + diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/combo/listener/MatchComboListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/combo/listener/MatchComboListener.java new file mode 100644 index 0000000..04fa3a8 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/combo/listener/MatchComboListener.java @@ -0,0 +1,21 @@ +package com.elevatemc.potpvp.gamemodes.combo.listener; + +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.event.MatchStartEvent; +import org.bukkit.Bukkit; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; + +import java.util.Objects; + +public class MatchComboListener implements Listener { + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onStart(MatchStartEvent event) { + Match match = event.getMatch(); + + int noDamageTicks = match.getGameMode().getId().contains("COMBO") ? 3 : 20; + match.getTeams().forEach(team -> team.getAliveMembers().stream().map(Bukkit::getPlayer).filter(Objects::nonNull).forEach(p -> p.setMaximumNoDamageTicks(noDamageTicks))); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/debuff/Debuff.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/debuff/Debuff.java new file mode 100644 index 0000000..f1210af --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/debuff/Debuff.java @@ -0,0 +1,63 @@ +package com.elevatemc.potpvp.gamemodes.debuff; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.HealingMethod; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import org.bukkit.Material; +import org.bukkit.material.MaterialData; + +import java.util.Collections; +import java.util.List; + +public class Debuff extends GameMode { + private final GameModeKit kit = GameModeKit.createFromGameMode(this); + @Override + public String getName() { + return "Debuff"; + } + + @Override + public String getDescription() { + return "TODO WRITE DESCRIPTION"; // TODO: Write description + } + + @Override + public MaterialData getIcon() { + return new MaterialData(Material.POTION, (byte) 4); + } + + @Override + public HealingMethod getHealingMethod() { + return HealingMethod.POTIONS; + } + + @Override + public List getKits() { + return Collections.singletonList(kit); + } + + @Override + public boolean getBuildingAllowed() { + return false; + } + + @Override + public boolean getHealthShown() { + return false; + } + + @Override + public boolean getHardcoreHealing() { + return false; + } + + @Override + public boolean getPearlDamage() { + return true; + } + + @Override + public boolean getSupportsCompetitive() { + return true; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/gapple/Gapple.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/gapple/Gapple.java new file mode 100644 index 0000000..8fb9db1 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/gapple/Gapple.java @@ -0,0 +1,64 @@ +package com.elevatemc.potpvp.gamemodes.gapple; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.HealingMethod; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import org.bukkit.Material; +import org.bukkit.material.MaterialData; + +import java.util.Collections; +import java.util.List; + +public class Gapple extends GameMode { + private final GameModeKit kit = GameModeKit.createFromGameMode(this); + + @Override + public String getName() { + return "Gapple"; + } + + @Override + public String getDescription() { + return "TODO WRITE DESCRIPTION"; // TODO: Write description + } + + @Override + public MaterialData getIcon() { + return new MaterialData(Material.GOLDEN_APPLE); + } + + @Override + public HealingMethod getHealingMethod() { + return HealingMethod.GOLDEN_APPLE; + } + + @Override + public List getKits() { + return Collections.singletonList(kit); + } + + @Override + public boolean getBuildingAllowed() { + return false; + } + + @Override + public boolean getHealthShown() { + return false; + } + + @Override + public boolean getHardcoreHealing() { + return false; + } + + @Override + public boolean getPearlDamage() { + return false; + } + + @Override + public boolean getSupportsCompetitive() { + return true; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/invaded/Invaded.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/invaded/Invaded.java new file mode 100644 index 0000000..06b058d --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/invaded/Invaded.java @@ -0,0 +1,63 @@ +package com.elevatemc.potpvp.gamemodes.invaded; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.HealingMethod; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import org.bukkit.Material; +import org.bukkit.material.MaterialData; + +import java.util.Collections; +import java.util.List; + +public class Invaded extends GameMode { + private final GameModeKit kit = GameModeKit.createFromGameMode(this); + @Override + public String getName() { + return "Invaded"; + } + + @Override + public String getDescription() { + return "TODO WRITE DESCRIPTION"; // TODO: Write description + } + + @Override + public MaterialData getIcon() { + return new MaterialData(Material.FISHING_ROD); + } + + @Override + public HealingMethod getHealingMethod() { + return HealingMethod.GOLDEN_APPLE; + } + + @Override + public List getKits() { + return Collections.singletonList(kit); + } + + @Override + public boolean getBuildingAllowed() { + return false; + } + + @Override + public boolean getHealthShown() { + return true; + } + + @Override + public boolean getHardcoreHealing() { + return false; + } + + @Override + public boolean getPearlDamage() { + return false; + } + + @Override + public boolean getSupportsCompetitive() { + return true; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/nodebuff/NoDebuff.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/nodebuff/NoDebuff.java new file mode 100644 index 0000000..3fda70a --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/nodebuff/NoDebuff.java @@ -0,0 +1,64 @@ +package com.elevatemc.potpvp.gamemodes.nodebuff; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.HealingMethod; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import org.bukkit.Material; +import org.bukkit.material.MaterialData; + +import java.util.Collections; +import java.util.List; + +public class NoDebuff extends GameMode { + private final GameModeKit kit = GameModeKit.createFromGameMode(this); + + @Override + public String getName() { + return "No Debuff"; + } + + @Override + public String getDescription() { + return "TODO WRITE DESCRIPTION"; // TODO: Write description + } + + @Override + public MaterialData getIcon() { + return new MaterialData(Material.POTION, (byte) 69); + } + + @Override + public HealingMethod getHealingMethod() { + return HealingMethod.POTIONS; + } + + @Override + public List getKits() { + return Collections.singletonList(kit); + } + + @Override + public boolean getBuildingAllowed() { + return false; + } + + @Override + public boolean getHealthShown() { + return false; + } + + @Override + public boolean getHardcoreHealing() { + return false; + } + + @Override + public boolean getPearlDamage() { + return true; + } + + @Override + public boolean getSupportsCompetitive() { + return true; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/pearlfight/PearlFight.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/pearlfight/PearlFight.java new file mode 100644 index 0000000..91d9369 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/pearlfight/PearlFight.java @@ -0,0 +1,69 @@ +package com.elevatemc.potpvp.gamemodes.pearlfight; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.HealingMethod; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import com.elevatemc.potpvp.gamemodes.pearlfight.listener.MatchPearlfightListener; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.material.MaterialData; + +import java.util.Collections; +import java.util.List; + +public class PearlFight extends GameMode { + private GameModeKit kit = GameModeKit.createFromGameMode(this); + public PearlFight() { + Bukkit.getPluginManager().registerEvents(new MatchPearlfightListener(), PotPvPSI.getInstance()); + } + @Override + public String getName() { + return "Pearl Fight"; + } + + @Override + public String getDescription() { + return "TODO WRITE DESCRIPTION"; // TODO: Write description + } + + @Override + public MaterialData getIcon() { + return new MaterialData(Material.ENDER_PEARL); + } + + @Override + public HealingMethod getHealingMethod() { + return null; + } + + @Override + public List getKits() { + return Collections.singletonList(kit); + } + + @Override + public boolean getBuildingAllowed() { + return true; + } + + @Override + public boolean getHealthShown() { + return false; + } + + @Override + public boolean getHardcoreHealing() { + return false; + } + + @Override + public boolean getPearlDamage() { + return false; + } + + @Override + public boolean getSupportsCompetitive() { + return true; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/pearlfight/listener/MatchPearlfightListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/pearlfight/listener/MatchPearlfightListener.java new file mode 100644 index 0000000..c97a99b --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/pearlfight/listener/MatchPearlfightListener.java @@ -0,0 +1,145 @@ +package com.elevatemc.potpvp.gamemodes.pearlfight.listener; + + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.spigot.event.BlockDropItemsEvent; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.DyeColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.FoodLevelChangeEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +public final class MatchPearlfightListener implements Listener { + private final List restores = new ArrayList<>(); + + public MatchPearlfightListener() { + new BlockRestoreTask().runTaskTimer(PotPvPSI.getInstance(), 0L, 20L); + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onBlockPlace(BlockPlaceEvent event) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match match = matchHandler.getMatchPlaying(event.getPlayer()); + + if (match != null && match.getGameMode().equals(GameModes.PEARL_FIGHT) && event.getBlock().getType().equals(Material.WOOL)) { + Block block = event.getBlock(); + restores.add(new BlockRestore(block.getLocation(), event.getPlayer().getUniqueId())); + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onBlockBreak(BlockBreakEvent event) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match match = matchHandler.getMatchPlaying(event.getPlayer()); + + if (match != null && match.getGameMode().equals(GameModes.PEARL_FIGHT) && event.getBlock().getType().equals(Material.WOOL)) { + restores.removeIf(restore -> restore.getBlockLocation().equals(event.getBlock().getLocation())); + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onBlockDrop(BlockDropItemsEvent event) { + Player recipient = event.getPlayer(); + if (recipient == null) return; + + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match match = matchHandler.getMatchPlaying(event.getPlayer()); + + if (match != null && match.getGameMode().equals(GameModes.PEARL_FIGHT) && event.getBlock().getType().equals(Material.WOOL)) { + restores.removeIf(restore -> restore.getBlockLocation().equals(event.getBlock().getLocation())); + } + } + + @EventHandler + public void onFoodLevelChange(FoodLevelChangeEvent event) { + if (event.getEntityType() != EntityType.PLAYER) { + return; + } + + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match match = matchHandler.getMatchPlaying((Player) event.getEntity()); + + if (match != null && match.getGameMode().equals(GameModes.PEARL_FIGHT)) { + event.setFoodLevel(20); + } + } + + + @EventHandler + public void onDamage(EntityDamageEvent event) { + if (event.getEntityType() != EntityType.PLAYER) { + return; + } + + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match match = matchHandler.getMatchPlaying((Player) event.getEntity()); + + if (match != null && match.getGameMode().equals(GameModes.PEARL_FIGHT)) { + if (event.getCause() == EntityDamageEvent.DamageCause.FALL) { + event.setCancelled(true); + } else { + event.setDamage(0); + } + } + } + + private class BlockRestoreTask extends BukkitRunnable { + @Override + public void run() { + Iterator itr = restores.iterator(); + while(itr.hasNext()) { + BlockRestore restore = itr.next(); + if(System.currentTimeMillis() >= restore.getExpireAt()) { + final Block block = restore.getBlockLocation().getWorld().getBlockAt(restore.getBlockLocation()); + if (block.getType().equals(Material.WOOL)) { + Player player = Bukkit.getPlayer(restore.getPlayer()); + if (player != null) { + Match match = PotPvPSI.getInstance().getMatchHandler().getMatchPlaying(player); + + if (match != null && match.getGameMode().equals(GameModes.PEARL_FIGHT)) { + ItemStack wool = new ItemStack(Material.WOOL); + if (match.getTeams().size() == 2) { + if (match.getTeams().get(0) == match.getTeam(player.getUniqueId())) { + wool.setDurability(DyeColor.BLUE.getWoolData()); + } else { + wool.setDurability(DyeColor.RED.getWoolData()); + } + } + player.getInventory().addItem(wool); + } + } + } + block.setType(Material.AIR); + itr.remove(); + } + } + } + } + + @Getter + @RequiredArgsConstructor + private static class BlockRestore { + private final Location blockLocation; + private final UUID player; + private final long expireAt = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(10); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/skywars/Skywars.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/skywars/Skywars.java new file mode 100644 index 0000000..e89ac04 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/skywars/Skywars.java @@ -0,0 +1,64 @@ +package com.elevatemc.potpvp.gamemodes.skywars; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.HealingMethod; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import org.bukkit.Material; +import org.bukkit.material.MaterialData; + +import java.util.Collections; +import java.util.List; + +public class Skywars extends GameMode { + private GameModeKit kit = GameModeKit.createFromGameMode(this); + + @Override + public String getName() { + return "Skywars"; + } + + @Override + public String getDescription() { + return "TODO WRITE DESCRIPTION"; // TODO: Write description + } + + @Override + public MaterialData getIcon() { + return new MaterialData(Material.EGG); + } + + @Override + public HealingMethod getHealingMethod() { + return HealingMethod.GOLDEN_APPLE; + } + + @Override + public List getKits() { + return Collections.singletonList(kit); + } + + @Override + public boolean getBuildingAllowed() { + return true; + } + + @Override + public boolean getHealthShown() { + return false; + } + + @Override + public boolean getHardcoreHealing() { + return false; + } + + @Override + public boolean getPearlDamage() { + return false; + } + + @Override + public boolean getSupportsCompetitive() { + return true; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/sotw/SOTW.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/sotw/SOTW.java new file mode 100644 index 0000000..c5c46da --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/sotw/SOTW.java @@ -0,0 +1,69 @@ +package com.elevatemc.potpvp.gamemodes.sotw; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.HealingMethod; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import com.elevatemc.potpvp.gamemodes.pearlfight.listener.MatchPearlfightListener; +import com.elevatemc.potpvp.gamemodes.sotw.listener.MatchSOTWListener; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.material.MaterialData; + +import java.util.Collections; +import java.util.List; + +public class SOTW extends GameMode { + public SOTW() { + Bukkit.getPluginManager().registerEvents(new MatchSOTWListener(), PotPvPSI.getInstance()); + } + @Override + public String getName() { + return "SOTW"; + } + + @Override + public String getDescription() { + return "TODO WRITE DESCRIPTION"; // TODO: Write description + } + + @Override + public MaterialData getIcon() { + return new MaterialData(Material.IRON_CHESTPLATE); + } + + @Override + public HealingMethod getHealingMethod() { + return HealingMethod.POTIONS; + } + + @Override + public List getKits() { + return Collections.emptyList(); + } + + @Override + public boolean getBuildingAllowed() { + return false; + } + + @Override + public boolean getHealthShown() { + return false; + } + + @Override + public boolean getHardcoreHealing() { + return false; + } + + @Override + public boolean getPearlDamage() { + return false; + } + + @Override + public boolean getSupportsCompetitive() { + return true; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/sotw/listener/MatchSOTWListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/sotw/listener/MatchSOTWListener.java new file mode 100644 index 0000000..36cc813 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/sotw/listener/MatchSOTWListener.java @@ -0,0 +1,123 @@ +package com.elevatemc.potpvp.gamemodes.sotw.listener; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.util.ItemBuilder; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.match.event.MatchCountdownStartEvent; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.ArrayList; +import java.util.List; + +public final class MatchSOTWListener implements Listener { + + public ItemStack[] helmets = { + ItemBuilder.of(Material.DIAMOND_HELMET).build(), + ItemBuilder.of(Material.DIAMOND_HELMET).enchant(Enchantment.PROTECTION_ENVIRONMENTAL, 1).build(), + ItemBuilder.of(Material.DIAMOND_HELMET).enchant(Enchantment.PROTECTION_ENVIRONMENTAL, 2).build(), + ItemBuilder.of(Material.IRON_HELMET).build(), + ItemBuilder.of(Material.IRON_HELMET).enchant(Enchantment.PROTECTION_ENVIRONMENTAL, 1).build(), + ItemBuilder.of(Material.IRON_HELMET).enchant(Enchantment.PROTECTION_ENVIRONMENTAL, 2).build() + }; + public ItemStack[] chestplates = { + ItemBuilder.of(Material.DIAMOND_CHESTPLATE).build(), + ItemBuilder.of(Material.DIAMOND_CHESTPLATE).enchant(Enchantment.PROTECTION_ENVIRONMENTAL, 1).build(), + ItemBuilder.of(Material.DIAMOND_CHESTPLATE).enchant(Enchantment.PROTECTION_ENVIRONMENTAL, 2).build(), + ItemBuilder.of(Material.IRON_CHESTPLATE).build(), + ItemBuilder.of(Material.IRON_CHESTPLATE).enchant(Enchantment.PROTECTION_ENVIRONMENTAL, 1).build(), + ItemBuilder.of(Material.IRON_CHESTPLATE).enchant(Enchantment.PROTECTION_ENVIRONMENTAL, 2).build(), + }; + public ItemStack[] leggings = { + ItemBuilder.of(Material.DIAMOND_LEGGINGS).build(), + ItemBuilder.of(Material.DIAMOND_LEGGINGS).enchant(Enchantment.PROTECTION_ENVIRONMENTAL, 1).build(), + ItemBuilder.of(Material.DIAMOND_LEGGINGS).enchant(Enchantment.PROTECTION_ENVIRONMENTAL, 2).build(), + ItemBuilder.of(Material.IRON_LEGGINGS).build(), + ItemBuilder.of(Material.IRON_LEGGINGS).enchant(Enchantment.PROTECTION_ENVIRONMENTAL, 1).build(), + ItemBuilder.of(Material.IRON_LEGGINGS).enchant(Enchantment.PROTECTION_ENVIRONMENTAL, 2).build() + }; + public ItemStack[] boots = { + ItemBuilder.of(Material.DIAMOND_BOOTS).build(), + ItemBuilder.of(Material.DIAMOND_BOOTS).enchant(Enchantment.PROTECTION_ENVIRONMENTAL, 1).build(), + ItemBuilder.of(Material.DIAMOND_BOOTS).enchant(Enchantment.PROTECTION_ENVIRONMENTAL, 2).build(), + ItemBuilder.of(Material.IRON_BOOTS).build(), + ItemBuilder.of(Material.IRON_BOOTS).enchant(Enchantment.PROTECTION_ENVIRONMENTAL, 1).build(), + ItemBuilder.of(Material.IRON_BOOTS).enchant(Enchantment.PROTECTION_ENVIRONMENTAL, 2).build() + }; + public ItemStack[] swords = { + ItemBuilder.of(Material.DIAMOND_SWORD).build(), + ItemBuilder.of(Material.DIAMOND_SWORD).enchant(Enchantment.DAMAGE_ALL, 1).build(), + ItemBuilder.of(Material.DIAMOND_SWORD).enchant(Enchantment.DAMAGE_ALL, 2).build(), + ItemBuilder.of(Material.IRON_SWORD).enchant(Enchantment.DAMAGE_ALL, 1).build(), + ItemBuilder.of(Material.IRON_SWORD).enchant(Enchantment.DAMAGE_ALL, 2).build() + }; + public ItemStack[] pearls = { + ItemBuilder.of(Material.ENDER_PEARL).amount(1).build(), + ItemBuilder.of(Material.ENDER_PEARL).amount(2).build(), + ItemBuilder.of(Material.ENDER_PEARL).amount(3).build(), + }; + public ItemStack[] heals = { + ItemBuilder.of(Material.POTION).data((short)16389).build(), // Splash I + ItemBuilder.of(Material.POTION).data((short)16421).build(), // Splash II + ItemBuilder.of(Material.POTION).data((short)8197).build(), // Drink I + ItemBuilder.of(Material.POTION).data((short)8229).build(), // Drink II + }; + + public ItemStack[] foods = { + ItemBuilder.of(Material.COOKED_BEEF).amount(8).build(), + ItemBuilder.of(Material.BREAD).amount(12).build(), + ItemBuilder.of(Material.CARROT_ITEM).amount(6).build() + }; + + public ItemStack[] extras = { + ItemBuilder.of(Material.FISHING_ROD).build(), + ItemBuilder.of(Material.EGG).amount(8).build(), + ItemBuilder.of(Material.SNOW_BALL).amount(16).build() + }; + + @EventHandler + public void matchCountDownStartEvent(MatchCountdownStartEvent e) { + if (!e.getMatch().getGameMode().getId().equals("SOTW")) return; + + for (MatchTeam team : e.getMatch().getTeams()) { + team.forEachAlive(p -> { + p.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, Integer.MAX_VALUE, 1)); + setupInventory(p); + }); + } + } + + public void setupInventory(Player player) { + ItemStack randomHelmet = helmets[PotPvPSI.RANDOM.nextInt(helmets.length)]; + ItemStack randomChestplate = chestplates[PotPvPSI.RANDOM.nextInt(chestplates.length)]; + ItemStack randomLeggings = leggings[PotPvPSI.RANDOM.nextInt(leggings.length)]; + ItemStack randomBoots = boots[PotPvPSI.RANDOM.nextInt(boots.length)]; + ItemStack randomSword = swords[PotPvPSI.RANDOM.nextInt(swords.length)]; + ItemStack randomPearls = pearls[PotPvPSI.RANDOM.nextInt(pearls.length)]; + ItemStack randomExtra = extras[PotPvPSI.RANDOM.nextInt(extras.length)]; + ItemStack randomFood = foods[PotPvPSI.RANDOM.nextInt(foods.length)]; + + List randomHeals = new ArrayList<>(); + for (int i = PotPvPSI.RANDOM.nextInt(5); i > 0; i--) { + randomHeals.add(heals[PotPvPSI.RANDOM.nextInt(heals.length)]); + } + PlayerInventory i = player.getInventory(); + i.setHelmet(randomHelmet); + i.setChestplate(randomChestplate); + i.setLeggings(randomLeggings); + i.setBoots(randomBoots); + i.addItem(randomSword, randomPearls, randomExtra); + for (ItemStack randomHeal : randomHeals) { + i.addItem(randomHeal); + } + i.setItem(8, randomFood); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/soup/SoupPVP.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/soup/SoupPVP.java new file mode 100644 index 0000000..964e157 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/soup/SoupPVP.java @@ -0,0 +1,73 @@ +package com.elevatemc.potpvp.gamemodes.soup; + + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.HealingMethod; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import com.elevatemc.potpvp.gamemodes.soup.listener.MatchSoupListener; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.material.MaterialData; + +import java.util.Collections; +import java.util.List; + +public class SoupPVP extends GameMode { + private final GameModeKit kit = GameModeKit.createFromGameMode(this); + + public SoupPVP() { + Bukkit.getPluginManager().registerEvents(new MatchSoupListener(), PotPvPSI.getInstance()); + } + + @Override + public String getName() { + return "Soup"; + } + + @Override + public String getDescription() { + return "TODO WRITE DESCRIPTION"; // TODO: Write description + } + + @Override + public MaterialData getIcon() { + return new MaterialData(Material.MUSHROOM_SOUP); + } + + @Override + public HealingMethod getHealingMethod() { + return HealingMethod.SOUP; + } + + @Override + public List getKits() { + return Collections.singletonList(kit); + } + + @Override + public boolean getBuildingAllowed() { + return false; + } + + @Override + public boolean getHealthShown() { + return false; + } + + @Override + public boolean getHardcoreHealing() { + return false; + } + + @Override + public boolean getPearlDamage() { + return false; + } + + @Override + public boolean getSupportsCompetitive() { + return true; + } +} + diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/soup/listener/MatchSoupListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/soup/listener/MatchSoupListener.java new file mode 100644 index 0000000..cae4aaa --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/soup/listener/MatchSoupListener.java @@ -0,0 +1,53 @@ +package com.elevatemc.potpvp.gamemodes.soup.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.gamemode.HealingMethod; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import org.bukkit.Material; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.FoodLevelChangeEvent; +import org.bukkit.event.player.PlayerInteractEvent; + +public final class MatchSoupListener implements Listener { + + @EventHandler(priority = EventPriority.MONITOR) + // no ignoreCancelled = true because right click on air + // events are by default cancelled (wtf Bukkit) + public void onPlayerInteract(PlayerInteractEvent event) { + if (!event.hasItem() || event.getItem().getType() != Material.MUSHROOM_SOUP || !event.getAction().name().contains("RIGHT_")) { + return; + } + + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Player player = event.getPlayer(); + Match match = matchHandler.getMatchPlaying(player); + + if (match != null && match.getGameMode().getHealingMethod() == HealingMethod.SOUP && player.getHealth() <= 19) { + double current = player.getHealth(); + double max = player.getMaxHealth(); + + player.getItemInHand().setType(Material.BOWL); + player.setHealth(Math.min(max, current + 7D)); + } + } + + @EventHandler + public void onFoodLevelChange(FoodLevelChangeEvent event) { + if (event.getEntityType() != EntityType.PLAYER) { + return; + } + + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match match = matchHandler.getMatchPlaying((Player) event.getEntity()); + + if (match != null && match.getGameMode().getHealingMethod() == HealingMethod.SOUP) { + event.setFoodLevel(20); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/spleef/Spleef.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/spleef/Spleef.java new file mode 100644 index 0000000..2f1f6d9 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/spleef/Spleef.java @@ -0,0 +1,64 @@ +package com.elevatemc.potpvp.gamemodes.spleef; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.HealingMethod; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import org.bukkit.Material; +import org.bukkit.material.MaterialData; + +import java.util.Collections; +import java.util.List; + +public class Spleef extends GameMode { + private GameModeKit kit = GameModeKit.createFromGameMode(this); + + @Override + public String getName() { + return "Spleef"; + } + + @Override + public String getDescription() { + return "TODO WRITE DESCRIPTION"; // TODO: Write description + } + + @Override + public MaterialData getIcon() { + return new MaterialData(Material.SNOW_BALL); + } + + @Override + public HealingMethod getHealingMethod() { + return null; + } + + @Override + public List getKits() { + return Collections.singletonList(kit); + } + + @Override + public boolean getBuildingAllowed() { + return false; + } + + @Override + public boolean getHealthShown() { + return false; + } + + @Override + public boolean getHardcoreHealing() { + return false; + } + + @Override + public boolean getPearlDamage() { + return false; + } + + @Override + public boolean getSupportsCompetitive() { + return true; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/sumo/Sumo.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/sumo/Sumo.java new file mode 100644 index 0000000..c1ec2e9 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/sumo/Sumo.java @@ -0,0 +1,66 @@ +package com.elevatemc.potpvp.gamemodes.sumo; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.HealingMethod; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import org.bukkit.Material; +import org.bukkit.material.MaterialData; + +import java.util.Collections; +import java.util.List; + +public class Sumo extends GameMode { + @Override + public String getName() { + return "Sumo"; + } + + public String getDescription() { + return "TODO WRITE DESCRIPTION"; // TODO: Write description + } + + @Override + public MaterialData getIcon() { + return new MaterialData(Material.LEASH); + } + + @Override + public HealingMethod getHealingMethod() { + return null; + } + + @Override + public List getKits() { + return Collections.emptyList(); + } + + @Override + public boolean getBuildingAllowed() { + return false; + } + + @Override + public boolean getHealthShown() { + return false; + } + + @Override + public boolean getHardcoreHealing() { + return false; + } + + @Override + public boolean getPearlDamage() { + return false; + } + + @Override + public boolean getSupportsCompetitive() { + return true; + } + + @Override + public boolean isVoidTeleport() { + return false; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/teamfight/Teamfight.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/teamfight/Teamfight.java new file mode 100644 index 0000000..9433920 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/teamfight/Teamfight.java @@ -0,0 +1,67 @@ +package com.elevatemc.potpvp.gamemodes.teamfight; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.HealingMethod; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import org.bukkit.Material; +import org.bukkit.material.MaterialData; + +import java.util.Arrays; +import java.util.List; + +public class Teamfight extends GameMode { + public final GameModeKit DIAMOND_HCF = new GameModeKit("DIAMOND_HCF", "Diamond", new MaterialData(Material.DIAMOND_CHESTPLATE)); + public final GameModeKit BARD_HCF = new GameModeKit("BARD_HCF", "Bard", new MaterialData(Material.GOLD_CHESTPLATE)); + public final GameModeKit ARCHER_HCF = new GameModeKit("ARCHER_HCF", "Archer", new MaterialData(Material.LEATHER_CHESTPLATE)); + public final GameModeKit ROGUE_HCF = new GameModeKit("ROGUE_HCF", "Rogue", new MaterialData(Material.CHAINMAIL_CHESTPLATE)); + + @Override + public String getName() { + return "Teamfight"; + } + + @Override + public String getDescription() { + return "TODO WRITE DESCRIPTION"; // TODO: Write description + } + + @Override + public MaterialData getIcon() { + return new MaterialData(Material.GOLD_NUGGET); + } + + @Override + public HealingMethod getHealingMethod() { + return HealingMethod.POTIONS; + } + + @Override + public List getKits() { + return Arrays.asList(DIAMOND_HCF, BARD_HCF, ARCHER_HCF, ROGUE_HCF); + } + + @Override + public boolean getBuildingAllowed() { + return false; + } + + @Override + public boolean getHealthShown() { + return false; + } + + @Override + public boolean getHardcoreHealing() { + return false; + } + + @Override + public boolean getPearlDamage() { + return true; + } + + @Override + public boolean getSupportsCompetitive() { + return false; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/teamfight/TeamfightDebuff.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/teamfight/TeamfightDebuff.java new file mode 100644 index 0000000..edc7013 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/teamfight/TeamfightDebuff.java @@ -0,0 +1,30 @@ +package com.elevatemc.potpvp.gamemodes.teamfight; + +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import org.bukkit.Material; +import org.bukkit.material.MaterialData; + +import java.util.Arrays; +import java.util.List; + +public class TeamfightDebuff extends Teamfight { + public final GameModeKit DEBUFF_DIAMOND_HCF = new GameModeKit("DEBUFF_DIAMOND_HCF", "Diamond", new MaterialData(Material.DIAMOND_CHESTPLATE)); + public final GameModeKit DEBUFF_BARD_HCF = new GameModeKit("DEBUFF_BARD_HCF", "Bard", new MaterialData(Material.GOLD_CHESTPLATE)); + public final GameModeKit DEBUFF_ARCHER_HCF = new GameModeKit("DEBUFF_ARCHER_HCF", "Archer", new MaterialData(Material.LEATHER_CHESTPLATE)); + public final GameModeKit DEBUFF_ROGUE_HCF = new GameModeKit("DEBUFF_ROGUE_HCF", "Rogue", new MaterialData(Material.CHAINMAIL_CHESTPLATE)); + + @Override + public String getName() { + return "Teamfight Debuff"; + } + + @Override + public String getDescription() { + return "TODO WRITE DESCRIPTION"; // TODO: Write description + } + + @Override + public List getKits() { + return Arrays.asList(DEBUFF_DIAMOND_HCF, DEBUFF_BARD_HCF, DEBUFF_ARCHER_HCF, DEBUFF_ROGUE_HCF); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/trapping/Trapping.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/trapping/Trapping.java new file mode 100644 index 0000000..c9281a9 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/trapping/Trapping.java @@ -0,0 +1,73 @@ +package com.elevatemc.potpvp.gamemodes.trapping; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.HealingMethod; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import com.elevatemc.potpvp.gamemodes.sotw.listener.MatchSOTWListener; +import com.elevatemc.potpvp.gamemodes.trapping.listener.MatchTrappingListener; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.material.MaterialData; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class Trapping extends GameMode { + public Trapping() { + Bukkit.getPluginManager().registerEvents(new MatchTrappingListener(), PotPvPSI.getInstance()); + } + public final GameModeKit RUNNER = new GameModeKit("HCF_RUN", "Runner", new MaterialData(Material.DIAMOND_SWORD)); + public final GameModeKit TRAPPER = new GameModeKit("HCF_TRAP", "Trapper", new MaterialData(Material.IRON_PICKAXE)); + + @Override + public String getName() { + return "Trapping"; + } + + @Override + public String getDescription() { + return "TODO WRITE DESCRIPTION"; // TODO: Write description + } + + @Override + public MaterialData getIcon() { + return new MaterialData(Material.MAGMA_CREAM); + } + + @Override + public HealingMethod getHealingMethod() { + return HealingMethod.POTIONS; + } + + @Override + public List getKits() { + return Arrays.asList(RUNNER, TRAPPER); + } + + @Override + public boolean getBuildingAllowed() { + return true; + } + + @Override + public boolean getHealthShown() { + return false; + } + + @Override + public boolean getHardcoreHealing() { + return false; + } + + @Override + public boolean getPearlDamage() { + return true; + } + + @Override + public boolean getSupportsCompetitive() { + return false; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/trapping/listener/MatchTrappingListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/trapping/listener/MatchTrappingListener.java new file mode 100644 index 0000000..814eef4 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/trapping/listener/MatchTrappingListener.java @@ -0,0 +1,384 @@ +package com.elevatemc.potpvp.gamemodes.trapping.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.type.AntiBlockup; +import com.elevatemc.potpvp.ability.type.TimeWarp; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.elevatemc.potpvp.listener.PearlCooldownListener; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.match.event.MatchEndEvent; +import com.elevatemc.potpvp.match.event.MatchTerminateEvent; +import com.elevatemc.potpvp.util.Elevator; +import com.elevatemc.spigot.event.PlayerPearlRefundEvent; +import net.minecraft.server.v1_8_R3.EntityEnderPearl; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; +import org.bukkit.block.Sign; +import org.bukkit.entity.EnderPearl; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.block.SignChangeEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.FoodLevelChangeEvent; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.*; +import java.util.stream.Collectors; + +import static com.elevatemc.potpvp.listener.PearlCooldownListener.PEARL_COOLDOWN_MILLIS; + +public class MatchTrappingListener implements Listener { + public static int ticks = 6; + + + public MatchTrappingListener() { + EntityEnderPearl.pearlAbleType = Arrays.asList("STEP", "STAIR"); + } + + @EventHandler(priority = EventPriority.LOW) + private void onQuit(PlayerQuitEvent event) { + final Player player = event.getPlayer(); + + if (player.hasMetadata("TRAPPER")) { + player.removeMetadata("TRAPPER", PotPvPSI.getInstance()); + } + + if (player.hasMetadata("ANTI_BUILD")) { + player.removeMetadata("ANTI_BUILD", PotPvPSI.getInstance()); + } + + if (player.hasMetadata("DROPDOWN_COUNT")) { + player.removeMetadata("DROPDOWN_COUNT", PotPvPSI.getInstance()); + } + + if (player.hasMetadata("ANTI_TRAP")) { + player.removeMetadata("ANTI_TRAP", PotPvPSI.getInstance()); + } + + if (player.hasMetadata("SPIDER")) { + player.removeMetadata("SPIDER", PotPvPSI.getInstance()); + } + + if (player.hasMetadata("NINJASTAR")) { + player.removeMetadata("NINJASTAR", PotPvPSI.getInstance()); + } + + if (player.hasMetadata("CHAOS")) { + player.removeMetadata("CHAOS", PotPvPSI.getInstance()); + } + } + + @EventHandler(priority = EventPriority.LOW) + private void onMatchEnd(MatchEndEvent event) { + final Match match = event.getMatch(); + + if (match.getGameMode() != GameModes.TRAPPING) { + return; + } + + for (MatchTeam team : match.getTeams()) { + for (UUID allMember : team.getAllMembers()) { + final Player player = PotPvPSI.getInstance().getServer().getPlayer(allMember); + + if (player == null) { + continue; + } + + if (player.hasMetadata("ANTI_BUILD")) { + player.removeMetadata("ANTI_BUILD", PotPvPSI.getInstance()); + } + + if (player.hasMetadata("DROPDOWN_COUNT")) { + player.removeMetadata("DROPDOWN_COUNT", PotPvPSI.getInstance()); + } + + if (player.hasMetadata("ANTI_TRAP")) { + player.removeMetadata("ANTI_TRAP", PotPvPSI.getInstance()); + } + + if (player.hasMetadata("SPIDER")) { + player.removeMetadata("SPIDER", PotPvPSI.getInstance()); + } + + if (player.hasMetadata("NINJASTAR")) { + player.removeMetadata("NINJASTAR", PotPvPSI.getInstance()); + } + + if (player.hasMetadata("CHAOS")) { + player.removeMetadata("CHAOS", PotPvPSI.getInstance()); + } + + if (player.hasMetadata("TRAPPER")) { + player.removeMetadata("TRAPPER", PotPvPSI.getInstance()); + } + } + } + } + + @EventHandler(priority = EventPriority.MONITOR) + private void onHunger(FoodLevelChangeEvent event) { + final Player player = (Player) event.getEntity(); + final MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + if (!matchHandler.isPlayingMatch(player)) { + return; + } + + final Match match = matchHandler.getMatchPlaying(player); + + if (match == null || match.getGameMode() != GameModes.TRAPPING) { + return; + } + + player.setFoodLevel(20); + event.setCancelled(true); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onRefund(PlayerPearlRefundEvent event) { + final Player player = event.getPlayer(); + + if (!player.isOnline()) { + return; + } + + final ItemStack itemStack = player.getItemInHand(); + + if (itemStack != null && itemStack.getType() == Material.ENDER_PEARL && itemStack.getAmount() < 16) { + itemStack.setAmount(itemStack.getAmount() + 1); + } else { + player.getInventory().addItem(new ItemStack(Material.ENDER_PEARL, 1)); + } + + player.updateInventory(); + + PearlCooldownListener.pearlCooldown.remove(player.getUniqueId()); + } + + @EventHandler(priority = EventPriority.MONITOR) + private void onDamage(EntityDamageByEntityEvent event) { + if (event.isCancelled() || !(event.getDamager() instanceof EnderPearl) || !(event.getEntity() instanceof Player)) { + return; + } + + final EnderPearl enderPearl = (EnderPearl) event.getDamager(); + if (!(enderPearl.getShooter() instanceof Player)) { + return; + } + + final Player damager = (Player) enderPearl.getShooter(); + final Player target = (Player) event.getEntity(); + + if (damager == target) { + return; + } + + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + if (!matchHandler.isPlayingMatch(damager)) { + // BasicPreventionListener handles this + return; + } + + if (matchHandler.getMatchPlaying(damager).getGameMode() != GameModes.TRAPPING) { + return; + } + + PearlCooldownListener.pearlCooldown.put(damager.getUniqueId(), System.currentTimeMillis() + PEARL_COOLDOWN_MILLIS); + + TimeWarp.pearlLocations.put(damager.getUniqueId(), damager.getLocation()); + + PotPvPSI.getInstance().getServer().getScheduler().runTaskLater(PotPvPSI.getInstance(), () -> { + final Location targetLocation = target.getLocation().clone(); + final BlockFace blockFace = getDirection(damager); + + if (blockFace == BlockFace.NORTH) { + targetLocation.setZ(targetLocation.getZ() + 0.5); + } + + if (blockFace == BlockFace.SOUTH) { + targetLocation.setZ(targetLocation.getZ() - 0.5); + } + + if (blockFace == BlockFace.WEST) { + targetLocation.setX(targetLocation.getX() + 0.5); + } + + if (blockFace == BlockFace.EAST) { + targetLocation.setX(targetLocation.getX() - 0.5); + } + + if (targetLocation.getBlock().getType() != Material.AIR) { + return; + } + + targetLocation.setYaw(damager.getLocation().getYaw()); + targetLocation.setPitch(damager.getLocation().getPitch()); + + damager.teleport(targetLocation); + }, ticks); + } + + private final List elevatorDirections = Collections.singletonList(Arrays.stream(Elevator.values()).map(Elevator::name).map(String::toLowerCase).collect(Collectors.joining())); + private final List signMaterials = Arrays.asList(Material.SIGN_POST, Material.WALL_SIGN); + + @EventHandler(priority = EventPriority.NORMAL) + public void onSignInteract(PlayerInteractEvent event) { + + final Player player = event.getPlayer(); + + if (event.getAction() != Action.RIGHT_CLICK_BLOCK || !this.signMaterials.contains(event.getClickedBlock().getType())) { + return; + } + + final BlockState blockState = event.getClickedBlock().getState(); + + if (!(blockState instanceof Sign)) { + return; + } + + final Sign sign = (Sign) blockState; + + if (!sign.getLine(0).contains("[Elevator]")) { + return; + } + + final Block block = player.getTargetBlock((HashSet) null,(int)sign.getLocation().distance(player.getLocation())); + + if (block != null && !(block.getState() instanceof Sign)) { + return; + } + + Elevator elevator; + + try { + elevator = Elevator.valueOf(ChatColor.stripColor(sign.getLine(1).toUpperCase())); + } catch (IllegalArgumentException ex) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Invalid elevator direction, try UP or DOWN."); + ex.printStackTrace(); + return; + } + + if (AntiBlockup.getCache().containsKey(player.getUniqueId())) { + player.sendMessage(ChatColor.translateAlternateColorCodes('&', "&cYou may not use elevator signs whilst on &6&lAnti-Blockup&c!")); + return; + } + + if (elevator == null) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Invalid elevator direction, try UP or DOWN."); + return; + } + + final Location toTeleport = elevator.getCalculatedLocation(sign.getLocation(), Elevator.Type.SIGN); + + if (toTeleport == null) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "There was an issue trying to find a valid location!"); + return; + } + + toTeleport.setYaw(player.getLocation().getYaw()); + toTeleport.setPitch(player.getLocation().getPitch()); + player.teleport(toTeleport.add(0.5,0,0.5)); + } + + @EventHandler + public void onSignChange(SignChangeEvent event) { + if (event.getLine(0).equalsIgnoreCase("[Elevator]") && event.getLine(1).equalsIgnoreCase("Up")) { + event.setLine(0, ChatColor.BLUE + "[Elevator]"); + event.setLine(1, "Up"); + } + if (event.getLine(0).equalsIgnoreCase("[Elevator]") && event.getLine(1).equalsIgnoreCase("Down")) { + event.setLine(0, ChatColor.BLUE + "[Elevator]"); + event.setLine(1, "Down"); + } + } + + @EventHandler(priority = EventPriority.HIGH) + private void onPlace(BlockPlaceEvent event) { + final Player player = event.getPlayer(); + final MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + if (event.isCancelled() || !matchHandler.isPlayingMatch(player)) { + return; + } + + final Match match = matchHandler.getMatchPlaying(player); + + if (match == null || match.getGameMode() != GameModes.TRAPPING) { + return; + } + + if (event.getBlock().getType() == Material.COBBLESTONE) { + player.getInventory().addItem(new ItemStack(Material.COBBLESTONE, 1)); + } + } + + @EventHandler + public void onFoodLevelChange(FoodLevelChangeEvent event) { + if (event.getEntityType() != EntityType.PLAYER) { + return; + } + + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match match = matchHandler.getMatchPlaying((Player) event.getEntity()); + + if (match != null && match.getGameMode().equals(GameModes.TRAPPING)) { + event.setFoodLevel(20); + } + } + + @EventHandler + public void onDeath(PlayerDeathEvent event) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match match = matchHandler.getMatchPlaying(event.getEntity()); + + if (match != null && match.getGameMode().equals(GameModes.TRAPPING)) { + UUID uuid = event.getEntity().getUniqueId(); + TimeWarp.pearlLocations.remove(uuid); + TimeWarp.oldPearlLocations.remove(uuid); + } + } + + @EventHandler + public void onDeath(MatchTerminateEvent event) { + Match match = event.getMatch(); + + if (match != null && match.getGameMode().equals(GameModes.TRAPPING)) { + match.getAllPlayers().forEach(uuid -> { + TimeWarp.pearlLocations.remove(uuid); + TimeWarp.oldPearlLocations.remove(uuid); + }); + } + } + + private BlockFace getDirection(Player player) { + float yaw = player.getLocation().getYaw(); + if (yaw < 0) { + yaw += 360; + } + if (yaw >= 315 || yaw < 45) { + return BlockFace.SOUTH; + } else if (yaw < 135) { + return BlockFace.WEST; + } else if (yaw < 225) { + return BlockFace.NORTH; + } else if (yaw < 315) { + return BlockFace.EAST; + } + return BlockFace.NORTH; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/vanilla/Vanilla.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/vanilla/Vanilla.java new file mode 100644 index 0000000..6dcc626 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/gamemodes/vanilla/Vanilla.java @@ -0,0 +1,64 @@ +package com.elevatemc.potpvp.gamemodes.vanilla; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.HealingMethod; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import org.bukkit.Material; +import org.bukkit.material.MaterialData; + +import java.util.Collections; +import java.util.List; + +public class Vanilla extends GameMode { + private GameModeKit kit = GameModeKit.createFromGameMode(this); + + @Override + public String getName() { + return "Vanilla"; + } + + @Override + public String getDescription() { + return "TODO WRITE DESCRIPTION"; // TODO: Write description + } + + @Override + public MaterialData getIcon() { + return new MaterialData(Material.POTION, (byte) 73); + } + + @Override + public HealingMethod getHealingMethod() { + return HealingMethod.POTIONS; + } + + @Override + public List getKits() { + return Collections.singletonList(kit); + } + + @Override + public boolean getBuildingAllowed() { + return false; + } + + @Override + public boolean getHealthShown() { + return false; + } + + @Override + public boolean getHardcoreHealing() { + return false; + } + + @Override + public boolean getPearlDamage() { + return false; + } + + @Override + public boolean getSupportsCompetitive() { + return true; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/HCTRankedHandler.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/HCTRankedHandler.java new file mode 100644 index 0000000..1ee68d9 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/HCTRankedHandler.java @@ -0,0 +1,28 @@ +package com.elevatemc.potpvp.hctranked; + +import com.elevatemc.elib.eLib; +import com.elevatemc.potpvp.hctranked.game.RankedGameHandler; +import com.elevatemc.potpvp.hctranked.listener.HCTPacketListener; +import com.elevatemc.potpvp.hctranked.packet.*; +import com.elevatemc.potpvp.hctranked.sync.SyncHandler; +import lombok.Getter; + +public class HCTRankedHandler { + + @Getter + private SyncHandler syncHandler; + @Getter + private RankedGameHandler gameHandler; + + public HCTRankedHandler() { + syncHandler = new SyncHandler(); + gameHandler = new RankedGameHandler(); + eLib.getInstance().getPidginHandler().registerListener(new HCTPacketListener()); + eLib.getInstance().getPidginHandler().registerPacket(PingPacket.class); + eLib.getInstance().getPidginHandler().registerPacket(PongPacket.class); + eLib.getInstance().getPidginHandler().registerPacket(LoadGamePacket.class); + eLib.getInstance().getPidginHandler().registerPacket(StartGamePacket.class); + eLib.getInstance().getPidginHandler().registerPacket(VoidGamePacket.class); + eLib.getInstance().getPidginHandler().registerPacket(WinGamePacket.class); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/command/RankedCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/command/RankedCommand.java new file mode 100644 index 0000000..d788088 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/command/RankedCommand.java @@ -0,0 +1,152 @@ +package com.elevatemc.potpvp.hctranked.command; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import com.elevatemc.elib.util.UUIDUtils; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.lobby.LobbyUtils; +import com.elevatemc.potpvp.pvpclasses.PvPClasses; +import com.elevatemc.potpvp.hctranked.game.RankedGame; +import com.elevatemc.potpvp.hctranked.game.RankedGameState; +import com.elevatemc.potpvp.hctranked.game.RankedGameTeam; +import com.elevatemc.potpvp.validation.PotPvPValidation; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.*; +import java.util.stream.Collectors; + +public class RankedCommand { + @Command(names = {"ranked"}, permission = "") + public static void join(Player sender) { + RankedGame game = PotPvPSI.getInstance().getHCTRankedHandler().getGameHandler().getInvitedGame(sender); + if (!PotPvPValidation.canJoinRankedGame(sender)) { + return; + } + if (game == null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "There is no game for you to join."); + return; + } + + if (game.getState() != RankedGameState.WAITING) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "This game is already in progress. Please report this to an admin."); + return; + } + + game.join(sender); + } + + @Command(names = {"ranked leave"}, permission = "") + public static void leave(Player sender) { + RankedGame game = PotPvPSI.getInstance().getHCTRankedHandler().getGameHandler().getJoinedGame(sender); + if (game == null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "There is no game for you to leave."); + return; + } + + game.leave(sender); + } + + @Command(names = {"ranked list"}, permission = "rankedhcf.admin") + public static void list(Player sender) { + Set games = PotPvPSI.getInstance().getHCTRankedHandler().getGameHandler().getRankedGames(); + for (RankedGame game : games) { + sender.sendMessage(ChatColor.GOLD.toString() + ChatColor.BOLD + "Game " + game.getGameId()); + sender.sendMessage("Match: " + ChatColor.DARK_AQUA + game.getMatchId()); + sender.sendMessage("Map: " + ChatColor.DARK_AQUA + game.getArena()); + sender.sendMessage("State: " + ChatColor.DARK_AQUA + game.getState()); + sender.sendMessage("Joined: " + ChatColor.DARK_AQUA + game.getJoinedPlayers().size() + "/" + game.getAllPlayers().size()); + } + } + + @Command(names = {"ranked void"}, permission = "rankedhcf.admin") + public static void voidGame(Player sender, @Parameter(name = "game") String text) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "If there was a game with that id it is voided."); + PotPvPSI.getInstance().getHCTRankedHandler().getGameHandler().voidGame(text); + } + + @Command(names = {"ranked info"}, permission = "") + public static void info(Player sender, @Parameter(name = "player", defaultValue = "self") Player player) { + RankedGame game = PotPvPSI.getInstance().getHCTRankedHandler().getGameHandler().getJoinedGame(player); + if (game == null) { + if (sender.getUniqueId().equals(player.getUniqueId())) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You are not in a game"); + } else { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "That player is not in a game."); + } + return; + } + + Set joinedPlayers = game.getJoinedPlayers(); + RankedGameTeam team1 = game.getTeam1(); + RankedGameTeam team2 = game.getTeam2(); + String team1Online = team1.getPlayers().stream().filter(m -> !team1.getCaptain().equals(m)).map(m -> joinedPlayers.contains(m) ? "&a" + UUIDUtils.name(m) : "&7" + UUIDUtils.name(m)).collect(Collectors.joining(", ")); + String team2Online = team2.getPlayers().stream().filter(m -> !team2.getCaptain().equals(m)).map(m -> joinedPlayers.contains(m) ? "&a" + UUIDUtils.name(m) : "&7" + UUIDUtils.name(m)).collect(Collectors.joining(", ")); + + UUID team1Captain = team1.getCaptain(); + UUID team2Captain = team2.getCaptain(); + + sender.sendMessage(ChatColor.translateAlternateColorCodes('&', "")); + sender.sendMessage(ChatColor.translateAlternateColorCodes('&', "&3Team 1 &7[" + team1.getJoinedPlayers().size() + "/" + team1.getPlayers().size() + "]")); + sender.sendMessage(ChatColor.translateAlternateColorCodes('&', "&f- Captain&7: " + (joinedPlayers.contains(team1Captain) ? "&a"+ UUIDUtils.name(team1Captain) : "&7" + UUIDUtils.name(team1Captain)))); + sender.sendMessage(ChatColor.translateAlternateColorCodes('&', "&f- Members&7: " + team1Online)); + sender.sendMessage(ChatColor.translateAlternateColorCodes('&', "&f- Ready&7: " + (game.getTeam1().isReady() ? "&aYes" : "&cNo"))); + sender.sendMessage(ChatColor.translateAlternateColorCodes('&', "")); + sender.sendMessage(ChatColor.translateAlternateColorCodes('&', "&3Team 2 &7[" + team2.getJoinedPlayers().size() + "/" + team2.getPlayers().size() + "]")); + sender.sendMessage(ChatColor.translateAlternateColorCodes('&', "&f- Captain&7: " + (joinedPlayers.contains(team2Captain) ? "&a" + UUIDUtils.name(team2Captain) : "&7" + UUIDUtils.name(team2Captain)))); + sender.sendMessage(ChatColor.translateAlternateColorCodes('&', "&f- Members&7: " + team2Online)); + sender.sendMessage(ChatColor.translateAlternateColorCodes('&', "&f- Ready&7: " + (game.getTeam2().isReady() ? "&aYes" : "&cNo"))); + sender.sendMessage(ChatColor.translateAlternateColorCodes('&', "")); + } + + private static final Map toggleReady = new HashMap<>(); + + @Command(names = {"ready"}, permission = "") + public static void ready(Player sender) { + RankedGame game = PotPvPSI.getInstance().getHCTRankedHandler().getGameHandler().getJoinedGame(sender); + if (game == null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You are not in a ranked game."); + return; + } + + RankedGameTeam team = game.getTeam(sender); + if (!team.getCaptain().equals(sender.getUniqueId())) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You must be a captain to ready up."); + return; + } + + boolean ready = !team.isReady(); + + if (ready && team.getJoinedPlayers().size() != team.getPlayers().size()) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Not everyone of your team joined the ranked game yet."); + return; + } + + Collection kits = team.getKits().values(); + int bards = Collections.frequency(kits, PvPClasses.BARD); + int archers = Collections.frequency(kits, PvPClasses.ARCHER); + + /* if (bards < 1) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You need a bard before you ready up."); + return; + } + + if (archers < 1) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You need an archer before you ready up."); + return; + } */ + + boolean togglePermitted = toggleReady.getOrDefault(sender.getUniqueId(), 0L) < System.currentTimeMillis(); + + if (!togglePermitted) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Please wait before doing this again!"); + return; + } + + toggleReady.put(sender.getUniqueId(), System.currentTimeMillis() + 3_000L); + team.setReady(ready); + LobbyUtils.resetInventory(sender); + + game.checkStart(); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/command/SyncCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/command/SyncCommand.java new file mode 100644 index 0000000..16c96b2 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/command/SyncCommand.java @@ -0,0 +1,30 @@ +package com.elevatemc.potpvp.hctranked.command; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.command.Command; +import com.elevatemc.potpvp.PotPvPSI; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import redis.clients.jedis.params.SetParams; + +public class SyncCommand { + @Command(names = {"discord"}, permission = "") + public static void sync(Player sender) { + if (PotPvPSI.getInstance().getHCTRankedHandler().getSyncHandler().isSynced(sender.getUniqueId())) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You are already synced! If you wish to unlink your account message an admin."); + return; + } + eLib.getInstance().runRedisCommand(redis -> { + String oldCode = redis.get("rankedhcf.sync.player." + sender.getUniqueId().toString()); + if (oldCode != null) { + sender.sendMessage(ChatColor.GREEN + "You already have a code. Please type " + ChatColor.RED + "/register " + oldCode + ChatColor.GREEN + " in the " + ChatColor.RED + "#register" + ChatColor.GREEN + " channel!"); + return null; + } + int code = PotPvPSI.RANDOM.nextInt(999999); + redis.set("rankedhcf.sync.player." + sender.getUniqueId().toString(), String.valueOf(code), new SetParams().ex(60 * 5)); + redis.set("rankedhcf.sync.code." + code, sender.getUniqueId().toString(), new SetParams().ex(60 * 5)); + sender.sendMessage(ChatColor.GREEN + "A code has been generated for you. Please type " + ChatColor.RED + "/register " + code + ChatColor.GREEN + " in the " + ChatColor.RED + "#register" + ChatColor.GREEN + " channel!"); + return null; + }); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/game/RankedGame.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/game/RankedGame.java new file mode 100644 index 0000000..79449e0 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/game/RankedGame.java @@ -0,0 +1,140 @@ +package com.elevatemc.potpvp.hctranked.game; + +import com.elevatemc.elib.eLib; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.elevatemc.potpvp.hctranked.packet.StartGamePacket; +import com.elevatemc.potpvp.hctranked.packet.WinGamePacket; +import com.elevatemc.potpvp.lobby.LobbyUtils; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.util.InventoryUtils; +import com.elevatemc.potpvp.util.VisibilityUtils; +import com.google.common.collect.ImmutableList; +import lombok.Getter; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +public class RankedGame { + @Getter + private final Set allPlayers; + + @Getter + private final Set joinedPlayers; + + @Getter + private String arena; + + @Getter + private final RankedGameTeam team1; + + @Getter + private final RankedGameTeam team2; + + @Getter + private RankedGameState state; + + @Getter + private final String gameId; + + @Getter + private String matchId = null; + + public RankedGame(String gameId, Set players, RankedGameTeam team1, RankedGameTeam team2, String arena) { + this.gameId = gameId; + this.allPlayers = players; + this.joinedPlayers = new HashSet<>(); + this.team1 = team1; + this.team2 = team2; + this.arena = arena; + this.state = RankedGameState.WAITING; + } + + public RankedGameTeam getTeam(Player player) { + if (team1.getPlayers().contains(player.getUniqueId())) { + return team1; + } else if (team2.getPlayers().contains(player.getUniqueId())) { + return team2; + } + return null; + } + + public void join(Player player) { + getJoinedPlayers().add(player.getUniqueId()); + getTeam(player).getJoinedPlayers().add(player.getUniqueId()); + InventoryUtils.resetInventoryDelayed(player); + Bukkit.getLogger().info("RankedHCF > Joined game Id: " + gameId + " Player: " + player.getName()); + messageJoined(ChatColor.GOLD + player.getName() + ChatColor.YELLOW + " has joined your ranked game."); + player.sendMessage(ChatColor.GREEN + "You joined the ranked game!"); + getAllPlayers().forEach(uuid -> { + Player p = Bukkit.getPlayer(uuid); + if (p != null) { + VisibilityUtils.updateVisibility(p); + } + }); + } + + public void leave(Player player) { + getJoinedPlayers().remove(player.getUniqueId()); + if (state.equals(RankedGameState.WAITING)) { + RankedGameTeam team = getTeam(player); + team.getJoinedPlayers().remove(player.getUniqueId()); + if (getState().equals(RankedGameState.WAITING) && team.isReady()) { + team.setReady(false); + if (team.getJoinedPlayers().contains(team.getCaptain())) { + Player captain = Bukkit.getPlayer(team.getCaptain()); + if (captain != null) LobbyUtils.resetInventory(captain); + } + } + + InventoryUtils.resetInventoryDelayed(player); + VisibilityUtils.updateVisibility(player); + Bukkit.getLogger().info("RankedHCF > Left game Id: " + gameId + " Player: " + player.getName()); + messageJoined(ChatColor.GOLD + player.getName() + ChatColor.YELLOW + " has left your ranked game."); + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You left the ranked game!"); + } + + } + + public void checkStart() { + if (team1.isReady() && team2.isReady()) { + start(); + } + } + + private void start() { + this.state = RankedGameState.IN_PROGRESS; + Match match = PotPvPSI.getInstance().getMatchHandler().startMatch( + ImmutableList.of(new MatchTeam(team1.getJoinedPlayers()), new MatchTeam(team2.getJoinedPlayers())), + GameModes.TEAMFIGHT, + arena, + false, + false + ); + matchId = match.get_id(); + Bukkit.getLogger().info("RankedHCF > Game started Id: " + gameId + " Map: " + arena); + eLib.getInstance().getPidginHandler().sendPacket(new StartGamePacket(gameId, matchId)); + } + + public void end(int winnerTeam) { + Bukkit.getLogger().info("RankedHCF > Game ended Id: " + gameId + " Winner Team: " + winnerTeam); + eLib.getInstance().getPidginHandler().sendPacket(new WinGamePacket(gameId, winnerTeam)); + PotPvPSI.getInstance().getHCTRankedHandler().getGameHandler().removeGame(this); + } + + public void messageJoined(String message) { + for (UUID pl : joinedPlayers) { + Player player = Bukkit.getPlayer(pl); + + if (player != null) { + player.sendMessage(message); + } + } + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/game/RankedGameHandler.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/game/RankedGameHandler.java new file mode 100644 index 0000000..c26d665 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/game/RankedGameHandler.java @@ -0,0 +1,90 @@ +package com.elevatemc.potpvp.hctranked.game; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.hctranked.game.listener.RankedGameListener; +import com.elevatemc.potpvp.hctranked.game.listener.RankedItemListener; +import com.elevatemc.potpvp.util.InventoryUtils; +import com.elevatemc.potpvp.validation.PotPvPValidation; +import lombok.Getter; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +public class RankedGameHandler { + @Getter + private final Set rankedGames = new HashSet<>(); + + public RankedGameHandler() { + Bukkit.getPluginManager().registerEvents(new RankedGameListener(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new RankedItemListener(), PotPvPSI.getInstance()); + } + + public void createGame(String gameId, Set players, RankedGameTeam team1, RankedGameTeam team2, String map) { + Bukkit.getLogger().info("RankedHCF > Created a new game Id: " + gameId + " Size: " + players.size() + " Map: " + map); + RankedGame game = new RankedGame(gameId, players, team1, team2, map); + rankedGames.add(game); + players.forEach(pl -> { + Player player = Bukkit.getPlayer(pl); + if (player != null && PotPvPValidation.canJoinRankedGameSilent(player)) { + game.join(player); + } + }); + } + + public RankedGame getInvitedGame(Player player) { + UUID uuid = player.getUniqueId(); + return rankedGames.stream().filter(g -> g.getAllPlayers().contains(uuid) && !g.getJoinedPlayers().contains(uuid) && g.getState().equals(RankedGameState.WAITING)).findFirst().orElse(null); + } + + public RankedGame getJoinedGame(Player player) { + UUID uuid = player.getUniqueId(); + return rankedGames.stream().filter(g -> g.getJoinedPlayers().contains(uuid)).findFirst().orElse(null); + } + + public RankedGame getGameByMatchId(String matchId) { + return rankedGames.stream().filter(g -> { + if (g.getMatchId() != null) { + return g.getMatchId().equals(matchId); + } else { + return false; + } + }).findFirst().orElse(null); + } + + public void voidGame(String gameId) { + RankedGame game = rankedGames.stream().filter(g -> g.getGameId().equals(gameId)).findFirst().orElse(null); + if (game == null) return; + Bukkit.getLogger().info("RankedHCF > Voided game Id: " + gameId + " Match: " + game.getMatchId()); + game.messageJoined(ChatColor.RED + "This game has been voided. You will not gain or lose any elo out of this. If you wish to play again please requeue."); + removeGame(game); + switch (game.getState()) { + case WAITING: + for (UUID pl : game.getJoinedPlayers()) { + Player player = Bukkit.getPlayer(pl); + + if (player != null) { + InventoryUtils.resetInventoryNow(player); + } + } + case IN_PROGRESS: + Match match = PotPvPSI.getInstance().getMatchHandler().getHostedMatches().stream().filter(m -> m.get_id().equals(game.getMatchId())).findFirst().orElse(null); + if (match != null) { + match.getTeams().get(0).getAliveMembers().forEach(p -> { + Player player = Bukkit.getPlayer(p); + if (player != null) { + player.setHealth(0); + } + }); + } + } + } + + public void removeGame(RankedGame game) { + rankedGames.remove(game); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/game/RankedGameItems.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/game/RankedGameItems.java new file mode 100644 index 0000000..fd497e6 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/game/RankedGameItems.java @@ -0,0 +1,27 @@ +package com.elevatemc.potpvp.hctranked.game; + +import com.elevatemc.elib.util.ItemUtils; +import org.bukkit.DyeColor; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + + +import static org.bukkit.ChatColor.*; + +public class RankedGameItems { + public static final ItemStack GAME_INFO = new ItemStack(Material.WATCH); + public static final ItemStack LEAVE_GAME = new ItemStack(Material.INK_SACK, 1, DyeColor.RED.getDyeData()); + public static final ItemStack ASSIGN_CLASSES = new ItemStack(Material.MAGMA_CREAM); + + public static final ItemStack NOT_READY = new ItemStack(Material.INK_SACK, 1, DyeColor.GRAY.getDyeData()); + public static final ItemStack READY = new ItemStack(Material.INK_SACK, 1, DyeColor.LIME.getDyeData()); + + static { + ItemUtils.setDisplayName(GAME_INFO, GRAY + "• " + RED + "Game Info" + GRAY + " •"); + ItemUtils.setDisplayName(LEAVE_GAME, GRAY + "• " + RED + "Leave Game" + GRAY + " •"); + ItemUtils.setDisplayName(ASSIGN_CLASSES, GRAY + "• " + AQUA + "Ranked Kits" + GRAY + " •"); + + ItemUtils.setDisplayName(NOT_READY, GRAY + "• " + RED + "Not Ready" + GRAY + " •"); + ItemUtils.setDisplayName(READY, GRAY + "• " + GREEN + "Ready" + GRAY + " •"); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/game/RankedGameState.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/game/RankedGameState.java new file mode 100644 index 0000000..4c7276a --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/game/RankedGameState.java @@ -0,0 +1,6 @@ +package com.elevatemc.potpvp.hctranked.game; + +public enum RankedGameState { + WAITING, + IN_PROGRESS +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/game/RankedGameTeam.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/game/RankedGameTeam.java new file mode 100644 index 0000000..b46fe4c --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/game/RankedGameTeam.java @@ -0,0 +1,35 @@ +package com.elevatemc.potpvp.hctranked.game; + +import com.elevatemc.potpvp.pvpclasses.PvPClasses; +import lombok.Getter; +import lombok.Setter; + +import java.util.*; + +public class RankedGameTeam { + /** + * All players who were ever part of this team, including those who logged off / died + */ + @Getter + private final Set players; + + @Getter + private final Set joinedPlayers; + + @Getter + private final UUID captain; + + @Getter @Setter + private boolean ready; + + @Getter + private Map kits = new HashMap<>(); + + // convenience constructor for 1v1s, queues, etc + public RankedGameTeam(Set players, UUID captain) { + this.players = players; + this.captain = captain; + this.joinedPlayers = new HashSet<>(); + this.ready = false; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/game/listener/RankedGameListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/game/listener/RankedGameListener.java new file mode 100644 index 0000000..fe59246 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/game/listener/RankedGameListener.java @@ -0,0 +1,45 @@ +package com.elevatemc.potpvp.hctranked.game.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.event.MatchSpectatorLeaveEvent; +import com.elevatemc.potpvp.match.event.MatchTerminateEvent; +import com.elevatemc.potpvp.hctranked.game.RankedGame; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; + +public final class RankedGameListener implements Listener { + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + Player player = event.getPlayer(); + RankedGame game = PotPvPSI.getInstance().getHCTRankedHandler().getGameHandler().getJoinedGame(player); + if (game == null) { + return; + } + + game.leave(player); + } + + @EventHandler + public void onSpectatorLeave(MatchSpectatorLeaveEvent event) { + Match match = event.getMatch(); + RankedGame game = PotPvPSI.getInstance().getHCTRankedHandler().getGameHandler().getGameByMatchId(match.get_id()); + if (game != null) { + game.leave(event.getSpectator()); + } + } + + @EventHandler + public void onMatchEnd(MatchTerminateEvent event) { + Match match = event.getMatch(); + RankedGame game = PotPvPSI.getInstance().getHCTRankedHandler().getGameHandler().getGameByMatchId(match.get_id()); + if (game != null) { + game.messageJoined(ChatColor.GREEN + "This match has been registered as a HCT Ranked game. If there were any issues please make a ticket in the discord."); + game.end(game.getTeam1().getPlayers().contains(match.getWinner().getFirstMember()) ? 1 : 2); + } + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/game/listener/RankedItemListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/game/listener/RankedItemListener.java new file mode 100644 index 0000000..d830881 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/game/listener/RankedItemListener.java @@ -0,0 +1,20 @@ +package com.elevatemc.potpvp.hctranked.game.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.hctranked.command.RankedCommand; +import com.elevatemc.potpvp.hctranked.game.RankedGameItems; +import com.elevatemc.potpvp.hctranked.game.menu.KitsMenu; +import com.elevatemc.potpvp.util.ItemListener; + +public class RankedItemListener extends ItemListener { + public RankedItemListener() { + addHandler(RankedGameItems.GAME_INFO, p -> RankedCommand.info(p, p)); + addHandler(RankedGameItems.ASSIGN_CLASSES, p -> { + new KitsMenu(PotPvPSI.getInstance().getHCTRankedHandler().getGameHandler().getJoinedGame(p).getTeam(p)).openMenu(p); + }); + addHandler(RankedGameItems.LEAVE_GAME, RankedCommand::leave); + + addHandler(RankedGameItems.READY, RankedCommand::ready); + addHandler(RankedGameItems.NOT_READY, RankedCommand::ready); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/game/menu/KitsMenu.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/game/menu/KitsMenu.java new file mode 100644 index 0000000..5662501 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/game/menu/KitsMenu.java @@ -0,0 +1,118 @@ +package com.elevatemc.potpvp.hctranked.game.menu; + +import com.elevatemc.elib.menu.Menu; +import com.elevatemc.elib.util.UUIDUtils; +import com.elevatemc.potpvp.pvpclasses.PvPClasses; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.potpvp.hctranked.game.RankedGameTeam; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.*; +import java.util.stream.Collectors; + +public class KitsMenu extends Menu { + + private final RankedGameTeam team; + + public KitsMenu(RankedGameTeam team) { + this.team = team; + + setAutoUpdate(true); + setUpdateAfterClick(true); + setPlaceholder(true); + } + + private static List classes = Arrays.stream(PvPClasses.values()).filter(c -> !c.equals(PvPClasses.ROGUE)).collect(Collectors.toList()); + + @Override + public Map getButtons(Player player) { + Map toReturn = new HashMap<>(); + + for (UUID uuid : new ArrayList<>(team.getKits().keySet())) { + if (!(team.getPlayers().contains(uuid))) { + team.getKits().remove(uuid); + } + } + + for (UUID uuid : team.getPlayers()) { + PvPClasses selected = team.getKits().getOrDefault(uuid, PvPClasses.DIAMOND); + + toReturn.put(toReturn.isEmpty() ? 0 : toReturn.size(), new Button() { + @Override + public String getName(Player player) { + return ChatColor.GOLD + UUIDUtils.name(uuid); + } + + @Override + public List getDescription(Player player) { + List description = new ArrayList<>(); + + for (PvPClasses kit : classes) { + if (kit == selected) { + description.add(ChatColor.GREEN + "» " + kit.getName()); + } else { + if (kit.allowed(team)) { + description.add(ChatColor.GRAY + kit.getName()); + } else { + description.add(ChatColor.RED + ChatColor.STRIKETHROUGH.toString() + kit.getName()); + } + } + } + + return description; + } + + @Override + public Material getMaterial(Player player) { + return selected.getIcon(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + if (team.getCaptain().equals(player.getUniqueId())) { + if (team.isReady()) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You can't modify your kits while being ready."); + return; + } + int index = classes.indexOf(selected); + PvPClasses next = null; + + int times = 0; + while (next == null && times <= 50) { + times++; + if (index+1 < classes.size()) { + next = classes.get(index+1); + if (!(next.allowed(team))) { + next = null; + index++; + } + } else { + index = -1; + } + } + + if (next == null) { + next = PvPClasses.DIAMOND; + } + team.getKits().put(uuid, next); + } + } + }); + } + + return toReturn; + } + + @Override + public int size(Player buttons) { + return 9*2; + } + + @Override + public String getTitle(Player player) { + return "Kits Menu"; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/listener/HCTPacketListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/listener/HCTPacketListener.java new file mode 100644 index 0000000..0f34c6e --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/listener/HCTPacketListener.java @@ -0,0 +1,39 @@ +package com.elevatemc.potpvp.hctranked.listener; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.pidgin.packet.handler.IncomingPacketHandler; +import com.elevatemc.elib.pidgin.packet.listener.PacketListener; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.hctranked.game.RankedGameTeam; +import com.elevatemc.potpvp.hctranked.packet.LoadGamePacket; +import com.elevatemc.potpvp.hctranked.packet.PingPacket; +import com.elevatemc.potpvp.hctranked.packet.PongPacket; +import com.elevatemc.potpvp.hctranked.packet.VoidGamePacket; +import org.bukkit.Bukkit; + +public class HCTPacketListener implements PacketListener { + @IncomingPacketHandler + public void onPingPacket(PingPacket packet) { + eLib.getInstance().getPidginHandler().sendPacket(new PongPacket()); + } + + @IncomingPacketHandler + public void onLoadGame(LoadGamePacket packet) { + Bukkit.broadcastMessage("LOAD GAME"); + RankedGameTeam team1 = new RankedGameTeam(packet.getTeam1(), packet.getTeam1Captain()); + RankedGameTeam team2 = new RankedGameTeam(packet.getTeam2(), packet.getTeam2Captain()); + Bukkit.getScheduler().runTask(PotPvPSI.getInstance(), () -> { + Bukkit.broadcastMessage("PROCESS"); + PotPvPSI.getInstance().getHCTRankedHandler().getGameHandler().createGame(packet.getId(), packet.getPlayers(), team1, team2, packet.getMap()); + }); + } + + @IncomingPacketHandler + public void onVoidGame(VoidGamePacket packet) { + Bukkit.broadcastMessage("VOID GAME PACKET!"); + Bukkit.getScheduler().runTask(PotPvPSI.getInstance(), () -> { + Bukkit.broadcastMessage("PROCESS VOIDD"); + PotPvPSI.getInstance().getHCTRankedHandler().getGameHandler().voidGame(packet.getId()); + }); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/packet/LoadGamePacket.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/packet/LoadGamePacket.java new file mode 100644 index 0000000..4e362d5 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/packet/LoadGamePacket.java @@ -0,0 +1,55 @@ +package com.elevatemc.potpvp.hctranked.packet; + +import com.elevatemc.elib.pidgin.packet.Packet; +import com.google.gson.JsonObject; +import it.unimi.dsi.fastutil.Hash; +import lombok.Getter; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +public class LoadGamePacket implements Packet { + + @Getter private String id; + @Getter private Set players = new HashSet<>(); + @Getter private UUID team1Captain; + @Getter private Set team1 = new HashSet<>(); + @Getter private UUID team2Captain; + @Getter private Set team2 = new HashSet<>(); + @Getter private String map; + + @Override + public int id() { + return 302; + } + + @Override + public JsonObject serialize() { + return null; + } + + @Override + public void deserialize(JsonObject object) { + id = object.get("id").getAsString(); + + object.getAsJsonArray("players").forEach(obj -> { + players.add(UUID.fromString(obj.getAsString())); + }); + + JsonObject team1Obj = object.get("team1").getAsJsonObject(); + JsonObject team2Obj = object.get("team2").getAsJsonObject(); + + team1Obj.get("players").getAsJsonArray().forEach(obj -> { + team1.add(UUID.fromString(obj.getAsString())); + }); + team2Obj.get("players").getAsJsonArray().forEach(obj -> { + team2.add(UUID.fromString(obj.getAsString())); + }); + + team1Captain = UUID.fromString(team1Obj.get("captain").getAsString()); + team2Captain = UUID.fromString(team2Obj.get("captain").getAsString()); + + map = object.get("map").getAsString(); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/packet/PingPacket.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/packet/PingPacket.java new file mode 100644 index 0000000..8d35111 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/packet/PingPacket.java @@ -0,0 +1,21 @@ +package com.elevatemc.potpvp.hctranked.packet; + +import com.elevatemc.elib.pidgin.packet.Packet; +import com.google.gson.JsonObject; + +public class PingPacket implements Packet { + @Override + public int id() { + return 300; + } + + @Override + public JsonObject serialize() { + return new JsonObject(); + } + + @Override + public void deserialize(JsonObject object) { + + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/packet/PongPacket.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/packet/PongPacket.java new file mode 100644 index 0000000..a05de9e --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/packet/PongPacket.java @@ -0,0 +1,21 @@ +package com.elevatemc.potpvp.hctranked.packet; + +import com.elevatemc.elib.pidgin.packet.Packet; +import com.google.gson.JsonObject; + +public class PongPacket implements Packet { + @Override + public int id() { + return 301; + } + + @Override + public JsonObject serialize() { + return new JsonObject(); + } + + @Override + public void deserialize(JsonObject object) { + + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/packet/StartGamePacket.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/packet/StartGamePacket.java new file mode 100644 index 0000000..f6076d6 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/packet/StartGamePacket.java @@ -0,0 +1,37 @@ +package com.elevatemc.potpvp.hctranked.packet; + +import com.elevatemc.elib.pidgin.packet.Packet; +import com.google.gson.JsonObject; +import lombok.Getter; + +public class StartGamePacket implements Packet { + @Getter private String gameId; + @Getter private String match; + + private JsonObject jsonObject; + + public StartGamePacket() { + + } + + public StartGamePacket(String gameId, String match) { + this.jsonObject = new JsonObject(); + this.jsonObject.addProperty("id", gameId); + this.jsonObject.addProperty("match", match); + } + + @Override + public int id() { + return 304; + } + + @Override + public JsonObject serialize() { + return jsonObject; + } + + @Override + public void deserialize(JsonObject object) { + + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/packet/VoidGamePacket.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/packet/VoidGamePacket.java new file mode 100644 index 0000000..84921dd --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/packet/VoidGamePacket.java @@ -0,0 +1,24 @@ +package com.elevatemc.potpvp.hctranked.packet; + +import com.elevatemc.elib.pidgin.packet.Packet; +import com.google.gson.JsonObject; +import lombok.Getter; + +public class VoidGamePacket implements Packet { + @Getter private String id; + + @Override + public int id() { + return 303; + } + + @Override + public JsonObject serialize() { + return null; + } + + @Override + public void deserialize(JsonObject object) { + id = object.get("id").getAsString(); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/packet/WinGamePacket.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/packet/WinGamePacket.java new file mode 100644 index 0000000..4dd341e --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/packet/WinGamePacket.java @@ -0,0 +1,36 @@ +package com.elevatemc.potpvp.hctranked.packet; + +import com.elevatemc.elib.pidgin.packet.Packet; +import com.google.gson.JsonObject; +import lombok.Getter; + +public class WinGamePacket implements Packet { + @Getter private String id; + @Getter private int team; + + private JsonObject jsonObject; + + public WinGamePacket() { + + } + public WinGamePacket(String gameId, int team) { + this.jsonObject = new JsonObject(); + this.jsonObject.addProperty("id", gameId); + this.jsonObject.addProperty("team", team); + } + + @Override + public int id() { + return 305; + } + + @Override + public JsonObject serialize() { + return jsonObject; + } + + @Override + public void deserialize(JsonObject object) { + + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/sync/SyncHandler.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/sync/SyncHandler.java new file mode 100644 index 0000000..5751fff --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hctranked/sync/SyncHandler.java @@ -0,0 +1,16 @@ +package com.elevatemc.potpvp.hctranked.sync; + +import com.elevatemc.potpvp.util.MongoUtils; +import com.mongodb.client.model.Filters; +import org.bson.Document; +import java.util.UUID; + +public class SyncHandler { + private static final String SYNCS_MONGO_COLLECTION_NAME = "syncs"; + private static final String RANKED_GAMES_MONGO_COLLECTION_NAME = "rankedgames"; + + public boolean isSynced(UUID uuid) { + Document document = MongoUtils.getCollection(SYNCS_MONGO_COLLECTION_NAME).find(Filters.eq("minecraft", uuid.toString())).first(); + return document != null; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/hologram/HologramHandler.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hologram/HologramHandler.java new file mode 100644 index 0000000..d4f1861 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/hologram/HologramHandler.java @@ -0,0 +1,91 @@ +package com.elevatemc.potpvp.hologram; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.hologram.Hologram; +import com.elevatemc.elib.util.TaskUtil; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.gamemode.GameMode; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.World; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public class HologramHandler { + + World world = Bukkit.getWorld("world"); + + public void registerWelcomeHologram() { + if(world == null) return; + Hologram welcomeHologram = new Hologram(new Location(world, 5, 111, 5)); + welcomeHologram.addLine("&3&lElevateMC"); + welcomeHologram.addLine("&r"); + welcomeHologram.addLine("Season 3 of &3&lElevateMC &ftook place on the 6th of August, 2022"); + welcomeHologram.addLine("Home of &3&lHCT Ranked"); + welcomeHologram.addLine("&r"); + welcomeHologram.addLine("&3Website: &felevatemc.com"); + welcomeHologram.addLine("&3Store: &fstore.elevatemc.com"); + welcomeHologram.addLine("&3Discord: &felevatemc.com/discord"); + eLib.getInstance().getHologramHandler().registerHologram(welcomeHologram); + } + + private int currentLeaderboard = 0; + + public void registerLeaderboardHologram() { + if(world == null) return; + Hologram leaderBoardHologram = new Hologram(new Location(world, 21, 109, 25)); + leaderBoardHologram.addLine("&3&lRanked"); + leaderBoardHologram.addLine("&3&lLeaderboards"); + leaderBoardHologram.addLine("&f"); + leaderBoardHologram.addLine("&7Loading..."); + leaderBoardHologram.addLine("&f"); + leaderBoardHologram.addLine("&7"); + leaderBoardHologram.addLine("&7"); + leaderBoardHologram.addLine("&7"); + leaderBoardHologram.addLine("&7"); + leaderBoardHologram.addLine("&7"); + leaderBoardHologram.addLine("&7"); + leaderBoardHologram.addLine("&7"); + leaderBoardHologram.addLine("&7"); + leaderBoardHologram.addLine("&7"); + leaderBoardHologram.addLine("&7"); + eLib.getInstance().getHologramHandler().registerHologram(leaderBoardHologram); + TaskUtil.scheduleAtFixedRateOnPool(() -> updateConsumer.accept(leaderBoardHologram), 5, 5, TimeUnit.SECONDS); + } + + private final Consumer updateConsumer = hologram -> { + List leaderboardGames = GameMode.getAll().stream().filter(GameMode::getSupportsCompetitive).collect(Collectors.toList()); + GameMode gameMode = leaderboardGames.get(currentLeaderboard); + if (hologram.getLines().size() >= 3) { + hologram.getLine(3).updateLine(ChatColor.GREEN + " • " + gameMode.getName() + " • "); + } else { + hologram.addLine(3, ChatColor.GREEN + " • " + gameMode.getName() + " • "); + } + + int i = 5; + + for (Map.Entry entry : PotPvPSI.getInstance().getEloHandler().topElo(gameMode).entrySet()) { + if (hologram.getLines().size() >= i) { + hologram.getLine(i).updateLine(ChatColor.GRAY.toString() + (i - 4) + ". " + entry.getKey() + ChatColor.GRAY + ": " + ChatColor.WHITE + entry.getValue()); + } else { + hologram.addLine(i, ChatColor.GRAY.toString() + (i - 4) + ". " + entry.getKey() + ChatColor.GRAY + ": " + ChatColor.WHITE + entry.getValue()); + } + + + i++; + } + + currentLeaderboard++; + if (currentLeaderboard > leaderboardGames.size() - 1) currentLeaderboard = 0; + }; + + public void registerHolograms() { + registerWelcomeHologram(); + registerLeaderboardHologram(); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/Kit.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/Kit.java new file mode 100644 index 0000000..8ef69c5 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/Kit.java @@ -0,0 +1,154 @@ +package com.elevatemc.potpvp.kit; + +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchTeam; +import lombok.Getter; +import lombok.Setter; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.pvpclasses.pvpclasses.BardClass; +import com.elevatemc.potpvp.util.ItemUtils; +import com.elevatemc.potpvp.util.PatchedPlayerUtils; +import org.bukkit.*; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.LeatherArmorMeta; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.Arrays; +import java.util.List; + +public final class Kit { + + @Getter @Setter private String name; + @Getter @Setter private int slot; // starts at 1, not 0 + @Getter @Setter private GameModeKit type; + @Getter @Setter private ItemStack[] inventoryContents; + + public static Kit ofDefaultKitCustomName(GameModeKit gameMode, String name) { + return ofDefaultKit(gameMode, name, 0); + } + + public static Kit ofDefaultKit(GameModeKit gameMode) { + return ofDefaultKit(gameMode, "Default Kit", 0); + } + + public static Kit ofDefaultKit(GameModeKit gameModeKit, String name, int slot) { + Kit kit = new Kit(); + + kit.setName(name); + kit.setType(gameModeKit); + kit.setSlot(slot); + kit.setInventoryContents(gameModeKit.getDefaultInventory()); + + return kit; + } + + public void apply(Player player, boolean replacePearlsWithPots) { + PatchedPlayerUtils.resetInventory(player); + + // we don't let players actually customize their armor, we just apply default + player.getInventory().setArmorContents(GameModeKit.byId(type.getId()).getDefaultArmor()); + player.getInventory().setContents(inventoryContents); + + if (replacePearlsWithPots) { + for (ItemStack item : player.getInventory().getContents()) { + if (item != null && item.getType().equals(Material.ENDER_PEARL)) { + item.setAmount(1); + item.setType(Material.POTION); + item.setDurability((short)16421); + } + }; + } + + if (type.getId().startsWith("HCF_")) { + player.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, Integer.MAX_VALUE, 1)); + player.addPotionEffect(new PotionEffect(PotionEffectType.FIRE_RESISTANCE, Integer.MAX_VALUE, 0)); + } + + if (type.getId().equals("PEARL_FIGHT")) { + Match match = PotPvPSI.getInstance().getMatchHandler().getMatchPlaying(player); + List teams = match.getTeams(); + + DyeColor white = DyeColor.WHITE; + + for (ItemStack item : player.getInventory().getArmorContents()) { + if (item != null && item.getType().name().startsWith("LEATHER_")) { + LeatherArmorMeta meta = (LeatherArmorMeta) item.getItemMeta(); + if (teams.size() == 2) { + if (teams.get(0) == match.getTeam(player.getUniqueId())) { + meta.setColor(Color.BLUE); + } else { + meta.setColor(Color.RED); + } + } else { + meta.setColor(white.getColor()); + } + item.setItemMeta(meta); + } + } + + for (ItemStack item : player.getInventory().getContents()) { + if (item != null && item.getType().equals(Material.WOOL)) { + if (teams.size() == 2) { + if (teams.get(0) == match.getTeam(player.getUniqueId())) { + item.setDurability(DyeColor.BLUE.getWoolData()); + } else { + item.setDurability(DyeColor.RED.getWoolData()); + } + } else { + // Randomize color + item.setDurability(white.getWoolData()); + } + } + } + } + + if (type.getId().equals("BARD_HCF")) { + Bukkit.getScheduler().runTaskLater(PotPvPSI.getInstance(), () -> BardClass.getEnergy().put(player.getName(), 100), 1L); + } + + Bukkit.getScheduler().runTaskLater(PotPvPSI.getInstance(), player::updateInventory, 1L); + } + + public int countHeals() { + return ItemUtils.countStacksMatching(inventoryContents, ItemUtils.INSTANT_HEAL_POTION_PREDICATE); + } + + public int countDebuffs() { + return ItemUtils.countStacksMatching(inventoryContents, ItemUtils.DEBUFF_POTION_PREDICATE); + } + + public int countFood() { + return ItemUtils.countStacksMatching(inventoryContents, ItemUtils.EDIBLE_PREDICATE); + } + + public int countPearls() { + return ItemUtils.countStacksMatching(inventoryContents, v -> v.getType() == Material.ENDER_PEARL); + } + + // we use this method instead of .toSelectableBook().isSimilar() + // to avoid the slight performance overhead of constructing + // that itemstack every time + public boolean isSelectionItem(ItemStack itemStack) { + if (itemStack.getType() != Material.ENCHANTED_BOOK) { + return false; + } + + ItemMeta meta = itemStack.getItemMeta(); + return meta.hasDisplayName() && meta.getDisplayName().equals(ChatColor.YELLOW.toString() + ChatColor.BOLD + name); + } + + public ItemStack createSelectionItem() { + ItemStack item = new ItemStack(Material.ENCHANTED_BOOK); + ItemMeta itemMeta = item.getItemMeta(); + + itemMeta.setDisplayName(ChatColor.YELLOW.toString() + ChatColor.BOLD + name); + + item.setItemMeta(itemMeta); + return item; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/KitHandler.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/KitHandler.java new file mode 100644 index 0000000..5b7e394 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/KitHandler.java @@ -0,0 +1,126 @@ +package com.elevatemc.potpvp.kit; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import com.google.common.collect.ImmutableList; +import com.mongodb.client.MongoCollection; +import com.mongodb.util.JSON; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.kit.listener.KitEditorListener; +import com.elevatemc.potpvp.kit.listener.KitItemListener; +import com.elevatemc.potpvp.kit.listener.KitLoadListener; +import com.elevatemc.potpvp.util.MongoUtils; +import org.bson.Document; +import org.bukkit.Bukkit; +import com.google.gson.reflect.TypeToken; +import org.bukkit.entity.Player; + +import java.lang.reflect.Type; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public final class KitHandler { + + public static final String MONGO_COLLECTION_NAME = "playerKits"; + public static final int KITS_PER_TYPE = 4; + + private final Map> kitData = new ConcurrentHashMap<>(); + + public KitHandler() { + Bukkit.getPluginManager().registerEvents(new KitEditorListener(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new KitItemListener(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new KitLoadListener(), PotPvPSI.getInstance()); + } + + public List getKits(Player player, GameModeKit gameMode) { + List kits = new ArrayList<>(); + + for (Kit kit : kitData.getOrDefault(player.getUniqueId(), ImmutableList.of())) { + if (kit.getType() == gameMode) { + kits.add(kit); + } + } + + return kits; + } + + public Optional getKit(Player player, GameModeKit gameMode, int slot) { + return kitData.getOrDefault(player.getUniqueId(), ImmutableList.of()) + .stream() + .filter(k -> k.getType() == gameMode && k.getSlot() == slot) + .findFirst(); + } + + public Kit saveDefaultKit(Player player, GameModeKit gameModeKit, int slot) { + Kit created = Kit.ofDefaultKit(gameModeKit, "Kit " + slot, slot); + + kitData.computeIfAbsent(player.getUniqueId(), i -> new ArrayList<>()).add(created); + saveKitsAsync(player); + + return created; + } + + public void removeKit(Player player, GameModeKit gameMode, int slot) { + boolean removed = kitData.computeIfAbsent(player.getUniqueId(), i -> new ArrayList<>()) + .removeIf(k -> k.getType() == gameMode && k.getSlot() == slot); + + if (removed) { + saveKitsAsync(player); + } + } + + public void saveKitsAsync(Player player) { + Bukkit.getScheduler().runTaskAsynchronously(PotPvPSI.getInstance(), () -> { + MongoCollection collection = MongoUtils.getCollection(MONGO_COLLECTION_NAME); + List kitJson = (List) JSON.parse(PotPvPSI.getGson().toJson(kitData.getOrDefault(player.getUniqueId(), ImmutableList.of()))); + + Document query = new Document("_id", player.getUniqueId().toString()); + Document kitUpdate = new Document("$set", new Document("kits", kitJson)); + + collection.updateOne(query, kitUpdate, MongoUtils.UPSERT_OPTIONS); + }); + } + + public void wipeKitsForPlayer(UUID target) { + kitData.remove(target); + + MongoCollection collection = MongoUtils.getCollection(MONGO_COLLECTION_NAME); + collection.deleteOne(new Document("_id", target.toString())); + } + + public int wipeKitsWithType(GameModeKit gameMode) { + // remove kits for online players + for (List playerKits : kitData.values()) { + playerKits.removeIf(k -> k.getType() == gameMode); + } + + // then the DB query + // ref: https://docs.mongodb.com/manual/reference/operator/update/pull/ + MongoCollection collection = MongoUtils.getCollection(MONGO_COLLECTION_NAME); + Document typeQuery = new Document("type", gameMode.getId()); + + collection.updateMany( + new Document("kits", new Document("$elemMatch", typeQuery)), + new Document("$pull", new Document("kits", typeQuery)) + ); + + return -1; + } + + public void loadKits(UUID playerUuid) { + MongoCollection collection = MongoUtils.getCollection(MONGO_COLLECTION_NAME); + Document playerKits = collection.find(new Document("_id", playerUuid.toString())).first(); + + if (playerKits != null) { + List kits = playerKits.get("kits", List.class); + Type listKit = new TypeToken>() {}.getType(); + + kitData.put(playerUuid, PotPvPSI.getGson().fromJson(JSON.serialize(kits), listKit)); + } + } + + public void unloadKits(Player player) { + kitData.remove(player.getUniqueId()); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/KitItems.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/KitItems.java new file mode 100644 index 0000000..732920b --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/KitItems.java @@ -0,0 +1,23 @@ +package com.elevatemc.potpvp.kit; + +import lombok.experimental.UtilityClass; +import com.elevatemc.elib.util.ItemUtils; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + + +import static org.bukkit.ChatColor.AQUA; +import static org.bukkit.ChatColor.GRAY; + +@UtilityClass +public final class KitItems { + + public static final ItemStack OPEN_EDITOR_ITEM = new ItemStack(Material.BOOK); + public static final ItemStack ANTIDOTE_ITEM = new ItemStack(Material.POTION, 1, (short) 8196); + + static { + ItemUtils.setDisplayName(OPEN_EDITOR_ITEM, GRAY + "• " + AQUA + "Kit Editor" + GRAY + " •"); + ItemUtils.setDisplayName(ANTIDOTE_ITEM, ChatColor.GREEN + "Antidote"); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/command/KitWipeKitsCommands.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/command/KitWipeKitsCommands.java new file mode 100644 index 0000000..1c62e0e --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/command/KitWipeKitsCommands.java @@ -0,0 +1,28 @@ +package com.elevatemc.potpvp.kit.command; + +import com.elevatemc.elib.util.UUIDUtils; +import com.elevatemc.potpvp.PotPvPSI; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.UUID; + +public final class KitWipeKitsCommands { + + @Command(names = "kit wipetype", permission = "op") + public static void kitWipeKitsType(Player sender, @Parameter(name="gamemode kit") GameModeKit gameModeKit) { + int modified = PotPvPSI.getInstance().getKitHandler().wipeKitsWithType(gameModeKit); + sender.sendMessage(ChatColor.YELLOW + "Wiped " + modified + " " + gameModeKit.getId() + " kits."); + } + + @Command(names = "kit wipeplayer", permission = "op") + public static void kitWipeKitsPlayer(Player sender, @Parameter(name="target") UUID target) { + PotPvPSI.getInstance().getKitHandler().wipeKitsForPlayer(target); + sender.sendMessage(ChatColor.YELLOW + "Wiped " + UUIDUtils.name(target) + "'s kits."); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/listener/KitEditorListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/listener/KitEditorListener.java new file mode 100644 index 0000000..e3d63c9 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/listener/KitEditorListener.java @@ -0,0 +1,52 @@ +package com.elevatemc.potpvp.kit.listener; + +import com.elevatemc.potpvp.kit.menu.editkit.EditKitMenu; +import com.elevatemc.elib.menu.Menu; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryDragEvent; + +/** + * "Modifications" needed to make the EditKitMenu work as expected + */ +public final class KitEditorListener implements Listener { + + /** + * Prevents placing items into the top inventory + */ + @EventHandler + public void onInventoryClick(InventoryClickEvent event) { + Player player = (Player) event.getWhoClicked(); + + if (event.getCursor() == null || event.getCursor().getType() == Material.AIR) { + return; + } + + if (event.getClickedInventory() != event.getView().getTopInventory()) { + return; + } + + if (Menu.getCurrentlyOpenedMenus().get(player.getUniqueId()) instanceof EditKitMenu) { + event.setCancelled(true); + } + } + + /** + * Prevents all forms of dragging (the goal of this is + * to prevent items being put into the top inventory, + * but item dragging overall is too complicated to deal + * with properly so we just disallow dragging.) + */ + @EventHandler + public void onInventoryDrag(InventoryDragEvent event) { + Player player = (Player) event.getWhoClicked(); + + if (Menu.getCurrentlyOpenedMenus().get(player.getUniqueId()) instanceof EditKitMenu) { + event.setCancelled(true); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/listener/KitItemListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/listener/KitItemListener.java new file mode 100644 index 0000000..e7390b4 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/listener/KitItemListener.java @@ -0,0 +1,32 @@ +package com.elevatemc.potpvp.kit.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import com.elevatemc.potpvp.gamemode.menu.editing.EditGameModeMenu; +import com.elevatemc.potpvp.gamemode.menu.editing.EditGameModeKitMenu; +import com.elevatemc.potpvp.kit.KitItems; +import com.elevatemc.potpvp.kit.menu.kits.KitsMenu; +import com.elevatemc.potpvp.lobby.LobbyHandler; +import com.elevatemc.potpvp.util.ItemListener; + +public final class KitItemListener extends ItemListener { + + public KitItemListener() { + addHandler(KitItems.OPEN_EDITOR_ITEM, player -> { + LobbyHandler lobbyHandler = PotPvPSI.getInstance().getLobbyHandler(); + + if (lobbyHandler.isInLobby(player)) { + new EditGameModeMenu(gameMode -> { + if (gameMode.getKits().size() > 1) { + new EditGameModeKitMenu(gameModeKit -> { + new KitsMenu(gameModeKit).openMenu(player); + }, gameMode).openMenu(player); + } else { + new KitsMenu(GameModeKit.byId(gameMode.getId())).openMenu(player); + } + }).openMenu(player); + } + }); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/listener/KitLoadListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/listener/KitLoadListener.java new file mode 100644 index 0000000..711fda1 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/listener/KitLoadListener.java @@ -0,0 +1,22 @@ +package com.elevatemc.potpvp.kit.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerPreLoginEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +public final class KitLoadListener implements Listener { + + @EventHandler(priority = EventPriority.LOWEST) // LOWEST runs first + public void onAsyncPlayerPreLogin(AsyncPlayerPreLoginEvent event) { + PotPvPSI.getInstance().getKitHandler().loadKits(event.getUniqueId()); + } + + @EventHandler(priority = EventPriority.MONITOR) // MONITOR runs last + public void onPlayerQuit(PlayerQuitEvent event) { + PotPvPSI.getInstance().getKitHandler().unloadKits(event.getPlayer()); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/menu/editkit/ArmorButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/menu/editkit/ArmorButton.java new file mode 100644 index 0000000..95e7554 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/menu/editkit/ArmorButton.java @@ -0,0 +1,55 @@ +package com.elevatemc.potpvp.kit.menu.editkit; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.elevatemc.elib.menu.Button; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.List; + +final class ArmorButton extends Button { + + private final ItemStack item; + + ArmorButton(ItemStack item) { + this.item = Preconditions.checkNotNull(item, "item"); + } + + // We just override this whole method, as we need to keep enchants/potion + // data/etc + @Override + public ItemStack getButtonItem(Player player) { + ItemStack newItem = item.clone(); + ItemMeta itemMeta = newItem.getItemMeta(); + + if (itemMeta != null) { + itemMeta.setLore(ImmutableList.of("", ChatColor.DARK_AQUA.toString() + ChatColor.BOLD + "✔ " + ChatColor.AQUA + "This is automatically equipped.")); + newItem.setItemMeta(itemMeta); + } + + return newItem; + } + + // We pass through the item given to us with some lore so all these + // are unused. The fact we have to do this does represent a gap in + // the menu api's functionality, but we can save that for another day. + @Override + public String getName(Player player) { + return null; + } + + @Override + public List getDescription(Player player) { + return null; + } + + @Override + public Material getMaterial(Player player) { + return null; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/menu/editkit/CancelKitEditButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/menu/editkit/CancelKitEditButton.java new file mode 100644 index 0000000..d805349 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/menu/editkit/CancelKitEditButton.java @@ -0,0 +1,57 @@ +package com.elevatemc.potpvp.kit.menu.editkit; + +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.elevatemc.potpvp.kit.menu.kits.KitsMenu; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.util.InventoryUtils; +import com.elevatemc.elib.menu.Button; +import org.bukkit.ChatColor; +import org.bukkit.DyeColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.List; + +final class CancelKitEditButton extends Button { + + private final GameModeKit gameModeKit; + + CancelKitEditButton(GameModeKit gameModeKit) { + this.gameModeKit = Preconditions.checkNotNull(gameModeKit, "gameMode"); + } + + @Override + public String getName(Player player) { + return ChatColor.DARK_RED.toString() + ChatColor.BOLD + "Cancel"; + } + + @Override + public List getDescription(Player player) { + return ImmutableList.of( + ChatColor.RED + "Click this to abort editing your kit,", + ChatColor.RED + "and return to the kit menu." + ); + } + + @Override + public Material getMaterial(Player player) { + return Material.STAINED_CLAY; + } + + @Override + public byte getDamageValue(Player player) { + return DyeColor.RED.getWoolData(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + player.closeInventory(); + InventoryUtils.resetInventoryDelayed(player); + + new KitsMenu(gameModeKit).openMenu(player); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/menu/editkit/EditKitMenu.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/menu/editkit/EditKitMenu.java new file mode 100644 index 0000000..1cb7b70 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/menu/editkit/EditKitMenu.java @@ -0,0 +1,101 @@ +package com.elevatemc.potpvp.kit.menu.editkit; + +import com.elevatemc.elib.eLib; +import com.google.common.base.Preconditions; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.kit.Kit; +import com.elevatemc.potpvp.util.InventoryUtils; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.Potion; +import org.bukkit.potion.PotionType; + +import java.util.HashMap; +import java.util.Map; + +public final class EditKitMenu extends Menu { + + private static final Button ARMOR_NOT_AVAILABLE = Button.placeholder(Material.STAINED_GLASS_PANE, (byte)14, ChatColor.RED + "No Armor"); + private static final Button EDITOR_ITEM_NOT_AVAILABLE = Button.placeholder(Material.STAINED_GLASS_PANE, (byte)14, ChatColor.RED + "N/A"); + + private final Kit kit; + + public EditKitMenu(Kit kit) { + + setPlaceholder(true); + setNoncancellingInventory(true); + setUpdateAfterClick(false); + + this.kit = Preconditions.checkNotNull(kit, "kit"); + } + + @Override + public void onOpen(Player player) { + player.getInventory().setContents(kit.getInventoryContents()); + + Bukkit.getScheduler().runTaskLater(PotPvPSI.getInstance(), player::updateInventory, 1L); + } + + @Override + public void onClose(Player player) { + InventoryUtils.resetInventoryDelayed(player); + } + + @Override + public String getTitle(Player player) { + return ChatColor.BLUE + "Editing " + kit.getName() + " (" + kit.getType().getDisplayName() + ")"; + } + + @Override + public Map getButtons(Player player) { + Map buttons = new HashMap<>(); + + // Buttons to manage the kit + buttons.put(getSlot(1, 1), new KitInfoButton(kit)); + buttons.put(getSlot(1, 2), new SaveButton(kit)); + buttons.put(getSlot(1, 3), new LoadDefaultKitButton(kit.getType())); + buttons.put(getSlot(1, 4), new CancelKitEditButton(kit.getType())); + + // Load armor in the middle + ItemStack[] armorItems = kit.getType().getDefaultArmor(); + int armorLength = armorItems.length; + for (int i = 3; i >= 0; i--) { + int slot = 4 - i; + if (i < armorLength) { + ItemStack armorItem = armorItems[i]; + if (armorItem != null && armorItem.getType() != Material.AIR) { + buttons.put(getSlot(4, slot), new ArmorButton(armorItem)); + continue; + } + } + buttons.put(getSlot(4, slot), ARMOR_NOT_AVAILABLE); + } + + // Load editor items + ItemStack[] editorItems = kit.getType().getEditorItems(); + int editorLength = editorItems.length; + for (int i = 0; i < 4; i++) { + int slot = 1 + i; + if (i < editorLength) { + ItemStack editorItem = editorItems[i]; + if (editorItem != null && editorItem.getType() != Material.AIR) { + buttons.put(getSlot(7, slot), new TakeItemButton(editorItem)); + continue; + } + } + buttons.put(getSlot( 7, slot), EDITOR_ITEM_NOT_AVAILABLE); + } + + return buttons; + } + + @Override + public int size(Player player) { + return 9*6; + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/menu/editkit/KitInfoButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/menu/editkit/KitInfoButton.java new file mode 100644 index 0000000..6202bf9 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/menu/editkit/KitInfoButton.java @@ -0,0 +1,89 @@ +package com.elevatemc.potpvp.kit.menu.editkit; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.kit.menu.kits.KitsMenu; +import com.elevatemc.potpvp.util.InventoryUtils; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.elevatemc.potpvp.kit.Kit; +import com.elevatemc.elib.menu.Button; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.conversations.ConversationContext; +import org.bukkit.conversations.ConversationFactory; +import org.bukkit.conversations.Prompt; +import org.bukkit.conversations.StringPrompt; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +final class KitInfoButton extends Button { + + private final Kit kit; + + KitInfoButton(Kit kit) { + this.kit = Preconditions.checkNotNull(kit, "kit"); + } + + @Override + public String getName(Player player) { + return ChatColor.DARK_AQUA.toString() + ChatColor.BOLD + "Editing " + kit.getName(); + } + + @Override + public List getDescription(Player player) { + return ImmutableList.of( + ChatColor.AQUA + "Click this to rename this kit" + ); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + ConversationFactory factory = new ConversationFactory(PotPvPSI.getInstance()).withFirstPrompt(new StringPrompt() { + + @Override + public String getPromptText(ConversationContext context) { + return ChatColor.DARK_AQUA + "✎ " + ChatColor.AQUA + "Enter the new name..."; + } + + @Override + public Prompt acceptInput(ConversationContext ctx, String s) { + if (s.length() > 20) { + ctx.getForWhom().sendRawMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Kit names can't have more than 20 characters!"); + return Prompt.END_OF_CONVERSATION; + } + + kit.setName(s); + + PotPvPSI.getInstance().getKitHandler().saveKitsAsync(player); + + ctx.getForWhom().sendRawMessage(ChatColor.DARK_GREEN.toString() + ChatColor.BOLD + "✔ " + ChatColor.GREEN + "Your kit has been renamed!"); + if (!PotPvPSI.getInstance().getMatchHandler().isPlayingMatch(player)) { + new EditKitMenu(kit).openMenu(player); + } + return Prompt.END_OF_CONVERSATION; + } + + }).withLocalEcho(false); + + // Go back to spawn modus + kit.setInventoryContents(player.getInventory().getContents()); + PotPvPSI.getInstance().getKitHandler().saveKitsAsync(player); + + player.setItemOnCursor(new ItemStack(Material.AIR)); + + player.closeInventory(); + InventoryUtils.resetInventoryDelayed(player); + + player.closeInventory(); + player.beginConversation(factory.buildConversation(player)); + } + + @Override + public Material getMaterial(Player player) { + return Material.NAME_TAG; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/menu/editkit/LoadDefaultKitButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/menu/editkit/LoadDefaultKitButton.java new file mode 100644 index 0000000..184b9cd --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/menu/editkit/LoadDefaultKitButton.java @@ -0,0 +1,61 @@ +package com.elevatemc.potpvp.kit.menu.editkit; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.elib.menu.Button; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.DyeColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +final class LoadDefaultKitButton extends Button { + + private final GameModeKit gameModeKit; + + LoadDefaultKitButton(GameModeKit gameModeKit) { + this.gameModeKit = Preconditions.checkNotNull(gameModeKit, "gameMode"); + } + + @Override + public String getName(Player player) { + return ChatColor.DARK_PURPLE.toString() + ChatColor.BOLD + "Load default kit"; + } + + @Override + public List getDescription(Player player) { + return ImmutableList.of( + ChatColor.LIGHT_PURPLE + "Click this to load the default kit", + ChatColor.LIGHT_PURPLE + "into the kit editing menu." + ); + } + + @Override + public Material getMaterial(Player player) { + return Material.STAINED_CLAY; + } + + @Override + public byte getDamageValue(Player player) { + return DyeColor.PURPLE.getWoolData(); + } + + @Override + public void clicked(final Player player, int slot, ClickType clickType) { + /* Duplication fix. When players click this button we must set whatever they might have in their hand to air + * Otherwise they can duplicate items infinitely. This exploits kits like archer and axe pvp. */ + player.setItemOnCursor(new ItemStack(Material.AIR)); + + player.getInventory().setContents(gameModeKit.getDefaultInventory()); + + Bukkit.getScheduler().runTaskLater(PotPvPSI.getInstance(), player::updateInventory, 1L); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/menu/editkit/SaveButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/menu/editkit/SaveButton.java new file mode 100644 index 0000000..75cc1b4 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/menu/editkit/SaveButton.java @@ -0,0 +1,62 @@ +package com.elevatemc.potpvp.kit.menu.editkit; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.kit.Kit; +import com.elevatemc.potpvp.kit.menu.kits.KitsMenu; +import com.elevatemc.potpvp.util.InventoryUtils; +import com.elevatemc.potpvp.util.ItemUtils; +import com.elevatemc.elib.menu.Button; +import org.bukkit.ChatColor; +import org.bukkit.DyeColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +final class SaveButton extends Button { + + private final Kit kit; + + SaveButton(Kit kit) { + this.kit = Preconditions.checkNotNull(kit, "kit"); + } + + @Override + public String getName(Player player) { + return ChatColor.DARK_GREEN.toString() + ChatColor.BOLD + "Save"; + } + + @Override + public List getDescription(Player player) { + return ImmutableList.of( + ChatColor.GREEN + "Click this to save your kit." + ); + } + + @Override + public Material getMaterial(Player player) { + return Material.STAINED_CLAY; + } + + @Override + public byte getDamageValue(Player player) { + return DyeColor.LIME.getWoolData(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + kit.setInventoryContents(player.getInventory().getContents()); + PotPvPSI.getInstance().getKitHandler().saveKitsAsync(player); + + player.setItemOnCursor(new ItemStack(Material.AIR)); + + player.closeInventory(); + InventoryUtils.resetInventoryDelayed(player); + new KitsMenu(kit.getType()).openMenu(player); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/menu/editkit/TakeItemButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/menu/editkit/TakeItemButton.java new file mode 100644 index 0000000..7771328 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/menu/editkit/TakeItemButton.java @@ -0,0 +1,48 @@ +package com.elevatemc.potpvp.kit.menu.editkit; + +import com.google.common.base.Preconditions; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.elib.menu.Button; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +final class TakeItemButton extends Button { + + private final ItemStack item; + + TakeItemButton(ItemStack item) { + this.item = Preconditions.checkNotNull(item, "item"); + } + + // We just override this whole method, as we need to keep enchants/potion data/etc + @Override + public ItemStack getButtonItem(Player player) { + return item; + } + + // We pass through the item given to us with some lore so all these + // are unused. The fact we have to do this does represent a gap in + // the menu api's functionality, but we can save that for another day. + @Override public String getName(Player player) { return null; } + @Override public List getDescription(Player player) { return null; } + @Override public Material getMaterial(Player player) { return null; } + + @Override + public void clicked(final Player player, final int slot, ClickType clickType) { + // make the item show up again + Bukkit.getScheduler().runTaskLater(PotPvPSI.getInstance(), () -> { + player.getOpenInventory().getTopInventory().setItem(slot, item); + }, 4L); + } + + @Override + public boolean shouldCancel(Player player, int slot, ClickType clickType) { + return false; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/menu/kits/KitEditButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/menu/kits/KitEditButton.java new file mode 100644 index 0000000..d4fa721 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/menu/kits/KitEditButton.java @@ -0,0 +1,79 @@ +package com.elevatemc.potpvp.kit.menu.kits; + +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.kit.Kit; +import com.elevatemc.potpvp.kit.KitHandler; +import com.elevatemc.potpvp.kit.menu.editkit.EditKitMenu; +import com.elevatemc.elib.menu.Button; +import org.bukkit.ChatColor; +import org.bukkit.DyeColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; + +import java.awt.*; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +final class KitEditButton extends Button { + + private final Optional kitOpt; + private final GameModeKit gameModeKit; + private final int slot; + + KitEditButton(Optional kitOpt, GameModeKit gameModeKit, int slot) { + this.kitOpt = Preconditions.checkNotNull(kitOpt, "kitOpt"); + this.gameModeKit = Preconditions.checkNotNull(gameModeKit, "gameMode"); + this.slot = slot; + } + + @Override + public String getName(Player player) { + return ChatColor.DARK_AQUA + (kitOpt.isPresent() ? kitOpt.get().getName() : "Free slot"); + } + + @Override + public List getDescription(Player player) { + return kitOpt.isPresent() ? + ImmutableList.of( + ChatColor.DARK_AQUA + "┃ " + ChatColor.WHITE + "Edit this kit " + ChatColor.GRAY + "(Click)", + ChatColor.DARK_AQUA + "┃ " + ChatColor.WHITE + "Delete this kit " + ChatColor.GRAY + "(Drop)" + ) : + ImmutableList.of( + ChatColor.DARK_AQUA + "┃ " + ChatColor.WHITE + "Click this to create a new kit" + ); + } + + @Override + public Material getMaterial(Player player) { + return kitOpt.isPresent() ? Material.ENCHANTED_BOOK : Material.INK_SACK; + } + + @Override + public byte getDamageValue(Player player) { + return kitOpt.isPresent() ? 0 : DyeColor.GRAY.getDyeData(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + if (clickType.equals(ClickType.DROP) && kitOpt.isPresent()) { + KitHandler kitHandler = PotPvPSI.getInstance().getKitHandler(); + kitHandler.removeKit(player, gameModeKit, this.slot); + + new KitsMenu(gameModeKit).openMenu(player); + } else { + Kit resolvedKit = kitOpt.orElseGet(() -> { + KitHandler kitHandler = PotPvPSI.getInstance().getKitHandler(); + return kitHandler.saveDefaultKit(player, gameModeKit, this.slot); + }); + + new EditKitMenu(resolvedKit).openMenu(player); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/menu/kits/KitsMenu.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/menu/kits/KitsMenu.java new file mode 100644 index 0000000..b6846f3 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/kit/menu/kits/KitsMenu.java @@ -0,0 +1,79 @@ +package com.elevatemc.potpvp.kit.menu.kits; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import com.elevatemc.potpvp.gamemode.menu.editing.EditGameModeMenu; +import com.elevatemc.potpvp.gamemode.menu.editing.EditGameModeKitMenu; +import com.elevatemc.potpvp.kit.Kit; +import com.elevatemc.potpvp.kit.KitHandler; +import com.elevatemc.potpvp.util.InventoryUtils; +import com.elevatemc.potpvp.util.menu.MenuBackButton; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import org.bukkit.ChatColor; +import org.bukkit.DyeColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public final class KitsMenu extends Menu { + + private final GameModeKit gameModeKit; + + public KitsMenu(GameModeKit gameModeKit) { + setPlaceholder(true); + setAutoUpdate(true); + + this.gameModeKit = gameModeKit; + } + + @Override + public void onClose(Player player) { + InventoryUtils.resetInventoryDelayed(player); + } + + @Override + public String getTitle(Player player) { + return ChatColor.BLUE.toString() + "Your " + gameModeKit.getDisplayName() + " kits"; + } + + @Override + public Map getButtons(Player player) { + KitHandler kitHandler = PotPvPSI.getInstance().getKitHandler(); + Map buttons = new HashMap<>(); + + // kit slots are 1-indexed + for (int kitSlot = 1; kitSlot <= KitHandler.KITS_PER_TYPE; kitSlot++) { + Optional kitOpt = kitHandler.getKit(player, gameModeKit, kitSlot); + int column = (kitSlot * 2) - 1; // - 1 to compensate for this being 0-indexed + + buttons.put(getSlot(column, 1), new KitEditButton(kitOpt, gameModeKit, kitSlot)); + + /*if (kitOpt.isPresent()) { + buttons.put(getSlot(column, 2), new KitRenameButton(kitOpt.get())); + buttons.put(getSlot(column, 3), new KitDeleteButton(gameModeKit, kitSlot)); + } else { + buttons.put(getSlot(column, 2), Button.placeholder(Material.STAINED_GLASS_PANE, DyeColor.GRAY.getWoolData(), "")); + buttons.put(getSlot(column, 3), Button.placeholder(Material.STAINED_GLASS_PANE, DyeColor.GRAY.getWoolData(), "")); + }*/ + } + + buttons.put(getSlot(0, 2), new MenuBackButton(p -> { + new EditGameModeMenu(gameMode -> { + if (gameMode.getKits().size() > 1) { + new EditGameModeKitMenu(gameModeKit -> { + new KitsMenu(gameModeKit).openMenu(player); + }, gameMode).openMenu(player); + } else { + new KitsMenu(GameModeKit.byId(gameMode.getId())).openMenu(player); + } + }).openMenu(player); + })); + + return buttons; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/listener/BasicPreventionListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/listener/BasicPreventionListener.java new file mode 100644 index 0000000..b9a1187 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/listener/BasicPreventionListener.java @@ -0,0 +1,144 @@ +package com.elevatemc.potpvp.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Material; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.*; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.event.inventory.CraftItemEvent; +import org.bukkit.event.inventory.PrepareItemCraftEvent; +import org.bukkit.event.player.*; +import org.bukkit.event.weather.ThunderChangeEvent; +import org.bukkit.event.weather.WeatherChangeEvent; + +public final class BasicPreventionListener implements Listener { + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + event.setJoinMessage(null); + } + + @EventHandler + public void onPlayerKick(PlayerKickEvent event) { + event.setLeaveMessage(null); + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + event.setQuitMessage(null); + } + + @EventHandler + public void onProjectileHit(ProjectileHitEvent event) { + if (event.getEntityType() == EntityType.ARROW) { + event.getEntity().remove(); + } + } + + @EventHandler + public void onThunderChange(ThunderChangeEvent event) { + event.setCancelled(true); + } + + @EventHandler + public void onWeatherChange(WeatherChangeEvent event) { + event.setCancelled(event.toWeatherState()); + } + + @EventHandler + public void onPlayerDeath(PlayerDeathEvent event) { + event.setDeathMessage(null); + event.setDroppedExp(0); + } + + @EventHandler + public void onBlockSpread(BlockSpreadEvent event) { + event.setCancelled(true); + } + + @EventHandler + public void onLeavesDecay(LeavesDecayEvent event) { + event.setCancelled(true); + } + + @EventHandler + public void onBlockFade(BlockFadeEvent event) { + event.setCancelled(true); + } + + @EventHandler + public void onBlockForm(BlockFormEvent event) { + event.setCancelled(true); + } + + @EventHandler + public void onPlayerPortal(PlayerPortalEvent event) { + event.setCancelled(true); + } + + @EventHandler + public void onBlockPlace(BlockPlaceEvent event) { + if (!canInteractWithBlocks(event.getPlayer())) { + event.setCancelled(true); + } + } + + @EventHandler + public void onBlockBreak(BlockBreakEvent event) { + if (!canInteractWithBlocks(event.getPlayer())) { + event.setCancelled(true); + } + } + + private boolean canInteractWithBlocks(Player player) { + if (PotPvPSI.getInstance().getMatchHandler().isPlayingMatch(player)) { + // completely ignore players in matches, MatchBuildListener handles this. + return true; + } + + boolean inLobby = PotPvPSI.getInstance().getLobbyHandler().isInLobby(player); + boolean isCreative = player.getGameMode() == GameMode.CREATIVE; + boolean isOp = player.isOp(); + boolean buildMeta = player.hasMetadata("build"); + + return inLobby && isCreative && isOp && buildMeta; + } + + @EventHandler + public void onPlayerInteract(PlayerInteractEvent event) { + if (event.getAction() == Action.PHYSICAL && event.getClickedBlock().getType() == Material.SOIL) { + event.setCancelled(true); + } + } + + @EventHandler + public void onPrepareCraft(PrepareItemCraftEvent event) { + event.getInventory().setResult(null); + } + + @EventHandler + public void onCraft(CraftItemEvent event) { + event.setCancelled(true); + } + + @EventHandler + public void onPreCommand(PlayerCommandPreprocessEvent event) { + if(event.getMessage().toLowerCase().startsWith("/me") || + event.getMessage().toLowerCase().startsWith("/minecraft:me") || + event.getMessage().toLowerCase().startsWith("/bukkit:me")) { + event.setCancelled(true); + } + } + + @EventHandler + public void onAchievement(PlayerAchievementAwardedEvent event) { + event.setCancelled(true); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/listener/BowHealthListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/listener/BowHealthListener.java new file mode 100644 index 0000000..bd4db19 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/listener/BowHealthListener.java @@ -0,0 +1,43 @@ +package com.elevatemc.potpvp.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.elib.util.PlayerUtils; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; + +public final class BowHealthListener implements Listener { + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onEntityDamageByEntity(EntityDamageByEntityEvent event) { + if (event.getEntityType() != EntityType.PLAYER || !(event.getDamager() instanceof Arrow)) { + return; + } + + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Player hit = (Player) event.getEntity(); + Player damager = PlayerUtils.getDamageSource(event.getDamager()); + + if (damager != null && !damager.getUniqueId().equals(hit.getUniqueId())) { + Bukkit.getScheduler().runTaskLater(PotPvPSI.getInstance(), () -> { + // in case the player died because of this hit + if (!matchHandler.isPlayingMatch(hit)) { + return; + } + + int outOf20 = (int) Math.ceil(hit.getHealth()); + // we specifically divide by 2.0 (not 2) so that we do floating point math + // as integer math will just round away the .5 + damager.sendMessage(ChatColor.GOLD + hit.getName() + "'s health: " + ChatColor.RED + (outOf20 / 2.0) + "❤"); + }, 1L); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/listener/FancyInventoryListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/listener/FancyInventoryListener.java new file mode 100644 index 0000000..3b446f2 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/listener/FancyInventoryListener.java @@ -0,0 +1,24 @@ +package com.elevatemc.potpvp.listener; + +import com.elevatemc.potpvp.util.FancyPlayerInventory; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +// this handles our "Fancy" player inventory used in matches to see +// inventories as spectators. All it really does is show the armor +// at the top, and move the hotbar all the way to the bottom. +public class FancyInventoryListener implements Listener { + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + FancyPlayerInventory.join(event.getPlayer()); + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + FancyPlayerInventory.quit(event.getPlayer()); + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/listener/NightModeListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/listener/NightModeListener.java new file mode 100644 index 0000000..12b71fb --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/listener/NightModeListener.java @@ -0,0 +1,37 @@ +package com.elevatemc.potpvp.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.setting.Setting; +import com.elevatemc.potpvp.setting.SettingHandler; +import com.elevatemc.potpvp.setting.event.SettingUpdateEvent; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; + +public final class NightModeListener implements Listener { + + public static final int NIGHT_TIME = 18_000; + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + SettingHandler settingHandler = PotPvPSI.getInstance().getSettingHandler(); + + if (settingHandler.getSetting(event.getPlayer(), Setting.NIGHT_MODE)) { + event.getPlayer().setPlayerTime(NIGHT_TIME, false); + } + } + + @EventHandler + public void onSettingUpdate(SettingUpdateEvent event) { + if (event.getSetting() != Setting.NIGHT_MODE) { + return; + } + + if (event.isEnabled()) { + event.getPlayer().setPlayerTime(NIGHT_TIME, false); + } else { + event.getPlayer().resetPlayerTime(); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/listener/PearlCooldownListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/listener/PearlCooldownListener.java new file mode 100644 index 0000000..b1f42e8 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/listener/PearlCooldownListener.java @@ -0,0 +1,141 @@ +package com.elevatemc.potpvp.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.match.event.MatchCountdownStartEvent; +import com.elevatemc.potpvp.match.event.MatchTerminateEvent; +import com.lunarclient.bukkitapi.cooldown.LunarClientAPICooldown; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.EnderPearl; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.entity.ProjectileLaunchEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +public final class PearlCooldownListener implements Listener { + + public static final long PEARL_COOLDOWN_MILLIS = TimeUnit.SECONDS.toMillis(16); + + public static Map pearlCooldown = new ConcurrentHashMap<>(); + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onProjectileLaunch(ProjectileLaunchEvent event) { + if (event.getEntityType() != EntityType.ENDER_PEARL) { + return; + } + + EnderPearl pearl = (EnderPearl) event.getEntity(); + Player shooter = (Player) pearl.getShooter(); + + pearlCooldown.put(shooter.getUniqueId(), System.currentTimeMillis() + PEARL_COOLDOWN_MILLIS); + LunarClientAPICooldown.sendCooldown(shooter, "Enderpearl"); + + // cannot be made a lambda because of cancel() usage + new BukkitRunnable() { + + public void run() { + long cooldownExpires = pearlCooldown.getOrDefault(shooter.getUniqueId(), 0L); + + if (cooldownExpires < System.currentTimeMillis()) { + cancel(); + return; + } + + int millisLeft = (int) (cooldownExpires - System.currentTimeMillis()); + float percentLeft = (float) millisLeft / PEARL_COOLDOWN_MILLIS; + + shooter.setExp(percentLeft); + shooter.setLevel(millisLeft / 1_000); + } + + }.runTaskTimer(PotPvPSI.getInstance(), 1L, 1L); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerInteract(PlayerInteractEvent event) { + if (!event.hasItem() || event.getItem().getType() != Material.ENDER_PEARL || !event.getAction().name().contains("RIGHT_")) { + return; + } + + Player player = event.getPlayer(); + long cooldownExpires = pearlCooldown.getOrDefault(player.getUniqueId(), 0L); + + if (cooldownExpires < System.currentTimeMillis()) { + return; + } + + int millisLeft = (int) (cooldownExpires - System.currentTimeMillis()); + double secondsLeft = millisLeft / 1000D; + // round to 1 digit past decimal + secondsLeft = Math.round(10D * secondsLeft) / 10D; + + event.setCancelled(true); + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You cannot use this for another " + ChatColor.BOLD + secondsLeft + ChatColor.RED + " seconds!"); + player.updateInventory(); + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + pearlCooldown.remove(event.getPlayer().getUniqueId()); + LunarClientAPICooldown.clearCooldown(event.getPlayer(), "Enderpearl"); + } + + @EventHandler + public void onPlayerDeath(PlayerDeathEvent event) { + Player player = event.getEntity(); + + // When players die, their enderpearls are still left on the map, + // allowing players to teleport after they die + for (EnderPearl pearl : player.getWorld().getEntitiesByClass(EnderPearl.class)) { + if (pearl.getShooter() == player) { + pearl.remove(); + } + } + + pearlCooldown.remove(player.getUniqueId()); + LunarClientAPICooldown.clearCooldown(player, "Enderpearl"); + } + + // reset pearl cooldowns when ending a match + // this is only so (most) players don't see the cooldown + // in the lobby - the 'actual' reset is the one prior to + // start a match, as with this we can 'forget' players who + // died (and aren't alive anymore) right before the end of + // a match. + @EventHandler + public void onMatchTerminate(MatchTerminateEvent event) { + for (MatchTeam team : event.getMatch().getTeams()) { + team.getAliveMembers().forEach(uuid -> { + pearlCooldown.remove(uuid); + final Player player = Bukkit.getPlayer(uuid); + if(player != null) LunarClientAPICooldown.clearCooldown(player, "Enderpearl"); + }); + } + } + + // see comment on #onMatchTerminate(MatchTerminateEvent) + @EventHandler + public void onMatchCountdownStart(MatchCountdownStartEvent event) { + for (MatchTeam team : event.getMatch().getTeams()) { + team.getAllMembers().forEach(uuid -> { + pearlCooldown.remove(uuid); + final Player player = Bukkit.getPlayer(uuid); + if(player != null) LunarClientAPICooldown.clearCooldown(player, "Enderpearl"); + }); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/listener/RankedMatchQualificationListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/listener/RankedMatchQualificationListener.java new file mode 100644 index 0000000..75ca114 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/listener/RankedMatchQualificationListener.java @@ -0,0 +1,139 @@ +package com.elevatemc.potpvp.listener; + +import com.mongodb.Block; +import com.mongodb.client.MongoCollection; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.match.event.MatchEndEvent; +import com.elevatemc.potpvp.util.MongoUtils; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import com.elevatemc.elib.eLib; +import org.bson.Document; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerPreLoginEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import redis.clients.jedis.Jedis; + +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public final class RankedMatchQualificationListener implements Listener { + + public static final String KEY_PREFIX = "potpvp:rankedMatchQualification:"; + public static final int MIN_MATCH_WINS = 10; + private static final Map rankedMatchQualificationWins = new ConcurrentHashMap<>(); + + public static int getWinsNeededToQualify(UUID playerUuid) { + return Math.max(0, MIN_MATCH_WINS - rankedMatchQualificationWins.getOrDefault(playerUuid, 0)); + } + + public static boolean isQualified(UUID playerUuid) { + return rankedMatchQualificationWins.getOrDefault(playerUuid, 0) >= MIN_MATCH_WINS; + } + + @EventHandler(priority = EventPriority.LOWEST) // LOWEST runs first + public void onAsyncPlayerPreLogin(AsyncPlayerPreLoginEvent event) { + try (Jedis jedis = eLib.getInstance().getLocalJedisPool().getResource()) { + String existing = jedis.get(KEY_PREFIX + event.getUniqueId()); + + if (existing != null && !existing.isEmpty()) { + rankedMatchQualificationWins.put(event.getUniqueId(), Integer.parseInt(existing)); + } + } + } + + @EventHandler(priority = EventPriority.MONITOR) // MONITOR runs last + public void onPlayerQuit(PlayerQuitEvent event) { + rankedMatchQualificationWins.remove(event.getPlayer().getUniqueId()); + } + + @EventHandler + public void onMatchEnd(MatchEndEvent event) { + Match match = event.getMatch(); + + // make sure match was unranked + had a winner + if (match.getWinner() == null || match.isRanked()) { + return; + } + + List teams = match.getTeams(); + + // make sure it's a 1v1 + if (teams.size() != 2 || teams.get(0).getAllMembers().size() != 1 || teams.get(1).getAllMembers().size() != 1) { + return; + } + + Bukkit.getScheduler().runTaskAsynchronously(PotPvPSI.getInstance(), () -> { + try (Jedis jedis = eLib.getInstance().getLocalJedisPool().getResource()) { + UUID winner = match.getWinner().getFirstAliveMember(); + rankedMatchQualificationWins.put(winner, (int) jedis.incr(KEY_PREFIX + winner)); + } + }); + } + + @Command(names = {"rmqRead"}, permission = "op") + public static void rmqRead(Player sender, @Parameter(name="target",defaultValue="self") Player target) { + sender.sendMessage(ChatColor.DARK_PURPLE + "Wins: " + ChatColor.GRAY + rankedMatchQualificationWins.getOrDefault(target.getUniqueId(), 0)); + sender.sendMessage(ChatColor.DARK_PURPLE + "Qualified: " + ChatColor.GRAY + isQualified(target.getUniqueId())); + } + + @Command(names = {"rmqSet"}, permission = "op") + public static void rmqSet(Player sender, @Parameter(name="target") Player target, @Parameter(name="count") int count) { + rankedMatchQualificationWins.put(target.getUniqueId(), count); + + try (Jedis jedis = eLib.getInstance().getLocalJedisPool().getResource()) { + jedis.set(KEY_PREFIX + target.getUniqueId(), String.valueOf(count)); + } + + sender.sendMessage(ChatColor.GOLD + "Updated!"); + } + + @Command(names = {"rmqImport"}, permission = "op", async = true) + public static void rmqImport(Player sender) { + MongoCollection matchCollection = MongoUtils.getCollection(MatchHandler.MONGO_COLLECTION_NAME); + + sender.sendMessage(ChatColor.GOLD + "Starting..."); + + try (Jedis jedis = eLib.getInstance().getLocalJedisPool().getResource()) { + matchCollection.find(new Document("ranked", false).append("winner", new Document("$gte", 0))).forEach((Block) match -> { + List teams = (List) match.get("teams", List.class); + + // make sure we have 2 teams + if (teams.size() != 2) { + return; + } + + // and make sure they both have one player each + for (Document team : teams) { + int size = team.get("allMembers", List.class).size(); + + if (size != 1) { + return; + } + } + + Document winnerTeam = teams.get(match.getInteger("winner")); + List winnerPlayers = (List) winnerTeam.get("allMembers", List.class); + + for (String winnerPlayer : winnerPlayers) { + jedis.incr(KEY_PREFIX + winnerPlayer); + } + + sender.sendMessage(ChatColor.GOLD + "Imported match " + ChatColor.WHITE + match.getObjectId("_id") + ChatColor.GOLD + "."); + }); + } + + sender.sendMessage(ChatColor.GREEN + "Done!"); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/listener/TabCompleteListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/listener/TabCompleteListener.java new file mode 100644 index 0000000..84b5af0 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/listener/TabCompleteListener.java @@ -0,0 +1,34 @@ +package com.elevatemc.potpvp.listener; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerChatTabCompleteEvent; +import org.bukkit.util.StringUtil; + +import java.util.Collection; + +public final class TabCompleteListener implements Listener { + + // we basically copy+paste the CraftBukkit code but modify the + // visiblity check to work better with how PotPvP uses invis + @EventHandler + public void onPlayerChatTabComplete(PlayerChatTabCompleteEvent event) { + String token = event.getLastToken(); + Collection completions = event.getTabCompletions(); + + completions.clear(); + + for (Player player : Bukkit.getOnlinePlayers()) { + if (!event.getPlayer().canSee(player) && player.hasMetadata("invisible")) { + continue; + } + + if (StringUtil.startsWithIgnoreCase(player.getName(), token)) { + completions.add(player.getName()); + } + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/LobbyHandler.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/LobbyHandler.java new file mode 100644 index 0000000..07356c5 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/LobbyHandler.java @@ -0,0 +1,114 @@ +package com.elevatemc.potpvp.lobby; + +import com.elevatemc.elib.eLib; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.follow.FollowHandler; +import com.elevatemc.potpvp.follow.command.UnfollowCommand; +import com.elevatemc.potpvp.lobby.listener.LobbyGeneralListener; +import com.elevatemc.potpvp.lobby.listener.LobbyItemListener; +import com.elevatemc.potpvp.lobby.listener.LobbyParkourListener; +import com.elevatemc.potpvp.lobby.listener.LobbySpecModeListener; +import com.elevatemc.potpvp.util.InventoryUtils; +import com.elevatemc.potpvp.util.PatchedPlayerUtils; +import com.elevatemc.potpvp.util.VisibilityUtils; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffect; + +import java.util.*; + +public final class LobbyHandler { + + /** + * Stores players who are in "spectator mode", which gives them fly mode + * and a different lobby hotbar. This setting is purely cosmetic, it doesn't + * change what a player can/can't do (with the exception of not giving them + * certain clickable items - but that's just a UX decision) + */ + private final Set spectatorMode = new HashSet<>(); + private final Map returnedToLobby = new HashMap<>(); + + public LobbyHandler() { + Bukkit.getPluginManager().registerEvents(new LobbyGeneralListener(this), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new LobbyItemListener(this), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new LobbySpecModeListener(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new LobbyParkourListener(), PotPvPSI.getInstance()); + } + + /** + * Returns a player to the main lobby. This includes performing + * the teleport, clearing their inventory, updating their nametag, + * etc. etc. + * @param player the player who is to be returned + */ + public void returnToLobby(Player player) { + returnToLobbySkipItemSlot(player); + player.getInventory().setHeldItemSlot(0); + } + + private void returnToLobbySkipItemSlot(Player player) { + player.teleport(getLobbyLocation()); + + eLib.getInstance().getNameTagHandler().reloadPlayer(player); + eLib.getInstance().getNameTagHandler().reloadOthersFor(player); + + VisibilityUtils.updateVisibility(player); + PatchedPlayerUtils.resetInventory(player, GameMode.SURVIVAL, true); + InventoryUtils.resetInventoryDelayed(player); + + for(PotionEffect effect : player.getActivePotionEffects()) + { + player.removePotionEffect(effect.getType()); + } + + player.setGameMode(GameMode.SURVIVAL); + + if(player.hasPermission("potpvp.lobby.flight")) { + player.setAllowFlight(true); + } + + returnedToLobby.put(player.getUniqueId(), System.currentTimeMillis()); + } + + public long getLastLobbyTime(Player player) { + return returnedToLobby.getOrDefault(player.getUniqueId(), 0L); + } + + public boolean isInLobby(Player player) { + return !PotPvPSI.getInstance().getMatchHandler().isPlayingOrSpectatingMatch(player); + } + + public boolean isInSpectatorMode(Player player) { + return spectatorMode.contains(player.getUniqueId()); + } + + public void setSpectatorMode(Player player, boolean mode) { + boolean changed; + + if (mode) { + changed = spectatorMode.add(player.getUniqueId()); + } else { + FollowHandler followHandler = PotPvPSI.getInstance().getFollowHandler(); + followHandler.getFollowing(player).ifPresent(i -> UnfollowCommand.unfollow(player)); + + changed = spectatorMode.remove(player.getUniqueId()); + } + + if (changed) { + InventoryUtils.resetInventoryNow(player); + + if (!mode) { + returnToLobbySkipItemSlot(player); + } + } + } + + public Location getLobbyLocation() { + Location spawn = Bukkit.getWorlds().get(0).getSpawnLocation(); + spawn.add(0.5, 0, 0.5); // 'prettify' so players spawn in middle of block + return spawn; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/LobbyItems.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/LobbyItems.java new file mode 100644 index 0000000..acce28c --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/LobbyItems.java @@ -0,0 +1,30 @@ +package com.elevatemc.potpvp.lobby; + +import lombok.experimental.UtilityClass; +import com.elevatemc.elib.util.ItemUtils; +import org.bukkit.ChatColor; +import org.bukkit.DyeColor; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import static org.bukkit.ChatColor.*; + +@UtilityClass +public final class LobbyItems { + + public static final ItemStack SPECTATE_RANDOM_ITEM = new ItemStack(Material.COMPASS); + public static final ItemStack SPECTATE_MENU_ITEM = new ItemStack(Material.PAPER); + public static final ItemStack UNFOLLOW_ITEM = new ItemStack(Material.INK_SACK, 1, DyeColor.RED.getDyeData()); + public static final ItemStack PLAYER_STATISTICS = new ItemStack(Material.EMERALD, 1); + public static final ItemStack CREATE_TEAM = new ItemStack(Material.NAME_TAG, 1); + public static final ItemStack HOST_EVENTS = new ItemStack(Material.BEACON, 1); + + static { + ItemUtils.setDisplayName(SPECTATE_RANDOM_ITEM, GRAY + "• " + AQUA + "Spectate Random Match" + GRAY + " •"); + ItemUtils.setDisplayName(SPECTATE_MENU_ITEM, GRAY + "• " + AQUA + "Spectator Menu" + GRAY + " •"); + ItemUtils.setDisplayName(UNFOLLOW_ITEM, GRAY + "• " + RED + BOLD + "Stop Following" + GRAY + " •"); + ItemUtils.setDisplayName(PLAYER_STATISTICS, GRAY + "• " + AQUA + "Statistics" + GRAY + " •"); + ItemUtils.setDisplayName(CREATE_TEAM, GRAY + "• " + AQUA + "Create Party" + GRAY + " •"); + ItemUtils.setDisplayName(HOST_EVENTS, GRAY + "• " + AQUA + "Host Events" + GRAY + " •"); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/LobbyUtils.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/LobbyUtils.java new file mode 100644 index 0000000..59a49eb --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/LobbyUtils.java @@ -0,0 +1,177 @@ +package com.elevatemc.potpvp.lobby; + +import com.elevatemc.potpvp.events.EventItems; +import com.elevatemc.potpvp.hctranked.game.*; +import lombok.experimental.UtilityClass; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.duel.DuelHandler; +import com.elevatemc.potpvp.follow.FollowHandler; +import com.elevatemc.potpvp.kit.KitItems; +import com.elevatemc.potpvp.kit.menu.editkit.EditKitMenu; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.potpvp.party.PartyHandler; +import com.elevatemc.potpvp.party.PartyItems; +import com.elevatemc.potpvp.queue.QueueHandler; +import com.elevatemc.potpvp.queue.QueueItems; +import com.elevatemc.potpvp.match.rematch.RematchData; +import com.elevatemc.potpvp.match.rematch.RematchHandler; +import com.elevatemc.potpvp.match.rematch.RematchItems; +import com.elevatemc.elib.menu.Menu; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; + +@UtilityClass +public final class LobbyUtils { + + public static void resetInventory(Player player) { + // prevents players with the kit editor from having their + // inventory updated (kit items go into their inventory) + // also, admins in GM don't get invs updated (to prevent annoying those editing kits) + if (Menu.getCurrentlyOpenedMenus().get(player.getName()) instanceof EditKitMenu || player.getGameMode() == GameMode.CREATIVE) { + return; + } + + PartyHandler partyHandler = PotPvPSI.getInstance().getPartyHandler(); + RankedGameHandler gameHandler = PotPvPSI.getInstance().getHCTRankedHandler().getGameHandler(); + PlayerInventory inventory = player.getInventory(); + + inventory.clear(); + inventory.setArmorContents(null); + Inventory inv = player.getOpenInventory().getTopInventory(); + if (inv.getType().equals(InventoryType.CRAFTING)) { + inv.clear(); + } + player.setItemOnCursor(null); + + RankedGame game = gameHandler.getJoinedGame(player); + if (game != null) { + renderRankedGameItems(player, inventory, game); + } else if (partyHandler.hasParty(player)) { + renderPartyItems(player, inventory, partyHandler.getParty(player)); + } else { + renderSoloItems(player, inventory); + } + + if(player.hasPermission("potpvp.lobby.flight")) { + player.setAllowFlight(true); + } + + Bukkit.getScheduler().runTaskLater(PotPvPSI.getInstance(), player::updateInventory, 1L); + } + + private void renderRankedGameItems(Player player, PlayerInventory inventory, RankedGame game) { + RankedGameTeam team = game.getTeam(player); + + inventory.setItem(0, RankedGameItems.GAME_INFO); + inventory.setItem(1, RankedGameItems.ASSIGN_CLASSES); + if (team.getCaptain().equals(player.getUniqueId())) { + if (team.isReady()) { + inventory.setItem(2, RankedGameItems.READY); + } else { + inventory.setItem(2, RankedGameItems.NOT_READY); + } + + } + inventory.setItem(8, RankedGameItems.LEAVE_GAME); + } + + private void renderPartyItems(Player player, PlayerInventory inventory, Party party) { + QueueHandler queueHandler = PotPvPSI.getInstance().getQueueHandler(); + + if (party.isLeader(player.getUniqueId())) { + int partySize = party.getMembers().size(); + + if (partySize == 2) { + if (!queueHandler.isQueuedUnranked(party)) { + inventory.setItem(1, QueueItems.JOIN_PARTY_UNRANKED_QUEUE_ITEM); + inventory.setItem(3, PartyItems.ASSIGN_CLASSES); + } else { + inventory.setItem(1, QueueItems.LEAVE_PARTY_UNRANKED_QUEUE_ITEM); + } + + if (!queueHandler.isQueuedRanked(party)) { + inventory.setItem(2, QueueItems.JOIN_PARTY_RANKED_QUEUE_ITEM); + inventory.setItem(3, PartyItems.ASSIGN_CLASSES); + } else { + inventory.setItem(2, QueueItems.LEAVE_PARTY_RANKED_QUEUE_ITEM); + } + } else if (partySize > 2 && !queueHandler.isQueued(party)) { + inventory.setItem(1, PartyItems.START_TEAM_SPLIT_ITEM); + inventory.setItem(2, PartyItems.START_FFA_ITEM); + inventory.setItem(3, PartyItems.ASSIGN_CLASSES); + } + + } else { + int partySize = party.getMembers().size(); + if (partySize >= 2) { + inventory.setItem(1, PartyItems.ASSIGN_CLASSES); + } + } + + inventory.setItem(0, PartyItems.PARTY_INFO); + inventory.setItem(6, PartyItems.OTHER_PARTIES_ITEM); + inventory.setItem(7, KitItems.OPEN_EDITOR_ITEM); + inventory.setItem(8, PartyItems.LEAVE_PARTY_ITEM); + } + + private void renderSoloItems(Player player, PlayerInventory inventory) { + RematchHandler rematchHandler = PotPvPSI.getInstance().getRematchHandler(); + QueueHandler queueHandler = PotPvPSI.getInstance().getQueueHandler(); + DuelHandler duelHandler = PotPvPSI.getInstance().getDuelHandler(); + FollowHandler followHandler = PotPvPSI.getInstance().getFollowHandler(); + LobbyHandler lobbyHandler = PotPvPSI.getInstance().getLobbyHandler(); + + boolean specMode = lobbyHandler.isInSpectatorMode(player); + boolean followingSomeone = followHandler.getFollowing(player).isPresent(); + + player.setAllowFlight(player.getGameMode() == GameMode.CREATIVE || specMode); + + if (specMode || followingSomeone) { + if (!followingSomeone) { + inventory.setItem(3, LobbyItems.SPECTATE_RANDOM_ITEM); + inventory.setItem(5, LobbyItems.SPECTATE_MENU_ITEM); + } else { + inventory.setItem(8, LobbyItems.UNFOLLOW_ITEM); + } + } else { + RematchData rematchData = rematchHandler.getRematchData(player); + + if (rematchData != null) { + Player target = Bukkit.getPlayer(rematchData.getTarget()); + + if (target != null) { + if (duelHandler.findInvite(player, target) != null) { + // if we've sent an invite to them + inventory.setItem(2, RematchItems.SENT_REMATCH_ITEM); + } else if (duelHandler.findInvite(target, player) != null) { + // if they've sent us an invite + inventory.setItem(2, RematchItems.ACCEPT_REMATCH_ITEM); + } else { + // if no one has sent an invite + inventory.setItem(2, RematchItems.REQUEST_REMATCH_ITEM); + } + } + } + + if (queueHandler.isQueuedRanked(player.getUniqueId())) { + inventory.setItem(8, QueueItems.LEAVE_SOLO_UNRANKED_QUEUE_ITEM); + } else if (queueHandler.isQueuedUnranked(player.getUniqueId())) { + inventory.setItem(8, QueueItems.LEAVE_SOLO_UNRANKED_QUEUE_ITEM); + } else { + inventory.setItem(0, QueueItems.JOIN_SOLO_UNRANKED_QUEUE_ITEM); + inventory.setItem(1, QueueItems.JOIN_SOLO_RANKED_QUEUE_ITEM); + inventory.setItem(4, LobbyItems.CREATE_TEAM); + inventory.setItem(7, LobbyItems.PLAYER_STATISTICS); + inventory.setItem(8, KitItems.OPEN_EDITOR_ITEM); + + final ItemStack eventItem = EventItems.getEventItem(); + if(eventItem != null) inventory.setItem(3, EventItems.getEventItem()); + } + } + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/listener/LobbyGeneralListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/listener/LobbyGeneralListener.java new file mode 100644 index 0000000..3b8c544 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/listener/LobbyGeneralListener.java @@ -0,0 +1,171 @@ +package com.elevatemc.potpvp.lobby.listener; + +import com.elevatemc.elib.util.PlayerUtils; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.lobby.LobbyHandler; +import com.elevatemc.potpvp.lobby.listener.LobbyParkourListener.Parkour; +import com.elevatemc.elib.menu.Menu; +import com.elevatemc.spigot.viaversion.api.Via; +import com.lunarclient.bukkitapi.LunarClientAPI; +import com.lunarclient.bukkitapi.serverrule.LunarClientAPIServerRule; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.GameMode; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.FoodLevelChangeEvent; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryDragEvent; +import org.bukkit.event.inventory.InventoryMoveItemEvent; +import org.bukkit.event.player.*; +import org.bukkit.inventory.InventoryHolder; +import org.github.paperspigot.Title; +import org.spigotmc.event.player.PlayerSpawnLocationEvent; + +public final class LobbyGeneralListener implements Listener { + + private final LobbyHandler lobbyHandler; + + public LobbyGeneralListener(LobbyHandler lobbyHandler) { + this.lobbyHandler = lobbyHandler; + } + + @EventHandler + public void onPlayerSpawnLocation(PlayerSpawnLocationEvent event) { + Parkour parkour = LobbyParkourListener.getParkourMap().get(event.getPlayer().getUniqueId()); + if (parkour != null && parkour.getCheckpoint() != null) { + event.setSpawnLocation(parkour.getCheckpoint().getLocation()); + return; + } + + event.setSpawnLocation(lobbyHandler.getLobbyLocation()); + } + + private Title WELCOME_TITLE = Title.builder().title(ChatColor.DARK_AQUA.toString() + ChatColor.BOLD + "Elevate Practice").subtitle("Welcome to Season 3!").stay(100).build(); + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + LunarClientAPIServerRule.sendServerRule(player); + PlayerUtils.sendTitle(player, WELCOME_TITLE); + lobbyHandler.returnToLobby(player); + } + + @EventHandler + public void onEntityDamage(EntityDamageEvent event) { + if (event.getEntityType() != EntityType.PLAYER) { + return; + } + + Player player = (Player) event.getEntity(); + + if (lobbyHandler.isInLobby(player)) { + if (event.getCause() == EntityDamageEvent.DamageCause.VOID) { + lobbyHandler.returnToLobby(player); + } + + event.setCancelled(true); + } + } + + @EventHandler + public void onFoodLevelChange(FoodLevelChangeEvent event) { + if (lobbyHandler.isInLobby((Player) event.getEntity())) { + event.setCancelled(true); + } + } + + @EventHandler + public void onPlayerPickupItem(PlayerPickupItemEvent event) { + if (lobbyHandler.isInLobby(event.getPlayer())) { + event.setCancelled(true); + } + } + + @EventHandler + public void onPlayerDropItem(PlayerDropItemEvent event) { + Player player = event.getPlayer(); + + if (!lobbyHandler.isInLobby(player)) { + return; + } + + Menu openMenu = Menu.getCurrentlyOpenedMenus().get(player.getUniqueId()); + if (player.hasMetadata("build") || (openMenu != null && openMenu.isNoncancellingInventory())) { + event.getItemDrop().remove(); + } else { + event.setCancelled(true); + } + } + + // cancel inventory interaction in the lobby except for menus + @EventHandler + public void onInventoryClick(InventoryClickEvent event) { + Player clicked = (Player) event.getWhoClicked(); + + if (!lobbyHandler.isInLobby(clicked) || clicked.hasMetadata("build") || Menu.getCurrentlyOpenedMenus().containsKey(clicked.getUniqueId())) { + return; + } + + event.setCancelled(true); + } + + @EventHandler + public void onInventoryDrag(InventoryDragEvent event) { + Player clicked = (Player) event.getWhoClicked(); + + if (!lobbyHandler.isInLobby(clicked) || clicked.hasMetadata("build") || Menu.getCurrentlyOpenedMenus().containsKey(clicked.getUniqueId())) { + return; + } + + event.setCancelled(true); + } + + @EventHandler + public void onPlayerDeath(PlayerDeathEvent event) { + if (lobbyHandler.isInLobby(event.getEntity())) { + event.getDrops().clear(); + } + } + + @EventHandler + public void onInventoryMove(InventoryMoveItemEvent event) { + InventoryHolder inventoryHolder = event.getSource().getHolder(); + + if (inventoryHolder instanceof Player) { + Player player = (Player) inventoryHolder; + + if (!lobbyHandler.isInLobby(player) || Menu.getCurrentlyOpenedMenus().containsKey(player.getUniqueId())) { + return; + } + + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerInteract(PlayerInteractEvent event) { + GameMode gameMode = event.getPlayer().getGameMode(); + + if (lobbyHandler.isInLobby(event.getPlayer()) && gameMode != GameMode.CREATIVE) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onPlayerTeleport(PlayerTeleportEvent event) { + if (event.getCause() != PlayerTeleportEvent.TeleportCause.ENDER_PEARL) { + return; + } + + if (lobbyHandler.isInLobby(event.getPlayer())) { + event.setCancelled(true); + } + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/listener/LobbyItemListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/listener/LobbyItemListener.java new file mode 100644 index 0000000..a020e7d --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/listener/LobbyItemListener.java @@ -0,0 +1,76 @@ +package com.elevatemc.potpvp.lobby.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.follow.command.UnfollowCommand; +import com.elevatemc.potpvp.lobby.LobbyHandler; +import com.elevatemc.potpvp.lobby.LobbyItems; +import com.elevatemc.potpvp.lobby.menu.SpectateMenu; +import com.elevatemc.potpvp.lobby.menu.StatisticsMenu; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.match.MatchState; +import com.elevatemc.potpvp.util.ItemListener; +import com.elevatemc.potpvp.validation.PotPvPValidation; +import com.elevatemc.elib.eLib; +import org.bukkit.ChatColor; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.*; + +public final class LobbyItemListener extends ItemListener { + + private final Map canUseRandomSpecItem = new HashMap<>(); + + public LobbyItemListener(LobbyHandler lobbyHandler) { + addHandler(LobbyItems.CREATE_TEAM, player -> player.performCommand("team create")); + + addHandler(LobbyItems.HOST_EVENTS, player -> player.performCommand("host")); + + addHandler(LobbyItems.SPECTATE_MENU_ITEM, player -> { + if (PotPvPValidation.canUseSpectateItemIgnoreMatchSpectating(player)) { + new SpectateMenu().openMenu(player); + } + }); + + addHandler(LobbyItems.SPECTATE_RANDOM_ITEM, player -> { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + if (!PotPvPValidation.canUseSpectateItemIgnoreMatchSpectating(player)) { + return; + } + + if (canUseRandomSpecItem.getOrDefault(player.getUniqueId(), 0L) > System.currentTimeMillis()) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Please wait before doing this again!"); + return; + } + + List matches = new ArrayList<>(matchHandler.getHostedMatches()); + matches.removeIf(m -> m.isSpectator(player.getUniqueId()) || m.getState() == MatchState.ENDING); + + if (matches.isEmpty()) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "There are no matches available to spectate."); + } else { + Match currentlySpectating = matchHandler.getMatchSpectating(player); + Match newSpectating = matches.get(PotPvPSI.RANDOM.nextInt(matches.size())); + + if (currentlySpectating != null) { + currentlySpectating.removeSpectator(player, false); + } + + newSpectating.addSpectator(player, null); + canUseRandomSpecItem.put(player.getUniqueId(), System.currentTimeMillis() + 3_000L); + } + }); + + addHandler(LobbyItems.PLAYER_STATISTICS, player -> new StatisticsMenu(player).openMenu(player)); + + addHandler(LobbyItems.UNFOLLOW_ITEM, UnfollowCommand::unfollow); + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + canUseRandomSpecItem.remove(event.getPlayer().getUniqueId()); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/listener/LobbyParkourListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/listener/LobbyParkourListener.java new file mode 100644 index 0000000..4718ec6 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/listener/LobbyParkourListener.java @@ -0,0 +1,114 @@ +package com.elevatemc.potpvp.lobby.listener; + +import com.google.common.collect.Maps; +import lombok.Getter; +import lombok.Setter; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.Map; +import java.util.UUID; + +public class LobbyParkourListener implements Listener { + + @Getter private static Map parkourMap = Maps.newHashMap(); + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = false) + public void onPlayerInteractEvent(PlayerInteractEvent event) { + Player player = event.getPlayer(); + + if (event.getAction() == Action.PHYSICAL) { + Block block = event.getClickedBlock(); + + if (block.getType() == Material.IRON_PLATE) { + event.setCancelled(true); + if (block.getRelative(BlockFace.DOWN).getType() == Material.EMERALD_BLOCK) { + if (Parkour.getStartingCheckpoint() == null) { + Parkour.setStartingCheckpoint(new Parkour.Checkpoint(block.getLocation())); + } + + Parkour parkour = parkourMap.get(player.getUniqueId()); + if (parkour == null) { + parkour = new Parkour(); + + parkour.getCheckpoint().ring(player); + + parkourMap.put(player.getUniqueId(), parkour); + player.sendMessage(ChatColor.YELLOW + "You've started the " + ChatColor.GREEN + "parkour" + ChatColor.YELLOW + " challenge!"); + } + + } else if (block.getRelative(BlockFace.DOWN).getType() == Material.REDSTONE_BLOCK) { + Parkour parkour = parkourMap.get(player.getUniqueId()); + if (parkour != null) { + player.sendMessage(ChatColor.YELLOW + "You've finished the " + ChatColor.GREEN + "parkour" + ChatColor.YELLOW + " challenge!"); + parkour.getCheckpoint().ring(player); + parkourMap.remove(player.getUniqueId()); + } + } + } else if (block.getType() == Material.GOLD_PLATE) { + event.setCancelled(true); + Parkour parkour = parkourMap.get(player.getUniqueId()); + if (parkour != null) { + Parkour.Checkpoint checkpoint = parkour.getCheckpoint(); + if (!checkpoint.getLocation().equals(block.getLocation())) { + parkour.setCheckpoint(new Parkour.Checkpoint(block.getLocation())); + parkour.getCheckpoint().ring(player); + player.sendMessage(ChatColor.YELLOW + "You've reached a new" + ChatColor.GOLD + " checkpoint" + ChatColor.YELLOW + "!"); + } + } + } + } + } + + @EventHandler + public void onQuit(PlayerQuitEvent event) { + parkourMap.remove(event.getPlayer().getUniqueId()); + } + + public static class Parkour { + + private static Checkpoint startingCheckpoint; + + @Getter private final long timeStarted; + @Getter @Setter private Checkpoint checkpoint; + + public Parkour() { + this.timeStarted = System.currentTimeMillis(); + if (startingCheckpoint != null) { + checkpoint = startingCheckpoint; + } + } + + public static class Checkpoint { + @Getter private final Location location; + + public Checkpoint(Location location) { + this.location = location; + } + + public void ring(Player player) { + player.playSound(player.getLocation(), Sound.NOTE_PLING, 1, 0); + } + + } + + public static Checkpoint getStartingCheckpoint() { + return startingCheckpoint; + } + + public static void setStartingCheckpoint(Checkpoint startingCheckpoint) { + Parkour.startingCheckpoint = startingCheckpoint; + } + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/listener/LobbySpecModeListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/listener/LobbySpecModeListener.java new file mode 100644 index 0000000..d78e86a --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/listener/LobbySpecModeListener.java @@ -0,0 +1,30 @@ +package com.elevatemc.potpvp.lobby.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.party.event.PartyCreateEvent; +import com.elevatemc.potpvp.party.event.PartyMemberJoinEvent; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; + +public final class LobbySpecModeListener implements Listener { + + @EventHandler + public void onPartyMemberJoin(PartyMemberJoinEvent event) { + PotPvPSI.getInstance().getLobbyHandler().setSpectatorMode(event.getMember(), false); + } + + @EventHandler + public void onPartyCreate(PartyCreateEvent event) { + Player leader = Bukkit.getPlayer(event.getParty().getLeader()); + PotPvPSI.getInstance().getLobbyHandler().setSpectatorMode(leader, false); + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + PotPvPSI.getInstance().getLobbyHandler().setSpectatorMode(event.getPlayer(), false); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/menu/HelpMenu.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/menu/HelpMenu.java new file mode 100644 index 0000000..0385f2d --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/menu/HelpMenu.java @@ -0,0 +1,27 @@ +package com.elevatemc.potpvp.lobby.menu; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; + +public class HelpMenu extends Menu { + + @Override + public String getTitle(Player player) { + return "Help"; + } + + @Override + public Map getButtons(Player player) { + Map buttons = new HashMap<>(); + + buttons.put(getSlot(4, 0), Button.placeholder(Material.MAP, ChatColor.BLUE + "Help")); + + return buttons; + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/menu/SpectateButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/menu/SpectateButton.java new file mode 100644 index 0000000..acb250d --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/menu/SpectateButton.java @@ -0,0 +1,98 @@ +package com.elevatemc.potpvp.lobby.menu; + +import com.google.common.base.Preconditions; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.validation.PotPvPValidation; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.util.UUIDUtils; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +final class SpectateButton extends Button { + + private final Match match; + + SpectateButton(Match match) { + this.match = Preconditions.checkNotNull(match, "match"); + } + + @Override + public String getName(Player player) { + return ChatColor.YELLOW.toString() + ChatColor.BOLD + match.getSimpleDescription(false); + } + + @Override + public List getDescription(Player player) { + List description = new ArrayList<>(); + MatchTeam teamA = match.getTeams().get(0); + MatchTeam teamB = match.getTeams().get(1); + + if (match.isRanked()) { + description.add(ChatColor.GREEN + "Competitive"); + } else { + description.add(ChatColor.GRAY + "Casual"); + } + + description.add(""); + description.add(ChatColor.DARK_AQUA + "Gamemode: " + ChatColor.WHITE + match.getGameMode().getName()); + description.add(ChatColor.DARK_AQUA + "Arena: " + ChatColor.WHITE + PotPvPSI.getInstance().getArenaHandler().getSchematic(match.getArena().getSchematic()).getDisplayName()); + + List spectators = new ArrayList<>(match.getSpectators()); + // don't count actual players and players in silent mode. + spectators.removeIf(uuid -> Bukkit.getPlayer(uuid) != null && Bukkit.getPlayer(uuid).hasMetadata("modmode") || match.getPreviousTeam(uuid) != null); + + if (teamA.getAliveMembers().size() != 1 || teamB.getAliveMembers().size() != 1) { + description.add(""); + + for (UUID member : teamA.getAliveMembers()) { + description.add(ChatColor.AQUA + UUIDUtils.name(member)); + } + + description.add(ChatColor.DARK_AQUA + " vs."); + + for (UUID member : teamB.getAliveMembers()) { + description.add(ChatColor.AQUA + UUIDUtils.name(member)); + } + } + + description.add(""); + description.add(ChatColor.GREEN + "(Left Click to Spectate)"); + + return description; + } + + @Override + public Material getMaterial(Player player) { + return match.getGameMode().getIcon().getItemType(); + } + + @Override + public byte getDamageValue(Player player) { + return match.getGameMode().getIcon().getData(); + } + + @Override + public void clicked(Player player, int i, ClickType clickType) { + if (!PotPvPValidation.canUseSpectateItemIgnoreMatchSpectating(player)) { + return; + } + + Match currentlySpectating = PotPvPSI.getInstance().getMatchHandler().getMatchSpectating(player); + + if (currentlySpectating != null) { + currentlySpectating.removeSpectator(player, false); + } + + match.addSpectator(player, null); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/menu/SpectateMenu.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/menu/SpectateMenu.java new file mode 100644 index 0000000..6dabc6c --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/menu/SpectateMenu.java @@ -0,0 +1,81 @@ +package com.elevatemc.potpvp.lobby.menu; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchState; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.setting.Setting; +import com.elevatemc.potpvp.setting.SettingHandler; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.pagination.PaginatedMenu; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public final class SpectateMenu extends PaginatedMenu { + + public SpectateMenu() { + setAutoUpdate(true); + } + + @Override + public String getPrePaginatedTitle(Player player) { + return "Spectate a match"; + } + + @Override + public Map getAllPagesButtons(Player player) { + SettingHandler settingHandler = PotPvPSI.getInstance().getSettingHandler(); + Map buttons = new HashMap<>(); + int i = 0; + + for (Match match : PotPvPSI.getInstance().getMatchHandler().getHostedMatches()) { + // players can view this menu while spectating + if (match.isSpectator(player.getUniqueId())) { + continue; + } + + if (match.getState() == MatchState.ENDING) { + continue; + } + + if (!PotPvPSI.getInstance().getTournamentHandler().isInTournament(match)) { + int numTotalPlayers = 0; + int numSpecDisabled = 0; + + for (MatchTeam team : match.getTeams()) { + for (UUID member : team.getAliveMembers()) { + numTotalPlayers++; + + if (!settingHandler.getSetting(Bukkit.getPlayer(member), Setting.ALLOW_SPECTATORS)) { + numSpecDisabled++; + } + } + } + + // if >= 50% of participants have spectators disabled + // we won't render this match in the menu + if ((float) numSpecDisabled / (float) numTotalPlayers >= 0.5) { + continue; + } + } + + buttons.put(i++, new SpectateButton(match)); + } + + return buttons; + } + + // we lock the size of this inventory at full, otherwise we'll have + // issues if it 'grows' into the next line while it's open (say we open + // the menu with 8 entries, then it grows to 11 [and onto the second row] + // - this breaks things) + @Override + public int size(Player buttons) { + return 9 * 6; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/menu/StatisticsMenu.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/menu/StatisticsMenu.java new file mode 100644 index 0000000..f3d5831 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/menu/StatisticsMenu.java @@ -0,0 +1,60 @@ +package com.elevatemc.potpvp.lobby.menu; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.lobby.menu.statistics.GlobalEloButton; +import com.elevatemc.potpvp.lobby.menu.statistics.KitButton; +import com.elevatemc.potpvp.lobby.menu.statistics.PlayerButton; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import com.elevatemc.elib.util.ItemBuilder; +import org.bukkit.DyeColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; + +public final class StatisticsMenu extends Menu { + private Player target; + + public StatisticsMenu(Player target) { + this.target = target; + + setAutoUpdate(true); + setPlaceholder(true); + } + + @Override + public String getTitle(Player player) { + return "Statistics"; + } + + @Override + public Map getButtons(Player player) { + Map buttons = new HashMap<>(); + + buttons.put(getSlot(4, 1), new GlobalEloButton(target)); + + int y = 3; + int x = 1; + + for (GameMode gameMode : GameMode.getAll()) { + if (!gameMode.getSupportsCompetitive()) continue; + + buttons.put(getSlot(x++, y), new KitButton(gameMode, target)); + + if (x == 8) { + y++; + x = 1; + } + } + + return buttons; + } + + @Override + public int size(Player buttons) { + return 9 * 6; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/menu/statistics/GlobalEloButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/menu/statistics/GlobalEloButton.java new file mode 100644 index 0000000..bcc4334 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/menu/statistics/GlobalEloButton.java @@ -0,0 +1,51 @@ +package com.elevatemc.potpvp.lobby.menu.statistics; + +import com.google.common.collect.Lists; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.elo.EloHandler; +import com.elevatemc.elib.menu.Button; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.Map.Entry; + +public class GlobalEloButton extends Button { + private Player target; + + public GlobalEloButton(Player target) { + this.target = target; + } + + private static EloHandler eloHandler = PotPvPSI.getInstance().getEloHandler(); + + @Override + public String getName(Player player) { + return ChatColor.AQUA + ChatColor.BOLD.toString() + "Global ❘ Top 10"; + } + + @Override + public List getDescription(Player player) { + List description = Lists.newArrayList(); + + description.add(ChatColor.GRAY.toString() + ChatColor.STRIKETHROUGH + ""); + + int counter = 1; + + for (Entry entry : eloHandler.topElo(null).entrySet()) { + String color = String.valueOf(ChatColor.AQUA); + description.add(color + "#" + counter + ChatColor.AQUA + ": " + entry.getKey() + ChatColor.GRAY + " - " + ChatColor.AQUA + entry.getValue()); + + counter++; + } + + + return description; + } + + @Override + public Material getMaterial(Player player) { + return Material.NETHER_STAR; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/menu/statistics/KitButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/menu/statistics/KitButton.java new file mode 100644 index 0000000..753ed70 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/menu/statistics/KitButton.java @@ -0,0 +1,60 @@ +package com.elevatemc.potpvp.lobby.menu.statistics; + +import com.google.common.collect.Lists; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.elo.EloHandler; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.elib.menu.Button; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.Map.Entry; + +public class KitButton extends Button { + + private static EloHandler eloHandler = PotPvPSI.getInstance().getEloHandler(); + + private GameMode gameMode; + private Player target; + + public KitButton(GameMode gameMode, Player target) { + this.target = target; + this.gameMode = gameMode; + } + + @Override + public String getName(Player player) { + return ChatColor.AQUA + ChatColor.BOLD.toString() + gameMode.getName() + " ❘ Top 10"; + } + + @Override + public List getDescription(Player player) { + List description = Lists.newArrayList(); + + description.add(ChatColor.GRAY.toString() + ChatColor.STRIKETHROUGH + ""); + + int counter = 1; + + for (Entry entry : eloHandler.topElo(gameMode).entrySet()) { + String color = ChatColor.AQUA.toString(); + description.add(color + "#" + counter + ChatColor.AQUA + ": " + entry.getKey() + ChatColor.GRAY + ChatColor.GRAY + " - " + ChatColor.WHITE + entry.getValue()); + + counter++; + } + + + return description; + } + + @Override + public Material getMaterial(Player player) { + return gameMode.getIcon().getItemType(); + } + + @Override + public byte getDamageValue(Player player) { + return gameMode.getIcon().getData(); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/menu/statistics/PlayerButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/menu/statistics/PlayerButton.java new file mode 100644 index 0000000..1c5900b --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/lobby/menu/statistics/PlayerButton.java @@ -0,0 +1,64 @@ +package com.elevatemc.potpvp.lobby.menu.statistics; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.google.common.collect.Lists; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.elo.EloHandler; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.util.UUIDUtils; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public class PlayerButton extends Button { + + private static final EloHandler eloHandler = PotPvPSI.getInstance().getEloHandler(); + private static final ProfileHandler profileHandler = PotPvPSI.getInstance().getPrime().getModuleHandler().getModule(ProfileHandler.class); + + @Override + public String getName(Player player) { + return getColoredName(player.getUniqueId()) + ChatColor.WHITE + "'s Statistics"; + } + + @Override + public List getDescription(Player player) { + List description = Lists.newArrayList(); + + description.add(ChatColor.GRAY.toString() + ChatColor.STRIKETHROUGH + ""); + description.add(ChatColor.DARK_AQUA + ChatColor.BOLD.toString() + "Global" + ChatColor.GRAY + ": " + ChatColor.WHITE + eloHandler.getGlobalElo(player.getUniqueId())); + description.add(ChatColor.GRAY.toString() + ChatColor.STRIKETHROUGH + ""); + + for (GameMode gameMode : GameMode.getAll()) { + if (gameMode.getSupportsCompetitive()) { + description.add(ChatColor.DARK_AQUA + gameMode.getName() + ChatColor.GRAY + " - " + ChatColor.WHITE + eloHandler.getElo(player, gameMode)); + } + } + + return description; + } + + @Override + public Material getMaterial(Player player) { + return Material.SKULL_ITEM; + } + + @Override + public byte getDamageValue(Player player) { + return (byte) 3; + } + + private String getColoredName(UUID uuid) { + Optional profileOptional = profileHandler.getProfile(uuid); + if(!profileOptional.isPresent()) { + return UUIDUtils.name(uuid); + } + + return profileOptional.get().getColoredName(); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/Match.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/Match.java new file mode 100644 index 0000000..743471d --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/Match.java @@ -0,0 +1,678 @@ +package com.elevatemc.potpvp.match; + +import com.elevatemc.elib.util.PlayerUtils; +import com.elevatemc.potpvp.ability.Ability; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.elevatemc.potpvp.match.listener.MatchKitSelectionListener; +import com.google.common.base.Preconditions; +import com.google.common.collect.*; +import lombok.Getter; +import lombok.Setter; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.Arena; +import com.elevatemc.potpvp.elo.EloCalculator; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.lobby.LobbyHandler; +import com.elevatemc.potpvp.match.event.*; +import com.elevatemc.potpvp.postmatchinv.PostMatchPlayer; +import com.elevatemc.potpvp.setting.SettingHandler; +import com.elevatemc.potpvp.util.*; +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.util.UUIDUtils; +import org.bson.Document; +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import com.google.gson.JsonObject; +import org.bukkit.entity.Player; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.BlockVector; +import org.bukkit.util.Vector; +import org.github.paperspigot.Title; + +import java.util.*; +import java.util.stream.Collectors; + +public final class Match { + + private static final int MATCH_END_DELAY_SECONDS = 3; + + @Getter + private final String _id = UUID.randomUUID().toString().substring(0, 7); + + @Getter + private final GameMode gameMode; + @Getter + private final Arena arena; + @Getter + private final List teams; // immutable so @Getter is ok + private final Map postMatchPlayers = new HashMap<>(); + private final Set spectators = new HashSet<>(); + + @Getter + private MatchTeam winner; + @Getter + private MatchEndReason endReason; + @Getter + private MatchState state; + @Getter + private Date startedAt; + @Getter + private Date endedAt; + @Getter + private boolean ranked; + + // we track if matches should give a rematch diamond manually. previouly + // we just checked if both teams had 1 player on them, but this wasn't + // always accurate. Scenarios like a team split of a 3 man team (with one + // sitting out) would get treated as a 1v1 when calculating rematches. + // https://github.com/ElevateOrb/PotPvP-SI/issues/19 + // this will also be set to false for ranked matches (which don't allow + // rematches) + @Getter + private boolean allowRematches; + @Getter + @Setter + private EloCalculator.Result eloChange; + + // this will keep track of blocks placed by players during this match. + // it'll only be populated if the GameMode allows building in the first place. + private final Set placedBlocks = new HashSet<>(); + + // we only spectators generate one message (either a join or a leave) + // per match, to prevent spam. This tracks who has used their one message + private final transient Set spectatorMessagesUsed = new HashSet<>(); + + @Getter + private Map lastHit = Maps.newHashMap(); + @Getter + private Map combos = Maps.newHashMap(); + @Getter + private Map totalHits = Maps.newHashMap(); + @Getter + private Map longestCombo = Maps.newHashMap(); + @Getter + private Map totalTags = Maps.newHashMap(); + @Getter + private Map missedPots = Maps.newHashMap(); + + + @Getter + private Set allPlayers = Sets.newHashSet(); + + @Getter + private Set winningPlayers; + @Getter + private Set losingPlayers; + + public Match(com.elevatemc.potpvp.gamemode.GameMode gameMode, Arena arena, List teams, boolean ranked, boolean allowRematches) { + this.gameMode = Preconditions.checkNotNull(gameMode, "gameMode"); + this.arena = Preconditions.checkNotNull(arena, "arena"); + this.teams = ImmutableList.copyOf(teams); + this.ranked = ranked; + this.allowRematches = allowRematches; + + saveState(); + } + + private void saveState() { + if (gameMode.getBuildingAllowed()) + this.arena.takeSnapshot(); + } + + void startCountdown() { + state = MatchState.COUNTDOWN; + + Map playingCache = PotPvPSI.getInstance().getMatchHandler().getPlayingMatchCache(); + Set updateVisiblity = new HashSet<>(); + + MatchTeam handedTo = null; + + for (MatchTeam team : this.getTeams()) { + if (handedTo == null) { + handedTo = team; + } + + for (UUID playerUuid : team.getAllMembers()) { + + if (!team.isAlive(playerUuid)) + continue; + + Player player = Bukkit.getPlayer(playerUuid); + + playingCache.put(player.getUniqueId(), this); + + Location spawn = (teams.size() > 2 ? arena.getTeam1Spawn() : (team == teams.get(0) ? arena.getTeam1Spawn() : arena.getTeam2Spawn())).clone(); + Vector oldDirection = spawn.getDirection(); + + if (handedTo == team && this.gameMode.equals(GameModes.TRAPPING)) { + spawn = arena.getTeam1Spawn(); + player.setMetadata("TRAPPER", new FixedMetadataValue(PotPvPSI.getInstance(), true)); + } else if (handedTo != team && this.gameMode.equals(GameModes.TRAPPING)) { + player.removeMetadata("TRAPPER", PotPvPSI.getInstance()); + spawn = arena.getTeam2Spawn(); + } + + Block block = spawn.getBlock(); + while (block.getRelative(BlockFace.DOWN).getType() == Material.AIR) { + block = block.getRelative(BlockFace.DOWN); + if (block.getY() <= 0) { + block = spawn.getBlock(); + break; + } + } + + spawn = block.getLocation(); + spawn.setDirection(oldDirection); + spawn.add(0.5, 0, 0.5); + + player.teleport(spawn); + player.getInventory().setHeldItemSlot(0); + + eLib.getInstance().getNameTagHandler().reloadPlayer(player); + eLib.getInstance().getNameTagHandler().reloadOthersFor(player); + + updateVisiblity.add(player); + PatchedPlayerUtils.resetInventory(player, org.bukkit.GameMode.SURVIVAL); + if(player.getAllowFlight()) player.setAllowFlight(false); + } + } + + // we wait to update visibility until everyone's been put in the player cache + // then we update vis, otherwise the update code will see 'partial' views of the + // match + updateVisiblity.forEach(VisibilityUtils::updateVisibilityFlicker); + + Bukkit.getPluginManager().callEvent(new MatchCountdownStartEvent(this)); + + new BukkitRunnable() { + + int countdownTimeRemaining = gameMode.equals(GameModes.SUMO) ? 3 : 5; + + public void run() { + if (state != MatchState.COUNTDOWN) { + cancel(); + return; + } + + if (countdownTimeRemaining == 0) { + playSoundAll(Sound.NOTE_PLING, 2F); + startMatch(); + return; // so we don't send '0...' message + } else if (countdownTimeRemaining <= 3) { + playSoundAll(Sound.NOTE_PLING, 1F); + } + + messageAll("Match starting in " + ChatColor.DARK_AQUA + countdownTimeRemaining + ChatColor.WHITE + "..."); + countdownTimeRemaining--; + } + + }.runTaskTimer(PotPvPSI.getInstance(), 0L, 20L); + } + + private void startMatch() { + state = MatchState.IN_PROGRESS; + startedAt = new Date(); + + messageAll("Match started."); + Bukkit.getPluginManager().callEvent(new MatchStartEvent(this)); + } + + public void endMatch(MatchEndReason reason) { + // prevent duplicate endings + if (state == MatchState.ENDING || state == MatchState.TERMINATED) { + return; + } + + state = MatchState.ENDING; + endedAt = new Date(); + endReason = reason; + + try { + for (MatchTeam matchTeam : this.getTeams()) { + matchTeam.removeRally(); + for (UUID playerUuid : matchTeam.getAliveMembers()) { + MatchKitSelectionListener.appliedKits.remove(playerUuid); + } + for (UUID playerUuid : matchTeam.getAllMembers()) { + allPlayers.add(playerUuid); + if (!matchTeam.isAlive(playerUuid)) + continue; + Player player = Bukkit.getPlayer(playerUuid); + + player.closeInventory(); + + if (this.gameMode.equals(GameModes.TRAPPING)) { + for (Ability value : PotPvPSI.getInstance().getAbilityHandler().getAbilities().values()) { + value.removeCooldown(player); + } + } + + postMatchPlayers.computeIfAbsent(playerUuid, v -> new PostMatchPlayer(player, gameMode.getHealingMethod(), totalHits.getOrDefault(player.getUniqueId(), 0), longestCombo.getOrDefault(player.getUniqueId(), 0), totalTags.getOrDefault(player.getUniqueId(), 0), missedPots.getOrDefault(player.getUniqueId(), 0))); + } + } + + messageAll(ChatColor.DARK_GREEN.toString() + ChatColor.BOLD + "✔ " + ChatColor.GREEN + "Match ended."); + Bukkit.getPluginManager().callEvent(new MatchEndEvent(this)); + } catch (Exception ex) { + ex.printStackTrace(); + } + + int delayTicks = MATCH_END_DELAY_SECONDS * 20; + if (JavaPlugin.getProvidingPlugin(this.getClass()).isEnabled()) { + Bukkit.getScheduler().runTaskLater(PotPvPSI.getInstance(), this::terminateMatch, delayTicks); + } else { + this.terminateMatch(); + } + } + + private void terminateMatch() { + // prevent double terminations + if (state == MatchState.TERMINATED) { + return; + } + + state = MatchState.TERMINATED; + + // if the match ends before the countdown ends + // we have to set this to avoid a NPE in Date#from + if (startedAt == null) { + startedAt = new Date(); + } + + // if endedAt wasn't set before (if terminateMatch was called directly) + // we want to make sure we set an ending time. Otherwise we keep the + // technically more accurate time set in endMatch + if (endedAt == null) { + endedAt = new Date(); + } + + this.winningPlayers = winner.getAllMembers(); + this.losingPlayers = teams.stream().filter(team -> team != winner).flatMap(team -> team.getAllMembers().stream()).collect(Collectors.toSet()); + + Bukkit.getPluginManager().callEvent(new MatchTerminateEvent(this)); + + // we have to make a few edits to the document so we use Gson (which has + // adapters + // for things like Locations) and then edit it + JsonObject document = PotPvPSI.getGson().toJsonTree(this).getAsJsonObject(); + + document.addProperty("winner", teams.indexOf(winner)); // replace the full team with their index in the full list + document.addProperty("arena", arena.getSchematic()); // replace the full arena with its schematic (website doesn't care which copy we + // used) + + Bukkit.getScheduler().runTaskAsynchronously(PotPvPSI.getInstance(), () -> { + // The Document#parse call really sucks. It generates literally thousands of + // objects per call. + // Hopefully we'll be moving to just posting to a web service soon enough (and + // then we don't have to run + // Mongo's stupid JSON parser) + Document parsedDocument = Document.parse(document.toString()); + parsedDocument.put("startedAt", startedAt); + parsedDocument.put("endedAt", endedAt); + MongoUtils.getCollection(MatchHandler.MONGO_COLLECTION_NAME).insertOne(parsedDocument); + }); + + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + LobbyHandler lobbyHandler = PotPvPSI.getInstance().getLobbyHandler(); + + Map playingCache = matchHandler.getPlayingMatchCache(); + Map spectateCache = matchHandler.getSpectatingMatchCache(); + + if (gameMode.getBuildingAllowed()) { + Bukkit.getLogger().info("Restored arena for match " + this.get_id()); + arena.restore(); + } + + PotPvPSI.getInstance().getArenaHandler().releaseArena(arena); + matchHandler.removeMatch(this); + + getTeams().forEach(team -> { + team.getAllMembers().forEach(player -> { + if (team.isAlive(player)) { + playingCache.remove(player); + spectateCache.remove(player); + lobbyHandler.returnToLobby(Bukkit.getPlayer(player)); + } + }); + }); + + spectators.forEach(player -> { + if (Bukkit.getPlayer(player) != null) { + playingCache.remove(player); + spectateCache.remove(player); + lobbyHandler.returnToLobby(Bukkit.getPlayer(player)); + } + }); + } + + public Set getSpectators() { + return ImmutableSet.copyOf(spectators); + } + + public Map getPostMatchPlayers() { + return ImmutableMap.copyOf(postMatchPlayers); + } + + private void checkEnded() { + if (state == MatchState.ENDING || state == MatchState.TERMINATED) { + return; + } + + List teamsAlive = new ArrayList<>(); + + for (MatchTeam team : teams) { + if (!team.getAliveMembers().isEmpty()) { + teamsAlive.add(team); + } + } + + if (teamsAlive.size() == 1) { + this.winner = teamsAlive.get(0); + endMatch(MatchEndReason.ENEMIES_ELIMINATED); + } + } + + public boolean isSpectator(UUID uuid) { + return spectators.contains(uuid); + } + + public void addSpectator(Player player, Player target) { + addSpectator(player, target, false); + } + + // fromMatch indicates if they were a player immediately before spectating. + // we use this for things like teleporting and messages + public void addSpectator(Player player, Player target, boolean fromMatch) { + if (!fromMatch && state == MatchState.ENDING) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "This match is no longer available for spectating."); + return; + } + + Map spectateCache = PotPvPSI.getInstance().getMatchHandler().getSpectatingMatchCache(); + + spectateCache.put(player.getUniqueId(), this); + spectators.add(player.getUniqueId()); + + if (!fromMatch) { + Location tpTo = arena.getSpectatorSpawn(); + + if (target != null) { + // we tp them a bit up so they're not inside of their target + tpTo = target.getLocation().clone().add(0, 1.5, 0); + } + + player.teleport(tpTo); + player.sendMessage(ChatColor.YELLOW + "Now spectating " + ChatColor.GOLD + getSimpleDescription(true) + ChatColor.YELLOW + "..."); + sendSpectatorMessage(player, ChatColor.AQUA + player.getName() + ChatColor.YELLOW + " is now spectating."); + } else { + // so players don't accidentally click the item to stop spectating + player.getInventory().setHeldItemSlot(0); + } + + eLib.getInstance().getNameTagHandler().reloadPlayer(player); + eLib.getInstance().getNameTagHandler().reloadOthersFor(player); + + VisibilityUtils.updateVisibility(player); + PatchedPlayerUtils.resetInventory(player, org.bukkit.GameMode.CREATIVE, true); // because we're about to reset their inv on a timer + InventoryUtils.resetInventoryDelayed(player); + + player.setAllowFlight(true); + player.setFlying(true); // called after PlayerUtils reset, make sure they don't fall out of the sky + ItemListener.addButtonCooldown(player, 1_500); + + Bukkit.getPluginManager().callEvent(new MatchSpectatorJoinEvent(player, this)); + } + + public void removeSpectator(Player player) { + removeSpectator(player, true); + } + + public void removeSpectator(Player player, boolean returnToLobby) { + Map spectateCache = PotPvPSI.getInstance().getMatchHandler().getSpectatingMatchCache(); + + spectateCache.remove(player.getUniqueId()); + spectators.remove(player.getUniqueId()); + ItemListener.addButtonCooldown(player, 1_500); + + if (returnToLobby) { + PotPvPSI.getInstance().getLobbyHandler().returnToLobby(player); + } + + Bukkit.getPluginManager().callEvent(new MatchSpectatorLeaveEvent(player, this)); + } + + private void sendSpectatorMessage(Player spectator, String message) { + // see comment on spectatorMessagesUsed field for more + if (spectator.hasMetadata("modmode") || !spectatorMessagesUsed.add(spectator.getUniqueId())) { + return; + } + + SettingHandler settingHandler = PotPvPSI.getInstance().getSettingHandler(); + +// for (Player online : Bukkit.getOnlinePlayers()) { +// if (online == spectator) { +// continue; +// } +// +// online.sendMessage(message); +// } + } + + public void markDead(Player player) { + MatchTeam team = getTeam(player.getUniqueId()); + + if (team == null) { + return; + } + + if (gameMode.equals(GameModes.PEARL_FIGHT)) { + MatchKitSelectionListener.appliedKits.remove(player.getUniqueId()); + } + + Map playingCache = PotPvPSI.getInstance().getMatchHandler().getPlayingMatchCache(); + + team.markDead(player.getUniqueId()); + playingCache.remove(player.getUniqueId()); + + postMatchPlayers.put(player.getUniqueId(), new PostMatchPlayer(player, gameMode.getHealingMethod(), totalHits.getOrDefault(player.getUniqueId(), 0), longestCombo.getOrDefault(player.getUniqueId(), 0), totalTags.getOrDefault(player.getUniqueId(), 0), missedPots.getOrDefault(player.getUniqueId(), 0))); + checkEnded(); + } + + public MatchTeam getTeam(UUID playerUuid) { + for (MatchTeam team : teams) { + if (team.isAlive(playerUuid)) { + return team; + } + } + + return null; + } + + public MatchTeam getPreviousTeam(UUID playerUuid) { + for (MatchTeam team : teams) { + if (team.getAllMembers().contains(playerUuid)) { + return team; + } + } + + return null; + } + + /** + * Creates a simple, one line description of this match This will include two + * players (if a 1v1) or player counts and the kit type + * + * @return A simple description of this match + */ + public String getSimpleDescription(boolean includeRankedUnranked) { + String players; + + if (teams.size() == 2) { + MatchTeam teamA = teams.get(0); + MatchTeam teamB = teams.get(1); + + if (teamA.getAliveMembers().size() == 1 && teamB.getAliveMembers().size() == 1) { + String nameA = UUIDUtils.name(teamA.getFirstAliveMember()); + String nameB = UUIDUtils.name(teamB.getFirstAliveMember()); + + players = nameA + " vs " + nameB; + } else { + players = teamA.getAliveMembers().size() + " vs " + teamB.getAliveMembers().size(); + } + } else { + int numTotalPlayers = 0; + + for (MatchTeam team : teams) { + numTotalPlayers += team.getAliveMembers().size(); + } + + players = numTotalPlayers + " player fight"; + } + + if (includeRankedUnranked) { + String rankedStr = ranked ? "Ranked" : "Unranked"; + return players + " (" + rankedStr + " " + gameMode.getName() + ")"; + } else { + return players; + } + } + + /** + * Sends a basic chat message to all alive participants and spectators + * + * @param message + * the message to send + */ + public void messageAll(String message) { + messageAlive(message); + messageSpectators(message); + } + + /** + * Plays a sound for all alive participants and spectators + * + * @param sound + * the Sound to play + * @param pitch + * the pitch to play the provided sound at + */ + public void playSoundAll(Sound sound, float pitch) { + playSoundAlive(sound, pitch); + playSoundSpectators(sound, pitch); + } + + /** + * Sends a basic chat message to all spectators + * + * @param message + * the message to send + */ + public void messageSpectators(String message) { + for (UUID spectator : spectators) { + Player spectatorBukkit = Bukkit.getPlayer(spectator); + + if (spectatorBukkit != null) { + spectatorBukkit.sendMessage(message); + } + } + } + + /** + * Plays a sound for all spectators + * + * @param sound + * the Sound to play + * @param pitch + * the pitch to play the provided sound at + */ + public void playSoundSpectators(Sound sound, float pitch) { + for (UUID spectator : spectators) { + Player spectatorBukkit = Bukkit.getPlayer(spectator); + + if (spectatorBukkit != null) { + spectatorBukkit.playSound(spectatorBukkit.getEyeLocation(), sound, 10F, pitch); + } + } + } + + /** + * Sends a basic chat message to all alive participants + * + * @see MatchTeam#messageAlive(String) + * @param message + * the message to send + */ + public void messageAlive(String message) { + for (MatchTeam team : teams) { + team.messageAlive(message); + } + } + + /** + * Plays a sound for all alive participants + * + * @param sound + * the Sound to play + * @param pitch + * the pitch to play the provided sound at + */ + public void playSoundAlive(Sound sound, float pitch) { + for (MatchTeam team : teams) { + team.playSoundAlive(sound, pitch); + } + } + + /** + * Records a placed block during this match. Used to keep track of which blocks + * can be broken. + */ + public void recordPlacedBlock(Block block) { + placedBlocks.add(block.getLocation().toVector().toBlockVector()); + } + + /** + * Checks if a block can be broken in this match. Only used if the GameMode + * allows building. + */ + public boolean canBeBroken(Block block, Player player) { + return (gameMode.equals(com.elevatemc.potpvp.gamemode.GameModes.TRAPPING) && player.hasMetadata("TRAPPER")) || gameMode.getId().equals("SPLEEF") && (block.getType() == Material.SNOW_BLOCK || block.getType() == Material.GRASS || block.getType() == Material.DIRT) || placedBlocks.contains(block.getLocation().toVector().toBlockVector()); + } + + public boolean canBeBroken(Block block) { + return (gameMode.equals(com.elevatemc.potpvp.gamemode.GameModes.TRAPPING)) || gameMode.getId().equals("SPLEEF") && (block.getType() == Material.SNOW_BLOCK || block.getType() == Material.GRASS || block.getType() == Material.DIRT) || placedBlocks.contains(block.getLocation().toVector().toBlockVector()); + } + + public List findAllPlayers() { + // Fuck this shit so aids whatever bro gotta do it this way + + final List toReturn = new ArrayList<>(); + + for (MatchTeam team : this.getTeams()) { + for (UUID aliveMember : team.getAllMembers()) { + final Player player = PotPvPSI.getInstance().getServer().getPlayer(aliveMember); + + if (player == null) { + continue; + } + + toReturn.add(player); + } + } + + for (UUID spectator : this.getSpectators()) { + final Player player = PotPvPSI.getInstance().getServer().getPlayer(spectator); + + if (player == null) { + continue; + } + + toReturn.add(player); + } + + return toReturn; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/MatchEndReason.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/MatchEndReason.java new file mode 100644 index 0000000..30c3f87 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/MatchEndReason.java @@ -0,0 +1,22 @@ +package com.elevatemc.potpvp.match; + +/** + * Describes a reason for a match's termination + */ +public enum MatchEndReason { + + /** + * All enemies have been eliminated, + * leaving only one {@link MatchTeam} with >= 1 alive players. + */ + ENEMIES_ELIMINATED, + + /** + * The match duration exceeded a predefined limit. + * @see com.elevatemc.potpvp.match.listener.MatchDurationLimitListener + */ + DURATION_LIMIT_EXCEEDED, + + + TERMINATED +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/MatchHandler.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/MatchHandler.java new file mode 100644 index 0000000..0a735fc --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/MatchHandler.java @@ -0,0 +1,252 @@ +package com.elevatemc.potpvp.match; + +import com.elevatemc.potpvp.events.game.GameQueue; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.elevatemc.potpvp.match.listener.MatchHealthDisplayListener; +import com.google.common.collect.ImmutableSet; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.Arena; +import com.elevatemc.potpvp.arena.ArenaHandler; +import com.elevatemc.potpvp.match.listener.*; +import com.elevatemc.elib.util.UUIDUtils; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; + +public final class MatchHandler { + + public static final String MONGO_COLLECTION_NAME = "matches"; + + private final Set hostedMatches = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + @Getter @Setter private boolean rankedMatchesDisabled; + @Getter @Setter private boolean unrankedMatchesDisabled; + + // these two maps are one of my least favorite bits of code in the match module, but + // they let us run O(1) lookups of player matches, so we absolutely should keep them. + // we do, however, have to be very careful to keep them updated with the "actuaL" data. + // if we ever have issues with these, we have /pstatus to compare the "actual" data (O(n) scan) + // with the O(1) data here + @Getter(AccessLevel.PACKAGE) private final Map playingMatchCache = new ConcurrentHashMap<>(); + @Getter(AccessLevel.PACKAGE) private final Map spectatingMatchCache = new ConcurrentHashMap<>(); + + public MatchHandler() { + Bukkit.getPluginManager().registerEvents(new MatchKitSelectionListener(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new MatchBlockPickupListener(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new MatchBuildListener(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new MatchCountdownListener(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new MatchDeathMessageListener(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new MatchDurationLimitListener(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new MatchEnderPearlDamageListener(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new MatchFreezeListener(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new MatchGeneralListener(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new MatchHardcoreHealingListener(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new MatchHealthDisplayListener(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new MatchPartySpectateListener(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new MatchStatsListener(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new MatchTitleListener(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new MatchEnderPearlListener(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new MatchAntidoteListener(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new MatchSpectatorItemListener(this), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new MatchSpectatorPreventionListener(), PotPvPSI.getInstance()); + new TeamViewerTask().runTaskTimer(PotPvPSI.getInstance(), 20L, 20L); + } + + public Match startMatch(List teams, GameMode gameMode, String arenaName, boolean ranked, boolean allowRematches) { + boolean anyOps = false; + + for (MatchTeam team : teams) { + for (UUID member : team.getAllMembers()) { + Player memberPlayer = Bukkit.getPlayer(member); + + if (!anyOps && memberPlayer.isOp()) { + anyOps = true; + } + + if (isPlayingOrSpectatingMatch(memberPlayer)) { + throw new IllegalArgumentException(UUIDUtils.name(member) + " is already in a match!"); + } + } + } + + if (!anyOps) { + if (ranked && rankedMatchesDisabled) { + throw new IllegalArgumentException("Ranked match creation is disabled!"); + } else if (unrankedMatchesDisabled) { + throw new IllegalArgumentException("Unranked match creation is disabled!"); + } + } + + ArenaHandler arenaHandler = PotPvPSI.getInstance().getArenaHandler(); + + // the archer only logic here was often a source of confusion while + // this code was being written. below is a table of the desired + // results / if a match can run in a given arena + // + // Arena is archer only Arena is not archer only + // Is Archer Yes Yes + // Not Archer No Yes + // + // the left side of the or statement covers the top row, and the + // right side covers the right side + Optional openArenaOpt; + openArenaOpt = arenaHandler.allocateUnusedArena(schematic -> + schematic.isEnabled() && + schematic.getEnabledGameModes().get(gameMode.getId()) && + (!ranked || schematic.isSupportsRanked()) && + (arenaName == null) || schematic.getName().equals(arenaName) + ); + + if (!openArenaOpt.isPresent()) { + PotPvPSI.getInstance().getLogger().warning("Failed to start match: No open arenas found"); + return null; + } + + if (gameMode.equals(GameModes.PEARL_FIGHT)) { + for (MatchTeam team : teams) { + team.setLives(3); + } + } + + Match match = new Match(gameMode, openArenaOpt.get(), teams, ranked, allowRematches); + + hostedMatches.add(match); + match.startCountdown(); + + return match; + } + + void removeMatch(Match match) { + hostedMatches.remove(match); + } + + /** + * Gets a read-only set containing all matches currently being hosted. + * This includes matches that are pre-start and ending. + * @return a read-only representation of all hosted matches + */ + public Set getHostedMatches() { + return ImmutableSet.copyOf(hostedMatches); + } + + /** + * Returns a sum of all players who are playing in an IN_PROGRESS match + * @return number of players playing in IN_PROGRESS matches + */ + public int countPlayersPlayingInProgressMatches() { + return countPlayersPlayingMatches(m -> m.getState() == MatchState.COUNTDOWN || m.getState() == MatchState.IN_PROGRESS); + } + + /** + * Returns a sum of all players who are playing in a {@link Match} + * that passes the {@link Predicate} provided. + * @return number of players playing in matches that + * pass the {@link Predicate} provided + */ + public int countPlayersPlayingMatches(Predicate inclusionPredicate) { + int result = 0; + + for (Match match : hostedMatches) { + if (inclusionPredicate.test(match)) { + for (MatchTeam team : match.getTeams()) { + result += team.getAliveMembers().size(); + } + } + } + + return result; + } + + /** + * Gets the match currently being played by the player provided. + * In this context, played means a player who alive and fighting on a team + * + * @param player player to look up match for + * @return the match being played by the provided player + */ + public Match getMatchPlaying(Player player) { + return playingMatchCache.get(player.getUniqueId()); + } + + /** + * Gets the match currently being spectated by the player provided. + * In this context, spectated includes both players who died while + * fighting who have not left and players who joined via /spectate. + * + * @param player player to look up match for + * @return the match being spectated by the provided player + */ + public Match getMatchSpectating(Player player) { + return spectatingMatchCache.get(player.getUniqueId()); + } + + /** + * Gets the match currently being spectated or played by the player provided. + * This method acts as a combination of {@link MatchHandler#getMatchPlaying(Player)} + * and {@link MatchHandler#getMatchSpectating(Player)}. + * + * @param player player to look up match for + * @return the match being played or spectated by the provided player + */ + public Match getMatchPlayingOrSpectating(Player player) { + Match playing = playingMatchCache.get(player.getUniqueId()); + + if (playing != null) { + return playing; + } else { + return spectatingMatchCache.get(player.getUniqueId()); + } + } + + /** + * Checks if the player specified is playing a match. + * See {@link MatchHandler#getMatchPlaying(Player)} for a definition + * of the term playing. + * @param player player to look up match for + * @return if a match is being played by the provided player + */ + public boolean isPlayingMatch(Player player) { + return playingMatchCache.containsKey(player.getUniqueId()); + } + + /** + * Checks if the player specified is spectating a match. + * See {@link MatchHandler#getMatchSpectating(Player)} (UUID)} for a + * definition of the term spectating. + * @param player player to look up match for + * @return if a match is being spectated by the provided player + */ + public boolean isSpectatingMatch(Player player) { + return spectatingMatchCache.containsKey(player.getUniqueId()); + } + + /** + * Check if the player specific is playing + * an event + * @param player player to look up event for + * @return if the player is playing an event + */ + public boolean isPlayingEvent(Player player) { + return GameQueue.INSTANCE.getCurrentGame(player) != null; + } + + /** + * Checks if the player specified is playing or spectating a match. + * See {@link MatchHandler#getMatchPlayingOrSpectating(Player)} for a definition + * of the term playing or spectating. + * @param player player to look up match for + * @return if a match is being played or spectated by the provided player + */ + public boolean isPlayingOrSpectatingMatch(Player player) { + if (player == null) return false; + return playingMatchCache.containsKey(player.getUniqueId()) || spectatingMatchCache.containsKey(player.getUniqueId()) || GameQueue.INSTANCE.getCurrentGame(player) != null; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/MatchState.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/MatchState.java new file mode 100644 index 0000000..f2cffad --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/MatchState.java @@ -0,0 +1,44 @@ +package com.elevatemc.potpvp.match; + +import com.elevatemc.potpvp.match.event.MatchStartEvent; + +/** + * Represents a possible state of a {@link Match} + */ +public enum MatchState { + + /** + * The match is currently in countdown and will soon transition to + * {@link MatchState#IN_PROGRESS} + * @see Match#startCountdown() + * @see com.elevatemc.potpvp.match.event.MatchCountdownStartEvent + */ + COUNTDOWN, + + /** + * The match is currently in progress and will transition to + * {@link MatchState#ENDING} once all players have died (or time + * has expired) + * @see Match#checkEnded() + * @see com.elevatemc.potpvp.match.listener.MatchDurationLimitListener + * @see MatchStartEvent + */ + IN_PROGRESS, + + /** + * The match is currently ending (giving players a moment to realize the + * match has ended and react) and will soon transition to + * {@link MatchState#TERMINATED}. + * @see com.elevatemc.potpvp.match.event.MatchEndEvent + */ + ENDING, + + /** + * The match is completely ended, and all players have been teleported back + * to the lobby. The match has been removed from {@link MatchHandler#getHostedMatches()} + * and will soon be garbage collected + * @see com.elevatemc.potpvp.match.event.MatchTerminateEvent + */ + TERMINATED + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/MatchTeam.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/MatchTeam.java new file mode 100644 index 0000000..0fdb843 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/MatchTeam.java @@ -0,0 +1,163 @@ +package com.elevatemc.potpvp.match; + +import com.elevatemc.elib.eLib; +import com.google.common.collect.ImmutableSet; +import com.lunarclient.bukkitapi.LunarClientAPI; +import com.lunarclient.bukkitapi.object.LCWaypoint; +import lombok.Getter; +import lombok.Setter; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Sound; +import org.bukkit.entity.Player; + +import java.util.Collection; +import java.util.Collections; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; + +/** + * Represents one team participating in a {@link Match} + */ +public final class MatchTeam { + + /** + * All players who were ever part of this team, including those who logged off / died + */ + @Getter private final Set allMembers; + + /** + * All players who are currently alive. + */ + private final Set aliveMembers = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + @Getter @Setter private int hits = 0; + + /** + * This is used for gamemodes with lives + */ + @Getter private int lives = 0; + + @Getter private UUID focus = null; + + // convenience constructor for 1v1s, queues, etc + public MatchTeam(UUID initialMember) { + this(ImmutableSet.of(initialMember)); + } + + + public MatchTeam(Collection initialMembers) { + this.allMembers = ImmutableSet.copyOf(initialMembers); + this.aliveMembers.addAll(initialMembers); + } + + /** + * Marks the given player as dead (will no longer appear in {@link MatchTeam#getAliveMembers()}, etc) + * @param playerUuid the player to mark as dead + */ + void markDead(UUID playerUuid) { + aliveMembers.remove(playerUuid); + } + + /** + * Checks if the given player is still alive (shorthand for .getAliveMembers().contains()) + * @param playerUuid the player to check + * @return if the given player is still alive + */ + public boolean isAlive(UUID playerUuid) { + return aliveMembers.contains(playerUuid); + } + + /** + * Gets a immutable set of all alive team members + * @see MatchTeam#aliveMembers + * @return immutable set of all alive team members + */ + public Set getAliveMembers() { + return ImmutableSet.copyOf(aliveMembers); + } + + public UUID getFirstAliveMember() { + return aliveMembers.iterator().next(); + } + + public UUID getFirstMember() { + return allMembers.iterator().next(); + } + + /** + * Sends a basic chat message to all alive members + * @see MatchTeam#aliveMembers + * @param message the message to send + */ + public void messageAlive(String message) { + forEachAlive(p -> p.sendMessage(message)); + } + + /** + * Plays a sound for all alive members + * @param sound the Sound to play + * @param pitch the pitch to play the provided sound at + */ + public void playSoundAlive(Sound sound, float pitch) { + forEachAlive(p -> p.playSound(p.getLocation(), sound, 10F, pitch)); + } + + /** + * Sends a rally waypoint to all alive members + * @param from the player that made the rally + */ + public void sendRally(Player from) { + forEachAlive(p -> { + p.sendMessage(ChatColor.DARK_AQUA + "⚐ " + from.getName() + ChatColor.AQUA + " has updated the rally point!"); + removeRallyForPlayer(p); + LunarClientAPI.getInstance().sendWaypoint(p, new LCWaypoint("Rally", from.getLocation(), 1, true, true)); + }); + } + + public void removeRally() { + forEachMembers(this::removeRallyForPlayer); + } + + public void removeRallyForPlayer(Player player) { + LunarClientAPI.getInstance().removeWaypoint(player, new LCWaypoint("Rally", player.getLocation(), 1, true, true)); + } + + public void setFocus(Player target) { + Player oldPlayer = Bukkit.getPlayer(focus); + if (target != null) { + focus = target.getUniqueId(); + eLib.getInstance().getNameTagHandler().reloadPlayer(target); + } else { + focus = null; + } + if (oldPlayer != null) eLib.getInstance().getNameTagHandler().reloadPlayer(oldPlayer); + } + + public void setLives(int lives) { + this.lives = lives; + } + + public void forEachAlive(Consumer consumer) { + for (UUID member : aliveMembers) { + Player memberBukkit = Bukkit.getPlayer(member); + + if (memberBukkit != null) { + consumer.accept(memberBukkit); + } + } + } + + public void forEachMembers(Consumer consumer) { + for (UUID member : allMembers) { + Player memberBukkit = Bukkit.getPlayer(member); + + if (memberBukkit != null) { + consumer.accept(memberBukkit); + } + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/MatchUtils.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/MatchUtils.java new file mode 100644 index 0000000..d70773d --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/MatchUtils.java @@ -0,0 +1,87 @@ +package com.elevatemc.potpvp.match; + +import com.elevatemc.potpvp.setting.Setting; +import lombok.experimental.UtilityClass; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.follow.FollowHandler; +import com.elevatemc.potpvp.lobby.LobbyItems; +import com.elevatemc.potpvp.party.PartyHandler; +import com.elevatemc.potpvp.setting.SettingHandler; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.PlayerInventory; + +@UtilityClass +public final class MatchUtils { + + public static void resetInventory(Player player) { + SettingHandler settingHandler = PotPvPSI.getInstance().getSettingHandler(); + FollowHandler followHandler = PotPvPSI.getInstance().getFollowHandler(); + PartyHandler partyHandler = PotPvPSI.getInstance().getPartyHandler(); + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + Match match = matchHandler.getMatchSpectating(player); + + // because we lookup their match with getMatchSpectating this will also + // return for players fighting in matches + if (match == null) { + return; + } + + PlayerInventory inventory = player.getInventory(); + + inventory.clear(); + inventory.setArmorContents(null); + Inventory inv = player.getOpenInventory().getTopInventory(); + if (inv.getType().equals(InventoryType.CRAFTING)) { + inv.clear(); + } + player.setItemOnCursor(null); + // don't give players who die (and cause the match to end) + // a fire item, they'll be sent back to the lobby in a few seconds anyway + if (match.getState() != MatchState.ENDING) { + // if they've been on any team or are staff they'll be able to + // use this item on at least 1 player. if they can't use it all + // we just don't give it to them (UX purposes) + boolean canViewInventories = player.hasPermission("potpvp.inventory.all"); + + if (!canViewInventories) { + for (MatchTeam team : match.getTeams()) { + if (team.getAllMembers().contains(player.getUniqueId())) { + canViewInventories = true; + break; + } + } + } + + // fill inventory with spectator items + if (canViewInventories) { + inventory.setItem(2, SpectatorItems.VIEW_INVENTORY_ITEM); + } + + if (settingHandler.getSetting(player, Setting.VIEW_OTHER_SPECTATORS)) { + inventory.setItem(4, SpectatorItems.HIDE_SPECTATORS_ITEM); + } else { + inventory.setItem(4, SpectatorItems.SHOW_SPECTATORS_ITEM); + } + + + // this bit is correct; see SpectatorItems file for more + if (partyHandler.hasParty(player)) { + inventory.setItem(8, SpectatorItems.LEAVE_PARTY_ITEM); + } else { + inventory.setItem(8, SpectatorItems.RETURN_TO_LOBBY_ITEM); + + if (!followHandler.getFollowing(player).isPresent()) { + inventory.setItem(3, LobbyItems.SPECTATE_RANDOM_ITEM); + inventory.setItem(5, LobbyItems.SPECTATE_MENU_ITEM); + } + } + } + + Bukkit.getScheduler().runTaskLater(PotPvPSI.getInstance(), player::updateInventory, 1L); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/SpectatorItems.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/SpectatorItems.java new file mode 100644 index 0000000..141e2b4 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/SpectatorItems.java @@ -0,0 +1,39 @@ +package com.elevatemc.potpvp.match; + +import lombok.experimental.UtilityClass; +import com.elevatemc.elib.util.ItemUtils; +import org.bukkit.DyeColor; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import static org.bukkit.ChatColor.GRAY; +import static org.bukkit.ChatColor.RED; +import static org.bukkit.ChatColor.YELLOW; + +@UtilityClass +public final class SpectatorItems { + + public static final ItemStack SHOW_SPECTATORS_ITEM = new ItemStack(Material.INK_SACK, 1, DyeColor.GRAY.getDyeData()); + public static final ItemStack HIDE_SPECTATORS_ITEM = new ItemStack(Material.INK_SACK, 1, DyeColor.LIME.getDyeData()); + + public static final ItemStack VIEW_INVENTORY_ITEM = new ItemStack(Material.BOOK); + + // these items both do the same thing but we change the name if + // clicking the item will reuslt in the player being removed + // from their party. both serve the function of returning a player + // to the lobby. + // https://github.com/ElevateOrb/PotPvP-SI/issues/37 + public static final ItemStack RETURN_TO_LOBBY_ITEM = new ItemStack(Material.INK_SACK, 1, DyeColor.RED.getDyeData()); + public static final ItemStack LEAVE_PARTY_ITEM = new ItemStack(Material.INK_SACK, 1, DyeColor.RED.getDyeData()); + + static { + ItemUtils.setDisplayName(SHOW_SPECTATORS_ITEM, GRAY + "• " + YELLOW + "Show spectators" + GRAY + " •"); + ItemUtils.setDisplayName(HIDE_SPECTATORS_ITEM, GRAY + "• " + YELLOW + "Hide spectators" + GRAY + " •"); + + ItemUtils.setDisplayName(VIEW_INVENTORY_ITEM, GRAY + "• " + YELLOW + "View player inventory" + GRAY + " •"); + + ItemUtils.setDisplayName(RETURN_TO_LOBBY_ITEM, GRAY + "• " + YELLOW + "Return to lobby" + GRAY + " •"); + ItemUtils.setDisplayName(LEAVE_PARTY_ITEM, GRAY + "• " + RED + "Leave Party" + GRAY + " •" + GRAY + " •"); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/TeamViewerTask.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/TeamViewerTask.java new file mode 100644 index 0000000..6cdad5f --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/TeamViewerTask.java @@ -0,0 +1,68 @@ +package com.elevatemc.potpvp.match; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.event.MatchTerminateEvent; +import com.lunarclient.bukkitapi.LunarClientAPI; +import com.lunarclient.bukkitapi.nethandler.client.LCPacketTeammates; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.*; + +public class TeamViewerTask extends BukkitRunnable implements Listener { + public TeamViewerTask() { + PotPvPSI.getInstance().getServer().getPluginManager().registerEvents(this, PotPvPSI.getInstance()); + } + + @Override + public void run() { + for (Match match : PotPvPSI.getInstance().getMatchHandler().getHostedMatches()) { + if (match.getState() != MatchState.TERMINATED) { + List teams = match.getTeams(); + for (MatchTeam team : teams) { + // Populate the alive array + ArrayList alive = new ArrayList<>(); + for (UUID u : team.getAliveMembers()) { + Player player = Bukkit.getPlayer(u); + if (player != null) alive.add(player); + } + // For each alive person we send the teammates + for (Player player : alive) { + sendTeammates(player, alive); + } + } + } + } + } + + @EventHandler + public void onMatchEnd(MatchTerminateEvent e) { + List teams = e.getMatch().getTeams(); + for (MatchTeam team : teams) { + for (UUID u : team.getAllMembers()) { + Player player = Bukkit.getPlayer(u); + if (player != null) LunarClientAPI.getInstance().sendTeammates(player, new LCPacketTeammates(null, 1, new HashMap<>())); + } + } + } + + public void sendTeammates(Player player, List targets) { + Map> playerMap = new HashMap<>(); + + for (Player target : targets) { + Map posMap = new HashMap<>(); + + posMap.put("x", target.getLocation().getX()); + posMap.put("y", target.getLocation().getY()); + posMap.put("z", target.getLocation().getZ()); + + playerMap.put(target.getUniqueId(), posMap); + } + + LunarClientAPI.getInstance().sendTeammates(player, new LCPacketTeammates(player.getUniqueId(), 1, playerMap)); + } +} + diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/command/FocusCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/command/FocusCommand.java new file mode 100644 index 0000000..6b2a210 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/command/FocusCommand.java @@ -0,0 +1,83 @@ +package com.elevatemc.potpvp.match.command; + +import com.elevatemc.elib.command.param.Parameter; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.elib.command.Command; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class FocusCommand { + + @Command(names = {"focus"}, permission = "") + public static void focus(Player sender, @Parameter(name="player") Player target) { + Match match = PotPvPSI.getInstance().getMatchHandler().getMatchPlaying(sender); + if (match == null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You must be in a match to do this."); + return; + } + + MatchTeam team = match.getPreviousTeam(sender.getUniqueId()); + + if (team == null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You don't have a team to do this."); + return; + } + + MatchTeam targetTeam = match.getTeam(target.getUniqueId()); + if (targetTeam == null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "The target must be alive and in the same match as you."); + return; + } + + if (team.equals(targetTeam)) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "The target can't be in your team."); + return; + } + + if (team.getAllMembers().size() < 2) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You have no teammates to do this for."); + return; + } + + if (team.getFocus() != null && team.getFocus().equals(target.getUniqueId())) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "That person is already focused."); + return; + } + + + team.setFocus(target); + + team.forEachAlive(p -> { + p.sendMessage(ChatColor.LIGHT_PURPLE + target.getName() + ChatColor.YELLOW + " has been focused by " + ChatColor.LIGHT_PURPLE + sender.getName() + ChatColor.YELLOW + "."); + }); + } + + @Command(names = {"unfocus"}, permission = "") + public static void unfocus(Player sender) { + Match match = PotPvPSI.getInstance().getMatchHandler().getMatchPlaying(sender); + if (match == null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You must be in a match to do this."); + return; + } + + MatchTeam team = match.getPreviousTeam(sender.getUniqueId()); + + if (team == null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You don't have a team to do this."); + return; + } + + if (team.getFocus() == null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "No one is focused."); + return; + } + + team.setFocus(null); + + team.forEachAlive(p -> { + p.sendMessage(ChatColor.YELLOW + sender.getName() + " has reset the focus."); + }); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/command/LeaveCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/command/LeaveCommand.java new file mode 100644 index 0000000..c307380 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/command/LeaveCommand.java @@ -0,0 +1,35 @@ +package com.elevatemc.potpvp.match.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.elib.command.Command; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class LeaveCommand { + + @Command(names = { "spawn", "leave" }, permission = "") + public static void leave(Player sender) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + if (matchHandler.isPlayingMatch(sender)) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You cannot do this while playing in a match."); + return; + } + + if(matchHandler.isPlayingEvent(sender)) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You cannot do this while playing an event."); + } + + Match spectating = matchHandler.getMatchSpectating(sender); + + if (spectating == null) { + PotPvPSI.getInstance().getLobbyHandler().returnToLobby(sender); + } else { + spectating.removeSpectator(sender); + } + + sender.sendMessage(ChatColor.DARK_GREEN.toString() + ChatColor.BOLD + "✔ " + ChatColor.GREEN + "Teleported to spawn"); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/command/MapCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/command/MapCommand.java new file mode 100644 index 0000000..44c99c1 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/command/MapCommand.java @@ -0,0 +1,26 @@ +package com.elevatemc.potpvp.match.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.Arena; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.elib.command.Command; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class MapCommand { + + @Command(names = { "map" }, permission = "op") + public static void map(Player sender) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match match = matchHandler.getMatchPlayingOrSpectating(sender); + + if (match == null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You are not in a match right now."); + return; + } + + Arena arena = match.getArena(); + sender.sendMessage(ChatColor.YELLOW + "Playing on copy " + ChatColor.GOLD + arena.getCopy() + ChatColor.YELLOW + " of " + ChatColor.GOLD + arena.getSchematic() + ChatColor.YELLOW + "."); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/command/RallyCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/command/RallyCommand.java new file mode 100644 index 0000000..06b2417 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/command/RallyCommand.java @@ -0,0 +1,35 @@ +package com.elevatemc.potpvp.match.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.elib.command.Command; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class RallyCommand { + + @Command(names = {"t rally", "team rally", "rally", "f rally", "p rally"}, permission = "") + public static void partyRally(Player sender) { + Match match = PotPvPSI.getInstance().getMatchHandler().getMatchPlaying(sender); + if (match == null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You must be in a match to do this."); + return; + } + + MatchTeam team = match.getPreviousTeam(sender.getUniqueId()); + + if (team == null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You don't have a team to do this."); + return; + } + + if (team.getAllMembers().size() < 2) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You have no teammates to do this for."); + return; + } + + team.sendRally(sender); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/command/SpectateCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/command/SpectateCommand.java new file mode 100644 index 0000000..b3fe39b --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/command/SpectateCommand.java @@ -0,0 +1,103 @@ +package com.elevatemc.potpvp.match.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.setting.Setting; +import com.elevatemc.potpvp.setting.SettingHandler; +import com.elevatemc.potpvp.validation.PotPvPValidation; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +public final class SpectateCommand { + + private static final int SPECTATE_COOLDOWN_SECONDS = 2; + private static final Map cooldowns = new HashMap<>(); + + @Command(names = {"spectate", "spec"}, permission = "") + public static void spectate(Player sender, @Parameter(name = "target") Player target) { + if (sender == target) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You cannot spectate yourself."); + return; + } else if (cooldowns.containsKey(sender.getUniqueId()) && cooldowns.get(sender.getUniqueId()) > System.currentTimeMillis()) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Please wait before using this command again."); + return; + } + + cooldowns.put(sender.getUniqueId(), System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(SPECTATE_COOLDOWN_SECONDS)); + + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + SettingHandler settingHandler = PotPvPSI.getInstance().getSettingHandler(); + + Match targetMatch = matchHandler.getMatchPlayingOrSpectating(target); + + if (targetMatch == null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + target.getName() + " is not in a match."); + return; + } + + //boolean bypassesSpectating = PotPvPSI.getInstance().getTournamentHandler().isInTournament(targetMatch); + boolean bypassesSpectating = false; + + // only check the seting if the target is actually playing in the match + if (!bypassesSpectating && (targetMatch.getTeam(target.getUniqueId()) != null && !settingHandler.getSetting(target, Setting.ALLOW_SPECTATORS))) { + if (sender.isOp() || sender.hasPermission("core.staffteam")) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Bypassing " + target.getName() + "'s no spectators preference..."); + } else { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + target.getName() + " has their spectators setting turned off right now."); + return; + } + } + + if ((!sender.isOp() && !sender.hasPermission("core.staffteam")) && targetMatch.getTeams().size() == 2 && !bypassesSpectating) { + MatchTeam teamA = targetMatch.getTeams().get(0); + MatchTeam teamB = targetMatch.getTeams().get(1); + + if (teamA.getAllMembers().size() == 1 && teamB.getAllMembers().size() == 1) { + UUID teamAPlayer = teamA.getFirstMember(); + UUID teamBPlayer = teamB.getFirstMember(); + + if ( + !settingHandler.getSetting(Bukkit.getPlayer(teamAPlayer), Setting.ALLOW_SPECTATORS) || + !settingHandler.getSetting(Bukkit.getPlayer(teamBPlayer), Setting.ALLOW_SPECTATORS) + ) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Not all players have their spectators setting turned off right now."); + return; + } + } + } + + Player teleportTo = null; + + // /spectate looks up matches being played OR watched by the target, + // so we can only target them if they're not spectating + if (!targetMatch.isSpectator(target.getUniqueId())) { + teleportTo = target; + } + + if (PotPvPValidation.canUseSpectateItemIgnoreMatchSpectating(sender)) { + Match currentlySpectating = matchHandler.getMatchSpectating(sender); + + if (currentlySpectating != null) { + if (currentlySpectating.equals(targetMatch)) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You're already spectating this match."); + return; + } + + currentlySpectating.removeSpectator(sender); + } + + targetMatch.addSpectator(sender, teleportTo); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/command/ToggleMatchCommands.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/command/ToggleMatchCommands.java new file mode 100644 index 0000000..1b8416f --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/command/ToggleMatchCommands.java @@ -0,0 +1,31 @@ +package com.elevatemc.potpvp.match.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.elib.command.Command; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class ToggleMatchCommands { + + @Command(names = { "toggleMatches unranked" }, permission = "op") + public static void toggleMatchesUnranked(Player sender) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + boolean newState = !matchHandler.isUnrankedMatchesDisabled(); + matchHandler.setUnrankedMatchesDisabled(newState); + + sender.sendMessage(ChatColor.YELLOW + "Unranked matches are now " + ChatColor.UNDERLINE + (newState ? "disabled" : "enabled") + ChatColor.YELLOW + "."); + } + + @Command(names = { "toggleMatches ranked" }, permission = "op") + public static void toggleMatchesRanked(Player sender) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + boolean newState = !matchHandler.isRankedMatchesDisabled(); + matchHandler.setRankedMatchesDisabled(newState); + + sender.sendMessage(ChatColor.YELLOW + "Ranked matches are now " + ChatColor.UNDERLINE + (newState ? "disabled" : "enabled") + ChatColor.YELLOW + "."); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/event/MatchCountdownStartEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/event/MatchCountdownStartEvent.java new file mode 100644 index 0000000..0da62d1 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/event/MatchCountdownStartEvent.java @@ -0,0 +1,25 @@ +package com.elevatemc.potpvp.match.event; + +import lombok.Getter; +import com.elevatemc.potpvp.match.Match; +import org.bukkit.event.HandlerList; + +/** + * Called when a match's countdown starts (when its {@link com.elevatemc.potpvp.match.MatchState} changes + * to {@link com.elevatemc.potpvp.match.MatchState#COUNTDOWN}) + * @see com.elevatemc.potpvp.match.MatchState#COUNTDOWN + */ +public final class MatchCountdownStartEvent extends MatchEvent { + + @Getter private static HandlerList handlerList = new HandlerList(); + + public MatchCountdownStartEvent(Match match) { + super(match); + } + + @Override + public HandlerList getHandlers() { + return handlerList; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/event/MatchDeathEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/event/MatchDeathEvent.java new file mode 100644 index 0000000..90f9c29 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/event/MatchDeathEvent.java @@ -0,0 +1,45 @@ +package com.elevatemc.potpvp.match.event; + +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchTeam; +import lombok.Getter; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; + +/** + * Called when a match's countdown ends (when its {@link com.elevatemc.potpvp.match.MatchState} changes + * to {@link com.elevatemc.potpvp.match.MatchState#IN_PROGRESS}) + * @see com.elevatemc.potpvp.match.MatchState#IN_PROGRESS + */ +public final class MatchDeathEvent extends MatchEvent { + + @Getter private static HandlerList handlerList = new HandlerList(); + @Getter private Player target; + @Getter private Player killer; + @Getter private DeathCause deathCause; + + public MatchDeathEvent(Match match, Player target, Player killer, DeathCause deathCause) { + super(match); + this.target = target; + this.killer = killer; + + } + + public MatchTeam findTargetTeam() { + return getMatch().getTeam(target.getUniqueId()); + } + + @Override + public HandlerList getHandlers() { + return handlerList; + } + + public enum DeathCause { + PLAYER_KILL, + DISCONNECT, + OTHER_DAMAGE, + KICK, + VOID + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/event/MatchEndEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/event/MatchEndEvent.java new file mode 100644 index 0000000..8d67845 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/event/MatchEndEvent.java @@ -0,0 +1,25 @@ +package com.elevatemc.potpvp.match.event; + +import lombok.Getter; +import com.elevatemc.potpvp.match.Match; +import org.bukkit.event.HandlerList; + +/** + * Called when a match is ended (when its {@link com.elevatemc.potpvp.match.MatchState} changes + * to {@link com.elevatemc.potpvp.match.MatchState#ENDING}) + * @see com.elevatemc.potpvp.match.MatchState#ENDING + */ +public final class MatchEndEvent extends MatchEvent { + + @Getter private static HandlerList handlerList = new HandlerList(); + + public MatchEndEvent(Match match) { + super(match); + } + + @Override + public HandlerList getHandlers() { + return handlerList; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/event/MatchEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/event/MatchEvent.java new file mode 100644 index 0000000..702cdfa --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/event/MatchEvent.java @@ -0,0 +1,22 @@ +package com.elevatemc.potpvp.match.event; + +import com.google.common.base.Preconditions; +import lombok.Getter; +import com.elevatemc.potpvp.match.Match; +import org.bukkit.event.Event; + +/** + * Represents an event involving a {@link Match} + */ +abstract class MatchEvent extends Event { + + /** + * The match involved in this event + */ + @Getter private final Match match; + + MatchEvent(Match match) { + this.match = Preconditions.checkNotNull(match, "match"); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/event/MatchSpectatorJoinEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/event/MatchSpectatorJoinEvent.java new file mode 100644 index 0000000..31cfa33 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/event/MatchSpectatorJoinEvent.java @@ -0,0 +1,31 @@ +package com.elevatemc.potpvp.match.event; + +import com.google.common.base.Preconditions; +import lombok.Getter; +import com.elevatemc.potpvp.match.Match; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; + +/** + * Called when a spectator starts spectating a {@link Match}. + * It should be noted that this event WILL be called for dead + * players who immediately begin spectating a match. + */ +public final class MatchSpectatorJoinEvent extends MatchEvent { + + @Getter private static HandlerList handlerList = new HandlerList(); + + @Getter private final Player spectator; + + public MatchSpectatorJoinEvent(Player spectator, Match match) { + super(match); + + this.spectator = Preconditions.checkNotNull(spectator, "spectator"); + } + + @Override + public HandlerList getHandlers() { + return handlerList; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/event/MatchSpectatorLeaveEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/event/MatchSpectatorLeaveEvent.java new file mode 100644 index 0000000..afa0862 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/event/MatchSpectatorLeaveEvent.java @@ -0,0 +1,31 @@ +package com.elevatemc.potpvp.match.event; + +import com.google.common.base.Preconditions; +import lombok.Getter; +import com.elevatemc.potpvp.match.Match; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; + +/** + * Called when a spectator stops spectating a {@link Match}. + * This event will be called for spectators who disconnect, /leave, + * etc. This event will not be called for spectators 'leaving' a match as it ends. + */ +public final class MatchSpectatorLeaveEvent extends MatchEvent { + + @Getter private static HandlerList handlerList = new HandlerList(); + + @Getter private final Player spectator; + + public MatchSpectatorLeaveEvent(Player spectator, Match match) { + super(match); + + this.spectator = Preconditions.checkNotNull(spectator, "spectator"); + } + + @Override + public HandlerList getHandlers() { + return handlerList; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/event/MatchStartEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/event/MatchStartEvent.java new file mode 100644 index 0000000..f4a0128 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/event/MatchStartEvent.java @@ -0,0 +1,25 @@ +package com.elevatemc.potpvp.match.event; + +import lombok.Getter; +import com.elevatemc.potpvp.match.Match; +import org.bukkit.event.HandlerList; + +/** + * Called when a match's countdown ends (when its {@link com.elevatemc.potpvp.match.MatchState} changes + * to {@link com.elevatemc.potpvp.match.MatchState#IN_PROGRESS}) + * @see com.elevatemc.potpvp.match.MatchState#IN_PROGRESS + */ +public final class MatchStartEvent extends MatchEvent { + + @Getter private static HandlerList handlerList = new HandlerList(); + + public MatchStartEvent(Match match) { + super(match); + } + + @Override + public HandlerList getHandlers() { + return handlerList; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/event/MatchTerminateEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/event/MatchTerminateEvent.java new file mode 100644 index 0000000..3ae0248 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/event/MatchTerminateEvent.java @@ -0,0 +1,26 @@ +package com.elevatemc.potpvp.match.event; + +import lombok.Getter; +import com.elevatemc.potpvp.match.Match; +import org.bukkit.event.HandlerList; + +/** + * Called when a match is terminated (when its {@link com.elevatemc.potpvp.match.MatchState} changes + * to {@link com.elevatemc.potpvp.match.MatchState#TERMINATED}) + * @see com.elevatemc.potpvp.match.MatchState#TERMINATED + */ +public final class MatchTerminateEvent extends MatchEvent { + + @Getter private static HandlerList handlerList = new HandlerList(); + + + public MatchTerminateEvent(Match match) { + super(match); + } + + @Override + public HandlerList getHandlers() { + return handlerList; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchAntidoteListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchAntidoteListener.java new file mode 100644 index 0000000..48eafda --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchAntidoteListener.java @@ -0,0 +1,31 @@ +package com.elevatemc.potpvp.match.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.kit.KitItems; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerItemConsumeEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; + +public class MatchAntidoteListener implements Listener { + @EventHandler(priority = EventPriority.LOWEST) + public void onPotionDrinkEvent(PlayerItemConsumeEvent event) { + Player player = event.getPlayer(); + if (event.getItem().isSimilar(KitItems.ANTIDOTE_ITEM)) { + event.setCancelled(true); + player.setItemInHand(new ItemStack(Material.AIR)); + player.removePotionEffect(PotionEffectType.SLOW); + player.removePotionEffect(PotionEffectType.POISON); + player.removePotionEffect(PotionEffectType.BLINDNESS); + player.removePotionEffect(PotionEffectType.WEAKNESS); + player.removePotionEffect(PotionEffectType.WITHER); + player.removePotionEffect(PotionEffectType.CONFUSION); + player.removePotionEffect(PotionEffectType.HUNGER); + player.removePotionEffect(PotionEffectType.SLOW_DIGGING); + } + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchBlockPickupListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchBlockPickupListener.java new file mode 100644 index 0000000..c97b703 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchBlockPickupListener.java @@ -0,0 +1,36 @@ +package com.elevatemc.potpvp.match.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.spigot.event.BlockDropItemsEvent; +import org.bukkit.Material; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +public class MatchBlockPickupListener implements Listener { + + @EventHandler + public void onBlockDropItems(BlockDropItemsEvent event) { + Player recipient = event.getPlayer(); + if (recipient == null) return; + + Match match = PotPvPSI.getInstance().getMatchHandler().getMatchPlaying(recipient); + if (match == null) return; + + if (!match.getGameMode().getId().equals("SPLEEF")) return; + + List items = event.getToDrop(); + for (Item item : items) { + ItemStack stack = item.getItemStack(); + stack.setType(Material.SNOW_BALL); + recipient.getInventory().addItem(stack); + } + + items.clear(); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchBuildListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchBuildListener.java new file mode 100644 index 0000000..e097c98 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchBuildListener.java @@ -0,0 +1,239 @@ +package com.elevatemc.potpvp.match.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.ability.type.AntiBlockup; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.match.MatchState; +import com.elevatemc.elib.cuboid.Cuboid; +import com.elevatemc.potpvp.util.InventoryUtils; +import com.elevatemc.potpvp.util.PotionUtil; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockFormEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.player.PlayerBucketEmptyEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.potion.Potion; + +import static org.bukkit.Material.POTION; + +public final class MatchBuildListener implements Listener { + + private static final int SEARCH_RADIUS = 3; + + @EventHandler + public void onBlockBreak(BlockBreakEvent event) { + Player player = event.getPlayer(); + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + if (!matchHandler.isPlayingMatch(player)) { + // BasicPreventionListener handles this + return; + } + + Match match = matchHandler.getMatchPlaying(player); + + if (!match.getGameMode().getBuildingAllowed() || match.getState() != MatchState.IN_PROGRESS) { + event.setCancelled(true); + } + + if (match.getGameMode().getBuildingAllowed() && match.getGameMode().equals(GameModes.TRAPPING) && !player.hasMetadata("TRAPPER")) { + event.setCancelled(true); + } + } + + @EventHandler + public void onBlockPlace(BlockPlaceEvent event) { + Player player = event.getPlayer(); + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + if (!matchHandler.isPlayingMatch(player)) { + // BasicPreventionListener handles this + return; + } + + Match match = matchHandler.getMatchPlaying(player); + + if (match.getGameMode().equals(GameModes.TRAPPING) && player.hasMetadata("TRAPPER")) { + return; + } + + if (!match.getGameMode().getBuildingAllowed()) { + event.setCancelled(true); + return; + } + + if (match.getState() != MatchState.IN_PROGRESS) { + event.setCancelled(true); + return; + } + + if (!canBePlaced(event.getBlock(), match)) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You can't build here."); + event.setCancelled(true); + player.teleport(player.getLocation()); // teleport them back so they can't block-glitch + return; + } + + // apparently this is a problem + if (event.getPlayer().getItemInHand().getType() == Material.FLINT_AND_STEEL && event.getBlockAgainst().getType() == Material.GLASS) { + event.setCancelled(true); + return; + } + + match.recordPlacedBlock(event.getBlock()); + } + + @EventHandler(priority = EventPriority.LOWEST) + private void onInteract(PlayerInteractEvent event) { + if (event.getAction() != Action.RIGHT_CLICK_BLOCK || event.getClickedBlock() == null) { + return; + } + + final Block clickedBlock = event.getClickedBlock(); + + if (!AntiBlockup.NO_INTERACT.contains(clickedBlock.getType()) && !clickedBlock.getType().name().contains("SIGN")) { + return; + } + + final Player player = event.getPlayer(); + + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + if (matchHandler.isSpectatingMatch(player)) { + event.setCancelled(true); + return; + } + + if (!matchHandler.isPlayingMatch(player)) { + // BasicPreventionListener handles this + return; + } + + final Match match = matchHandler.getMatchPlaying(player); + + if (match.getGameMode() != GameModes.TRAPPING) { + return; + } + + if (player.hasMetadata("TRAPPER")) { + return; + } + + if (event.getItem() != null && event.getItem().getType() == POTION && event.getItem().getDurability() != 0) { + Potion potion = Potion.fromItemStack(event.getItem()); + + if (potion.isSplash()) { + PotionUtil.splashPotion(player, event.getItem()); + if (player.getItemInHand() != null && player.getItemInHand().isSimilar(event.getItem())) { + player.setItemInHand(null); + player.updateInventory(); + } else { + InventoryUtils.removeAmountFromInventory(player.getInventory(), event.getItem(), 1); + } + } + } + + event.setCancelled(true); + } + + @EventHandler + public void onBucketEmpty(PlayerBucketEmptyEvent event) { + Player player = event.getPlayer(); + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + if (!matchHandler.isPlayingMatch(player)) { + return; + } + + Match match = matchHandler.getMatchPlaying(player); + + if (!match.getGameMode().getBuildingAllowed() || match.getState() != MatchState.IN_PROGRESS) { + event.setCancelled(true); + return; + } + + if (!canBePlaced(event.getBlockClicked(), match)) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You can't build here."); + event.setCancelled(true); + } + } + + @EventHandler + public void onBlockForm(BlockFormEvent event) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + for (Match match : matchHandler.getHostedMatches()) { + if (!match.getArena().getBounds().contains(event.getBlock()) || !match.getGameMode().getBuildingAllowed()) { + continue; + } + + match.recordPlacedBlock(event.getBlock()); + break; + } + } + + private boolean canBePlaced(Block placedBlock, Match match) { + for (int x = -SEARCH_RADIUS; x <= SEARCH_RADIUS; x++) { + for (int y = -SEARCH_RADIUS; y <= SEARCH_RADIUS; y++) { + for (int z = -SEARCH_RADIUS; z <= SEARCH_RADIUS; z++) { + if (x == 0 && y == 0 && z == 0) { + continue; + } + + Block current = placedBlock.getRelative(x, y, z); + + if (current.isEmpty()) { + continue; + } + + if (isBlacklistedBlock(current)) { + continue; + } + + if (isBorderGlass(current, match)) { + continue; + } + + if (!match.canBeBroken(current)) { + return true; + } + } + } + } + + return false; + } + + private boolean isBlacklistedBlock(Block block) { + return block.isLiquid() || block.getType().name().contains("LOG") || block.getType().name().contains("LEAVES"); + } + + private boolean isBorderGlass(Block block, Match match) { + if (block.getType() != Material.GLASS) { + return false; + } + + Cuboid cuboid = match.getArena().getBounds(); + + // the reason we do a buffer of 3 blocks here is because sometimes + // schematics aren't perfectly copied and the glass isn't exactly on the + // limit of the arena. + return (getDistanceBetween(block.getX(), cuboid.getLowerX()) <= 3 || getDistanceBetween(block.getX(), cuboid.getUpperX()) <= 3) || (getDistanceBetween(block.getZ(), cuboid.getLowerZ()) <= 3 || getDistanceBetween(block.getZ(), cuboid.getUpperZ()) <= 3); + } + + private int getDistanceBetween(int x, int z) { + return Math.abs(x - z); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchCountdownListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchCountdownListener.java new file mode 100644 index 0000000..03f345c --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchCountdownListener.java @@ -0,0 +1,82 @@ +package com.elevatemc.potpvp.match.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.match.MatchState; +import org.bukkit.Material; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.ProjectileLaunchEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.Potion; + +// the name of this listener is definitely kind of iffy (as it's really any non-IN_PROGRESS match), +// but any other ideas I had were even less descriptive +public final class MatchCountdownListener implements Listener { + + /** + * Prevents damage in non IN_PROGRESS matches + */ + @EventHandler + public void onEntityDamage(EntityDamageEvent event) { + if (event.getEntityType() != EntityType.PLAYER) { + return; + } + + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match match = matchHandler.getMatchPlaying((Player) event.getEntity()); + + if (match != null && match.getState() != MatchState.IN_PROGRESS) { + event.setCancelled(true); + } + } + + /** + * Prevents throwing potions and enderpearls in in-COUNTDOWN matches + */ + @EventHandler + public void onPlayerInteract(PlayerInteractEvent event) { + if (!event.hasItem() || !event.getAction().name().contains("RIGHT_")) { + return; + } + + ItemStack item = event.getItem(); + Material type = item.getType(); + + if ((type == Material.POTION && item.getDurability() != 0 && Potion.fromItemStack(item).isSplash()) || type == Material.ENDER_PEARL || type == Material.SNOW_BALL) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match match = matchHandler.getMatchPlaying(event.getPlayer()); + + if (match != null && match.getState() == MatchState.COUNTDOWN) { + event.setCancelled(true); + event.getPlayer().updateInventory(); + } + } + } + + + /** + * Prevents bow-shooting, rods and projectiles in general from being used in non IN_PROGRESS matches. + * @param event + */ + @EventHandler + public void onPlayerShoot(ProjectileLaunchEvent event) { + if (!(event.getEntity().getShooter() instanceof Player)) { + return; + } + + Player player = (Player) event.getEntity().getShooter(); + + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match match = matchHandler.getMatchPlaying(player); + + if (match != null && match.getState() == MatchState.COUNTDOWN) { + event.setCancelled(true); + } + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchDeathMessageListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchDeathMessageListener.java new file mode 100644 index 0000000..fd6aea4 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchDeathMessageListener.java @@ -0,0 +1,101 @@ +package com.elevatemc.potpvp.match.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.deathmessage.DeathMessageHandler; +import com.elevatemc.potpvp.deathmessage.objects.Damage; +import com.elevatemc.potpvp.deathmessage.util.UnknownDamage; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.nametag.PotPvPNametagProvider; +import com.elevatemc.potpvp.setting.SettingHandler; +import net.minecraft.server.v1_8_R3.PacketPlayOutSpawnEntityWeather; +import net.minecraft.server.v1_8_R3.PlayerConnection; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Sound; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.PlayerDeathEvent; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; +import java.util.UUID; + +public final class MatchDeathMessageListener implements Listener { + + private static final String NO_KILLER_MESSAGE = ChatColor.translateAlternateColorCodes('&', "%s&7 died."); + private static final String KILLED_BY_OTHER_MESSAGE = ChatColor.translateAlternateColorCodes('&', "%s&7 killed %s&7."); + + @EventHandler(priority = EventPriority.LOW) + public void onPlayerDeath(PlayerDeathEvent event) { + Player killed = event.getEntity(); + Player killer = killed.getKiller(); + + SettingHandler settingHandler = PotPvPSI.getInstance().getSettingHandler(); + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match match = matchHandler.getMatchPlaying(killed); + + if (match == null) { + return; + } + + PacketPlayOutSpawnEntityWeather lightningPacket = createLightningPacket(killed.getLocation()); + + float thunderSoundPitch = 0.8F + PotPvPSI.RANDOM.nextFloat() * 0.2F; + float explodeSoundPitch = 0.5F + PotPvPSI.RANDOM.nextFloat() * 0.2F; + + List record = PotPvPSI.getInstance().getDeathMessageHandler().getDamage(killed); + + for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { + UUID onlinePlayerUuid = onlinePlayer.getUniqueId(); + + // if this player has no relation to the match skip + if (match.getTeam(onlinePlayerUuid) == null && !match.isSpectator(onlinePlayerUuid)) { + continue; + } + + // if the killer died before the player did we just pretend they weren't + // involved (their name would show up as a spectator, which would be confusing + // for players) + if (killer == null || match.isSpectator(killer.getUniqueId())) { + UnknownDamage dmg = new UnknownDamage(killed.getName(), 1.0D); + onlinePlayer.sendMessage(dmg.getDeathMessage(onlinePlayer)); + } else { + if (record != null && record.size() > 0) { + onlinePlayer.sendMessage(record.get(record.size() - 1).getDeathMessage(onlinePlayer)); + } else { + UnknownDamage dmg = new UnknownDamage(killed.getName(), 1.0D); + onlinePlayer.sendMessage(dmg.getDeathMessage(onlinePlayer)); + } + } + + onlinePlayer.playSound(killed.getLocation(), Sound.AMBIENCE_THUNDER, 10000F, thunderSoundPitch); + onlinePlayer.playSound(killed.getLocation(), Sound.EXPLODE, 2.0F, explodeSoundPitch); + + sendLightningPacket(onlinePlayer, lightningPacket); + } + + PotPvPSI.getInstance().getDeathMessageHandler().clearDamage(killed); + } + + private PacketPlayOutSpawnEntityWeather createLightningPacket(Location location) { + PacketPlayOutSpawnEntityWeather packet = new PacketPlayOutSpawnEntityWeather(); + packet.setA(128); // entity id of 128 + packet.setB(1); // type of lightning (1) + packet.setC((int) (location.getX() * 32.0D)); // x + packet.setD((int) (location.getY() * 32.0D)); // y + packet.setE((int) (location.getZ() * 32.0D)); // z + + return packet; + } + + private void sendLightningPacket(Player target, PacketPlayOutSpawnEntityWeather packet) { + PlayerConnection playerConnection = ((CraftPlayer) target).getHandle().playerConnection; + playerConnection.sendPacket(packet); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchDurationLimitListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchDurationLimitListener.java new file mode 100644 index 0000000..5d6106e --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchDurationLimitListener.java @@ -0,0 +1,72 @@ +package com.elevatemc.potpvp.match.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchEndReason; +import com.elevatemc.potpvp.match.MatchState; +import com.elevatemc.potpvp.match.event.MatchStartEvent; +import com.elevatemc.elib.util.TimeUtils; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +public final class MatchDurationLimitListener implements Listener { + + private static final int DURATION_LIMIT_SECONDS = (int) TimeUnit.MINUTES.toSeconds(60); + private static final String TIME_WARNING_MESSAGE = ChatColor.RED + "The match will forcefully end in %s."; + private static final String TIME_EXCEEDED_MESSAGE = ChatColor.RED.toString() + ChatColor.BOLD + "Match time exceeded %s. Ending match..."; + + @EventHandler + public void onMatchCountdownEnd(MatchStartEvent event) { + Match match = event.getMatch(); + + new BukkitRunnable() { + + int secondsRemaining = DURATION_LIMIT_SECONDS; + + @Override + public void run() { + if (match.getState() != MatchState.IN_PROGRESS) { + cancel(); + return; + } + + // Very ugly to do it here, but I don't want to put another runnable per match + if (match.getGameMode().equals(GameModes.SUMO)) { + match.getTeams().forEach(t -> t.getAllMembers().stream().map(Bukkit::getPlayer).filter(Objects::nonNull).forEach(p -> { + p.setHealth(20); + p.setFoodLevel(20); + p.setSaturation(20); + })); + } + + switch (secondsRemaining) { + case 120: + case 60: + case 30: + case 15: + case 10: + case 5: + match.messageAll(String.format(TIME_WARNING_MESSAGE, TimeUtils.formatIntoDetailedString(secondsRemaining))); + break; + case 0: + match.messageAll(String.format(TIME_EXCEEDED_MESSAGE, TimeUtils.formatIntoDetailedString(DURATION_LIMIT_SECONDS))); + match.endMatch(MatchEndReason.DURATION_LIMIT_EXCEEDED); + break; + default: + break; + } + + secondsRemaining--; + } + + }.runTaskTimer(PotPvPSI.getInstance(), 20L, 20L); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchEnderPearlDamageListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchEnderPearlDamageListener.java new file mode 100644 index 0000000..30adf40 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchEnderPearlDamageListener.java @@ -0,0 +1,28 @@ +package com.elevatemc.potpvp.match.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import org.bukkit.entity.EnderPearl; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; + +public final class MatchEnderPearlDamageListener implements Listener { + + @EventHandler + public void onEntityDamageByEntity(EntityDamageByEntityEvent event) { + if (event.getCause() == EntityDamageEvent.DamageCause.FALL && event.getEntity() instanceof Player && event.getDamager() instanceof EnderPearl) { + Player player = (Player) event.getEntity(); + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match match = matchHandler.getMatchPlaying(player); + + if (match != null && !match.getGameMode().getPearlDamage()) { + event.setCancelled(true); + } + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchEnderPearlListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchEnderPearlListener.java new file mode 100644 index 0000000..fc9ba23 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchEnderPearlListener.java @@ -0,0 +1,36 @@ +package com.elevatemc.potpvp.match.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.ArenaSchematic; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerInteractEvent; + +public final class MatchEnderPearlListener implements Listener { + + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerInteract(PlayerInteractEvent event) { + if (!event.hasItem() || event.getItem().getType() != Material.ENDER_PEARL || !event.getAction().name().contains("RIGHT_")) { + return; + } + Player player = event.getPlayer(); + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match match = matchHandler.getMatchPlaying(player); + if (match == null) { + return; + } + ArenaSchematic schematic = PotPvPSI.getInstance().getArenaHandler().getSchematic(match.getArena().getSchematic()); + if (!schematic.isPearlsAllowed()) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED.toString() + ChatColor.BOLD + "Invalid Pearl! " + ChatColor.YELLOW + "You cannot Enderpearl into this region!"); + event.setCancelled(true); + player.updateInventory(); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchFreezeListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchFreezeListener.java new file mode 100644 index 0000000..3d12d4d --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchFreezeListener.java @@ -0,0 +1,31 @@ +package com.elevatemc.potpvp.match.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchState; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerMoveEvent; + +public class MatchFreezeListener implements Listener { + + @EventHandler + public void onCountdownEnd(PlayerMoveEvent event) { + Player player = event.getPlayer(); + + Location from = event.getFrom(); + Location to = event.getTo(); + + if (from.getBlockX() == to.getBlockX() && from.getBlockZ() == to.getBlockZ()) return; + + Match match = PotPvPSI.getInstance().getMatchHandler().getMatchPlaying(player); + + if (match == null || !match.getGameMode().equals(GameModes.SUMO) || match.getState() != MatchState.COUNTDOWN) return; + + event.getPlayer().teleport(from); + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchGeneralListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchGeneralListener.java new file mode 100644 index 0000000..32753f9 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchGeneralListener.java @@ -0,0 +1,382 @@ +package com.elevatemc.potpvp.match.listener; + +import com.elevatemc.elib.util.UUIDUtils; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.Arena; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import com.elevatemc.potpvp.kit.Kit; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.match.MatchState; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.nametag.PotPvPNametagProvider; +import com.elevatemc.elib.cuboid.Cuboid; +import com.elevatemc.elib.util.PlayerUtils; +import com.lunarclient.bukkitapi.LunarClientAPI; +import com.lunarclient.bukkitapi.nethandler.client.LCPacketTeammates; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.entity.Snowball; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.*; +import org.bukkit.inventory.ItemStack; + +import java.util.*; + +public final class MatchGeneralListener implements Listener { + + @EventHandler + public void onPlayerDeath(PlayerDeathEvent event) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Player player = event.getEntity(); + Match match = matchHandler.getMatchPlaying(player); + + if (match == null) { + return; + } + + MatchTeam team = match.getTeam(player.getUniqueId()); + if (team != null) { + team.removeRallyForPlayer(player); + LunarClientAPI.getInstance().sendTeammates(player, new LCPacketTeammates(null, 1, new HashMap<>())); + + if (match.getGameMode().equals(GameModes.PEARL_FIGHT)) { + event.getDrops().clear(); + team.setLives(team.getLives() - 1); + if (team.getLives() > 0) { + player.spigot().respawn(); + if (team == match.getTeams().get(1)) { + player.teleport(match.getArena().getTeam2Spawn()); + } else { + player.teleport(match.getArena().getTeam1Spawn()); + } + + if (team.getAllMembers().size() == 1) { + match.messageAll(ChatColor.DARK_AQUA + UUIDUtils.name(team.getFirstAliveMember()) + " has " + team.getLives() + " lives left."); + } + + Kit appliedKit = MatchKitSelectionListener.appliedKits.get(player.getUniqueId()); + if (appliedKit == null) { + Kit.ofDefaultKit(GameModeKit.byId("PEARL_FIGHT")).apply(player, false); + } else { + appliedKit.apply(player, false); + } + } else { + if (team.getAllMembers().size() == 1) { + } + match.messageAll(ChatColor.DARK_AQUA + UUIDUtils.name(team.getFirstAliveMember()) + " has no lives left."); + + team.forEachAlive(p -> { + match.markDead(p); + match.addSpectator(p, null, true); + }); + match.getTeams().forEach(t -> t.getAliveMembers().forEach(p -> MatchKitSelectionListener.appliedKits.remove(p))); + } + return; + } + } + + // creates 'proper' player death animation (of the player falling over) + // which we don't get due to our immediate respawn + PlayerUtils.animateDeath(player); + + match.markDead(player); + match.addSpectator(player, null, true); + player.teleport(player.getLocation().add(0, 2, 0)); + + // if we're ending the match we don't drop pots/bowls + if (match.getState() == MatchState.ENDING) { + event.getDrops().removeIf(i -> i.getType() == Material.POTION || i.getType() == Material.GLASS_BOTTLE || i.getType() == Material.MUSHROOM_SOUP || i.getType() == Material.BOWL); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerQuit(PlayerQuitEvent event) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Player player = event.getPlayer(); + Match match = matchHandler.getMatchPlaying(player); + + if (match == null) { + return; + } + + MatchState state = match.getState(); + + if (state == MatchState.COUNTDOWN || state == MatchState.IN_PROGRESS) { + for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { + UUID onlinePlayerUuid = onlinePlayer.getUniqueId(); + + // if this player has no relation to the match skip + if (match.getTeam(onlinePlayerUuid) == null && !match.isSpectator(onlinePlayerUuid)) { + continue; + } + + ChatColor playerColor = PotPvPNametagProvider.getNameColor(player, onlinePlayer); + String playerFormatted = playerColor + player.getName(); + + onlinePlayer.sendMessage(playerFormatted + ChatColor.GRAY + " disconnected."); + } + } + + // run this regardless of match state + match.markDead(player); + } + + // "natural" teleports (like enderpearls) are forwarded down and + // treated as a move event, plugin teleports (specifically + // those originating in this plugin) are ignored. + @EventHandler + public void onPlayerTeleport(PlayerTeleportEvent event) { + switch (event.getCause()) { + case PLUGIN: + case COMMAND: + case UNKNOWN: + return; + default: + break; + } + + onPlayerMove(event); + } + + @EventHandler + public void onPlayerMove(PlayerMoveEvent event) { + Player player = event.getPlayer(); + Location from = event.getFrom(); + Location to = event.getTo(); + + if ( + from.getBlockX() == to.getBlockX() && + from.getBlockY() == to.getBlockY() && + from.getBlockZ() == to.getBlockZ() + ) { + return; + } + + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match match = matchHandler.getMatchPlayingOrSpectating(player); + + if (match == null) { + return; + } + + Arena arena = match.getArena(); + Cuboid bounds = arena.getBounds(); + + // pretend the vertical bounds of the arena are 2 blocks lower than they + // are to avoid issues with players hitting their heads on the glass (Jon said to do this) + // looks kind of funny but in a high frequency event this is by far the fastest + if (!bounds.contains(to) || !bounds.contains(to.getBlockX(), to.getBlockY() + 2, to.getBlockZ())) { + // spectators get a nice message, players just get cancelled + if (match.isSpectator(player.getUniqueId())) { + player.teleport(arena.getSpectatorSpawn()); + } else if (to.getBlockY() >= bounds.getUpperY() || to.getBlockY() <= bounds.getLowerY()) { // if left vertically + if (!match.getGameMode().isVoidTeleport()) { + if (to.getBlockY() <= bounds.getLowerY() && bounds.getLowerY() - to.getBlockY() <= 20) return; // let the player fall 10 blocks + match.markDead(player); + match.addSpectator(player, null, true); + } + + if (match.getGameMode().equals(GameModes.PEARL_FIGHT)) { + if (to.getBlockY() <= bounds.getLowerY() - 30) { + MatchTeam team = match.getTeam(player.getUniqueId()); + if (team == null) { + player.teleport(arena.getSpectatorSpawn()); + } else if (team == match.getTeams().get(1)) { + player.teleport(arena.getTeam1Spawn()); + player.setHealth(0); + } else { + player.teleport(arena.getTeam2Spawn()); + player.setHealth(0); + } + } + + } else { + player.teleport(arena.getSpectatorSpawn()); + } + } else { + if (!match.getGameMode().isVoidTeleport()) { // if they left horizontally + match.markDead(player); + match.addSpectator(player, null, true); + player.teleport(arena.getSpectatorSpawn()); + } + + event.setCancelled(true); + } + } else if (to.getBlockY() + 5 < arena.getSpectatorSpawn().getBlockY()) { // if the player is still in the arena bounds but fell down from the spawn point + if (match.getGameMode().equals(GameModes.SUMO)) { + match.markDead(player); + match.addSpectator(player, null, true); + player.teleport(arena.getSpectatorSpawn()); + } + } + } + + /** + * Prevents (non-fall) damage between ANY two players not on opposing {@link MatchTeam}s. + * This includes cancelling damage from a player not in a match attacking a player in a match. + */ + @EventHandler + public void onEntityDamageByEntity(EntityDamageByEntityEvent event) { + if (event.getEntityType() != EntityType.PLAYER) { + return; + } + + // in the context of an EntityDamageByEntityEvent, DamageCause.FALL + // is the 0 hearts of damage and knockback applied when hitting + // another player with a thrown enderpearl. We allow this damage + // in order to be consistent with HCTeams + if (event.getCause() == EntityDamageEvent.DamageCause.FALL) { + return; + } + + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Player victim = (Player) event.getEntity(); + Player damager = PlayerUtils.getDamageSource(event.getDamager()); + + if (damager == null) { + return; + } + + Match match = matchHandler.getMatchPlaying(damager); + boolean isSpleef = match != null && match.getGameMode().getId().equals("SPLEEF"); + boolean isSumo = match != null && match.getGameMode().equals(GameModes.SUMO); + boolean isInvaded = match != null && match.getGameMode().getId().equals("INVADED"); + + // we only specifically allow damage where both players are in a match together + // and not on the same team, everything else is cancelled. + // Match is not null + if (match != null) { + MatchTeam victimTeam = match.getTeam(victim.getUniqueId()); + MatchTeam damagerTeam = match.getTeam(damager.getUniqueId()); + + // allow snowballs + if (isSpleef && event.getDamager() instanceof Snowball) return; + + // set zero damage on boxing & sumo + if (isSumo && victimTeam != null && victimTeam != damagerTeam) { + // Ugly hack because people actually lose health & hunger in sumo somehow + event.setDamage(0); + return; + } + + // allow opponent hit opponent && spleef should be false + if (victimTeam != null && victimTeam != damagerTeam && !isSpleef) { + return; + } + + // allow boosting in invaded + if (victim.getUniqueId().equals(damager.getUniqueId()) && isInvaded) { + return; + } + } + + event.setCancelled(true); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onDamage(EntityDamageByEntityEvent event) { + if (event.getEntity() instanceof Player && event.getDamager() instanceof Player) { + Player player = (Player) event.getDamager(); + Match match = PotPvPSI.getInstance().getMatchHandler().getMatchPlaying(player); + if (match != null) { + MatchTeam team = match.getTeam(player.getUniqueId()); + if (team != null) { + team.setHits(team.getHits() + 1); + } + } + } + + } + + @EventHandler + public void onPlayerDropItem(PlayerDropItemEvent event) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Player player = event.getPlayer(); + + if (!matchHandler.isPlayingMatch(player)) { + return; + } + + ItemStack itemStack = event.getItemDrop().getItemStack(); + Material itemType = itemStack.getType(); + String itemTypeName = itemType.name().toLowerCase(); + int heldSlot = player.getInventory().getHeldItemSlot(); + + // don't let players drop swords, axes, and bows in the first slot + if (!PlayerUtils.hasOwnInventoryOpen(player) && heldSlot == 0 && (itemTypeName.contains("sword") || itemTypeName.contains("axe") || itemType == Material.BOW)) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You can't drop that while you're holding it in slot 1."); + event.setCancelled(true); + } + + // glass bottles and bowls are removed from inventories but + // don't spawn items on the ground + if (itemType == Material.GLASS_BOTTLE || itemType == Material.BOWL) { + event.getItemDrop().remove(); + } + } + + @EventHandler + public void onPickup(PlayerPickupItemEvent event) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Player player = event.getPlayer(); + + if (!matchHandler.isPlayingMatch(player)) { + return; + } + + Match match = matchHandler.getMatchPlaying(event.getPlayer()); + if (match == null) return; + + if (match.getState() == MatchState.ENDING || match.getState() == MatchState.TERMINATED) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onConsume(PlayerItemConsumeEvent event) { + ItemStack stack = event.getItem(); + if (stack == null || stack.getType() != Material.POTION) return; + + Bukkit.getScheduler().runTaskLater(PotPvPSI.getInstance(), () -> { + //event.getPlayer().setItemInHand(null); + }, 1L); + } + + private final List disallowedMaterial = Arrays.asList(Material.CHEST, Material.TRAPPED_CHEST, Material.ENDER_CHEST, Material.SIGN, Material.SIGN_POST, Material.HOPPER, Material.ENCHANTMENT_TABLE, Material.DROPPER, Material.DISPENSER, Material.BEACON, Material.ANVIL); + + @EventHandler + public void onInteractEvent(PlayerInteractEvent event) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Player player = event.getPlayer(); + + if (!matchHandler.isPlayingMatch(player)) { + return; + } + + GameMode gameMode = matchHandler.getMatchPlaying(player).getGameMode(); + if (gameMode.equals(GameModes.TEAMFIGHT) || gameMode.equals(GameModes.TEAMFIGHT_DEBUFF)) { + if (event.getAction() != Action.RIGHT_CLICK_BLOCK || event.getClickedBlock() == null) { + return; + } + + if (this.disallowedMaterial.contains(event.getClickedBlock().getType())) { + event.setCancelled(true); + event.setUseInteractedBlock(Event.Result.DENY); + } + } + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchHardcoreHealingListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchHardcoreHealingListener.java new file mode 100644 index 0000000..050d911 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchHardcoreHealingListener.java @@ -0,0 +1,33 @@ +package com.elevatemc.potpvp.match.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityRegainHealthEvent; + +public final class MatchHardcoreHealingListener implements Listener { + + @EventHandler + public void onEntityRegainHealth(EntityRegainHealthEvent event) { + if (!(event.getEntity() instanceof Player) || event.getRegainReason() != EntityRegainHealthEvent.RegainReason.SATIATED) { + return; + } + + Player player = (Player) event.getEntity(); + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + if (!matchHandler.isPlayingMatch(player)) { + return; + } + + Match match = matchHandler.getMatchPlaying(player); + + if (match.getGameMode().getHardcoreHealing()) { + event.setCancelled(true); + } + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchHealthDisplayListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchHealthDisplayListener.java new file mode 100644 index 0000000..21b1d8d --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchHealthDisplayListener.java @@ -0,0 +1,232 @@ +package com.elevatemc.potpvp.match.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.match.event.MatchSpectatorJoinEvent; +import com.elevatemc.potpvp.match.event.MatchSpectatorLeaveEvent; +import com.elevatemc.potpvp.match.event.MatchStartEvent; +import com.elevatemc.potpvp.match.event.MatchTerminateEvent; +import com.elevatemc.spigot.event.PlayerHealthChangeEvent; +import net.minecraft.server.v1_8_R3.PacketPlayOutScoreboardScore; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.scoreboard.DisplaySlot; +import org.bukkit.scoreboard.Objective; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +/** + * Handles registering and un-registering the Objective that shows the health + * below player's name-tags. This is also responsible for listening to health + * changes and sending the update score packets manually, for consistency. + */ +public final class MatchHealthDisplayListener implements Listener { + + private static final String OBJECTIVE_NAME = "HealthDisplay"; + + @EventHandler + public void onMatchCountdownStart(MatchStartEvent event) { + Match match = event.getMatch(); + + if (!match.getGameMode().getHealthShown()) { + return; + } + + Bukkit.getScheduler().runTaskLater(PotPvPSI.getInstance(), () -> { + // initialize the objective for all of the recipients (players + spectators) + for (Player player : getRecipients(match)) { + initialize(player); + } + + // send the health of the players in the match to all of the recipients + for (Player player : getPlayers(match)) { + sendToAll(player, match); + } + }, 1L); + } + + @EventHandler + public void onMatchTerminate(MatchTerminateEvent event) { + Match match = event.getMatch(); + + if (!match.getGameMode().getHealthShown()) { + return; + } + + // clear the objective for all players and spectators + for (Player player : getRecipients(match)) { + clearAll(player); + } + } + + @EventHandler(priority = EventPriority.LOW) + public void onPlayerDeath(PlayerDeathEvent event) { + Player player = event.getEntity(); + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + if (!matchHandler.isPlayingMatch(player)) { + return; + } + + Match match = matchHandler.getMatchPlaying(player); + + // remove the dead player's scores + if (match.getGameMode().getHealthShown()) { + for (Player viewer : getRecipients(match)) { + clear(viewer, player); + } + } + } + + @EventHandler + public void onSpectatorJoin(MatchSpectatorJoinEvent event) { + Match match = event.getMatch(); + + if (!match.getGameMode().getHealthShown()) { + return; + } + + // initialize the objective and send everyone's health to the spectator who joined + initialize(event.getSpectator()); + sendAllTo(event.getSpectator(), match); + } + + @EventHandler + public void onSpectatorLeave(MatchSpectatorLeaveEvent event) { + if (!event.getMatch().getGameMode().getHealthShown()) { + return; + } + + // clear the spectator's health display objective + clearAll(event.getSpectator()); + } + + @EventHandler + public void onHealthChange(PlayerHealthChangeEvent event) { + Player player = event.getPlayer(); + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + if (!matchHandler.isPlayingMatch(player)) { + return; + } + + Match match = matchHandler.getMatchPlaying(player); + + // send the health change to everyone + if (match.getGameMode().getHealthShown()) { + sendToAll(player, match); + } + } + + private void sendAllTo(Player viewer, Match match) { + Objective objective = viewer.getScoreboard().getObjective(DisplaySlot.BELOW_NAME); + + if (objective == null) { + return; // not initialized + } + + for (Player target : getPlayers(match)) { + try { + PacketPlayOutScoreboardScore packet = new PacketPlayOutScoreboardScore(); + aField.set(packet, target.getDisplayName()); + bField.set(packet, OBJECTIVE_NAME); + cField.set(packet, getHealth(target)); + dField.set(packet, 0); + + ((CraftPlayer) viewer).getHandle().playerConnection.sendPacket(packet); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private void sendToAll(Player target, Match match) { + try { + PacketPlayOutScoreboardScore packet = new PacketPlayOutScoreboardScore(); + aField.set(packet, target.getDisplayName()); + bField.set(packet, OBJECTIVE_NAME); + cField.set(packet, getHealth(target)); + dField.set(packet, 0); + + for (Player viewer : getRecipients(match)) { + ((CraftPlayer) viewer).getHandle().playerConnection.sendPacket(packet); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void initialize(Player player) { + if (player.getScoreboard().getObjective(DisplaySlot.BELOW_NAME) == null) { + Objective objective = player.getScoreboard().registerNewObjective(OBJECTIVE_NAME, "dummy"); + objective.setDisplayName(ChatColor.DARK_RED + "❤"); + objective.setDisplaySlot(DisplaySlot.BELOW_NAME); + } + } + + private void clearAll(Player player) { + Objective objective = player.getScoreboard().getObjective(DisplaySlot.BELOW_NAME); + if (objective != null) { + objective.unregister(); + } + player.getScoreboard().clearSlot(DisplaySlot.BELOW_NAME); + } + + private void clear(Player viewer, Player target) { + viewer.getScoreboard().resetScores(target.getDisplayName()); + } + + private List getRecipients(Match match) { + List recipients = new ArrayList<>(getPlayers(match)); + match.getSpectators().stream().map(Bukkit::getPlayer).forEach(recipients::add); + return recipients; + } + + private List getPlayers(Match match) { + List players = new ArrayList<>(); + + for (MatchTeam team : match.getTeams()) { + team.getAliveMembers().stream().map(Bukkit::getPlayer).forEach(players::add); + } + + return players; + } + + private int getHealth(Player player) { + return (int) Math.ceil(player.getHealth() + ((CraftPlayer) player).getHandle().getAbsorptionHearts()); + } + + private static Field aField = null; + private static Field bField = null; + private static Field cField = null; + private static Field dField = null; + + static { + try { + aField = PacketPlayOutScoreboardScore.class.getDeclaredField("a"); + aField.setAccessible(true); + + bField = PacketPlayOutScoreboardScore.class.getDeclaredField("b"); + bField.setAccessible(true); + + cField = PacketPlayOutScoreboardScore.class.getDeclaredField("c"); + cField.setAccessible(true); + + dField = PacketPlayOutScoreboardScore.class.getDeclaredField("d"); + dField.setAccessible(true); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchKitSelectionListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchKitSelectionListener.java new file mode 100644 index 0000000..eda8e3a --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchKitSelectionListener.java @@ -0,0 +1,268 @@ +package com.elevatemc.potpvp.match.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.elevatemc.potpvp.gamemode.kit.GameModeKit; +import com.elevatemc.potpvp.kit.Kit; +import com.elevatemc.potpvp.kit.KitHandler; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.match.event.MatchCountdownStartEvent; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.potpvp.pvpclasses.PvPClasses; +import com.elevatemc.potpvp.hctranked.game.RankedGame; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public final class MatchKitSelectionListener implements Listener { + + public static Map appliedKits = new HashMap<>(); + + /** + * Give players their kits when their match countdown starts + */ + @EventHandler + public void onMatchCountdownStart(MatchCountdownStartEvent event) { + KitHandler kitHandler = PotPvPSI.getInstance().getKitHandler(); + Match match = event.getMatch(); + GameMode gameMode = match.getGameMode(); + + if (gameMode.equals(GameModes.SUMO)) return; // no kits for sumo + if (gameMode.equals(GameModes.SOTW)) return; // no kits for SOTW - it is randomised + + for (Player player : Bukkit.getOnlinePlayers()) { + MatchTeam team = match.getTeam(player.getUniqueId()); + + if (team == null) { + continue; + } + + List customKits = null; + ItemStack defaultKitItem = null; + + if (gameMode.equals(GameModes.TEAMFIGHT) || gameMode.equals(GameModes.TEAMFIGHT_DEBUFF)) { + String kitPrefix = ""; + if (gameMode.equals(GameModes.TEAMFIGHT_DEBUFF)) { + kitPrefix = "DEBUFF_"; + } + GameModeKit diamond = GameModeKit.byId(kitPrefix + "DIAMOND_HCF"); + GameModeKit bard = GameModeKit.byId(kitPrefix + "BARD_HCF"); + GameModeKit archer = GameModeKit.byId(kitPrefix + "ARCHER_HCF"); + GameModeKit rogue = GameModeKit.byId(kitPrefix + "ROGUE_HCF"); + + Party party = PotPvPSI.getInstance().getPartyHandler().getParty(player); + RankedGame game = PotPvPSI.getInstance().getHCTRankedHandler().getGameHandler().getJoinedGame(player); + + if (party == null && game == null) { + customKits = kitHandler.getKits(player, diamond); + defaultKitItem = Kit.ofDefaultKit(diamond).createSelectionItem(); + applyPlayerSelectItems(player, customKits, defaultKitItem); + } else { + PvPClasses kit; + if (party != null) { + kit = party.getKits().getOrDefault(player.getUniqueId(), PvPClasses.DIAMOND); + } else if (game != null) { + kit = game.getTeam(player).getKits().getOrDefault(player.getUniqueId(), PvPClasses.DIAMOND); + } else { + kit = PvPClasses.DIAMOND; + } + + + switch (kit) { + case DIAMOND: + customKits = kitHandler.getKits(player, diamond); + defaultKitItem = Kit.ofDefaultKit(diamond).createSelectionItem(); + break; + case BARD: + customKits = kitHandler.getKits(player, bard); + defaultKitItem = Kit.ofDefaultKit(bard).createSelectionItem(); + break; + case ARCHER: + customKits = kitHandler.getKits(player, archer); + defaultKitItem = Kit.ofDefaultKit(archer).createSelectionItem(); + break; + case ROGUE: + customKits = kitHandler.getKits(player, rogue); + defaultKitItem = Kit.ofDefaultKit(archer).createSelectionItem(); + break; + } + + + } + } else if (gameMode.equals(GameModes.TRAPPING)) { + GameModeKit trapping = GameModeKit.byId("HCF_TRAP"); + GameModeKit runningIn = GameModeKit.byId("HCF_RUN"); + + if (player.hasMetadata("TRAPPER")) { + customKits = kitHandler.getKits(player, trapping); + defaultKitItem = Kit.ofDefaultKit(trapping).createSelectionItem(); + } else { + customKits = kitHandler.getKits(player, runningIn); + defaultKitItem = Kit.ofDefaultKit(runningIn).createSelectionItem(); + } + } + + if (customKits == null) { + customKits = kitHandler.getKits(player, GameModeKit.byId(gameMode.getId())); + } + if (defaultKitItem == null) { + defaultKitItem = Kit.ofDefaultKit(GameModeKit.byId(gameMode.getId())).createSelectionItem(); + } + + applyPlayerSelectItems(player, customKits, defaultKitItem); + + + player.updateInventory(); + } + } + + public void applyPlayerSelectItems(Player player, List customKits, ItemStack defaultKitItem) { + if (customKits.isEmpty()) { + player.getInventory().setItem(0, defaultKitItem); + } else { + for (Kit customKit : customKits) { + // subtract one to convert from 1-indexed kts to 0-indexed inventories + player.getInventory().setItem(customKit.getSlot() - 1, customKit.createSelectionItem()); + } + + player.getInventory().setItem(8, defaultKitItem); + } + } + + /** + * Don't let players drop their kit selection books via the Q key + */ + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onPlayerDropItem(PlayerDropItemEvent event) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match match = matchHandler.getMatchPlaying(event.getPlayer()); + + if (match == null) { + return; + } + + if (event.getItemDrop().getItemStack().getType().equals(Material.ENCHANTED_BOOK)) { + event.setCancelled(true); + } + } + + /** + * Don't let players drop their kit selection items via death + */ + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerDeath(PlayerDeathEvent event) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match match = matchHandler.getMatchPlaying(event.getEntity()); + + if (match == null) { + return; + } + + event.getDrops().removeIf(itemStack -> itemStack.getType().equals(Material.ENCHANTED_BOOK)); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerInteract(PlayerInteractEvent event) { + if (!event.hasItem() || !event.getAction().name().contains("RIGHT_")) { + return; + } + + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match match = matchHandler.getMatchPlaying(event.getPlayer()); + + if (match == null) { + return; + } + + GameMode gameMode = match.getGameMode(); + if (gameMode.equals(GameModes.SUMO) || gameMode.equals(GameModes.SOTW)) { + return; + } + + boolean pearlsAllowed = PotPvPSI.getInstance().getArenaHandler().getSchematic(match.getArena().getSchematic()).isPearlsAllowed(); + + KitHandler kitHandler = PotPvPSI.getInstance().getKitHandler(); + ItemStack clickedItem = event.getItem(); + GameModeKit gameModeKit = null; + Player player = event.getPlayer(); + + if (gameMode.equals(GameModes.TEAMFIGHT) || gameMode.equals(GameModes.TEAMFIGHT_DEBUFF)) { + String kitId = ""; + + Party party = PotPvPSI.getInstance().getPartyHandler().getParty(player); + RankedGame game = PotPvPSI.getInstance().getHCTRankedHandler().getGameHandler().getJoinedGame(player); + PvPClasses kit; + + if (party != null) { + kit = party.getKits().getOrDefault(player.getUniqueId(), PvPClasses.DIAMOND); + } else if (game != null) { + kit = game.getTeam(player).getKits().getOrDefault(player.getUniqueId(), PvPClasses.DIAMOND); + } else { + kit = PvPClasses.DIAMOND; + } + + switch (kit) { + case DIAMOND: + kitId = "DIAMOND_HCF"; + break; + case BARD: + kitId = "BARD_HCF"; + break; + case ARCHER: + kitId = "ARCHER_HCF"; + break; + case ROGUE: + kitId = "ROGUE_HCF"; + break; + } + if (gameMode.equals(GameModes.TEAMFIGHT_DEBUFF)) { + kitId = "DEBUFF_" + kitId; + } + gameModeKit = GameModeKit.byId(kitId); + } else if (gameMode.equals(GameModes.TRAPPING)) { + GameModeKit trapping = GameModeKit.byId("HCF_TRAP"); + GameModeKit runningIn = GameModeKit.byId("HCF_RUN"); + + if (player.hasMetadata("TRAPPER")) { + gameModeKit = trapping; + } else { + gameModeKit = runningIn; + } + } + + if (gameModeKit == null) { + gameModeKit = GameModeKit.byId(gameMode.getId()); + } + + for (Kit kit : kitHandler.getKits(player, gameModeKit)) { + if (kit.isSelectionItem(clickedItem)) { + kit.apply(player, !pearlsAllowed); + if (gameMode.equals(GameModes.PEARL_FIGHT)) appliedKits.put(player.getUniqueId(), kit); + return; + } + } + + Kit defaultKit = Kit.ofDefaultKit(gameModeKit); + + if (defaultKit.isSelectionItem(clickedItem)) { + defaultKit.apply(player, !pearlsAllowed); + if (gameMode.equals(GameModes.PEARL_FIGHT)) appliedKits.put(player.getUniqueId(), defaultKit); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchPartySpectateListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchPartySpectateListener.java new file mode 100644 index 0000000..360d799 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchPartySpectateListener.java @@ -0,0 +1,28 @@ +package com.elevatemc.potpvp.match.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.party.event.PartyMemberJoinEvent; +import org.bukkit.Bukkit; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +/** + * When a player joins a party, attempt to have them spectate their + * party's active match, if there is one. + * https://github.com/ElevateOrb/PotPvP-SI/issues/32 + */ +public final class MatchPartySpectateListener implements Listener { + + @EventHandler + public void onPartyMemberJoin(PartyMemberJoinEvent event) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match leaderMatch = matchHandler.getMatchPlayingOrSpectating(Bukkit.getPlayer(event.getParty().getLeader())); + + if (leaderMatch != null) { + leaderMatch.addSpectator(event.getMember(), null); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchReplayActionListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchReplayActionListener.java new file mode 100644 index 0000000..6d4828f --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchReplayActionListener.java @@ -0,0 +1,23 @@ +package com.elevatemc.potpvp.match.listener; + +public class MatchReplayActionListener { + + + // match start = player spawn + // player move + // player teleport + // player item switch + // equipment update + // start drinking potion + // stop drinking potion + // potion drink sounds + // entity spawn + // entity despawn + // each tick track locations of items + // player animation + // pot splash sounds + // pot splash particles + // match end = player despawn + + // I want to maintain a map of entity -> match, just so we can lookup all of these without going location -> arena which is intensive +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchSpectatorItemListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchSpectatorItemListener.java new file mode 100644 index 0000000..8acf966 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchSpectatorItemListener.java @@ -0,0 +1,96 @@ +package com.elevatemc.potpvp.match.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.*; +import com.elevatemc.potpvp.match.command.LeaveCommand; +import com.elevatemc.potpvp.setting.Setting; +import com.elevatemc.potpvp.setting.SettingHandler; +import com.elevatemc.potpvp.util.FancyPlayerInventory; +import com.elevatemc.potpvp.util.ItemListener; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; + +public final class MatchSpectatorItemListener extends ItemListener { + + private final Map toggleVisiblityUsable = new ConcurrentHashMap<>(); + + public MatchSpectatorItemListener(MatchHandler matchHandler) { + setPreProcessPredicate(matchHandler::isSpectatingMatch); + + Consumer toggleSpectatorsConsumer = player -> { + SettingHandler settingHandler = PotPvPSI.getInstance().getSettingHandler(); + UUID playerUuid = player.getUniqueId(); + boolean togglePermitted = toggleVisiblityUsable.getOrDefault(playerUuid, 0L) < System.currentTimeMillis(); + + if (!togglePermitted) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Please wait before doing this again!"); + return; + } + + boolean enabled = !settingHandler.getSetting(player, Setting.VIEW_OTHER_SPECTATORS); + settingHandler.updateSetting(player, Setting.VIEW_OTHER_SPECTATORS, enabled); + + if (enabled) { + player.sendMessage(ChatColor.GREEN + "Now showing other spectators."); + } else { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Now hiding other spectators."); + } + + MatchUtils.resetInventory(player); + toggleVisiblityUsable.put(playerUuid, System.currentTimeMillis() + 3_000L); + }; + + addHandler(SpectatorItems.RETURN_TO_LOBBY_ITEM, LeaveCommand::leave); + addHandler(SpectatorItems.LEAVE_PARTY_ITEM, LeaveCommand::leave); + addHandler(SpectatorItems.SHOW_SPECTATORS_ITEM, toggleSpectatorsConsumer); + addHandler(SpectatorItems.HIDE_SPECTATORS_ITEM, toggleSpectatorsConsumer); + } + + @EventHandler + public void onPlayerInteractEntity(PlayerInteractEntityEvent event) { + if (!(event.getRightClicked() instanceof Player)) { + return; + } + + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match clickerMatch = matchHandler.getMatchSpectating(event.getPlayer()); + Player clicker = event.getPlayer(); + + if (clickerMatch == null || !clicker.getItemInHand().isSimilar(SpectatorItems.VIEW_INVENTORY_ITEM)) { + return; + } + + Player clicked = (Player) event.getRightClicked(); + MatchTeam clickedTeam = clickerMatch.getTeam(clicked.getUniqueId()); + + // should only happen when clicking other spectators + if (clickedTeam == null) { + clicker.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Cannot view inventory of " + clicked.getName()); + return; + } + + boolean bypassPerm = clicker.hasPermission("potpvp.inventory.all"); + boolean sameTeam = clickedTeam.getAllMembers().contains(clicker.getUniqueId()); + + if (bypassPerm || sameTeam) { + clicker.sendMessage(ChatColor.AQUA + "Opening inventory of: " + clicked.getName()); + FancyPlayerInventory.open(clicked, clicker); // show a fancy inventory with armor and stuff! + } else { + clicker.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + clicked.getName() + " is not on your team."); + } + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + toggleVisiblityUsable.remove(event.getPlayer().getUniqueId()); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchSpectatorPreventionListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchSpectatorPreventionListener.java new file mode 100644 index 0000000..b0f20dc --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchSpectatorPreventionListener.java @@ -0,0 +1,183 @@ +package com.elevatemc.potpvp.match.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.setting.Setting; +import com.elevatemc.potpvp.setting.event.SettingUpdateEvent; +import com.elevatemc.potpvp.util.VisibilityUtils; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.*; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryDragEvent; +import org.bukkit.event.inventory.InventoryMoveItemEvent; +import org.bukkit.event.player.*; +import org.bukkit.inventory.InventoryHolder; + +public final class MatchSpectatorPreventionListener implements Listener { + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Match match = matchHandler.getMatchSpectating(event.getPlayer()); + + if (match != null) { + match.removeSpectator(event.getPlayer()); + } + } + + /** + * Prevent spectator items from dropping in the rare case spectators die + */ + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerDeath(PlayerDeathEvent event) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + if (matchHandler.isSpectatingMatch(event.getEntity())) { + event.setKeepInventory(true); + } + } + + /** + * Prevent damage caused by spectators + */ + @EventHandler + public void onEntityDamageByEntity(EntityDamageByEntityEvent event) { + if (event.getDamager() instanceof Player) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Player damager = (Player) event.getDamager(); + + if (matchHandler.isSpectatingMatch(damager)) { + event.setCancelled(true); + } + } + } + + /** + * Prevent spectators being damaged + */ + @EventHandler + public void onEntityDamage(EntityDamageEvent event) { + if(event.getEntity() instanceof Player) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Player player = (Player) event.getEntity(); + + if (matchHandler.isSpectatingMatch(player)) { + event.setCancelled(true); + } + } + } + + /** + * Prevent item drops by spectators + */ + @EventHandler + public void onPlayerDropitem(PlayerDropItemEvent event) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + if (matchHandler.isSpectatingMatch(event.getPlayer())) { + event.setCancelled(true); + } + } + + /** + * Prevent item pickups by spectators + */ + @EventHandler + public void onPlayerPickupitem(PlayerPickupItemEvent event) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + if (matchHandler.isSpectatingMatch(event.getPlayer())) { + event.setCancelled(true); + } + } + + @EventHandler + public void onPlayerBucketFill(PlayerBucketFillEvent event) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + if (matchHandler.isSpectatingMatch(event.getPlayer())) { + event.setCancelled(true); + } + } + + @EventHandler + public void onPlayerBucketEmpty(PlayerBucketEmptyEvent event) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + if (matchHandler.isSpectatingMatch(event.getPlayer())) { + event.setCancelled(true); + } + } + + @EventHandler + public void onInventoryClick(InventoryClickEvent event) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + if (matchHandler.isSpectatingMatch((Player) event.getWhoClicked())) { + event.setCancelled(true); + } + } + + @EventHandler + public void onInventoryDrag(InventoryDragEvent event) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + if (matchHandler.isSpectatingMatch((Player) event.getWhoClicked())) { + event.setCancelled(true); + } + } + + @EventHandler + public void onInventoryMoveItem(InventoryMoveItemEvent event) { + InventoryHolder inventoryHolder = event.getSource().getHolder(); + + if (inventoryHolder instanceof Player) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + if (matchHandler.isSpectatingMatch((Player) inventoryHolder)) { + event.setCancelled(true); + } + } + } + + @EventHandler + public void onSettingUpdate(SettingUpdateEvent event) { + if (event.getSetting() == Setting.VIEW_OTHER_SPECTATORS) { + VisibilityUtils.updateVisibility(event.getPlayer()); + } + } + + @EventHandler + public void onProjectileLaunch(ProjectileLaunchEvent event) { + Entity shooter = (Entity) event.getEntity().getShooter(); + + if (shooter instanceof Player) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + if (matchHandler.isSpectatingMatch((Player) shooter)) { + event.setCancelled(true); + } + } + } + + /** + * Don't let spectators be affected by potions dropped near them + */ + @EventHandler + public void onPotionSplash(PotionSplashEvent event) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + for (LivingEntity entity : event.getAffectedEntities()) { + if (entity instanceof Player && matchHandler.isSpectatingMatch((Player) entity)) { + event.setIntensity(entity, 0F); + } + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchStatsListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchStatsListener.java new file mode 100644 index 0000000..16e2628 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchStatsListener.java @@ -0,0 +1,106 @@ +package com.elevatemc.potpvp.match.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.entity.ThrownPotion; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.PotionSplashEvent; +import org.bukkit.event.entity.ProjectileLaunchEvent; +import org.bukkit.projectiles.ProjectileSource; + +import java.util.Map; +import java.util.UUID; + +public class MatchStatsListener implements Listener { + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onDamage(EntityDamageByEntityEvent event) { + if (!(event.getEntity() instanceof Player) || !(event.getDamager() instanceof Player)) return; + + Player damager = (Player) event.getDamager(); + Player damaged = (Player) event.getEntity(); + + Match damagerMatch = PotPvPSI.getInstance().getMatchHandler().getMatchPlaying(damager); + if (damagerMatch == null) return; + + Map lastHitMap = damagerMatch.getLastHit(); + Map combos = damagerMatch.getCombos(); + Map totalHits = damagerMatch.getTotalHits(); + Map longestCombo = damagerMatch.getLongestCombo(); + + UUID lastHit = lastHitMap.put(damager.getUniqueId(), damaged.getUniqueId()); + if (lastHit != null) { + if (lastHit.equals(damaged.getUniqueId())) { + combos.put(damager.getUniqueId(), combos.getOrDefault(damager.getUniqueId(), 0) + 1); + } else { + combos.put(damager.getUniqueId(), 1); + } + + longestCombo.put(damager.getUniqueId(), Math.max(combos.get(damager.getUniqueId()), longestCombo.getOrDefault(damager.getUniqueId(), 1))); + } else { + combos.put(damager.getUniqueId(), 0); + } + + totalHits.put(damager.getUniqueId(), totalHits.getOrDefault(damager.getUniqueId(), 0) + 1); + while (lastHitMap.values().remove(damager.getUniqueId())); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPotionLaunch(ProjectileLaunchEvent event) { + Projectile thrownEntity = event.getEntity(); + if (!(thrownEntity instanceof ThrownPotion)) return; + + ThrownPotion thrownPotion = (ThrownPotion) thrownEntity; + + ProjectileSource projectileSource = thrownPotion.getShooter(); + if (!(projectileSource instanceof Player)) return; + + Player player = (Player) projectileSource; + Match match = PotPvPSI.getInstance().getMatchHandler().getMatchPlaying(player); + + if (match == null) return; + match.getMissedPots().put(player.getUniqueId(), match.getMissedPots().getOrDefault(player.getUniqueId(), 0) + 1); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onSplash(PotionSplashEvent event) { + ThrownPotion thrownPotion = event.getEntity(); + + if (thrownPotion.getItem().getDurability() != 16421) return; // now we know it's a health pot! + + ProjectileSource projectileSource = thrownPotion.getShooter(); + if (!(projectileSource instanceof Player)) return; + + Player player = (Player) projectileSource; + Match match = PotPvPSI.getInstance().getMatchHandler().getMatchPlaying(player); + + if (match == null) return; + + for (LivingEntity affectedEntity : event.getAffectedEntities()) { + if (!affectedEntity.getUniqueId().equals(player.getUniqueId())) continue; + + if (event.getIntensity(affectedEntity) == 1.0D) { + match.getMissedPots().put(player.getUniqueId(), Math.max(match.getMissedPots().getOrDefault(player.getUniqueId(), 1) - 1, 0)); + } + } + } +/* + @EventHandler(priority = EventPriority.MONITOR) + public void onMatchEnd(MatchEndEvent event) { + Match match = event.getMatch(); + match.getTeams().forEach(team -> { + if (match.getWinner() == team) { + team.getAllMembers().forEach(PotPvPSI.getInstance().getWinsMap()::incrementWins); + } else { + team.getAllMembers().forEach(PotPvPSI.getInstance().getLossMap()::incrementLosses); + } + }); + } + */ +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchTitleListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchTitleListener.java new file mode 100644 index 0000000..d83446e --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/listener/MatchTitleListener.java @@ -0,0 +1,80 @@ +package com.elevatemc.potpvp.match.listener; + +import com.elevatemc.elib.util.PlayerUtils; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.match.event.MatchEndEvent; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.github.paperspigot.Title; + +import java.util.Set; +import java.util.UUID; + +public class MatchTitleListener implements Listener { + + private final Title.Builder VICTORY = Title.builder().title(ChatColor.GREEN.toString() + ChatColor.BOLD + "VICTORY"); + private final Title SOLO_VICTORY = VICTORY.subtitle("You won the match").stay(100).build(); + private final Title TEAM_VICTORY = VICTORY.subtitle("Your team won this match").stay(100).build(); + + // We do not send a solo loss because we send a dead message titel already + private final Title.Builder LOSS = Title.builder().title(ChatColor.RED.toString() + ChatColor.BOLD + "LOSS"); + private final Title TEAM_LOSS = VICTORY.subtitle("Your team lost").stay(100).build(); + + private final Title DIED = Title.builder().title(ChatColor.RED.toString() + ChatColor.BOLD + "YOU DIED").subtitle("You are now a spectator").stay(100).build(); + + @EventHandler + public void onMatchEnd(MatchEndEvent event) { + Match match = event.getMatch(); + + MatchTeam winner = match.getWinner(); + Set losingPlayers = match.getLosingPlayers(); + + boolean winnerIsTeam = winner.getAllMembers().size() > 1; + winner.forEachAlive(player -> { + PlayerUtils.sendTitle(player, winnerIsTeam ? TEAM_VICTORY : SOLO_VICTORY); + }); + + for (UUID spectator : match.getSpectators()) { + if (winner.getAllMembers().contains(spectator)) { + Player player = Bukkit.getPlayer(spectator); + PlayerUtils.sendTitle(player, winnerIsTeam ? TEAM_VICTORY : SOLO_VICTORY); + } + + if (losingPlayers != null && losingPlayers.contains(spectator)) { + if (match.getTeam(spectator) != null && match.getTeam(spectator).getAllMembers().size() > 1) { + Player player = Bukkit.getPlayer(spectator); + PlayerUtils.sendTitle(player, TEAM_LOSS); + } + } + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onMatchEnd(PlayerDeathEvent event) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Player player = event.getEntity(); + + Match match = matchHandler.getMatchPlayingOrSpectating(player); + if (match == null) { + return; + } + + MatchTeam team = match.getPreviousTeam(player.getUniqueId()); + if (team == null) { + return; + } + + if (team.getAliveMembers().size() > 0 || team.getAllMembers().size() == 1) { + PlayerUtils.sendTitle(player, DIED); + } + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/package-info.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/package-info.java new file mode 100644 index 0000000..b25af46 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/package-info.java @@ -0,0 +1 @@ +package com.elevatemc.potpvp.match; \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/rematch/RematchData.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/rematch/RematchData.java new file mode 100644 index 0000000..32ab747 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/rematch/RematchData.java @@ -0,0 +1,37 @@ +package com.elevatemc.potpvp.match.rematch; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.google.common.base.Preconditions; +import lombok.Getter; +import lombok.ToString; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.UUID; + +@ToString +public final class RematchData { + + @Getter private final UUID sender; + @Getter private final UUID target; + @Getter private final GameMode gameMode; + @Getter private final Instant expiresAt; + @Getter private final String arenaName; + + RematchData(UUID sender, UUID target, GameMode gameMode, int durationSeconds, String arenaName) { + this.sender = Preconditions.checkNotNull(sender, "sender"); + this.target = Preconditions.checkNotNull(target, "target"); + this.gameMode = Preconditions.checkNotNull(gameMode, "gameMode"); + this.expiresAt = Instant.now().plusSeconds(durationSeconds); + this.arenaName = arenaName; // Null arena -> random arena + } + + public boolean isExpired() { + return Instant.now().isAfter(expiresAt); + } + + public int getSecondsUntilExpiration() { + return (int) ChronoUnit.SECONDS.between(Instant.now(), expiresAt); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/rematch/RematchHandler.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/rematch/RematchHandler.java new file mode 100644 index 0000000..b98aac5 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/rematch/RematchHandler.java @@ -0,0 +1,101 @@ +package com.elevatemc.potpvp.match.rematch; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.match.rematch.listener.RematchGeneralListener; +import com.elevatemc.potpvp.match.rematch.listener.RematchItemListener; +import com.elevatemc.potpvp.match.rematch.listener.RematchUnloadListener; +import com.elevatemc.potpvp.util.InventoryUtils; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public final class RematchHandler { + + private final static int REMATCH_TIMEOUT_SECONDS = 30; + + // maps the player the potential sender to the rematch they can send (or have sent) + private final Map rematches = new ConcurrentHashMap<>(); + + public RematchHandler() { + Bukkit.getPluginManager().registerEvents(new RematchGeneralListener(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new RematchItemListener(this), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new RematchUnloadListener(), PotPvPSI.getInstance()); + + // remove expired entries + Bukkit.getScheduler().runTaskTimer(PotPvPSI.getInstance(), () -> { + Iterator iterator = rematches.values().iterator(); + + while (iterator.hasNext()) { + RematchData rematchData = iterator.next(); + + if (rematchData.isExpired()) { + Player sender = Bukkit.getPlayer(rematchData.getSender()); + + if (sender != null) { + InventoryUtils.resetInventoryDelayed(sender); + } + + iterator.remove(); + } + } + }, 20L, 20L); + } + + public void registerRematches(Match match) { + // see Match#allowRematches + if (!match.isAllowRematches()) { + return; + } + + List teams = match.getTeams(); + + if (teams.size() == 2) { + MatchTeam team1 = teams.get(0); + MatchTeam team2 = teams.get(1); + + // can only send rematches for 1v1s + if (team1.getAllMembers().size() != 1 || team2.getAllMembers().size() != 1) { + return; + } + + UUID player1Uuid = team1.getFirstMember(); + UUID player2Uuid = team2.getFirstMember(); + GameMode gameMode = match.getGameMode(); + + // rematches are mutual + rematches.put(player1Uuid, new RematchData(player1Uuid, player2Uuid, gameMode, REMATCH_TIMEOUT_SECONDS, match.getArena().getSchematic())); + rematches.put(player2Uuid, new RematchData(player2Uuid, player1Uuid, gameMode, REMATCH_TIMEOUT_SECONDS, match.getArena().getSchematic())); + } + } + + public RematchData getRematchData(Player player) { + return rematches.get(player.getUniqueId()); + } + + public void unloadRematchData(Player player) { + RematchData removed = rematches.remove(player.getUniqueId()); + + if (removed != null) { + // remove opponent's rematch for them too. + rematches.remove(removed.getTarget()); + + // attempt to update the target's inventory (to remove the item) + // we don't need to do this for us as we'll only unload + // rematch data when we quit. + Player targetPlayer = Bukkit.getPlayer(removed.getTarget()); + + if (targetPlayer != null) { + InventoryUtils.resetInventoryDelayed(targetPlayer); + } + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/rematch/RematchItems.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/rematch/RematchItems.java new file mode 100644 index 0000000..40ab6cb --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/rematch/RematchItems.java @@ -0,0 +1,25 @@ +package com.elevatemc.potpvp.match.rematch; + +import lombok.experimental.UtilityClass; +import com.elevatemc.elib.util.ItemUtils; +import org.bukkit.DyeColor; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + + +import static org.bukkit.ChatColor.*; + +@UtilityClass +public final class RematchItems { + + public static final ItemStack REQUEST_REMATCH_ITEM = new ItemStack(Material.BLAZE_POWDER); + public static final ItemStack SENT_REMATCH_ITEM = new ItemStack(Material.INK_SACK, 1, DyeColor.RED.getDyeData()); + public static final ItemStack ACCEPT_REMATCH_ITEM = new ItemStack(Material.EMERALD); + + static { + ItemUtils.setDisplayName(REQUEST_REMATCH_ITEM, GRAY + "• " + RED + "Request Rematch" + GRAY + " •"); + ItemUtils.setDisplayName(SENT_REMATCH_ITEM, GRAY + "• " + GREEN + "Sent Rematch" + GRAY + " •"); + ItemUtils.setDisplayName(ACCEPT_REMATCH_ITEM, GRAY + "• " + GREEN + "Accept Rematch" + GRAY + " •"); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/rematch/listener/RematchGeneralListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/rematch/listener/RematchGeneralListener.java new file mode 100644 index 0000000..d13cd4e --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/rematch/listener/RematchGeneralListener.java @@ -0,0 +1,15 @@ +package com.elevatemc.potpvp.match.rematch.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.event.MatchTerminateEvent; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +public final class RematchGeneralListener implements Listener { + + @EventHandler + public void onMatchTerminate(MatchTerminateEvent event) { + PotPvPSI.getInstance().getRematchHandler().registerRematches(event.getMatch()); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/rematch/listener/RematchItemListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/rematch/listener/RematchItemListener.java new file mode 100644 index 0000000..295dd49 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/rematch/listener/RematchItemListener.java @@ -0,0 +1,41 @@ +package com.elevatemc.potpvp.match.rematch.listener; + +import com.elevatemc.potpvp.duel.command.AcceptCommand; +import com.elevatemc.potpvp.duel.command.DuelCommand; +import com.elevatemc.potpvp.match.rematch.RematchHandler; +import com.elevatemc.potpvp.match.rematch.RematchItems; +import com.elevatemc.potpvp.match.rematch.RematchData; +import com.elevatemc.potpvp.util.InventoryUtils; +import com.elevatemc.potpvp.util.ItemListener; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class RematchItemListener extends ItemListener { + + public RematchItemListener(RematchHandler rematchHandler) { + addHandler(RematchItems.REQUEST_REMATCH_ITEM, player -> { + RematchData rematchData = rematchHandler.getRematchData(player); + + if (rematchData != null) { + Player target = Bukkit.getPlayer(rematchData.getTarget()); + DuelCommand.duel(player, target, rematchData.getGameMode(), rematchData.getArenaName()); + + InventoryUtils.resetInventoryDelayed(player); + InventoryUtils.resetInventoryDelayed(target); + } + }); + + addHandler(RematchItems.SENT_REMATCH_ITEM, p -> p.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You have already sent a rematch request.")); + + addHandler(RematchItems.ACCEPT_REMATCH_ITEM, player -> { + RematchData rematchData = rematchHandler.getRematchData(player); + + if (rematchData != null) { + Player target = Bukkit.getPlayer(rematchData.getTarget()); + AcceptCommand.accept(player, target); + } + }); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/rematch/listener/RematchUnloadListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/rematch/listener/RematchUnloadListener.java new file mode 100644 index 0000000..9c5628d --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/match/rematch/listener/RematchUnloadListener.java @@ -0,0 +1,15 @@ +package com.elevatemc.potpvp.match.rematch.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; + +public final class RematchUnloadListener implements Listener { + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + PotPvPSI.getInstance().getRematchHandler().unloadRematchData(event.getPlayer()); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/nametag/PotPvPNametagProvider.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/nametag/PotPvPNametagProvider.java new file mode 100644 index 0000000..b45faa7 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/nametag/PotPvPNametagProvider.java @@ -0,0 +1,118 @@ +package com.elevatemc.potpvp.nametag; + +import com.elevatemc.elib.nametag.construct.NameTagInfo; +import com.elevatemc.elib.nametag.provider.NameTagProvider; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.events.event.impl.lms.LastManStandingGameEventLogic; +import com.elevatemc.potpvp.events.game.Game; +import com.elevatemc.potpvp.events.game.GameQueue; +import com.elevatemc.potpvp.events.game.GameState; +import com.elevatemc.potpvp.follow.FollowHandler; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.pvpclasses.pvpclasses.ArcherClass; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public final class PotPvPNametagProvider extends NameTagProvider { + + public PotPvPNametagProvider() { + super("PotPvP Provider", 5); + } + + @Override + public NameTagInfo fetchNameTag(Player toRefresh, Player refreshFor) { + ChatColor prefixColor = getNameColor(toRefresh, refreshFor); + + final Game game = GameQueue.INSTANCE.getCurrentGame(toRefresh); + + if(game != null && game.getPlayers().contains(toRefresh) && game.getPlayers().contains(refreshFor) && game.getState() != GameState.ENDED) { + return createNameTag(game.getEvent().getNameTag(game, toRefresh, refreshFor), ""); + } + + return createNameTag(prefixColor.toString(), ""); + } + + public static ChatColor getNameColor(Player toRefresh, Player refreshFor) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + if (matchHandler.isPlayingOrSpectatingMatch(toRefresh) && !matchHandler.isPlayingEvent(toRefresh)) { + return getNameColorMatch(toRefresh, refreshFor); + } else { + return getNameColorLobby(toRefresh, refreshFor); + } + } + + private static ChatColor getNameColorMatch(Player toRefresh, Player refreshFor) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + Match toRefreshMatch = matchHandler.getMatchPlayingOrSpectating(toRefresh); + MatchTeam toRefreshTeam = toRefreshMatch.getTeam(toRefresh.getUniqueId()); + + // they're a spectator, so we see them as gray + if (toRefreshTeam == null) { + return ChatColor.GRAY; + } + + MatchTeam refreshForTeam = toRefreshMatch.getTeam(refreshFor.getUniqueId()); + + // if we can't find a current team, check if they have any + // previously teams we can use for this + if (refreshForTeam == null) { + refreshForTeam = toRefreshMatch.getPreviousTeam(refreshFor.getUniqueId()); + } + + // if we were/are both on teams display a friendly/enemy color + if (refreshForTeam != null) { + if (toRefreshTeam == refreshForTeam) { + return ChatColor.GREEN; + } else { + if (ArcherClass.getMarkedPlayers().containsKey(toRefresh.getName()) && System.currentTimeMillis() < ArcherClass.getMarkedPlayers().get(toRefresh.getName())) { + if (toRefresh.getUniqueId().equals(refreshForTeam.getFocus())) { + return ChatColor.DARK_PURPLE; + } else { + return ChatColor.YELLOW; + } + } + if (toRefresh.getUniqueId().equals(refreshForTeam.getFocus())) { + return ChatColor.LIGHT_PURPLE; + } + return ChatColor.RED; + } + } + + // if we're a spectator just display standard colors + List teams = toRefreshMatch.getTeams(); + + // we have predefined colors for 'normal' matches + if (teams.size() == 2) { + // team 1 = LIGHT_PURPLE, team 2 = AQUA + if (toRefreshTeam == teams.get(0)) { + return ChatColor.LIGHT_PURPLE; + } else { + return ChatColor.AQUA; + } + } else { + // we don't have colors defined for larger matches + // everyone is just red for spectators + return ChatColor.RED; + } + } + + private static ChatColor getNameColorLobby(Player toRefresh, Player refreshFor) { + FollowHandler followHandler = PotPvPSI.getInstance().getFollowHandler(); + + Optional following = followHandler.getFollowing(refreshFor); + boolean refreshForFollowingTarget = following.isPresent() && following.get().equals(toRefresh.getUniqueId()); + + if (refreshForFollowingTarget) { + return ChatColor.AQUA; + } else { + return ChatColor.GREEN; + } + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/Party.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/Party.java new file mode 100644 index 0000000..4367dc5 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/Party.java @@ -0,0 +1,302 @@ +package com.elevatemc.potpvp.party; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import lombok.Getter; +import lombok.Setter; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.party.event.*; +import com.elevatemc.potpvp.pvpclasses.PvPClasses; +import com.elevatemc.potpvp.util.InventoryUtils; +import com.elevatemc.potpvp.util.VisibilityUtils; +import com.elevatemc.potpvp.validation.PotPvPValidation; +import com.elevatemc.elib.eLib; +import net.md_5.bungee.api.ChatColor; +import org.bukkit.Bukkit; +import org.bukkit.Sound; +import org.bukkit.entity.Player; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; + +/** + * Represents a collection of players which can perform + * various actions (ex queue, have elo, etc) together. + * + * All members, the leader, and all {@link PartyInvite} + * targets (although not senders) are guaranteed to be online. + */ +public final class Party { + + /** + * {@link UUID} of this party + * + * New UUID = new Party + */ + @Getter private final UUID partyId = new UUID(PotPvPSI.RANDOM.nextLong(), PotPvPSI.RANDOM.nextLong()); + + // the maximum party size for non-op leaders + public static final int MAX_SIZE = 50; + + /** + * Leader of the party, given permission to perform + * administrative commands (and perform actions like queueing) + * on behalf of the party. Guaranteed to be online. + */ + @Getter private UUID leader; + + @Getter private Map kits = new HashMap<>(); + + /** + * All players who are currently part of this party. + * Each player will only be a member of one party at a time. + * Guaranteed to all be online. + */ + private final Set members = Sets.newLinkedHashSet(); + + /** + * All active (non-expired) {@link PartyInvite}s. Players can have + * active invitations from more than one party at a time. All targets + * (but not senders) are guaranteed to be online. + */ + private final Set invites = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + /** + * Current access restriction in place for joining this party + * @see PartyAccessRestriction + */ + @Getter @Setter private PartyAccessRestriction accessRestriction = PartyAccessRestriction.INVITE_ONLY; + + /** + * Password requires to join this party, only active if + * {@link #accessRestriction} is {@link PartyAccessRestriction#PASSWORD}. + * @see PartyAccessRestriction#PASSWORD + */ + @Getter @Setter private String password = null; + + Party(UUID leader) { + this.leader = Preconditions.checkNotNull(leader, "leader"); + this.members.add(leader); + + PotPvPSI.getInstance().getPartyHandler().updatePartyCache(leader, this); + Bukkit.getPluginManager().callEvent(new PartyCreateEvent(this)); + } + + /** + * Checks if the player provided is a member of this party + * @param playerUuid the player to check + * @return true if the player provided is a member of this party, + * false otherwise. + */ + public boolean isMember(UUID playerUuid) { + return members.contains(playerUuid); + } + + /** + * Checks if the player provided is the leader of this party + * @param playerUuid the player to check + * @return true if the player provided is the leader of this party, + * false otherwise. + */ + public boolean isLeader(UUID playerUuid) { + return leader.equals(playerUuid); + } + + /** + * Gets an immutable set of all players currently + * in this party. + * @see Party#members + * @return immutable set of all members + */ + public Set getMembers() { + return ImmutableSet.copyOf(members); + } + + /** + * Gets an immutable set of all active {@link PartyInvite}s + * @return immutable set of all active invites + */ + public Set getInvites() { + return ImmutableSet.copyOf(invites); + } + + /** + * Finds an active {@link PartyInvite} whose target is equal to the + * player provided + * @param target player who must match the result's {@link PartyInvite#getTarget()} + * @return a PartyInvite targeting the player provided, if one exists + */ + public PartyInvite getInvite(UUID target) { + for (PartyInvite invite : invites) { + if (invite.getTarget().equals(target)) { + return invite; + } + } + + return null; + } + + public void revokeInvite(PartyInvite invite) { + invites.remove(invite); + } + + public void invite(Player target) { + PartyInvite invite = new PartyInvite(this, target.getUniqueId()); + + target.spigot().sendMessage(PartyLang.inviteAcceptPrompt(this)); + message(ChatColor.DARK_AQUA + "✎ " + ChatColor.AQUA + target.getName() + " has been invited to join your party."); + + invites.add(invite); + Bukkit.getScheduler().runTaskLater(PotPvPSI.getInstance(), () -> invites.remove(invite), PartyHandler.INVITE_EXPIRATION_SECONDS * 20); + } + + public void join(Player player) { + if (members.contains(player.getUniqueId())) { + return; + } + + if (!PotPvPValidation.canJoinParty(player, this)) { + return; + } + + PartyInvite invite = getInvite(player.getUniqueId()); + + if (invite != null) { + revokeInvite(invite); + } + + Player leaderBukkit = Bukkit.getPlayer(leader); + player.sendMessage(ChatColor.DARK_GREEN.toString() + ChatColor.BOLD + "✔ " + ChatColor.GREEN + "You have joined " + leaderBukkit.getName() + "'s party."); + + message(ChatColor.DARK_AQUA.toString() + ChatColor.BOLD + "✔ " + ChatColor.DARK_AQUA + player.getName() + ChatColor.AQUA + " has joined your party."); + + members.add(player.getUniqueId()); + PotPvPSI.getInstance().getPartyHandler().updatePartyCache(player.getUniqueId(), this); + + Bukkit.getPluginManager().callEvent(new PartyMemberJoinEvent(player, this)); + + forEachOnline(VisibilityUtils::updateVisibility); + resetInventoriesDelayed(); + } + + public void leave(Player player) { + if (isLeader(player.getUniqueId()) && members.size() == 1) { + disband(); + return; + } + + if (!members.remove(player.getUniqueId())) { + return; + } + + PotPvPSI.getInstance().getPartyHandler().updatePartyCache(player.getUniqueId(), null); + + // randomly elect new leader if needed + if (leader.equals(player.getUniqueId())) { + UUID[] membersArray = members.toArray(new UUID[members.size()]); + Player newLeader = Bukkit.getPlayer(membersArray[PotPvPSI.RANDOM.nextInt(membersArray.length)]); + + this.leader = newLeader.getUniqueId(); + message(ChatColor.DARK_AQUA.toString() + ChatColor.BOLD + "♛ " + ChatColor.DARK_AQUA + newLeader.getName() + ChatColor.AQUA + " has been randomly promoted to leader of your party."); + } + + player.sendMessage(org.bukkit.ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You have left your party."); + message(ChatColor.DARK_AQUA + "✖ " + player.getName() + ChatColor.AQUA + " has left your party."); + + VisibilityUtils.updateVisibility(player); + forEachOnline(VisibilityUtils::updateVisibility); + + Bukkit.getPluginManager().callEvent(new PartyMemberLeaveEvent(player, this)); + + InventoryUtils.resetInventoryDelayed(player); + resetInventoriesDelayed(); + } + + public void setLeader(Player player) { + this.leader = player.getUniqueId(); + + message(ChatColor.DARK_AQUA.toString() + ChatColor.BOLD + "♛ " + ChatColor.DARK_AQUA + player.getName() + ChatColor.AQUA + " has been promoted to leader of your party."); + resetInventoriesDelayed(); + } + + public void disband() { + Bukkit.getPluginManager().callEvent(new PartyDisbandEvent(this)); + PotPvPSI.getInstance().getPartyHandler().unregisterParty(this); + + forEachOnline(player -> { + VisibilityUtils.updateVisibility(player); + PotPvPSI.getInstance().getPartyHandler().updatePartyCache(player.getUniqueId(), null); + }); + + message(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Your party has been disbanded."); + resetInventoriesDelayed(); + } + + public void kick(Player player) { + if (!members.remove(player.getUniqueId())) { + return; + } + + PotPvPSI.getInstance().getPartyHandler().updatePartyCache(player.getUniqueId(), null); + + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You have been kicked from your party."); + message(ChatColor.DARK_AQUA + "✎ " + ChatColor.AQUA + player.getName() + " has been kicked from your party."); + + VisibilityUtils.updateVisibility(player); + forEachOnline(VisibilityUtils::updateVisibility); + + Bukkit.getPluginManager().callEvent(new PartyMemberKickEvent(player, this)); + + InventoryUtils.resetInventoryDelayed(player); + resetInventoriesDelayed(); + } + + /** + * Sends a basic chat message to all members + * @param message the message to send + */ + public void message(String message) { + forEachOnline(p -> p.sendMessage(message)); + } + + /** + * Plays a sound for all members + * @param sound the Sound to play + * @param pitch the pitch to play the provided sound at + */ + public void playSound(Sound sound, float pitch) { + forEachOnline(p -> p.playSound(p.getLocation(), sound, 10F, pitch)); + } + + /** + * Resets all members' inventories + * @see InventoryUtils#resetInventoryDelayed(Player) + */ + public void resetInventoriesDelayed() { + // we use one runnable and then call resetInventoriesNow instead of + // directly using to InventoryUtils#resetInventoryDelayed to reduce + // the number of tasks we submit to the scheduler + Bukkit.getScheduler().runTaskLater(PotPvPSI.getInstance(), this::resetInventoriesNow, InventoryUtils.RESET_DELAY_TICKS); + } + + /** + * Resets all members' inventories + * @see InventoryUtils#resetInventoryNow(Player) + */ + public void resetInventoriesNow() { + forEachOnline(InventoryUtils::resetInventoryNow); + } + + private void forEachOnline(Consumer consumer) { + for (UUID member : members) { + Player memberBukkit = Bukkit.getPlayer(member); + + if (memberBukkit != null) { + consumer.accept(memberBukkit); + } + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/PartyAccessRestriction.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/PartyAccessRestriction.java new file mode 100644 index 0000000..4924abc --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/PartyAccessRestriction.java @@ -0,0 +1,31 @@ +package com.elevatemc.potpvp.party; + +/** + * Determines what requirements are in place for + * players attempting to join a party + */ +public enum PartyAccessRestriction { + + /** + * Anyone can join the party (with /party join) + * Typically used by staff / famous players + */ + PUBLIC, + + /** + * Players need to be invited (with /party invite) + * to be able to join. Default restricction level, + * used by most parties. + */ + INVITE_ONLY, + + /** + * Players need to have a password to be able to join. + * (with /party join password) Typically used by players + * in TeamSpeak / Skype / etc (when it's more convenient to + * distribute one password than to invite everyone) + * @see Party#password + */ + PASSWORD + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/PartyHandler.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/PartyHandler.java new file mode 100644 index 0000000..1be3fea --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/PartyHandler.java @@ -0,0 +1,100 @@ +package com.elevatemc.potpvp.party; + +import com.google.common.collect.ImmutableSet; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.party.listener.PartyChatListener; +import com.elevatemc.potpvp.party.listener.PartyItemListener; +import com.elevatemc.potpvp.party.listener.PartyLeaveListener; +import com.elevatemc.potpvp.util.InventoryUtils; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Handles accessing and storage of {@link Party} data + */ +public final class PartyHandler { + + /** + * Number of seconds it takes for a {@link PartyInvite} + * to expire after being sent. + */ + static final int INVITE_EXPIRATION_SECONDS = 30; + + private final Set parties = Collections.newSetFromMap(new ConcurrentHashMap<>()); + private final Map playerPartyCache = new ConcurrentHashMap<>(); + + public PartyHandler() { + Bukkit.getPluginManager().registerEvents(new PartyChatListener(), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new PartyItemListener(this), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new PartyLeaveListener(), PotPvPSI.getInstance()); + } + + /** + * Finds all parties with at least one member + * @return immutable set of all existing parties + */ + public Set getParties() { + return ImmutableSet.copyOf(parties); + } + + /** + * Checks if the player provided is in a party ({@link Party#isMember(UUID) would return true} + * @param player the player to check + * @return if the player provided is in a party + */ + public boolean hasParty(Player player) { + return playerPartyCache.containsKey(player.getUniqueId()); + } + + /** + * Looks up a player's party (a player's party is considered a party + * for which {@link Party#isMember(UUID)} returns true) + * @param player the player to lookup + * @return the player's party, or null if the player is not in a party. + */ + public Party getParty(Player player) { + return playerPartyCache.get(player.getUniqueId()); + } + + public Party getParty(UUID uuid) { + return playerPartyCache.get(uuid); + } + + /** + * Looks up a player's party, or creates a Party (with the player as the leader) + * if none exists. + * @param player the player to lookup + * @return the player's existing party, or a new Party + * if the player was not in a party + */ + public Party getOrCreateParty(Player player) { + Party party = getParty(player); + + if (party == null) { + party = new Party(player.getUniqueId()); + parties.add(party); + InventoryUtils.resetInventoryDelayed(player); + } + + return party; + } + + void unregisterParty(Party party) { + parties.remove(party); + } + + public void updatePartyCache(UUID playerUuid, Party party) { + if (party != null) { + playerPartyCache.put(playerUuid, party); + } else { + playerPartyCache.remove(playerUuid); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/PartyInvite.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/PartyInvite.java new file mode 100644 index 0000000..c9af01e --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/PartyInvite.java @@ -0,0 +1,40 @@ +package com.elevatemc.potpvp.party; + +import com.google.common.base.Preconditions; +import lombok.Getter; + +import java.time.Instant; +import java.util.UUID; + +/** + * Represents an invitation for a player (the target) + * to join a {@link Party} + */ +public final class PartyInvite { + + /** + * The party the target will be joining upon accepting + * this invitation. + */ + @Getter private Party party; + + /** + * Player that will be joining the party, + * if they accept this invitation. + */ + @Getter private UUID target; + + /** + * The time this invite was sent, used to determine if this + * invitation is still active. + * Sent and created are synonymous in this context + */ + @Getter private Instant timeSent; + + PartyInvite(Party party, UUID target) { + this.party = Preconditions.checkNotNull(party, "party"); + this.target = Preconditions.checkNotNull(target, "target"); + this.timeSent = Instant.now(); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/PartyItems.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/PartyItems.java new file mode 100644 index 0000000..9715cc6 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/PartyItems.java @@ -0,0 +1,31 @@ +package com.elevatemc.potpvp.party; + +import lombok.experimental.UtilityClass; +import com.elevatemc.elib.util.ItemUtils; +import com.elevatemc.elib.util.UUIDUtils; +import org.bukkit.DyeColor; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import static org.bukkit.ChatColor.*; + +@UtilityClass +public final class PartyItems { + + public static final ItemStack PARTY_INFO = new ItemStack(Material.NETHER_STAR);; + public static final ItemStack LEAVE_PARTY_ITEM = new ItemStack(Material.INK_SACK, 1, DyeColor.RED.getDyeData()); + public static final ItemStack ASSIGN_CLASSES = new ItemStack(Material.MAGMA_CREAM); + public static final ItemStack START_TEAM_SPLIT_ITEM = new ItemStack(Material.DIAMOND_SWORD); + public static final ItemStack START_FFA_ITEM = new ItemStack(Material.GOLD_SWORD); + public static final ItemStack OTHER_PARTIES_ITEM = new ItemStack(Material.SKULL_ITEM); + + static { + ItemUtils.setDisplayName(PARTY_INFO, GRAY + "• " + AQUA + "View Party" + GRAY + " •"); + ItemUtils.setDisplayName(LEAVE_PARTY_ITEM, GRAY + "• " + RED + "Leave Party" + GRAY + " •"); + ItemUtils.setDisplayName(ASSIGN_CLASSES, GRAY + "• " + AQUA + "Teamfight Kits" + GRAY + " •"); + ItemUtils.setDisplayName(START_TEAM_SPLIT_ITEM, GRAY + "• " + AQUA + "Start Team Split" + GRAY + " •"); + ItemUtils.setDisplayName(START_FFA_ITEM, GRAY + "• " + AQUA + "Start Party FFA" + GRAY + " •"); + ItemUtils.setDisplayName(OTHER_PARTIES_ITEM, GRAY + "• " + AQUA + "Other Parties" + GRAY + " •"); + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/PartyLang.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/PartyLang.java new file mode 100644 index 0000000..9b780f9 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/PartyLang.java @@ -0,0 +1,118 @@ +package com.elevatemc.potpvp.party; + +import lombok.experimental.UtilityClass; +import com.elevatemc.elib.util.UUIDUtils; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@UtilityClass +public final class PartyLang { + + private static final TextComponent INVITE_PREFIX = new TextComponent("✎ "); + + private static final TextComponent INVITED_YOU_TO_JOIN = new TextComponent(" invited you to join. "); + + private static final TextComponent ACCEPT_BUTTON = new TextComponent("[Accept]"); + private static final TextComponent INFO_BUTTON = new TextComponent("[Info]"); + + static { + INVITE_PREFIX.setColor(ChatColor.DARK_AQUA); + + INVITED_YOU_TO_JOIN.setColor(ChatColor.AQUA); + + HoverEvent.Action showText = HoverEvent.Action.SHOW_TEXT; // readability + BaseComponent[] acceptTooltip = new ComponentBuilder("Click to join party").color(ChatColor.GREEN).create(); + + ACCEPT_BUTTON.setColor(ChatColor.DARK_AQUA); + ACCEPT_BUTTON.setHoverEvent(new HoverEvent(showText, acceptTooltip)); + + INFO_BUTTON.setColor(ChatColor.DARK_AQUA); + } + + public static TextComponent inviteAcceptPrompt(Party party) { + ClickEvent.Action runCommand = ClickEvent.Action.RUN_COMMAND; + String partyLeader = UUIDUtils.name(party.getLeader()); + + // create copies via constructor (we're going to update their click event) + TextComponent acceptButton = new TextComponent(ACCEPT_BUTTON); + TextComponent infoButton = new TextComponent(INFO_BUTTON); + + acceptButton.setClickEvent(new ClickEvent(runCommand, "/p join " + partyLeader)); + + infoButton.setHoverEvent(hoverablePreviewTooltip(party)); + infoButton.setClickEvent(new ClickEvent(runCommand, "/p info " + partyLeader)); + + TextComponent builder = new TextComponent(""); + + builder.addExtra(INVITE_PREFIX); + builder.addExtra(hoverablePartyName(party)); + builder.addExtra(INVITED_YOU_TO_JOIN); + builder.addExtra(acceptButton); + builder.addExtra(new TextComponent(" ")); + builder.addExtra(infoButton); + + return builder; + } + + public static TextComponent hoverablePartyName(Party party) { + TextComponent previewComponent = new TextComponent(); + String leaderName = UUIDUtils.name(party.getLeader()); + + // only show an actual tooltip for parties with >= 2 members, + // parties that (to the user) don't exist yet just show up as a name + if (party.getMembers().size() > 1) { + HoverEvent hoverEvent = hoverablePreviewTooltip(party); + + previewComponent.setText("[" + leaderName + "'s Party]"); + previewComponent.setHoverEvent(hoverEvent); + } else { + previewComponent.setText(leaderName); + } + + previewComponent.setColor(ChatColor.AQUA); + return previewComponent; + } + + public static HoverEvent hoverablePreviewTooltip(Party party) { + ComponentBuilder builder = new ComponentBuilder("Members (").color(ChatColor.DARK_AQUA); + String size = "" + party.getMembers().size(); + + builder.append(size).color(ChatColor.AQUA); + builder.append("):").color(ChatColor.DARK_AQUA); + + for (String member : getMemberPreviewNames(party)) { + builder.append("\n"); + builder.append(member); + } + + HoverEvent.Action action = HoverEvent.Action.SHOW_TEXT; + return new HoverEvent(action, builder.create()); + } + + // this method is probably named badly; + // it puts min(partySize, 6) member display names into a set, + // with a String indicating how many more members are present (if there are any) + private static List getMemberPreviewNames(Party party) { + List members = new ArrayList<>(party.getMembers()); + int partySize = members.size(); + List displayNames = new ArrayList<>(); + + for (int i = 0; i < Math.min(partySize, 6); i++) { + UUID member = members.remove(0); + String suffix = party.isLeader(member) ? "*" : ""; + + displayNames.add(ChatColor.AQUA + UUIDUtils.name(member) + suffix); + } + + if (!members.isEmpty()) { + displayNames.add(ChatColor.GRAY + "+ " + members.size() + " more"); + } + + return displayNames; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/PartyUtils.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/PartyUtils.java new file mode 100644 index 0000000..e8b3454 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/PartyUtils.java @@ -0,0 +1,148 @@ +package com.elevatemc.potpvp.party; + +import com.elevatemc.potpvp.arena.menu.select.teamfight.SelectArenaTeamfightCategoryMenu; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.elevatemc.potpvp.gamemode.menu.select.SelectGameModeMenu; +import com.google.common.collect.ImmutableList; +import lombok.experimental.UtilityClass; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.menu.select.SelectArenaMenu; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.party.menu.oddmanout.OddManOutMenu; +import com.elevatemc.potpvp.pvpclasses.PvPClasses; +import com.elevatemc.potpvp.validation.PotPvPValidation; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.*; + +@UtilityClass +public final class PartyUtils { + + public static void startTeamSplit(Party party, Player initiator) { + // will be called again but we fail fast if possible + if (!PotPvPValidation.canStartTeamSplit(party, initiator)) { + return; + } + + new SelectGameModeMenu(gameMode -> { + if (gameMode.equals(GameModes.TEAMFIGHT) || gameMode.equals(GameModes.TEAMFIGHT_DEBUFF)) { + new SelectArenaTeamfightCategoryMenu(category -> { + new SelectArenaMenu(gameMode, category, arenaName -> { + initiator.closeInventory(); + + if (party.getMembers().size() % 2 == 0) { + startTeamSplit(party, initiator, gameMode, arenaName, false); + } else { + new OddManOutMenu(oddManOut -> { + initiator.closeInventory(); + startTeamSplit(party, initiator, gameMode, arenaName, oddManOut); + }).openMenu(initiator); + } + }).openMenu(initiator); + }).openMenu(initiator); + } else { + new SelectArenaMenu(gameMode, arenaName -> { + initiator.closeInventory(); + + if (party.getMembers().size() % 2 == 0) { + startTeamSplit(party, initiator, gameMode, arenaName, false); + } else { + new OddManOutMenu(oddManOut -> { + initiator.closeInventory(); + startTeamSplit(party, initiator, gameMode, arenaName, oddManOut); + }).openMenu(initiator); + } + }).openMenu(initiator); + } + }, "Select gamemode for teamsplit").openMenu(initiator); + } + + public static void startTeamSplit(Party party, Player initiator, GameMode gameMode, String arenaName, boolean oddManOut) { + if (!PotPvPValidation.canStartTeamSplit(party, initiator)) { + return; + } + + List members = new ArrayList<>(party.getMembers()); + Collections.shuffle(members); + + Set team1 = new HashSet<>(); + Set team2 = new HashSet<>(); + Player spectator = null; // only can be one + + if (gameMode.equals(GameModes.TEAMFIGHT)) { + members.sort((a, b) -> { + PvPClasses firstPvPClass = party.getKits().getOrDefault(a, PvPClasses.DIAMOND); + PvPClasses secondPvPClass = party.getKits().getOrDefault(b, PvPClasses.DIAMOND); + int firstWeight = 0; + int secondWeight = 0; + switch (firstPvPClass) { + case DIAMOND: + firstWeight = 1; + break; + case ROGUE: + firstWeight = 2; + break; + case ARCHER: + firstWeight = 3; + break; + case BARD: + firstWeight = 4; + } + switch (secondPvPClass) { + case DIAMOND: + secondWeight = 1; + break; + case ROGUE: + secondWeight = 2; + break; + case ARCHER: + secondWeight = 3; + break; + case BARD: + secondWeight = 4; + break; + } + return secondWeight - firstWeight; + }); + } + + while (members.size() >= 2) { + team1.add(members.remove(0)); + team2.add(members.remove(0)); + } + + if (!members.isEmpty()) { + if (oddManOut) { + spectator = Bukkit.getPlayer(members.remove(0)); + party.message(ChatColor.DARK_AQUA + "✎ " + ChatColor.AQUA + spectator.getName() + " was selected as the odd-man out."); + } else { + team1.add(members.remove(0)); + } + } + + Match match = PotPvPSI.getInstance().getMatchHandler().startMatch( + ImmutableList.of( + new MatchTeam(team1), + new MatchTeam(team2) + ), + gameMode, + arenaName, + false, + false + ); + + if (match == null) { + initiator.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Failed to start team split."); + return; + } + + if (spectator != null) { + match.addSpectator(spectator, null); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyCreateCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyCreateCommand.java new file mode 100644 index 0000000..052238a --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyCreateCommand.java @@ -0,0 +1,29 @@ +package com.elevatemc.potpvp.party.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.party.PartyHandler; +import com.elevatemc.elib.command.Command; +import com.elevatemc.potpvp.hctranked.game.RankedGame; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class PartyCreateCommand { + + @Command(names = {"party create", "p create", "t create", "team create", "f create"}, permission = "") + public static void partyCreate(Player sender) { + RankedGame game = PotPvPSI.getInstance().getHCTRankedHandler().getGameHandler().getJoinedGame(sender); + if (game != null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You can't create a party while in a ranked game."); + return; + } + + PartyHandler partyHandler = PotPvPSI.getInstance().getPartyHandler(); + if (partyHandler.hasParty(sender)) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You are already in a party."); + return; + } + + partyHandler.getOrCreateParty(sender); + sender.sendMessage(ChatColor.DARK_GREEN.toString() + ChatColor.BOLD + "✔ " + ChatColor.GREEN + "Your party has been created!"); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyDisbandCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyDisbandCommand.java new file mode 100644 index 0000000..ab46dd7 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyDisbandCommand.java @@ -0,0 +1,28 @@ +package com.elevatemc.potpvp.party.command; + +import com.elevatemc.potpvp.PotPvPLang; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.elib.command.Command; +import org.bukkit.entity.Player; + +public final class PartyDisbandCommand { + + @Command(names = {"party disband", "p disband", "t disband", "team disband", "f disband"}, permission = "") + public static void partyDisband(Player sender) { + Party party = PotPvPSI.getInstance().getPartyHandler().getParty(sender); + + if (party == null) { + sender.sendMessage(PotPvPLang.NOT_IN_PARTY); + return; + } + + if (!party.isLeader(sender.getUniqueId())) { + sender.sendMessage(PotPvPLang.NOT_LEADER_OF_PARTY); + return; + } + + party.disband(); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyFfaCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyFfaCommand.java new file mode 100644 index 0000000..3952e50 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyFfaCommand.java @@ -0,0 +1,79 @@ +package com.elevatemc.potpvp.party.command; + +import com.elevatemc.potpvp.PotPvPLang; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.arena.menu.select.SelectArenaMenu; +import com.elevatemc.potpvp.arena.menu.select.teamfight.SelectArenaTeamfightCategoryMenu; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.elevatemc.potpvp.gamemode.menu.select.SelectGameModeMenu; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.potpvp.party.PartyHandler; +import com.elevatemc.potpvp.validation.PotPvPValidation; +import com.elevatemc.elib.command.Command; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public final class PartyFfaCommand { + + @Command(names = {"party ffa", "p ffa", "t ffa", "team ffa", "f ffa"}, permission = "") + public static void partyFfa(Player sender) { + PartyHandler partyHandler = PotPvPSI.getInstance().getPartyHandler(); + Party party = partyHandler.getParty(sender); + + if (party == null) { + sender.sendMessage(PotPvPLang.NOT_IN_PARTY); + } else if (!party.isLeader(sender.getUniqueId())) { + sender.sendMessage(PotPvPLang.NOT_LEADER_OF_PARTY); + } else { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + if (!PotPvPValidation.canStartFfa(party, sender)) { + return; + } + + new SelectGameModeMenu(gameMode -> { + if (gameMode.equals(GameModes.TEAMFIGHT) || gameMode.equals(GameModes.TEAMFIGHT_DEBUFF)) { + new SelectArenaTeamfightCategoryMenu(category -> { + new SelectArenaMenu(gameMode, category, arenaName -> { + sender.closeInventory(); + + if (!PotPvPValidation.canStartFfa(party, sender)) { + return; + } + + List teams = new ArrayList<>(); + + for (UUID member : party.getMembers()) { + teams.add(new MatchTeam(member)); + } + + matchHandler.startMatch(teams, gameMode, arenaName,false, false); + }).openMenu(sender); + }).openMenu(sender); + } else { + new SelectArenaMenu(gameMode, arenaName -> { + sender.closeInventory(); + + if (!PotPvPValidation.canStartFfa(party, sender)) { + return; + } + + List teams = new ArrayList<>(); + + for (UUID member : party.getMembers()) { + teams.add(new MatchTeam(member)); + } + + matchHandler.startMatch(teams, gameMode, arenaName,false, false); + }).openMenu(sender); + } + }, "Select gamemode for FFA").openMenu(sender); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyHelpCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyHelpCommand.java new file mode 100644 index 0000000..2b35a87 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyHelpCommand.java @@ -0,0 +1,38 @@ +package com.elevatemc.potpvp.party.command; + +import com.google.common.collect.ImmutableList; +import com.elevatemc.potpvp.PotPvPLang; +import com.elevatemc.elib.command.Command; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.List; + +public final class PartyHelpCommand { + + private static final List HELP_MESSAGE = ImmutableList.of( + ChatColor.GRAY + PotPvPLang.LONG_LINE, + "§bParty Commands:", + "§3/party create §7- §fCreates a party", + "§3/party invite §7- §fInvite a player to join your party", + "§3/party leave §7- §fLeave your current party", + "§3/party accept (player) §7- §fAccept party invitation", + "§3/party info (player) §7- §fView the info of a players party", + "", + "§bLeader Commands:", + "§3/party open §7 - §fOpen your party for other players", + "§3/party lock §7 - §fLock party from others joining", + "§3/party password §7 - §fSets a password", + "§3/party kick (player) §7- §fKick a player from your party", + "§3/party disband §7 - §fDisband the party", + "", + "To use §3party chat§f, prefix your message with the §3@ §fsign.", + ChatColor.GRAY + PotPvPLang.LONG_LINE + ); + + @Command(names = {"party", "p", "t", "team", "f", "party help", "p help", "t help", "team help", "f help"}, permission = "") + public static void party(Player sender) { + HELP_MESSAGE.forEach(sender::sendMessage); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyInfoCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyInfoCommand.java new file mode 100644 index 0000000..2b1ae25 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyInfoCommand.java @@ -0,0 +1,57 @@ +package com.elevatemc.potpvp.party.command; + +import com.elevatemc.potpvp.PotPvPLang; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import com.elevatemc.elib.util.UUIDUtils; +import com.elevatemc.potpvp.util.PatchedPlayerUtils; +import com.google.common.base.Joiner; +import net.md_5.bungee.api.ChatColor; +import org.bukkit.entity.Player; + +public final class PartyInfoCommand { + + @Command(names = {"party info", "p info", "t info", "team info", "f info", "p i", "t i", "f i", "party i", "team i"}, permission = "") + public static void partyInfo(Player sender, @Parameter(name = "player", defaultValue = "self") Player target) { + Party party = PotPvPSI.getInstance().getPartyHandler().getParty(target); + + if (party == null) { + if (sender == target) { + sender.sendMessage(PotPvPLang.NOT_IN_PARTY); + } else { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + target.getName() + " isn't in a party right now."); + } + + return; + } + + String leaderName = UUIDUtils.name(party.getLeader()); + int memberCount = party.getMembers().size(); + String members = Joiner.on(", ").join(PatchedPlayerUtils.mapToNames(party.getMembers())); + String privacy = ""; + switch (party.getAccessRestriction()) { + case PUBLIC: + privacy = ChatColor.GREEN + "Open"; + break; + case INVITE_ONLY: + privacy = ChatColor.GOLD + "Invite-Only"; + break; + case PASSWORD: + privacy = ChatColor.RED + "Password Protected"; + break; + default: + break; + } + + + sender.sendMessage(ChatColor.translateAlternateColorCodes('&', "")); + sender.sendMessage(ChatColor.translateAlternateColorCodes('&', "&3&lParty Info")); + sender.sendMessage(ChatColor.translateAlternateColorCodes('&', "&3❘ &fLeader&7: &3" + leaderName)); + sender.sendMessage(ChatColor.translateAlternateColorCodes('&', "&3❘ &fMembers&7&7: " + "&3(" + memberCount + ") " + ChatColor.GRAY + members)); + sender.sendMessage(ChatColor.translateAlternateColorCodes('&', "&3❘ &fPrivacy&7: " + privacy)); + sender.sendMessage(ChatColor.translateAlternateColorCodes('&', "")); + + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyInviteCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyInviteCommand.java new file mode 100644 index 0000000..9a5fed4 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyInviteCommand.java @@ -0,0 +1,125 @@ +package com.elevatemc.potpvp.party.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.potpvp.party.PartyHandler; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.TextComponent; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.UUID; + +public final class PartyInviteCommand { + + @Command(names = {"party invite", "p invite", "t invite", "team invite", "invite", "inv", "party inv", "p inv", "t inv", "team invite", "f invite", "f inv"}, permission = "") + public static void partyInvite(Player sender, @Parameter(name = "player") Player target) { + PartyHandler partyHandler = PotPvPSI.getInstance().getPartyHandler(); + Party party = partyHandler.getParty(sender); + + if (sender == target) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You cannot invite yourself to your own party."); + return; + } + + if (sender.hasMetadata("modmode")) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You cannot do this while in mod mode!"); + return; + } + + if (party != null) { + if (party.isMember(target.getUniqueId())) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + target.getName() + " is already in your party."); + return; + } + + if (party.getInvite(target.getUniqueId()) != null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + target.getName() + " already has a pending party invite."); + return; + } + } + + if (partyHandler.hasParty(target)) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + target.getName() + " is already in another party."); + return; + } + + // only create party if validations succeed + party = partyHandler.getOrCreateParty(sender); + + if (party.getMembers().size() >= Party.MAX_SIZE && !sender.isOp()) { // I got the permission from "/party invite **" below + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Your party has reached the " + Party.MAX_SIZE + " player limit."); + return; + } + + if (party.isLeader(sender.getUniqueId())) { + party.invite(target); + } else { + askLeaderToInvite(party, sender, target); + } + } + + @Command(names = {"party invite **", "p invite **", "t invite **", "team invite **", "invite **", "inv **", "party inv **", "p inv **", "t inv **", "team invite **", "f invite **", "f inv **"}, permission = "op") + public static void partyInviteAll(Player sender) { + PartyHandler partyHandler = PotPvPSI.getInstance().getPartyHandler(); + Party party = partyHandler.getOrCreateParty(sender); + + if (sender.hasMetadata("modmode")) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You cannot do this while in mod mode!"); + return; + } + + int sent = 0; + + for (Player player : Bukkit.getOnlinePlayers()) { + UUID playerUuid = player.getUniqueId(); + boolean isMember = party.isMember(playerUuid); + boolean hasInvite = party.getInvite(playerUuid) != null; + + if (!isMember && !hasInvite) { + party.invite(player); + sent++; + } + } + + if (sent == 0) { + sender.sendMessage(ChatColor.YELLOW + "No invites to send."); + } else { + sender.sendMessage(ChatColor.YELLOW + "Sent " + sent + " invite" + (sent == 1 ? "" : "s") + "."); + } + } + + private static void askLeaderToInvite(Party party, Player requester, Player target) { + requester.sendMessage(ChatColor.YELLOW + "You have requested to invite " + target.getDisplayName() + ChatColor.YELLOW + "."); + + Player leader = Bukkit.getPlayer(party.getLeader()); + + // should never happen + if (leader == null) { + return; + } + + leader.sendMessage(requester.getDisplayName() + ChatColor.YELLOW + " wants you to invite " + target.getDisplayName() + ChatColor.YELLOW + "."); + leader.spigot().sendMessage(createInviteButton(target)); + } + + private static TextComponent createInviteButton(Player target) { + BaseComponent[] hoverTooltip = { new TextComponent(ChatColor.GREEN + "Click to invite") }; + HoverEvent.Action showText = HoverEvent.Action.SHOW_TEXT; + ClickEvent.Action runCommand = ClickEvent.Action.RUN_COMMAND; + + TextComponent inviteButton = new TextComponent("Click here to send the invitation"); + + inviteButton.setColor(ChatColor.AQUA); + inviteButton.setHoverEvent(new HoverEvent(showText, hoverTooltip)); + inviteButton.setClickEvent(new ClickEvent(runCommand, "/invite " + target.getName())); + + return inviteButton; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyJoinCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyJoinCommand.java new file mode 100644 index 0000000..edd4f3b --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyJoinCommand.java @@ -0,0 +1,68 @@ +package com.elevatemc.potpvp.party.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.potpvp.party.PartyHandler; +import com.elevatemc.potpvp.party.PartyInvite; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class PartyJoinCommand { + + // default value for password parameter used to detect that password + // wasn't provided. No Optional :( + private static final String NO_PASSWORD_PROVIDED = "pelado"; + + @Command(names = {"party join", "p join", "t join", "team join", "f join"}, permission = "") + public static void partyJoin(Player sender, @Parameter(name = "player") Player target, @Parameter(name = "password", defaultValue = NO_PASSWORD_PROVIDED) String providedPassword) { + PartyHandler partyHandler = PotPvPSI.getInstance().getPartyHandler(); + Party targetParty = partyHandler.getParty(target); + + if (partyHandler.hasParty(sender)) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You are already in a party. You must leave your current party first."); + return; + } + + if (targetParty == null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + target.getName() + " is not in a party."); + return; + } + + PartyInvite invite = targetParty.getInvite(sender.getUniqueId()); + + switch (targetParty.getAccessRestriction()) { + case PUBLIC: + targetParty.join(sender); + break; + case INVITE_ONLY: + if (invite != null) { + targetParty.join(sender); + } else { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You don't have an invitation to this party."); + } + + break; + case PASSWORD: + if (providedPassword.equals(NO_PASSWORD_PROVIDED) && invite == null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You need the password or an invitation to join this party."); + sender.sendMessage(ChatColor.DARK_AQUA + "Ω " + ChatColor.AQUA + "To join with a password, use " + ChatColor.DARK_AQUA + "/party join " + target.getName() + " "); + return; + } + + String correctPassword = targetParty.getPassword(); + + if (invite == null && !correctPassword.equals(providedPassword)) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Invalid password."); + } else { + targetParty.join(sender); + } + + break; + default: + break; + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyKickCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyKickCommand.java new file mode 100644 index 0000000..bfa0cd2 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyKickCommand.java @@ -0,0 +1,30 @@ +package com.elevatemc.potpvp.party.command; + +import com.elevatemc.potpvp.PotPvPLang; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class PartyKickCommand { + + @Command(names = {"party kick", "p kick", "t kick", "team kick", "f kick"}, permission = "") + public static void partyKick(Player sender, @Parameter(name = "player") Player target) { + Party party = PotPvPSI.getInstance().getPartyHandler().getParty(sender); + + if (party == null) { + sender.sendMessage(PotPvPLang.NOT_IN_PARTY); + } else if (!party.isLeader(sender.getUniqueId())) { + sender.sendMessage(PotPvPLang.NOT_LEADER_OF_PARTY); + } else if (sender == target) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You cannot kick yourself."); + } else if (!party.isMember(target.getUniqueId())) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + target.getName() + " isn't in your party."); + } else { + party.kick(target); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyLeaderCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyLeaderCommand.java new file mode 100644 index 0000000..748a8a5 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyLeaderCommand.java @@ -0,0 +1,30 @@ +package com.elevatemc.potpvp.party.command; + +import com.elevatemc.potpvp.PotPvPLang; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class PartyLeaderCommand { + + @Command(names = {"party leader", "p leader", "t leader", "team leader", "leader", "f leader"}, permission = "") + public static void partyLeader(Player sender, @Parameter(name = "player") Player target) { + Party party = PotPvPSI.getInstance().getPartyHandler().getParty(sender); + + if (party == null) { + sender.sendMessage(PotPvPLang.NOT_IN_PARTY); + } else if (!party.isLeader(sender.getUniqueId())) { + sender.sendMessage(PotPvPLang.NOT_LEADER_OF_PARTY); + } else if (!party.isMember(target.getUniqueId())) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + target.getName() + " isn't in your party."); + } else if (sender == target) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You cannot promote yourself to the leader of your own party."); + } else { + party.setLeader(target); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyLeaveCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyLeaveCommand.java new file mode 100644 index 0000000..b317ab5 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyLeaveCommand.java @@ -0,0 +1,22 @@ +package com.elevatemc.potpvp.party.command; + +import com.elevatemc.potpvp.PotPvPLang; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.elib.command.Command; +import org.bukkit.entity.Player; + +public final class PartyLeaveCommand { + + @Command(names = {"party leave", "p leave", "t leave", "team leave", "leave", "f leave"}, permission = "") + public static void partyLeave(Player sender) { + Party party = PotPvPSI.getInstance().getPartyHandler().getParty(sender); + + if (party == null) { + sender.sendMessage(PotPvPLang.NOT_IN_PARTY); + } else { + party.leave(sender); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyLockCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyLockCommand.java new file mode 100644 index 0000000..7413d78 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyLockCommand.java @@ -0,0 +1,29 @@ +package com.elevatemc.potpvp.party.command; + +import com.elevatemc.potpvp.PotPvPLang; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.potpvp.party.PartyAccessRestriction; +import com.elevatemc.elib.command.Command; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class PartyLockCommand { + + @Command(names = {"party lock", "p lock", "t lock", "team lock", "f lock"}, permission = "") + public static void partyLock(Player sender) { + Party party = PotPvPSI.getInstance().getPartyHandler().getParty(sender); + + if (party == null) { + sender.sendMessage(PotPvPLang.NOT_IN_PARTY); + } else if (!party.isLeader(sender.getUniqueId())) { + sender.sendMessage(PotPvPLang.NOT_LEADER_OF_PARTY); + } else if (party.getAccessRestriction() == PartyAccessRestriction.INVITE_ONLY) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Your party is already locked."); + } else { + party.setAccessRestriction(PartyAccessRestriction.INVITE_ONLY); + sender.sendMessage(ChatColor.DARK_AQUA + "۩ " + ChatColor.AQUA + "Your party is now " + ChatColor.RED + "locked" + ChatColor.AQUA + "."); + } + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyOpenCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyOpenCommand.java new file mode 100644 index 0000000..16a2289 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyOpenCommand.java @@ -0,0 +1,29 @@ +package com.elevatemc.potpvp.party.command; + +import com.elevatemc.potpvp.PotPvPLang; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.potpvp.party.PartyAccessRestriction; +import com.elevatemc.elib.command.Command; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class PartyOpenCommand { + + @Command(names = {"party open", "p open", "t open", "team open", "f open", "party unlock", "p unlock", "t unlock", "team unlock", "f unlock"}, permission = "") + public static void partyOpen(Player sender) { + Party party = PotPvPSI.getInstance().getPartyHandler().getParty(sender); + + if (party == null) { + sender.sendMessage(PotPvPLang.NOT_IN_PARTY); + } else if (!party.isLeader(sender.getUniqueId())) { + sender.sendMessage(PotPvPLang.NOT_LEADER_OF_PARTY); + } else if (party.getAccessRestriction() == PartyAccessRestriction.PUBLIC) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Your party is already open."); + } else { + party.setAccessRestriction(PartyAccessRestriction.PUBLIC); + sender.sendMessage(ChatColor.DARK_AQUA + "۩ " + ChatColor.AQUA + "Your party is now " + ChatColor.GREEN + "open" + ChatColor.AQUA + "."); + } + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyPasswordCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyPasswordCommand.java new file mode 100644 index 0000000..91e85bd --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyPasswordCommand.java @@ -0,0 +1,30 @@ +package com.elevatemc.potpvp.party.command; + +import com.elevatemc.potpvp.PotPvPLang; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.potpvp.party.PartyAccessRestriction; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public final class PartyPasswordCommand { + + @Command(names = {"party password", "p password", "t password", "team password", "party pass", "p pass", "t pass", "team pass", "f password", "f pass"}, permission = "") + public static void partyPassword(Player sender, @Parameter(name = "password") String password) { + Party party = PotPvPSI.getInstance().getPartyHandler().getParty(sender); + + if (party == null) { + sender.sendMessage(PotPvPLang.NOT_IN_PARTY); + } else if (!party.isLeader(sender.getUniqueId())) { + sender.sendMessage(PotPvPLang.NOT_LEADER_OF_PARTY); + } else { + party.setAccessRestriction(PartyAccessRestriction.PASSWORD); + party.setPassword(password); + + sender.sendMessage(ChatColor.DARK_AQUA + "۩ " + ChatColor.AQUA + "Your party's password is now " + ChatColor.RED + password + ChatColor.AQUA + "."); + } + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyTeamSplitCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyTeamSplitCommand.java new file mode 100644 index 0000000..7bd7697 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/command/PartyTeamSplitCommand.java @@ -0,0 +1,27 @@ +package com.elevatemc.potpvp.party.command; + +import com.elevatemc.potpvp.PotPvPLang; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.potpvp.party.PartyHandler; +import com.elevatemc.potpvp.party.PartyUtils; +import com.elevatemc.elib.command.Command; +import org.bukkit.entity.Player; + +public final class PartyTeamSplitCommand { + + @Command(names = {"party teamsplit", "p teamsplit", "t teamsplit", "team teamsplit", "f teamsplit"}, permission = "") + public static void partyTeamSplit(Player sender) { + PartyHandler partyHandler = PotPvPSI.getInstance().getPartyHandler(); + Party party = partyHandler.getParty(sender); + + if (party == null) { + sender.sendMessage(PotPvPLang.NOT_IN_PARTY); + } else if (!party.isLeader(sender.getUniqueId())) { + sender.sendMessage(PotPvPLang.NOT_LEADER_OF_PARTY); + } else { + PartyUtils.startTeamSplit(party, sender); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/event/PartyCreateEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/event/PartyCreateEvent.java new file mode 100644 index 0000000..cbda37d --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/event/PartyCreateEvent.java @@ -0,0 +1,26 @@ +package com.elevatemc.potpvp.party.event; + +import lombok.Getter; +import com.elevatemc.potpvp.party.Party; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; + +/** + * Called when a {@link Party} is created. + * @see com.elevatemc.potpvp.party.command.PartyCreateCommand + * @see com.elevatemc.potpvp.party.PartyHandler#getOrCreateParty(Player) + */ +public final class PartyCreateEvent extends PartyEvent { + + @Getter private static HandlerList handlerList = new HandlerList(); + + public PartyCreateEvent(Party party) { + super(party); + } + + @Override + public HandlerList getHandlers() { + return handlerList; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/event/PartyDisbandEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/event/PartyDisbandEvent.java new file mode 100644 index 0000000..3c74ebf --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/event/PartyDisbandEvent.java @@ -0,0 +1,25 @@ +package com.elevatemc.potpvp.party.event; + +import lombok.Getter; +import com.elevatemc.potpvp.party.Party; +import org.bukkit.event.HandlerList; + +/** + * Called when a {@link Party} is disbanded. + * @see com.elevatemc.potpvp.party.command.PartyDisbandCommand + * @see Party#disband() + */ +public final class PartyDisbandEvent extends PartyEvent { + + @Getter private static HandlerList handlerList = new HandlerList(); + + public PartyDisbandEvent(Party party) { + super(party); + } + + @Override + public HandlerList getHandlers() { + return handlerList; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/event/PartyEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/event/PartyEvent.java new file mode 100644 index 0000000..412fff0 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/event/PartyEvent.java @@ -0,0 +1,22 @@ +package com.elevatemc.potpvp.party.event; + +import com.google.common.base.Preconditions; +import lombok.Getter; +import com.elevatemc.potpvp.party.Party; +import org.bukkit.event.Event; + +/** + * Represents an event involving a {@link Party} + */ +abstract class PartyEvent extends Event { + + /** + * The party involved in this event + */ + @Getter private final Party party; + + PartyEvent(Party party) { + this.party = Preconditions.checkNotNull(party, "party"); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/event/PartyMemberJoinEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/event/PartyMemberJoinEvent.java new file mode 100644 index 0000000..494dbb6 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/event/PartyMemberJoinEvent.java @@ -0,0 +1,31 @@ +package com.elevatemc.potpvp.party.event; + +import com.google.common.base.Preconditions; +import lombok.Getter; +import com.elevatemc.potpvp.party.Party; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; + +/** + * Called when a player successfully joins a {@link Party}. + * This event will not be called for the leader 'joining' their + * own party, upon creation. + */ +public final class PartyMemberJoinEvent extends PartyEvent { + + @Getter private static HandlerList handlerList = new HandlerList(); + + @Getter private final Player member; + + public PartyMemberJoinEvent(Player member, Party party) { + super(party); + + this.member = Preconditions.checkNotNull(member, "member"); + } + + @Override + public HandlerList getHandlers() { + return handlerList; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/event/PartyMemberKickEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/event/PartyMemberKickEvent.java new file mode 100644 index 0000000..c279e67 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/event/PartyMemberKickEvent.java @@ -0,0 +1,29 @@ +package com.elevatemc.potpvp.party.event; + +import com.google.common.base.Preconditions; +import lombok.Getter; +import com.elevatemc.potpvp.party.Party; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; + +/** + * Called when a player is kicked from their {@link Party}. + */ +public final class PartyMemberKickEvent extends PartyEvent { + + @Getter private static HandlerList handlerList = new HandlerList(); + + @Getter private final Player member; + + public PartyMemberKickEvent(Player member, Party party) { + super(party); + + this.member = Preconditions.checkNotNull(member, "member"); + } + + @Override + public HandlerList getHandlers() { + return handlerList; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/event/PartyMemberLeaveEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/event/PartyMemberLeaveEvent.java new file mode 100644 index 0000000..b73f307 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/event/PartyMemberLeaveEvent.java @@ -0,0 +1,31 @@ +package com.elevatemc.potpvp.party.event; + +import com.google.common.base.Preconditions; +import lombok.Getter; +import com.elevatemc.potpvp.party.Party; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; + +/** + * Called when a player leaves a {@link Party}. + * This event will not be called if a player 'leaves' their party by + * having it be disbanded. + */ +public final class PartyMemberLeaveEvent extends PartyEvent { + + @Getter private static HandlerList handlerList = new HandlerList(); + + @Getter private final Player member; + + public PartyMemberLeaveEvent(Player member, Party party) { + super(party); + + this.member = Preconditions.checkNotNull(member, "member"); + } + + @Override + public HandlerList getHandlers() { + return handlerList; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/listener/PartyChatListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/listener/PartyChatListener.java new file mode 100644 index 0000000..b2cb15b --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/listener/PartyChatListener.java @@ -0,0 +1,39 @@ +package com.elevatemc.potpvp.party.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.party.Party; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerChatEvent; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public final class PartyChatListener implements Listener { + @EventHandler(ignoreCancelled = true) + public void onAsyncPlayerChat(AsyncPlayerChatEvent event) { + if (!event.getMessage().startsWith("@")) { + return; + } + + event.setCancelled(true); + + Player player = event.getPlayer(); + String message = event.getMessage().substring(1).trim(); + Party party = PotPvPSI.getInstance().getPartyHandler().getParty(player); + + if (party == null) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You have to be in a party to use this chat!"); + return; + } + + ChatColor prefixColor = party.isLeader(player.getUniqueId()) ? ChatColor.UNDERLINE : ChatColor.AQUA; + party.message(ChatColor.DARK_AQUA + "✭ " + ChatColor.AQUA + prefixColor + player.getName() + ": " + ChatColor.WHITE + message); + + PotPvPSI.getInstance().getLogger().info("[Party] " + player.getName() + ": " + message); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/listener/PartyItemListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/listener/PartyItemListener.java new file mode 100644 index 0000000..3fecbab --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/listener/PartyItemListener.java @@ -0,0 +1,23 @@ +package com.elevatemc.potpvp.party.listener; + +import com.elevatemc.potpvp.party.PartyHandler; +import com.elevatemc.potpvp.party.PartyItems; +import com.elevatemc.potpvp.party.command.PartyFfaCommand; +import com.elevatemc.potpvp.party.command.PartyInfoCommand; +import com.elevatemc.potpvp.party.command.PartyLeaveCommand; +import com.elevatemc.potpvp.party.command.PartyTeamSplitCommand; +import com.elevatemc.potpvp.party.menu.RosterMenu; +import com.elevatemc.potpvp.party.menu.otherparties.OtherPartiesMenu; +import com.elevatemc.potpvp.util.ItemListener; + +public final class PartyItemListener extends ItemListener { + + public PartyItemListener(PartyHandler partyHandler) { + addHandler(PartyItems.PARTY_INFO, p -> { PartyInfoCommand.partyInfo(p, p); }); + addHandler(PartyItems.LEAVE_PARTY_ITEM, PartyLeaveCommand::partyLeave); + addHandler(PartyItems.START_TEAM_SPLIT_ITEM, PartyTeamSplitCommand::partyTeamSplit); + addHandler(PartyItems.START_FFA_ITEM, PartyFfaCommand::partyFfa); + addHandler(PartyItems.OTHER_PARTIES_ITEM, p -> new OtherPartiesMenu().openMenu(p)); + addHandler(PartyItems.ASSIGN_CLASSES, p -> new RosterMenu(partyHandler.getParty(p)).openMenu(p)); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/listener/PartyLeaveListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/listener/PartyLeaveListener.java new file mode 100644 index 0000000..bede0ab --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/listener/PartyLeaveListener.java @@ -0,0 +1,49 @@ +package com.elevatemc.potpvp.party.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.event.MatchSpectatorLeaveEvent; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.potpvp.party.PartyHandler; +import com.elevatemc.potpvp.party.PartyInvite; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.UUID; + +public final class PartyLeaveListener implements Listener { + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + Player player = event.getPlayer(); + UUID playerUuid = player.getUniqueId(); + + for (Party party : PotPvPSI.getInstance().getPartyHandler().getParties()) { + if (party.isMember(playerUuid)) { + party.leave(player); + } + + PartyInvite invite = party.getInvite(playerUuid); + + if (invite != null) { + party.revokeInvite(invite); + } + } + } + + // players who leave a match while in a party won't be able to + // do anything (most validation checks disallow players in a party) + // so as a convenience we remove them from their party. + // see @jhalt for any further explanation + @EventHandler + public void onMatchSpectatorLeave(MatchSpectatorLeaveEvent event) { + PartyHandler partyHandler = PotPvPSI.getInstance().getPartyHandler(); + Party party = partyHandler.getParty(event.getSpectator()); + + if (party != null) { + party.leave(event.getSpectator()); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/menu/RosterMenu.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/menu/RosterMenu.java new file mode 100644 index 0000000..194353c --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/menu/RosterMenu.java @@ -0,0 +1,140 @@ +package com.elevatemc.potpvp.party.menu; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.potpvp.pvpclasses.PvPClasses; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.pagination.PaginatedMenu; +import com.elevatemc.potpvp.util.PatchedPlayerUtils; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.*; + +public class RosterMenu extends PaginatedMenu { + + private final Party party; + + public RosterMenu(Party party) { + this.party = party; + + setAutoUpdate(true); + setUpdateAfterClick(true); + setPlaceholder(true); + } + + + + @Override + public String getPrePaginatedTitle(Player player) { + return "Team Roster"; + } + + @Override + public Map getAllPagesButtons(Player player) { + Map toReturn = new HashMap<>(); + + if (party.getMembers().size() == 1) { + player.closeInventory(); + return toReturn; + } + + for (UUID uuid : new ArrayList<>(party.getKits().keySet())) { + if (!(party.getMembers().contains(uuid))) { + party.getKits().remove(uuid); + } + } + + for (UUID uuid : party.getMembers()) { + Player member = Bukkit.getPlayer(uuid); + + if (member == null) { + continue; + } + + PvPClasses selected = party.getKits().getOrDefault(uuid, PvPClasses.DIAMOND); + + toReturn.put(toReturn.isEmpty() ? 0 : toReturn.size(), new Button() { + @Override + public String getName(Player player) { + return PatchedPlayerUtils.getFormattedName(member.getUniqueId()); + } + + @Override + public List getDescription(Player player) { + List description = new ArrayList<>(); + + for (PvPClasses kit : PvPClasses.values()) { + if (kit == selected) { + description.add(ChatColor.GREEN + "> " + kit.getName()); + } else { + if (kit.allowed(party)) { + description.add(ChatColor.GRAY + kit.getName()); + } else { + description.add(ChatColor.RED + ChatColor.STRIKETHROUGH.toString() + kit.getName()); + } + } + } + + return description; + } + + @Override + public Material getMaterial(Player player) { + return selected.getIcon(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + if (party.isLeader(player.getUniqueId())) { + List kits = Arrays.asList(PvPClasses.values()); + int index = kits.indexOf(selected); + PvPClasses next = null; + + int times = 0; + while (next == null && times <= 50) { + times++; + if (index+1 < kits.size()) { + next = kits.get(index+1); + if (!(next.allowed(party))) { + next = null; + index++; + } + } else { + index = -1; + } + } + + if (next == null) { + next = PvPClasses.DIAMOND; + } + + party.message(ChatColor.YELLOW + member.getDisplayName() + "'s kit has been set to " + ChatColor.GOLD + next.getName()); + + party.getKits().put(uuid, next); + + for (UUID other : party.getMembers()) { + PotPvPSI.getInstance().getPartyHandler().updatePartyCache(other, party); + } + + } + } + }); + } + + return toReturn; + } + + @Override + public int size(Player buttons) { + return 9*5; + } + + @Override + public int getMaxItemsPerPage(Player player) { + return 9*4; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/menu/oddmanout/OddManOutButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/menu/oddmanout/OddManOutButton.java new file mode 100644 index 0000000..923d48d --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/menu/oddmanout/OddManOutButton.java @@ -0,0 +1,54 @@ +package com.elevatemc.potpvp.party.menu.oddmanout; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.util.Callback; +import org.bukkit.ChatColor; +import org.bukkit.DyeColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.List; + +final class OddManOutButton extends Button { + + private final boolean oddManOut; + private final Callback callback; + + OddManOutButton(boolean oddManOut, Callback callback) { + this.oddManOut = oddManOut; + this.callback = Preconditions.checkNotNull(callback, "callback"); + } + + @Override + public String getName(Player player) { + if (oddManOut) { + return ChatColor.GREEN.toString() + ChatColor.BOLD + "Have a random player sit out"; + } else { + return ChatColor.RED.toString() + ChatColor.BOLD + "Continue with uneven teams"; + } + } + + @Override + public List getDescription(Player player) { + return ImmutableList.of(); + } + + @Override + public Material getMaterial(Player player) { + return Material.WOOL; + } + + @Override + public byte getDamageValue(Player player) { + return (oddManOut ? DyeColor.GREEN : DyeColor.RED).getWoolData(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + callback.callback(oddManOut); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/menu/oddmanout/OddManOutMenu.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/menu/oddmanout/OddManOutMenu.java new file mode 100644 index 0000000..764c6d9 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/menu/oddmanout/OddManOutMenu.java @@ -0,0 +1,40 @@ +package com.elevatemc.potpvp.party.menu.oddmanout; + +import com.google.common.base.Preconditions; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import com.elevatemc.elib.util.Callback; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; + +public final class OddManOutMenu extends Menu { + + private final Callback callback; + + public OddManOutMenu(Callback callback) { + this.callback = Preconditions.checkNotNull(callback, "callback"); + setPlaceholder(true); + } + + @Override + public String getTitle(Player player) { + return "Continue with unbalanced teams?"; + } + + @Override + public Map getButtons(Player player) { + Map buttons = new HashMap<>(); + + buttons.put(getSlot(2, 1), new OddManOutButton(true, callback)); + buttons.put(getSlot(6, 1), new OddManOutButton(false, callback)); + + return buttons; + } + + @Override + public int size(Player player) { + return 9 * 3; + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/menu/otherparties/OtherPartiesMenu.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/menu/otherparties/OtherPartiesMenu.java new file mode 100644 index 0000000..224a519 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/menu/otherparties/OtherPartiesMenu.java @@ -0,0 +1,77 @@ +package com.elevatemc.potpvp.party.menu.otherparties; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.lobby.LobbyHandler; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.potpvp.party.PartyHandler; +import com.elevatemc.potpvp.setting.Setting; +import com.elevatemc.potpvp.setting.SettingHandler; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.pagination.PaginatedMenu; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.*; + +public final class OtherPartiesMenu extends PaginatedMenu { + + public OtherPartiesMenu() { + setPlaceholder(true); + setAutoUpdate(true); + } + + @Override + public String getPrePaginatedTitle(Player player) { + return ChatColor.BLUE + "Other parties"; + } + + @Override + public Map getAllPagesButtons(Player player) { + SettingHandler settingHandler = PotPvPSI.getInstance().getSettingHandler(); + PartyHandler partyHandler = PotPvPSI.getInstance().getPartyHandler(); + LobbyHandler lobbyHandler = PotPvPSI.getInstance().getLobbyHandler(); + + Map buttons = new HashMap<>(); + List parties = new ArrayList<>(partyHandler.getParties()); + int index = 0; + + parties.sort(Comparator.comparing(p -> p.getMembers().size())); + + for (Party party : parties) { + if (party.isMember(player.getUniqueId())) { + continue; + } + + if (!lobbyHandler.isInLobby(Bukkit.getPlayer(party.getLeader()))) { + continue; + } + + if (!settingHandler.getSetting(Bukkit.getPlayer(party.getLeader()), Setting.RECEIVE_DUELS)) { + continue; + } + + /* if (PotPvPSI.getInstance().getTournamentHandler().isInTournament(party)) { + continue; + } */ + + buttons.put(index++, new OtherPartyButton(party)); + } + + return buttons; + } + + // we lock the size of this inventory at full, otherwise we'll have + // issues if it 'grows' into the next line while it's open (say we open + // the menu with 8 entries, then it grows to 11 [and onto the second row] + // - this breaks things) + @Override + public int size(Player buttons) { + return 9 * 6; + } + + @Override + public int getMaxItemsPerPage(Player player) { + return 9 * 5; // top row is dedicated to switching + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/menu/otherparties/OtherPartyButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/menu/otherparties/OtherPartyButton.java new file mode 100644 index 0000000..10d1e5d --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/party/menu/otherparties/OtherPartyButton.java @@ -0,0 +1,80 @@ +package com.elevatemc.potpvp.party.menu.otherparties; + +import com.google.common.base.Preconditions; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.duel.command.DuelCommand; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.util.UUIDUtils; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +final class OtherPartyButton extends Button { + + private final Party party; + + OtherPartyButton(Party party) { + this.party = Preconditions.checkNotNull(party, "party"); + } + + @Override + public String getName(Player player) { + return ChatColor.DARK_AQUA + UUIDUtils.name(party.getLeader()); + } + + @Override + public List getDescription(Player player) { + List description = new ArrayList<>(); + + description.add(""); + description.add(ChatColor.DARK_AQUA + "◎ " + ChatColor.WHITE + "Party Members"); + + for (UUID member : party.getMembers()) { + ChatColor color = party.isLeader(member) ? ChatColor.DARK_AQUA : ChatColor.AQUA; + description.add(color + " " + UUIDUtils.name(member)); + } + + description.add(""); + description.add(ChatColor.DARK_AQUA + "❘ " + ChatColor.WHITE + "Click here to duel this party"); + + return description; + } + + @Override + public Material getMaterial(Player player) { + return Material.SKULL_ITEM; + } + + @Override + public byte getDamageValue(Player player) { + return (byte) 3; // player head + } + + @Override + public int getAmount(Player player) { + return party.getMembers().size(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + Party senderParty = PotPvPSI.getInstance().getPartyHandler().getParty(player); + + if (senderParty == null) { + return; + } + + if (senderParty.isLeader(player.getUniqueId())) { + DuelCommand.duel(player, Bukkit.getPlayer(party.getLeader())); + } else { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Only the leader can duel other parties!"); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/PostMatchInvHandler.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/PostMatchInvHandler.java new file mode 100644 index 0000000..be0367c --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/PostMatchInvHandler.java @@ -0,0 +1,186 @@ +package com.elevatemc.potpvp.postmatchinv; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableMap; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.postmatchinv.listener.PostMatchInvGeneralListener; +import com.elevatemc.potpvp.util.PatchedPlayerUtils; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.ComponentBuilder; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.TextComponent; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public final class PostMatchInvHandler { + + // uuid -> their "view" of their last match + // this varies per player, so we must store them all individually + private final Map> playerData = new ConcurrentHashMap<>(); + + public PostMatchInvHandler() { + Bukkit.getPluginManager().registerEvents(new PostMatchInvGeneralListener(), PotPvPSI.getInstance()); + } + + public void recordMatch(Match match) { + saveInventories(match); + messagePlayers(match); + } + + private void saveInventories(Match match) { + Map matchPlayers = match.getPostMatchPlayers(); + + for (MatchTeam team : match.getTeams()) { + for (UUID member : team.getAliveMembers()) { + playerData.put(member, matchPlayers); + } + } + + for (UUID spectator : match.getSpectators()) { + playerData.put(spectator, matchPlayers); + } + } + + private void messagePlayers(Match match) { + Map invMessages = new HashMap<>(); + + BaseComponent[] spectatorLine; + List spectatorUuids = new ArrayList<>(match.getSpectators()); + + // don't count actual players and players in silent mode. + spectatorUuids.removeIf(uuid -> { + Player player = Bukkit.getPlayer(uuid); + return player.hasMetadata("modmode") || match.getPreviousTeam(uuid) != null; + }); + + if (!spectatorUuids.isEmpty()) { + List spectatorNames = PatchedPlayerUtils.mapToNames(spectatorUuids); + spectatorNames.sort(String::compareToIgnoreCase); + + String firstFourNames = Joiner.on(", ").join( + spectatorNames.subList( + 0, + Math.min(spectatorNames.size(), 4) + ) + ); + + if (spectatorNames.size() > 4) { + firstFourNames += " (+" + (spectatorNames.size() - 4) + " more)"; + } + + HoverEvent hover = new HoverEvent( + HoverEvent.Action.SHOW_TEXT, + spectatorNames.stream() + .map(n -> new TextComponent(ChatColor.AQUA + n + '\n')) + .toArray(BaseComponent[]::new) + ); + + spectatorLine = new ComponentBuilder("Spectators (" + spectatorNames.size() + "): ").color(ChatColor.AQUA) + .append(firstFourNames).color(ChatColor.GRAY).event(hover) + .create(); + } else { + // this is dumb but it lets us make the variable effectively final + // (and avoid a working variable) + spectatorLine = null; + } + + createInvMessages(match, invMessages); + + invMessages.forEach((uuid, lines) -> { + Player player = Bukkit.getPlayer(uuid); + + if (player == null) { + return; + } + + player.sendMessage(PostMatchInvLang.LINE); + player.sendMessage(org.bukkit.ChatColor.DARK_AQUA + "Post-Match Inventories " + ChatColor.GRAY + "(click name to view)"); + + for (Object line : lines) { + if (line instanceof TextComponent[]) { + player.spigot().sendMessage((TextComponent[]) line); + } else if (line instanceof TextComponent) { + player.spigot().sendMessage((TextComponent) line); + } else if (line instanceof String) { + player.sendMessage((String) line); + } + } + + if (spectatorLine != null) { + player.spigot().sendMessage(spectatorLine); + } + + player.sendMessage(PostMatchInvLang.LINE); + }); + } + + private void createInvMessages(Match match, Map invMessages) { + List teams = match.getTeams(); + + if (teams.size() != 2) { + // matches without 2 teams just get big 'participants' sections + Object[] generic = PostMatchInvLang.genGenericInvs(teams); + + writeSpecInvMessages(match, invMessages, generic); + writeTeamInvMessages(teams, invMessages, generic); + return; + } + + MatchTeam winnerTeam = match.getWinner(); + MatchTeam loserTeam = teams.get(0) == winnerTeam ? teams.get(1) : teams.get(0); + + if (winnerTeam.getAllMembers().size() == 1 && loserTeam.getAllMembers().size() == 1) { + // 1v1 messages + Object[] generic = PostMatchInvLang.gen1v1PlayerInvs(winnerTeam.getFirstMember(), loserTeam.getFirstMember()); + + writeSpecInvMessages(match, invMessages, generic); + writeTeamInvMessages(teams, invMessages, generic); + } else { + // normal 2 team messages + writeSpecInvMessages(match, invMessages, PostMatchInvLang.genSpectatorInvs(winnerTeam, loserTeam)); + writeTeamInvMessages(winnerTeam, invMessages, PostMatchInvLang.genTeamInvs(winnerTeam, winnerTeam, loserTeam)); + writeTeamInvMessages(loserTeam, invMessages, PostMatchInvLang.genTeamInvs(loserTeam, winnerTeam, loserTeam)); + } + } + + private void writeTeamInvMessages(Iterable teams, Map messageMap, Object[] messages) { + for (MatchTeam team : teams) { + writeTeamInvMessages(team, messageMap, messages); + } + } + + private void writeTeamInvMessages(MatchTeam team, Map messageMap, Object[] messages) { + for (UUID member : team.getAllMembers()) { + // on this containsKey check: + // we only want to send messages to players who are alive or were on this team in the match + // we always add messages from the least specific (ex generic for spectators) + // to most specific (ex per team messages), so checking if they're already added is a good + // way to check if they're going to get a message. + // we can't just use getAllMembers because players could've left and started a new fight + if (messageMap.containsKey(member) || team.isAlive(member)) { + messageMap.put(member, messages); + } + } + } + + private void writeSpecInvMessages(Match match, Map messageMap, Object[] messages) { + for (UUID spectator : match.getSpectators()) { + messageMap.put(spectator, messages); + } + } + + public Map getPostMatchData(UUID forWhom) { + return playerData.getOrDefault(forWhom, ImmutableMap.of()); + } + + public void removePostMatchData(UUID forWhom) { + playerData.remove(forWhom); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/PostMatchInvLang.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/PostMatchInvLang.java new file mode 100644 index 0000000..e08f32f --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/PostMatchInvLang.java @@ -0,0 +1,99 @@ +package com.elevatemc.potpvp.postmatchinv; + +import lombok.experimental.UtilityClass; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.elib.util.UUIDUtils; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.ComponentBuilder; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.TextComponent; + +import java.util.*; +import java.util.stream.Collectors; + +@UtilityClass +public final class PostMatchInvLang { + + static final String LINE = ChatColor.GRAY.toString() + ChatColor.STRIKETHROUGH + "-----------------------------------------------------"; + static final String INVENTORY_HEADER = ChatColor.LIGHT_PURPLE + "Post-Match Inventories " + ChatColor.GRAY + "(click name to view)"; + + private static final String WINNER = ChatColor.GREEN + "Winner:" + ChatColor.GRAY; + private static final String LOSER = ChatColor.RED + "Loser:" + ChatColor.GRAY; + private static final String PARTICIPANTS = ChatColor.GREEN + "Participants:"; + + private static final TextComponent COMMA_COMPONENT = new TextComponent(", "); + + static { + COMMA_COMPONENT.setColor(ChatColor.YELLOW); + } + + static Object[] gen1v1PlayerInvs(UUID winner, UUID loser) { + return new Object[] { + new TextComponent[] { + new TextComponent(ChatColor.GREEN + "Winner: "), + clickToViewLine(winner), + new TextComponent(ChatColor.GRAY + " - " + ChatColor.RED + "Loser: "), + clickToViewLine(loser) + } + }; + } + + // when viewing a 2 team match as a spectator + static Object[] genSpectatorInvs(MatchTeam winner, MatchTeam loser) { + return new Object[] { + WINNER, + clickToViewLine(winner.getAllMembers()), + LOSER, + clickToViewLine(loser.getAllMembers()), + }; + } + + // when viewing a 2 team match as a participant + static Object[] genTeamInvs(MatchTeam viewer, MatchTeam winner, MatchTeam loser) { + return new Object[] { + WINNER + (viewer == winner ? " (Your team)" : " (Enemy team)"), + clickToViewLine(winner.getAllMembers()), + LOSER + (viewer == loser ? " (Your team)" : " (Enemy team)"), + clickToViewLine(loser.getAllMembers()), + }; + } + + // when viewing a non-2 team match from any perspective + static Object[] genGenericInvs(Collection teams) { + Set members = teams.stream() + .flatMap(t -> t.getAllMembers().stream()) + .collect(Collectors.toSet()); + + return new Object[] { + PARTICIPANTS, + clickToViewLine(members), + }; + } + + private static TextComponent clickToViewLine(UUID member) { + String memberName = UUIDUtils.name(member); + TextComponent component = new TextComponent(); + + component.setText(memberName); + component.setColor(ChatColor.YELLOW); + component.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(ChatColor.GREEN + "Click to view inventory of " + ChatColor.LIGHT_PURPLE + memberName).create())); + component.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/_ " + memberName)); + + return component; + } + + private static TextComponent[] clickToViewLine(Set members) { + List components = new ArrayList<>(); + + for (UUID member : members) { + components.add(clickToViewLine(member)); + components.add(COMMA_COMPONENT); + } + + components.remove(components.size() - 1); // remove trailing comma + return components.toArray(new TextComponent[components.size()]); + } + + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/PostMatchPlayer.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/PostMatchPlayer.java new file mode 100644 index 0000000..0727db7 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/PostMatchPlayer.java @@ -0,0 +1,46 @@ +package com.elevatemc.potpvp.postmatchinv; + +import com.google.common.collect.ImmutableList; +import lombok.Getter; +import com.elevatemc.potpvp.gamemode.HealingMethod; +import com.elevatemc.elib.util.PlayerUtils; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; + +import java.util.List; +import java.util.UUID; + +public final class PostMatchPlayer { + + @Getter private final UUID playerUuid; + @Getter private final String lastUsername; + @Getter private final ItemStack[] armor; + @Getter private final ItemStack[] inventory; + @Getter private final List potionEffects; + @Getter private final int hunger; + @Getter private final int health; // out of 10 + @Getter private final transient HealingMethod healingMethodUsed; + @Getter private final int totalHits; + @Getter private final int longestCombo; + @Getter private final int totalTags; + @Getter private final int missedPots; + @Getter private final int ping; + + public PostMatchPlayer(Player player, HealingMethod healingMethodUsed, int totalHits, int longestCombo, int totalTags, int missedPots) { + this.playerUuid = player.getUniqueId(); + this.lastUsername = player.getName(); + this.armor = player.getInventory().getArmorContents(); + this.inventory = player.getInventory().getContents(); + this.potionEffects = ImmutableList.copyOf(player.getActivePotionEffects()); + this.hunger = player.getFoodLevel(); + this.health = (int) player.getHealth(); + this.healingMethodUsed = healingMethodUsed; + this.totalHits = totalHits; + this.longestCombo = longestCombo; + this.totalTags = totalTags; + this.missedPots = missedPots; + this.ping = PlayerUtils.getPing(player); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/command/CheckPostMatchInvCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/command/CheckPostMatchInvCommand.java new file mode 100644 index 0000000..605ff15 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/command/CheckPostMatchInvCommand.java @@ -0,0 +1,29 @@ +package com.elevatemc.potpvp.postmatchinv.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.postmatchinv.PostMatchInvHandler; +import com.elevatemc.potpvp.postmatchinv.PostMatchPlayer; +import com.elevatemc.potpvp.postmatchinv.menu.PostMatchMenu; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import com.elevatemc.elib.util.UUIDUtils; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.Map; +import java.util.UUID; + +public final class CheckPostMatchInvCommand { + + @Command(names = { "checkPostMatchInv", "_" }, permission = "") + public static void checkPostMatchInv(Player sender, @Parameter(name = "target") UUID target) { + PostMatchInvHandler postMatchInvHandler = PotPvPSI.getInstance().getPostMatchInvHandler(); + Map players = postMatchInvHandler.getPostMatchData(sender.getUniqueId()); + + if (players.containsKey(target)) { + new PostMatchMenu(players.get(target)).openMenu(sender); + } else { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Data for " + UUIDUtils.name(target) + "'s last match inventory could not be found."); + } + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/listener/PostMatchInvGeneralListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/listener/PostMatchInvGeneralListener.java new file mode 100644 index 0000000..4b6ecc3 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/listener/PostMatchInvGeneralListener.java @@ -0,0 +1,42 @@ +package com.elevatemc.potpvp.postmatchinv.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.match.event.MatchCountdownStartEvent; +import com.elevatemc.potpvp.match.event.MatchTerminateEvent; +import com.elevatemc.potpvp.postmatchinv.PostMatchInvHandler; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.UUID; + +public final class PostMatchInvGeneralListener implements Listener { + + @EventHandler + public void onMatchTerminate(MatchTerminateEvent event) { + PostMatchInvHandler postMatchInvHandler = PotPvPSI.getInstance().getPostMatchInvHandler(); + postMatchInvHandler.recordMatch(event.getMatch()); + } + + // remove 'old' post match data when their match starts + @EventHandler + public void onMatchCountdownStart(MatchCountdownStartEvent event) { + PostMatchInvHandler postMatchInvHandler = PotPvPSI.getInstance().getPostMatchInvHandler(); + + for (MatchTeam team : event.getMatch().getTeams()) { + for (UUID member : team.getAllMembers()) { + postMatchInvHandler.removePostMatchData(member); + } + } + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + PostMatchInvHandler postMatchInvHandler = PotPvPSI.getInstance().getPostMatchInvHandler(); + UUID playerUuid = event.getPlayer().getUniqueId(); + + postMatchInvHandler.removePostMatchData(playerUuid); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/menu/PostMatchBowStatisticsButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/menu/PostMatchBowStatisticsButton.java new file mode 100644 index 0000000..e62422b --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/menu/PostMatchBowStatisticsButton.java @@ -0,0 +1,45 @@ +package com.elevatemc.potpvp.postmatchinv.menu; + +import com.elevatemc.elib.util.UUIDUtils; +import com.google.common.collect.ImmutableList; +import com.elevatemc.elib.menu.Button; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.UUID; + +final class PostMatchBowStatisticsButton extends Button { + private final UUID player; + private final int tags; + + + PostMatchBowStatisticsButton(UUID player, int tags) { + this.player = player; + this.tags = tags; + } + + @Override + public String getName(Player player) { + return ChatColor.GREEN + "Bow Statistics"; + } + + @Override + public List getDescription(Player player) { + return ImmutableList.of( + ChatColor.YELLOW + UUIDUtils.name(this.player) + " had " + this.tags + " tag" + (tags == 1 ? "" : "s") + " in total." + ); + } + + @Override + public Material getMaterial(Player player) { + return Material.BOW; + } + + @Override + public int getAmount(Player player) { + return 1; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/menu/PostMatchFoodLevelButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/menu/PostMatchFoodLevelButton.java new file mode 100644 index 0000000..8d1b1e7 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/menu/PostMatchFoodLevelButton.java @@ -0,0 +1,39 @@ +package com.elevatemc.potpvp.postmatchinv.menu; + +import com.google.common.collect.ImmutableList; +import com.elevatemc.elib.menu.Button; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.util.List; + +final class PostMatchFoodLevelButton extends Button { + + private final int foodLevel; + + PostMatchFoodLevelButton(int foodLevel) { + this.foodLevel = foodLevel; + } + + @Override + public String getName(Player player) { + return ChatColor.GREEN.toString() + foodLevel + "/20 Hunger"; + } + + @Override + public List getDescription(Player player) { + return ImmutableList.of(); + } + + @Override + public Material getMaterial(Player player) { + return Material.COOKED_BEEF; + } + + @Override + public int getAmount(Player player) { + return foodLevel; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/menu/PostMatchHealsLeftButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/menu/PostMatchHealsLeftButton.java new file mode 100644 index 0000000..33de594 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/menu/PostMatchHealsLeftButton.java @@ -0,0 +1,59 @@ +package com.elevatemc.potpvp.postmatchinv.menu; + +import com.google.common.collect.ImmutableList; +import com.elevatemc.potpvp.gamemode.HealingMethod; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.util.UUIDUtils; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.List; +import java.util.UUID; + +final class PostMatchHealsLeftButton extends Button { + + private final UUID player; + private final HealingMethod healingMethod; + private final int healsRemaining; + private final int missedHeals; + + PostMatchHealsLeftButton(UUID player, HealingMethod healingMethod, int healsRemaining, int missedHeals) { + this.player = player; + this.healingMethod = healingMethod; + this.healsRemaining = healsRemaining; + this.missedHeals = missedHeals; + } + + @Override + public String getName(Player player) { + return ChatColor.GREEN.toString() + healsRemaining + " " + (healsRemaining == 1 ? healingMethod.getLongSingular() : healingMethod.getLongPlural()); + } + + @Override + public List getDescription(Player player) { + return ImmutableList.of( + ChatColor.YELLOW + UUIDUtils.name(this.player) + " had " + healsRemaining + " " + (healsRemaining == 1 ? healingMethod.getLongSingular() : healingMethod.getLongPlural()) + " left.", + ChatColor.YELLOW + UUIDUtils.name(this.player) + " missed " + missedHeals + " health potion" + (missedHeals == 1 ? "." : "s.") + ); + } + + @Override + public Material getMaterial(Player player) { + return healingMethod.getIconType(); + } + + @Override + public ItemStack getButtonItem(Player player) { + ItemStack item = super.getButtonItem(player); + item.setDurability(healingMethod.getIconDurability()); + return item; + } + + @Override + public int getAmount(Player player) { + return healsRemaining; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/menu/PostMatchHealthButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/menu/PostMatchHealthButton.java new file mode 100644 index 0000000..9ab6909 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/menu/PostMatchHealthButton.java @@ -0,0 +1,39 @@ +package com.elevatemc.potpvp.postmatchinv.menu; + +import com.google.common.collect.ImmutableList; +import com.elevatemc.elib.menu.Button; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.util.List; + +final class PostMatchHealthButton extends Button { + + private final double health; + + PostMatchHealthButton(double health) { + this.health = health; + } + + @Override + public String getName(Player player) { + return ChatColor.GREEN.toString() + health + "/10 ❤"; + } + + @Override + public List getDescription(Player player) { + return ImmutableList.of(); + } + + @Override + public Material getMaterial(Player player) { + return Material.SPECKLED_MELON; + } + + @Override + public int getAmount(Player player) { + return (int) Math.ceil(health); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/menu/PostMatchMenu.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/menu/PostMatchMenu.java new file mode 100644 index 0000000..324348a --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/menu/PostMatchMenu.java @@ -0,0 +1,107 @@ +package com.elevatemc.potpvp.postmatchinv.menu; + +import com.google.common.base.Preconditions; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.gamemode.HealingMethod; +import com.elevatemc.potpvp.postmatchinv.PostMatchInvHandler; +import com.elevatemc.potpvp.postmatchinv.PostMatchPlayer; +import com.elevatemc.potpvp.util.InventoryUtils; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import com.elevatemc.elib.util.UUIDUtils; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.*; + +public final class PostMatchMenu extends Menu { + + private final PostMatchPlayer target; + + public PostMatchMenu(PostMatchPlayer target) { + this.target = Preconditions.checkNotNull(target, "target"); + } + + @Override + public String getTitle(Player player) { + return "Inventory of " + UUIDUtils.name(target.getPlayerUuid()); + } + + @Override + public Map getButtons(Player player) { + Map buttons = new HashMap<>(); + int x = 0; + int y = 0; + + List targetInv = new ArrayList<>(Arrays.asList(target.getInventory())); + + // we want the hotbar (the first 9 items) to be at the bottom (end), + // not the top (start) of the list, so we rotate them. + for (int i = 0; i < 9; i++) { + targetInv.add(targetInv.remove(0)); + } + + for (ItemStack inventoryItem : targetInv) { + if (inventoryItem != null && inventoryItem.getType() != Material.AIR) { + buttons.put(getSlot(x, y), Button.fromItem(inventoryItem)); + } + + if (x++ > 7) { + x = 0; + y++; + } + } + + x = 3; // start armor backwards, helm first + + for (ItemStack armorItem : target.getArmor()) { + x--; + if (armorItem != null && armorItem.getType() != Material.AIR) { + buttons.put(getSlot(x, y), Button.fromItem(armorItem)); + } + } + + y++; // advance line for status buttons + + int position = 0; + buttons.put(getSlot(position++, y), new PostMatchHealthButton(target.getHealth())); + buttons.put(getSlot(position++, y), new PostMatchFoodLevelButton(target.getHunger())); + buttons.put(getSlot(position++, y), new PostMatchPotionEffectsButton(target.getPotionEffects())); + + HealingMethod healingMethod = target.getHealingMethodUsed(); + + if (healingMethod != null) { + int count = healingMethod.count(targetInv.toArray(new ItemStack[targetInv.size()])); + buttons.put(getSlot(position++, y), new PostMatchHealsLeftButton(target.getPlayerUuid(), healingMethod, count, target.getMissedPots())); + } + + buttons.put(getSlot(position++, y), new PostMatchStatisticsButton(target.getTotalHits(), target.getLongestCombo())); + if (target.getTotalTags() > 0) { + buttons.put(getSlot(position++, y), new PostMatchBowStatisticsButton(target.getPlayerUuid(), target.getTotalTags())); + } + // swap to other player button (for 1v1s) + PostMatchInvHandler postMatchInvHandler = PotPvPSI.getInstance().getPostMatchInvHandler(); + Collection postMatchPlayers = postMatchInvHandler.getPostMatchData(player.getUniqueId()).values(); + + if (postMatchPlayers.size() == 2) { + PostMatchPlayer otherPlayer = null; + + for (PostMatchPlayer postMatchPlayer : postMatchPlayers) { + if (!postMatchPlayer.getPlayerUuid().equals(target.getPlayerUuid())) { + otherPlayer = postMatchPlayer; + } + } + + buttons.put(getSlot(8, y), new PostMatchSwapTargetButton(otherPlayer)); + } + + return buttons; + } + + @Override + public void onClose(Player player) { + InventoryUtils.resetInventoryDelayed(player); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/menu/PostMatchPotionEffectsButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/menu/PostMatchPotionEffectsButton.java new file mode 100644 index 0000000..612894c --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/menu/PostMatchPotionEffectsButton.java @@ -0,0 +1,65 @@ +package com.elevatemc.potpvp.postmatchinv.menu; + +import com.google.common.collect.ImmutableList; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.util.TimeUtils; +import org.apache.commons.lang.StringUtils; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.List; +import java.util.stream.Collectors; + +final class PostMatchPotionEffectsButton extends Button { + + private final List effects; + + PostMatchPotionEffectsButton(List effects) { + this.effects = ImmutableList.copyOf(effects); + } + + @Override + public String getName(Player player) { + return ChatColor.GREEN + "Potion Effects"; + } + + @Override + public List getDescription(Player player) { + if (!effects.isEmpty()) { + return effects.stream() + .map(effect -> + ChatColor.AQUA + + formatEffectType(effect.getType()) + + " " + + (effect.getAmplifier() + 1) + // 0-indexed to 1-indexed + ChatColor.GRAY + + " - " + + TimeUtils.formatIntoMMSS(effect.getDuration() / 20) // / 20 to convert ticks to seconds + ) + .collect(Collectors.toList()); + } else { + return ImmutableList.of( + "", + ChatColor.GRAY + "No potion effects." + ); + } + } + + @Override + public Material getMaterial(Player player) { + return Material.POTION; + } + + private String formatEffectType(PotionEffectType type) { + switch (type.getName().toLowerCase()) { + case "fire_resistance": return "Fire Resistance"; + case "increase_damage": return "Strength"; + case "damage_resistance": return "Resistance"; + default: return StringUtils.capitalize(type.getName().toLowerCase()); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/menu/PostMatchStatisticsButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/menu/PostMatchStatisticsButton.java new file mode 100644 index 0000000..9c13268 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/menu/PostMatchStatisticsButton.java @@ -0,0 +1,41 @@ +package com.elevatemc.potpvp.postmatchinv.menu; + +import com.google.common.collect.ImmutableList; +import com.elevatemc.elib.menu.Button; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.util.List; + +final class PostMatchStatisticsButton extends Button { + + private final int totalHits; + private final int longestCombo; + + PostMatchStatisticsButton(int totalHits, int longestCombo) { + this.totalHits = totalHits; + this.longestCombo = longestCombo; + } + + @Override + public String getName(Player player) { + return ChatColor.GREEN + "Statistics"; + } + + @Override + public List getDescription(Player player) { + return ImmutableList.of(ChatColor.AQUA + "Longest Combo" + ChatColor.GRAY + " - " + longestCombo + " Hit" + (longestCombo == 1 ? "" : "s"), ChatColor.AQUA + "Total Hits" + ChatColor.GRAY + " - " + totalHits + " Hit" + (totalHits == 1 ? "" : "s")); + } + + @Override + public Material getMaterial(Player player) { + return Material.DIAMOND_SWORD; + } + + @Override + public int getAmount(Player player) { + return 1; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/menu/PostMatchSwapTargetButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/menu/PostMatchSwapTargetButton.java new file mode 100644 index 0000000..62ea69f --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/postmatchinv/menu/PostMatchSwapTargetButton.java @@ -0,0 +1,46 @@ +package com.elevatemc.potpvp.postmatchinv.menu; + +import com.google.common.base.Preconditions; +import com.elevatemc.potpvp.postmatchinv.PostMatchPlayer; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.util.UUIDUtils; +import com.google.common.collect.ImmutableList; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.List; + +final class PostMatchSwapTargetButton extends Button { + + private final PostMatchPlayer newTarget; + + PostMatchSwapTargetButton(PostMatchPlayer newTarget) { + this.newTarget = Preconditions.checkNotNull(newTarget, "newTarget"); + } + + @Override + public String getName(Player player) { + return ChatColor.GREEN + "View " + UUIDUtils.name(newTarget.getPlayerUuid()) + "'s inventory"; + } + + @Override + public List getDescription(Player player) { + return ImmutableList.of( + "", + ChatColor.YELLOW + "Swap your view to " + UUIDUtils.name(newTarget.getPlayerUuid()) + "'s inventory" + ); + } + + @Override + public Material getMaterial(Player player) { + return Material.LEVER; + } + + @Override + public void clicked(Player player, int i, ClickType clickType) { + new PostMatchMenu(newTarget).openMenu(player); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/pvpclasses/PvPClass.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/pvpclasses/PvPClass.java new file mode 100644 index 0000000..630c693 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/pvpclasses/PvPClass.java @@ -0,0 +1,128 @@ +package com.elevatemc.potpvp.pvpclasses; + +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.PotionEffectAddEvent; +import org.bukkit.event.entity.PotionEffectExpireEvent; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.Collection; +import java.util.List; +import java.util.UUID; + +public abstract class PvPClass implements Listener { + + @Getter String name; + @Getter int warmup; + @Getter String armorContains; + @Getter List consumables; + + private static final Table restores = HashBasedTable.create(); + + public PvPClass(String name, int warmup, String armorContains, List consumables) { + this.name = name; + this.warmup = warmup; + this.armorContains = armorContains; + this.consumables = consumables; + this.warmup = 5; + } + + public void apply(Player player) { + + } + + public void tick(Player player) { + + } + + public void remove(Player player) { + + } + + public boolean canApply(Player player) { + return (true); + } + + public static void removeInfiniteEffects(Player player) { + for (PotionEffect potionEffect : player.getActivePotionEffects()) { + if (potionEffect.getDuration() > 1_000_000) { + player.removePotionEffect(potionEffect.getType()); + } + } + } + + public boolean itemConsumed(Player player, Material type) { + return (true); + } + + public boolean qualifies(PlayerInventory armor) { + return (armor.getHelmet() != null && armor.getChestplate() != null && armor.getLeggings() != null && armor.getBoots() != null && + armor.getHelmet().getType().name().startsWith(armorContains) && armor.getChestplate().getType().name().startsWith(armorContains) && armor.getLeggings().getType().name().startsWith(armorContains) && armor.getBoots().getType().name().startsWith(armorContains)); + } + + public static void smartAddPotion(final Player player, PotionEffect potionEffect, boolean persistOldValues, PvPClass pvpClass) { + setRestoreEffect(player, potionEffect); + } + + @AllArgsConstructor + public static class SavedPotion { + + @Getter PotionEffect potionEffect; + @Getter long time; + @Getter private boolean perm; + + } + + public static void setRestoreEffect(Player player, PotionEffect effect) { + boolean shouldCancel = true; + Collection activeList = player.getActivePotionEffects(); + + for (PotionEffect active : activeList) { + if (!active.getType().equals(effect.getType())) continue; + + // If the current potion effect has a higher amplifier, ignore this one. + if (effect.getAmplifier() < active.getAmplifier()) { + return; + } else if (effect.getAmplifier() == active.getAmplifier()) { + // If the current potion effect has a longer duration, ignore this one. + if (0 < active.getDuration() && (effect.getDuration() <= active.getDuration() || effect.getDuration() - active.getDuration() < 10)) { + return; + } + } + restores.put(player.getUniqueId(), active.getType(), active); + shouldCancel = false; + break; + } + + // Cancel the previous restore. + player.addPotionEffect(effect, true); + if (shouldCancel && effect.getDuration() > 120 && effect.getDuration() < 9600) { + restores.remove(player.getUniqueId(), effect.getType()); + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onPotionEffectExpire(PotionEffectExpireEvent event) { + LivingEntity livingEntity = event.getEntity(); + if (livingEntity instanceof Player) { + Player player = (Player) livingEntity; + PotionEffect previous = restores.remove(player.getUniqueId(), event.getEffect().getType()); + if (previous != null && previous.getDuration() < 1_000_000) { + event.setCancelled(true); + player.addPotionEffect(previous, true); + Bukkit.getLogger().info("Restored " + previous.getType().toString() + " for " + player.getName() + ". duration: " + previous.getDuration() + ". amp: " + previous.getAmplifier()); + } + } + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/pvpclasses/PvPClassHandler.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/pvpclasses/PvPClassHandler.java new file mode 100644 index 0000000..a2cf529 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/pvpclasses/PvPClassHandler.java @@ -0,0 +1,172 @@ +package com.elevatemc.potpvp.pvpclasses; + +import com.elevatemc.potpvp.gamemode.GameModes; +import lombok.Getter; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.pvpclasses.event.BardRestoreEvent; +import com.elevatemc.potpvp.pvpclasses.pvpclasses.ArcherClass; +import com.elevatemc.potpvp.pvpclasses.pvpclasses.BardClass; +import com.elevatemc.potpvp.pvpclasses.pvpclasses.RogueClass; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.*; +import org.bukkit.potion.PotionEffect; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.*; + +@SuppressWarnings("deprecation") +public class PvPClassHandler extends BukkitRunnable implements Listener { + + @Getter private static Map equippedKits = new HashMap<>(); + @Getter private static Map savedPotions = new HashMap<>(); + @Getter List pvpClasses = new ArrayList<>(); + + public PvPClassHandler() { + pvpClasses.add(new ArcherClass()); + pvpClasses.add(new BardClass()); + pvpClasses.add(new RogueClass()); + + for (PvPClass pvpClass : pvpClasses) { + PotPvPSI.getInstance().getServer().getPluginManager().registerEvents(pvpClass, PotPvPSI.getInstance()); + } + + PotPvPSI.getInstance().getServer().getScheduler().runTaskTimer(PotPvPSI.getInstance(), this, 2L, 2L); + PotPvPSI.getInstance().getServer().getPluginManager().registerEvents(this, PotPvPSI.getInstance()); + } + + @Override + public void run() { + for (Player player : PotPvPSI.getInstance().getServer().getOnlinePlayers()) { + // Remove kit if player took off armor, otherwise .tick(); + if (equippedKits.containsKey(player.getName())) { + PvPClass equippedPvPClass = equippedKits.get(player.getName()); + if (!equippedPvPClass.qualifies(player.getInventory())) { + equippedKits.remove(player.getName()); + equippedPvPClass.remove(player); + PvPClass.removeInfiniteEffects(player); + } else if (!player.hasMetadata("frozen")) { + equippedPvPClass.tick(player); + } + } else { + Match match = PotPvPSI.getInstance().getMatchHandler().getMatchPlayingOrSpectating(player); + if (match == null) continue; + if (!match.getGameMode().equals(GameModes.TEAMFIGHT) && !match.getGameMode().equals(GameModes.TEAMFIGHT_DEBUFF)) continue; + + // Start kit warmup + for (PvPClass pvpClass : pvpClasses) { + if (pvpClass.qualifies(player.getInventory()) && pvpClass.canApply(player) && !player.hasMetadata("frozen")) { + pvpClass.apply(player); + PvPClassHandler.getEquippedKits().put(player.getName(), pvpClass); + break; + } + } + } + } + checkSavedPotions(); + } + + public void checkSavedPotions() { + Iterator> idIterator = savedPotions.entrySet().iterator(); + while (idIterator.hasNext()) { + Map.Entry id = idIterator.next(); + Player player = Bukkit.getPlayer(id.getKey()); + if (player != null && player.isOnline()) { + Bukkit.getPluginManager().callEvent(new BardRestoreEvent(player, id.getValue())); + if (id.getValue().getTime() < System.currentTimeMillis() && !id.getValue().isPerm()) { + if (player.hasPotionEffect(id.getValue().getPotionEffect().getType())) { + player.getActivePotionEffects().forEach(potion -> { + PotionEffect restore = id.getValue().getPotionEffect(); + if (potion.getType() == restore.getType() && potion.getDuration() < restore.getDuration() && potion.getAmplifier() <= restore.getAmplifier()) { + player.removePotionEffect(restore.getType()); + } + }); + } + + if (player.addPotionEffect(id.getValue().getPotionEffect(), true)) { + Bukkit.getLogger().info(id.getValue().getPotionEffect().getType() + ", " + id.getValue().getPotionEffect().getDuration() + ", " + id.getValue().getPotionEffect().getAmplifier()); + idIterator.remove(); + } + } + } else { + idIterator.remove(); + } + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerInteract(PlayerInteractEvent event) { + if (event.getPlayer().getItemInHand() == null || (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK)) { + return; + } + + for (PvPClass pvPClass : pvpClasses) { + if (hasKitOn(event.getPlayer(), pvPClass) && pvPClass.getConsumables() != null && pvPClass.getConsumables().contains(event.getPlayer().getItemInHand().getType())) { + if (pvPClass.itemConsumed(event.getPlayer(), event.getItem().getType())) { + if (event.getPlayer().getItemInHand().getAmount() > 1) { + event.getPlayer().getItemInHand().setAmount(event.getPlayer().getItemInHand().getAmount() - 1); + } else { + event.getPlayer().getInventory().remove(event.getPlayer().getItemInHand()); + //event.getPlayer().setItemInHand(new ItemStack(Material.AIR)); + } + } + } + } + } + + public static PvPClass getPvPClass(Player player) { + return equippedKits.getOrDefault(player.getName(), null); + } + + public static boolean hasKitOn(Player player, PvPClass pvpClass) { + return (equippedKits.containsKey(player.getName()) && equippedKits.get(player.getName()) == pvpClass); + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + if (equippedKits.containsKey(event.getPlayer().getName())) { + equippedKits.get(event.getPlayer().getName()).remove(event.getPlayer()); + equippedKits.remove(event.getPlayer().getName()); + } + } + + @EventHandler + public void onPlayerKick(PlayerKickEvent event) { + if (equippedKits.containsKey(event.getPlayer().getName())) { + equippedKits.get(event.getPlayer().getName()).remove(event.getPlayer()); + equippedKits.remove(event.getPlayer().getName()); + } + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + if (equippedKits.containsKey(event.getPlayer().getName())) { + equippedKits.get(event.getPlayer().getName()).remove(event.getPlayer()); + equippedKits.remove(event.getPlayer().getName()); + } + + for (PotionEffect potionEffect : event.getPlayer().getActivePotionEffects()) { + if (potionEffect.getDuration() > 1_000_000) { + event.getPlayer().removePotionEffect(potionEffect.getType()); + } + } + } + + @EventHandler + public void onPlayerDamageEvent(PlayerItemDamageEvent event) { + Player player = event.getPlayer(); + PvPClass kit = equippedKits.get(player.getName()); + if (kit != null && kit.getName().equalsIgnoreCase("bard")) { + if (Arrays.asList(player.getInventory().getArmorContents()).contains(event.getItem())) { + if (new Random().nextBoolean()) { + event.setCancelled(true); + } + } + } + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/pvpclasses/PvPClasses.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/pvpclasses/PvPClasses.java new file mode 100644 index 0000000..3bd0105 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/pvpclasses/PvPClasses.java @@ -0,0 +1,78 @@ +package com.elevatemc.potpvp.pvpclasses; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.elevatemc.potpvp.hctranked.game.RankedGameTeam; +import com.elevatemc.potpvp.tournament.Tournament; +import lombok.Getter; +import com.elevatemc.potpvp.party.Party; +import org.apache.commons.lang.StringUtils; +import org.bukkit.Material; + +public enum PvPClasses { + DIAMOND(Material.DIAMOND_CHESTPLATE, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE), + BARD(Material.GOLD_CHESTPLATE, 2, 2, 4), + ARCHER(Material.LEATHER_CHESTPLATE, 2, 2, 4), + ROGUE(Material.CHAINMAIL_CHESTPLATE, 2, 2, 4); + + @Getter private final Material icon; + @Getter private final int maxForFive; + @Getter private final int maxForTen; + @Getter private final int maxForTwenty; + + PvPClasses(Material icon, int maxForFive, int maxForTen, int maxForTwenty) { + this.icon = icon; + this.maxForFive = maxForFive; + this.maxForTen = maxForTen; + this.maxForTwenty = maxForTwenty; + } + + public boolean allowed(Party party) { + if (this.equals(PvPClasses.DIAMOND)) { + return true; + } + + int current = (int) party.getKits().values().stream().filter(pvPClasses -> pvPClasses == this).count(); + int size = party.getMembers().size(); + + Tournament tournament = PotPvPSI.getInstance().getTournamentHandler().getTournament(); + if (tournament != null && tournament.isInTournament(party)) { + if (!tournament.getType().equals(GameModes.TEAMFIGHT) || !tournament.getType().equals(GameModes.TEAMFIGHT_DEBUFF)) return false; + if (this.equals(PvPClasses.ROGUE)) { + return false; + } + if (this.equals(PvPClasses.BARD) && current < tournament.getBards()) { + return true; + } + if (this.equals(PvPClasses.ARCHER) && current < tournament.getArchers()) { + return true; + } + return false; + } else { + if (size < 10 && current >= maxForFive) { + return false; + } + + if (size < 20 && current >= maxForTen) { + return false; + } + + return current < maxForTwenty; + } + } + + public boolean allowed(RankedGameTeam team) { + if (this.equals(PvPClasses.DIAMOND)) { + return true; + } + + int current = (int) team.getKits().values().stream().filter(pvPClasses -> pvPClasses == this).count(); + + return current < 1; + } + + public String getName() { + return StringUtils.capitalize(this.name().toLowerCase()); + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/pvpclasses/event/BardRestoreEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/pvpclasses/event/BardRestoreEvent.java new file mode 100644 index 0000000..479ce90 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/pvpclasses/event/BardRestoreEvent.java @@ -0,0 +1,26 @@ +package com.elevatemc.potpvp.pvpclasses.event; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import com.elevatemc.potpvp.pvpclasses.PvPClass; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +@AllArgsConstructor +public class BardRestoreEvent extends Event { + + private static final HandlerList handlers = new HandlerList(); + + @Getter private Player player; + @Getter private PvPClass.SavedPotion potions; + + public HandlerList getHandlers() { + return (handlers); + } + + public static HandlerList getHandlerList() { + return (handlers); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/pvpclasses/pvpclasses/ArcherClass.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/pvpclasses/pvpclasses/ArcherClass.java new file mode 100644 index 0000000..f13a5c5 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/pvpclasses/pvpclasses/ArcherClass.java @@ -0,0 +1,274 @@ +package com.elevatemc.potpvp.pvpclasses.pvpclasses; + +import com.elevatemc.elib.util.Pair; +import com.elevatemc.potpvp.match.Match; +import lombok.Getter; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.pvpclasses.PvPClass; +import com.elevatemc.potpvp.pvpclasses.PvPClassHandler; +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.util.TimeUtils; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityShootBowEvent; +import org.bukkit.event.entity.PotionEffectExpireEvent; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +public class ArcherClass extends PvPClass { + + public static final int MARK_SECONDS = 6; + + private static final Map lastSpeedUsage = new HashMap<>(); + private static final Map lastJumpUsage = new HashMap<>(); + @Getter private static final Map markedPlayers = new ConcurrentHashMap<>(); + + @Getter private static Map>> markedBy = new HashMap<>(); + + private static final PotionEffect PERMANENT_SPEED_THREE = new PotionEffect(PotionEffectType.SPEED, Integer.MAX_VALUE, 2); + private static final PotionEffect PERMANENT_RESISTANCE = new PotionEffect(PotionEffectType.DAMAGE_RESISTANCE, Integer.MAX_VALUE, 1); + private static final PotionEffect SPEED_FOUR = new PotionEffect(PotionEffectType.SPEED, 20 * 10, 3); + private static final PotionEffect JUMP = new PotionEffect(PotionEffectType.JUMP, 20 * 5, 6); + + public ArcherClass() { + super("Archer", 15, "LEATHER_", Arrays.asList(Material.SUGAR, Material.FEATHER, Material.IRON_INGOT)); + } + + @Override + public void apply(Player player) { + player.addPotionEffect(PERMANENT_SPEED_THREE, true); + player.addPotionEffect(PERMANENT_RESISTANCE, true); + } + + @Override + public void tick(Player player) { + if (!this.qualifies(player.getInventory())) { super.tick(player); return; } + + if (!player.hasPotionEffect(PotionEffectType.SPEED)) { + player.addPotionEffect(PERMANENT_SPEED_THREE, true); + } + + if (!player.hasPotionEffect(PotionEffectType.DAMAGE_RESISTANCE)) { + player.addPotionEffect(PERMANENT_RESISTANCE, true); + } + super.tick(player); + } + + @EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true) + public void onEntityArrowHit(EntityDamageByEntityEvent event) { + if (event.getEntity() instanceof Player && event.getDamager() instanceof Arrow) { + Arrow arrow = (Arrow) event.getDamager(); + final Player victim = (Player) event.getEntity(); + + if (!(arrow.getShooter() instanceof Player)) { + return; + } + + Player shooter = (Player) arrow.getShooter(); + float pullback = arrow.getMetadata("Pullback").get(0).asFloat(); + if (!PvPClassHandler.hasKitOn(shooter, this)) { + return; + } + + double damage = isMarked(victim) ? 3 : 2; + + if (pullback <= 0.75F) { + damage = 1; + } + + if (victim.getHealth() - damage <= 0D) { + event.setCancelled(true); + } else { + event.setDamage(0D); + } + + // The 'ShotFromDistance' metadata is applied in the deathmessage module. + Location shotFrom = (Location) arrow.getMetadata("ShotFromDistance").get(0).value(); + double distance = shotFrom.distance(victim.getLocation()); + + victim.setHealth(Math.max(0D, victim.getHealth() - damage)); + + String arrowRangePrefix = ChatColor.YELLOW + "[" + ChatColor.DARK_AQUA + "Arrow Range" + ChatColor.YELLOW + " (" + ChatColor.RED + (int) distance + ChatColor.YELLOW + ")] "; + String heartsSuffix = ChatColor.DARK_AQUA.toString() + ChatColor.BOLD + "(" + damage / 2 + " heart" + ((damage / 2 == 1) ? "" : "s") + ")"; + + if (PvPClassHandler.hasKitOn(victim, this)) { + shooter.sendMessage(arrowRangePrefix + ChatColor.RED + "Cannot mark other Archers. " + heartsSuffix); + } else if (pullback >= 0.5F) { + shooter.sendMessage(arrowRangePrefix + ChatColor.GOLD + "Marked player for " + MARK_SECONDS + " seconds. " + heartsSuffix); + + // Only send the message if they're not already marked. + if (!isMarked(victim)) { + victim.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED.toString() + ChatColor.BOLD + "Marked! " + ChatColor.YELLOW + "An archer has shot you and marked you (+20% damage) for " + MARK_SECONDS + " seconds."); + } + + PotionEffect invis = null; + + for (PotionEffect potionEffect : victim.getActivePotionEffects()) { + if (potionEffect.getType().equals(PotionEffectType.INVISIBILITY)) { + invis = potionEffect; + break; + } + } + + if (invis != null) { + victim.removePotionEffect(invis.getType()); + } + + getMarkedPlayers().put(victim.getName(), System.currentTimeMillis() + (MARK_SECONDS * 1000)); + + getMarkedBy().putIfAbsent(shooter.getName(), new HashSet<>()); + getMarkedBy().get(shooter.getName()).add(new Pair<>(victim.getName(), System.currentTimeMillis() + (MARK_SECONDS * 1000))); + + eLib.getInstance().getNameTagHandler().reloadPlayer(victim); + + Match match = PotPvPSI.getInstance().getMatchHandler().getMatchPlaying(shooter); + if (match != null) { + match.getTotalTags().put(shooter.getUniqueId(), match.getTotalTags().getOrDefault(shooter.getUniqueId(), 0) + 1); + } + + new BukkitRunnable() { + + public void run() { + eLib.getInstance().getNameTagHandler().reloadPlayer(victim); + } + + }.runTaskLater(PotPvPSI.getInstance(), (MARK_SECONDS * 20) + 5); + } else { + shooter.sendMessage(arrowRangePrefix + ChatColor.RED + "Bow wasn't fully drawn back. " + heartsSuffix); + } + } + } + + @EventHandler + public void onEntityDamageByEntity(EntityDamageByEntityEvent event) { + if (event.getEntity() instanceof Player) { + Player player = (Player) event.getEntity(); + + if (isMarked(player)) { + Player damager = null; + if (event.getDamager() instanceof Player) { + damager = (Player) event.getDamager(); + } else if (event.getDamager() instanceof Projectile && ((Projectile) event.getDamager()).getShooter() instanceof Player) { + damager = (Player) ((Projectile) event.getDamager()).getShooter(); + } + + if (damager != null && !canUseMark(damager, player)) { + return; + } + + // Apply 120% damage if they're 'marked' + event.setDamage(event.getDamage() * 1.20D); + } + } + } + + @EventHandler + public void onEntityShootBow(EntityShootBowEvent event) { + event.getProjectile().setMetadata("ShotFromDistance", new FixedMetadataValue(PotPvPSI.getInstance(), event.getProjectile().getLocation())); + event.getProjectile().setMetadata("Pullback", new FixedMetadataValue(PotPvPSI.getInstance(), event.getForce())); + } + + @Override + public boolean itemConsumed(Player player, Material material) { + if (material == Material.SUGAR) { + if (lastSpeedUsage.containsKey(player.getName()) && lastSpeedUsage.get(player.getName()) > System.currentTimeMillis()) { + long millisLeft = lastSpeedUsage.get(player.getName()) - System.currentTimeMillis(); + String msg = TimeUtils.formatIntoDetailedString((int) millisLeft / 1000); + + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You cannot use this for another §c§l" + msg + "§c."); + return (false); + } + + lastSpeedUsage.put(player.getName(), System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(30)); + player.addPotionEffect(SPEED_FOUR, true); + return (true); + } else { + if (lastJumpUsage.containsKey(player.getName()) && lastJumpUsage.get(player.getName()) > System.currentTimeMillis()) { + long millisLeft = lastJumpUsage.get(player.getName()) - System.currentTimeMillis(); + String msg = TimeUtils.formatIntoDetailedString((int) millisLeft / 1000); + + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You cannot use this for another §c§l" + msg + "§c."); + return (false); + } + + lastJumpUsage.put(player.getName(), System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(1)); + player.addPotionEffect(JUMP, true); + return (true); + } + } + + @EventHandler + public void onPotionEffectExpire(PotionEffectExpireEvent e) { + if (e.getEntity() instanceof Player) { + Player player = (Player) e.getEntity(); + if (PvPClassHandler.hasKitOn(player, this)) { + if (e.getEffect().getType().equals(PotionEffectType.SPEED)) { + player.addPotionEffect(PERMANENT_SPEED_THREE, true); + } + if (e.getEffect().getType().equals(PotionEffectType.DAMAGE_RESISTANCE)) { + player.addPotionEffect(PERMANENT_RESISTANCE, true); + } + } + } + } + + + public static boolean isMarked(Player player) { + return (getMarkedPlayers().containsKey(player.getName()) && getMarkedPlayers().get(player.getName()) > System.currentTimeMillis()); + } + + private boolean canUseMark(Player player, Player victim) { + if (PotPvPSI.getInstance().getMatchHandler().getMatchPlaying(player) != null) { + MatchTeam team = PotPvPSI.getInstance().getMatchHandler().getMatchPlaying(player).getTeam(player.getUniqueId()); + + if (team != null) { + int amount = 0; + for (UUID memberUUID : team.getAllMembers()) { + Player member = Bukkit.getPlayer(memberUUID); + + if (member == null) continue; + if (PvPClassHandler.hasKitOn(member, this)) { + amount++; + + if (amount > 3) { + break; + } + } + } + + if (amount > 3) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Your team has too many archers. Archer mark was not applied."); + return false; + } + } + } + + if (markedBy.containsKey(player.getName())) { + for (Pair pair : markedBy.get(player.getName())) { + if (victim.getName().equals(pair.getKey()) && pair.getValue() > System.currentTimeMillis()) { + return false; + } + } + + return true; + } else { + return true; + } + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/pvpclasses/pvpclasses/BardClass.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/pvpclasses/pvpclasses/BardClass.java new file mode 100644 index 0000000..8c949f2 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/pvpclasses/pvpclasses/BardClass.java @@ -0,0 +1,287 @@ +package com.elevatemc.potpvp.pvpclasses.pvpclasses; + +import com.google.common.collect.ImmutableSet; +import lombok.Getter; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.pvpclasses.PvPClass; +import com.elevatemc.potpvp.pvpclasses.PvPClassHandler; +import com.elevatemc.potpvp.pvpclasses.pvpclasses.bard.BardEffect; +import org.bukkit.ChatColor; +import org.bukkit.GameMode; +import org.bukkit.Material; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public class BardClass extends PvPClass implements Listener { + + /* + Things commented with // CUSTOM + are the 'unique' abilities, or things that have custom behaviour not seen by most other effects. + An example is invis, whose passive cannot be used while its click is on cooldown. + This is therefore commented with // CUSTOM + */ + + public final Map BARD_CLICK_EFFECTS = new HashMap<>(); + public final Map BARD_PASSIVE_EFFECTS = new HashMap<>(); + + @Getter private static Map lastEffectUsage = new ConcurrentHashMap<>(); + @Getter private static Map energy = new ConcurrentHashMap<>(); + private static final Set DEBUFFS = ImmutableSet.of(PotionEffectType.POISON, PotionEffectType.SLOW, PotionEffectType.WEAKNESS, PotionEffectType.HARM, PotionEffectType.WITHER); + + public static final int BARD_RANGE = 30; + public static final int EFFECT_COOLDOWN = 10 * 1000; + public static final int MAX_ENERGY = 100; + public static final int ENERGY_REGEN_PER_SECOND = 1; + + public BardClass() { + super("Bard", 15, "GOLD_", null); + + // Click buffs + BARD_CLICK_EFFECTS.put(Material.BLAZE_POWDER, BardEffect.fromPotionAndEnergyAndName(new PotionEffect(PotionEffectType.INCREASE_DAMAGE, 20 * 5, 1), 45, ChatColor.RED + "Strength II")); + BARD_CLICK_EFFECTS.put(Material.SUGAR, BardEffect.fromPotionAndEnergyAndName(new PotionEffect(PotionEffectType.SPEED, 20 * 6, 2), 20, ChatColor.AQUA + "Speed III")); + BARD_CLICK_EFFECTS.put(Material.FEATHER, BardEffect.fromPotionAndEnergyAndName(new PotionEffect(PotionEffectType.JUMP, 20 * 5, 6), 25, ChatColor.WHITE + "Jump Boost VII")); + BARD_CLICK_EFFECTS.put(Material.IRON_INGOT, BardEffect.fromPotionAndEnergyAndName(new PotionEffect(PotionEffectType.DAMAGE_RESISTANCE, 20 * 5, 2), 40, ChatColor.BLUE + "Resistance III")); + BARD_CLICK_EFFECTS.put(Material.GHAST_TEAR, BardEffect.fromPotionAndEnergyAndName(new PotionEffect(PotionEffectType.REGENERATION, 20 * 5, 2), 40, ChatColor.LIGHT_PURPLE + "Regeneration III")); + BARD_CLICK_EFFECTS.put(Material.MAGMA_CREAM, BardEffect.fromPotionAndEnergyAndName(new PotionEffect(PotionEffectType.FIRE_RESISTANCE, 20 * 45, 0), 40, ChatColor.GOLD + "Fire Resistance I")); + BARD_CLICK_EFFECTS.put(Material.WHEAT, BardEffect.fromEnergyAndName(25, ChatColor.AQUA + "Food")); + + // Click debuffs + BARD_CLICK_EFFECTS.put(Material.SPIDER_EYE, BardEffect.fromPotionAndEnergyAndName(new PotionEffect(PotionEffectType.WITHER, 20 * 5, 1), 35, ChatColor.GRAY + "Wither II")); + + // Passive buffs + BARD_PASSIVE_EFFECTS.put(Material.BLAZE_POWDER, BardEffect.fromPotion(new PotionEffect(PotionEffectType.INCREASE_DAMAGE, 20 * 6, 0))); + BARD_PASSIVE_EFFECTS.put(Material.SUGAR, BardEffect.fromPotion(new PotionEffect(PotionEffectType.SPEED, 20 * 6, 1))); + BARD_PASSIVE_EFFECTS.put(Material.FEATHER, BardEffect.fromPotion(new PotionEffect(PotionEffectType.JUMP, 20 * 6, 1))); + BARD_PASSIVE_EFFECTS.put(Material.IRON_INGOT, BardEffect.fromPotion(new PotionEffect(PotionEffectType.DAMAGE_RESISTANCE, 20 * 6, 0))); + BARD_PASSIVE_EFFECTS.put(Material.GHAST_TEAR, BardEffect.fromPotion(new PotionEffect(PotionEffectType.REGENERATION, 20 * 6, 0))); + BARD_PASSIVE_EFFECTS.put(Material.MAGMA_CREAM, BardEffect.fromPotion(new PotionEffect(PotionEffectType.FIRE_RESISTANCE, 20 * 6, 0))); + + + new BukkitRunnable() { + + public void run() { + for (Player player : PotPvPSI.getInstance().getServer().getOnlinePlayers()) { + if (!PvPClassHandler.hasKitOn(player, BardClass.this)) { + continue; + } + + if (energy.containsKey(player.getName())) { + if (energy.get(player.getName()) == MAX_ENERGY) { + continue; + } + + energy.put(player.getName(), Math.min(MAX_ENERGY, energy.get(player.getName()) + ENERGY_REGEN_PER_SECOND)); + } else { + energy.put(player.getName(), 0); + } + + int manaInt = energy.get(player.getName()).intValue(); + + if (manaInt % 10 == 0) { + player.sendMessage(ChatColor.AQUA + "Bard Energy: " + ChatColor.GREEN + manaInt); + } + } + } + + }.runTaskTimer(PotPvPSI.getInstance(), 15L, 20L); + } + + @Override + public void apply(Player player) { + player.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, Integer.MAX_VALUE, 1)); + player.addPotionEffect(new PotionEffect(PotionEffectType.DAMAGE_RESISTANCE, Integer.MAX_VALUE, 1), true); + player.addPotionEffect(new PotionEffect(PotionEffectType.REGENERATION, Integer.MAX_VALUE, 0), true); + } + + @Override + public void tick(Player player) { + if (!(this.qualifies(player.getInventory()))) { + super.tick(player); + return; + } + + if (!player.hasPotionEffect(PotionEffectType.SPEED)) { + player.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, Integer.MAX_VALUE, 1)); + } + + if (!player.hasPotionEffect(PotionEffectType.DAMAGE_RESISTANCE)) { + player.addPotionEffect(new PotionEffect(PotionEffectType.DAMAGE_RESISTANCE, Integer.MAX_VALUE, 1)); + } + + if (!player.hasPotionEffect(PotionEffectType.REGENERATION)) { + player.addPotionEffect(new PotionEffect(PotionEffectType.REGENERATION, Integer.MAX_VALUE, 0)); + } + + if (player.getItemInHand() != null && BARD_PASSIVE_EFFECTS.containsKey(player.getItemInHand().getType())) { + // CUSTOM + if (player.getItemInHand().getType() == Material.FERMENTED_SPIDER_EYE && getLastEffectUsage().containsKey(player.getName()) && getLastEffectUsage().get(player.getName()) > System.currentTimeMillis()) { + return; + } + + giveBardEffect(player, BARD_PASSIVE_EFFECTS.get(player.getItemInHand().getType()), true, false); + } + super.tick(player); + } + + + @Override + public void remove(Player player) { + energy.remove(player.getName()); + + for (BardEffect bardEffect : BARD_CLICK_EFFECTS.values()) { + bardEffect.getLastMessageSent().remove(player.getName()); + } + + for (BardEffect bardEffect : BARD_CLICK_EFFECTS.values()) { + bardEffect.getLastMessageSent().remove(player.getName()); + } + } + + @EventHandler + public void onPlayerInteract(PlayerInteractEvent event) { + if (!event.getAction().name().contains("RIGHT_") || !event.hasItem() || !BARD_CLICK_EFFECTS.containsKey(event.getItem().getType()) || !PvPClassHandler.hasKitOn(event.getPlayer(), this) || !energy.containsKey(event.getPlayer().getName())) { + return; + } + + if (getLastEffectUsage().containsKey(event.getPlayer().getName()) && getLastEffectUsage().get(event.getPlayer().getName()) > System.currentTimeMillis() && event.getPlayer().getGameMode() != GameMode.CREATIVE) { + long millisLeft = getLastEffectUsage().get(event.getPlayer().getName()) - System.currentTimeMillis(); + + double value = (millisLeft / 1000D); + double sec = Math.round(10.0 * value) / 10.0; + + event.getPlayer().sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You cannot use this for another " + ChatColor.BOLD + sec + ChatColor.RED + " seconds!"); + return; + } + + BardEffect bardEffect = BARD_CLICK_EFFECTS.get(event.getItem().getType()); + + if (bardEffect.getEnergy() > energy.get(event.getPlayer().getName())) { + event.getPlayer().sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You do not have enough energy for this! You need " + bardEffect.getEnergy() + " energy, but you only have " + energy.get(event.getPlayer().getName()).intValue()); + return; + } + + energy.put(event.getPlayer().getName(), energy.get(event.getPlayer().getName()) - bardEffect.getEnergy()); + + boolean negative = bardEffect.getPotionEffect() != null && DEBUFFS.contains(bardEffect.getPotionEffect().getType()); + + getLastEffectUsage().put(event.getPlayer().getName(), System.currentTimeMillis() + EFFECT_COOLDOWN); + giveBardEffect(event.getPlayer(), bardEffect, !negative, true); + int appliedTo = giveBardEffect(event.getPlayer(), bardEffect, !negative, true); + + if (negative) { + appliedTo--; + event.getPlayer().sendMessage(ChatColor.DARK_AQUA + "☘ " + ChatColor.AQUA + "You have given " + bardEffect.getName() + ChatColor.AQUA + " to " + ChatColor.DARK_AQUA + appliedTo + ChatColor.AQUA + " enemies."); + } else { + if (appliedTo > 1) { + if (bardEffect.getPotionEffect() != null && !bardEffect.getPotionEffect().getType().equals(PotionEffectType.INCREASE_DAMAGE)) { + // Remove the bard from the amount of people the effect has been given to. Ignores strength since the bard wouldnt have gotten added to it anyways. + appliedTo--; + } + if (appliedTo > 1) { + event.getPlayer().sendMessage(ChatColor.DARK_AQUA + "☘ " + ChatColor.AQUA + "You have given " + bardEffect.getName() + ChatColor.AQUA + " to " + ChatColor.DARK_AQUA + appliedTo + ChatColor.AQUA + " teammates."); + } else { + event.getPlayer().sendMessage(ChatColor.DARK_AQUA + "☘ " + ChatColor.AQUA + "You have given " + bardEffect.getName() + ChatColor.AQUA + " to " + ChatColor.DARK_AQUA + appliedTo + ChatColor.AQUA + " teammate."); + } + + } else { + if (appliedTo == 1) { + event.getPlayer().sendMessage(ChatColor.DARK_AQUA + "☘ " + ChatColor.AQUA + "You have given " + bardEffect.getName() + ChatColor.AQUA + " to yourself."); + } else { + event.getPlayer().sendMessage(ChatColor.DARK_AQUA + "☘ " + ChatColor.AQUA + "You have given " + bardEffect.getName() + ChatColor.AQUA + " to no one."); + } + + } + } + + if (event.getPlayer().getItemInHand().getAmount() == 1) { + event.getPlayer().setItemInHand(new ItemStack(Material.AIR)); + event.getPlayer().updateInventory(); + } else { + event.getPlayer().getItemInHand().setAmount(event.getPlayer().getItemInHand().getAmount() - 1); + } + } + + public int giveBardEffect(Player source, BardEffect bardEffect, boolean friendly, boolean persistOldValues) { + int appliedTo = 0; + for (Player player : getNearbyPlayers(source, friendly)) { + // CUSTOM + // Bards can't get Strength. + // Yes, that does need to use .equals. PotionEffectType is NOT an enum. + if (PvPClassHandler.hasKitOn(player, this) && bardEffect.getPotionEffect() != null && bardEffect.getPotionEffect().getType().equals(PotionEffectType.INCREASE_DAMAGE)) { + continue; + } + + appliedTo++; + if (bardEffect.getPotionEffect() != null) { + smartAddPotion(player, bardEffect.getPotionEffect(), persistOldValues, this); + } else { + Material material = source.getItemInHand().getType(); + giveCustomBardEffect(player, material); + } + } + return appliedTo; + } + + public void giveCustomBardEffect(Player player, Material material) { + switch (material) { + case WHEAT: + for (Player nearbyPlayer : getNearbyPlayers(player, true)) { + nearbyPlayer.setFoodLevel(20); + nearbyPlayer.setSaturation(10F); + } + + break; + case FERMENTED_SPIDER_EYE: + + + break; + default: + PotPvPSI.getInstance().getLogger().warning("No custom Bard effect defined for " + material + "."); + } + } + + public List getNearbyPlayers(Player player, boolean friendly) { + List valid = new ArrayList<>(); + Match match = PotPvPSI.getInstance().getMatchHandler().getMatchPlaying(player); + MatchTeam sourceTeam = match.getTeam(player.getUniqueId()); + + // We divide by 2 so that the range isn't as much on the Y level (and can't be abused by standing on top of / under events) + for (Entity entity : player.getNearbyEntities(BARD_RANGE, BARD_RANGE / 2, BARD_RANGE)) { + if (entity instanceof Player) { + Player nearbyPlayer = (Player) entity; + + if (sourceTeam == null) { + if (!friendly) { + valid.add(nearbyPlayer); + } + + continue; + } + + boolean isFriendly = sourceTeam.getAliveMembers().contains(nearbyPlayer.getUniqueId()); + + if (friendly && isFriendly) { + valid.add(nearbyPlayer); + } else if (!friendly && !isFriendly) { // the isAlly is here so you can't give your allies negative effects, but so you also can't give them positive effects. + valid.add(nearbyPlayer); + } + } + } + + valid.add(player); + return (valid); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/pvpclasses/pvpclasses/RogueClass.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/pvpclasses/pvpclasses/RogueClass.java new file mode 100644 index 0000000..39209b9 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/pvpclasses/pvpclasses/RogueClass.java @@ -0,0 +1,161 @@ +package com.elevatemc.potpvp.pvpclasses.pvpclasses; + +import com.elevatemc.potpvp.pvpclasses.PvPClass; +import com.elevatemc.potpvp.pvpclasses.PvPClassHandler; +import com.elevatemc.elib.util.TimeUtils; +import org.bukkit.*; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.PotionEffectExpireEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.util.Vector; + +import java.util.*; + +public class RogueClass extends PvPClass { + + private static Map lastSpeedUsage = new HashMap<>(); + private static Map lastJumpUsage = new HashMap<>(); + private static Map backstabCooldown = new HashMap<>(); + + private static PotionEffect PERMANENT_SPEED_THREE = new PotionEffect(PotionEffectType.SPEED, Integer.MAX_VALUE, 2); + private static PotionEffect PERMANENT_JUMP = new PotionEffect(PotionEffectType.JUMP, Integer.MAX_VALUE, 1); + private static PotionEffect PERMANENT_RESISTANCE = new PotionEffect(PotionEffectType.DAMAGE_RESISTANCE, Integer.MAX_VALUE, 0); + private static PotionEffect SPEED_FIVE = new PotionEffect(PotionEffectType.SPEED, 200, 4); + private static PotionEffect JUMP = new PotionEffect(PotionEffectType.JUMP, 200, 6); + private static PotionEffect SLOW = new PotionEffect(PotionEffectType.SLOW, 2 * 20, 2); + + public RogueClass() { + super("Rogue", 15, "CHAINMAIL_", Arrays.asList(Material.SUGAR, Material.FEATHER)); + } + @Override + public void apply(Player player) { + player.addPotionEffect(PERMANENT_SPEED_THREE, true); + player.addPotionEffect(PERMANENT_JUMP, true); + player.addPotionEffect(PERMANENT_RESISTANCE, true); + } + + @Override + public void tick(Player player) { + if (!(this.qualifies(player.getInventory()))) { + super.tick(player); + return; + } + + if (!player.hasPotionEffect(PotionEffectType.SPEED)) { + player.addPotionEffect(PERMANENT_SPEED_THREE); + } + + if (!player.hasPotionEffect(PotionEffectType.JUMP)) { + player.addPotionEffect(PERMANENT_JUMP); + } + + if (!player.hasPotionEffect(PotionEffectType.DAMAGE_RESISTANCE)) { + player.addPotionEffect(PERMANENT_RESISTANCE); + } + } + + @Override + public void remove(Player player) { + removeInfiniteEffects(player); + } + + @Override + public boolean itemConsumed(Player player, Material material) { + if (material == Material.SUGAR) { + if (lastSpeedUsage.containsKey(player.getName()) && lastSpeedUsage.get(player.getName()) > System.currentTimeMillis()) { + long millisLeft = ((lastSpeedUsage.get(player.getName()) - System.currentTimeMillis()) / 1000L) * 1000L; + String msg = TimeUtils.formatIntoDetailedString((int) (millisLeft / 1000)); + + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You cannot use this for another §c§l" + msg + "§c."); + return (false); + } + + lastSpeedUsage.put(player.getName(), System.currentTimeMillis() + (1000L * 60 * 2)); + player.addPotionEffect(SPEED_FIVE, true); + + } else { + if (lastJumpUsage.containsKey(player.getName()) && lastJumpUsage.get(player.getName()) > System.currentTimeMillis()) { + long millisLeft = ((lastJumpUsage.get(player.getName()) - System.currentTimeMillis()) / 1000L) * 1000L; + String msg = TimeUtils.formatIntoDetailedString((int) (millisLeft / 1000)); + + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You cannot use this for another §c§l" + msg + "§c."); + return (false); + } + + lastJumpUsage.put(player.getName(), System.currentTimeMillis() + (1000L * 60 * 2)); + player.addPotionEffect(JUMP, true); + } + + return (true); + } + + @EventHandler + public void onPotionEffectExpire(PotionEffectExpireEvent e) { + if (e.getEntity() instanceof Player) { + Player player = (Player) e.getEntity(); + if (PvPClassHandler.hasKitOn(player, this)) { + if (e.getEffect().getType().equals(PotionEffectType.SPEED)) { + player.addPotionEffect(PERMANENT_SPEED_THREE, true); + } + if (e.getEffect().getType().equals(PotionEffectType.JUMP)) { + player.addPotionEffect(PERMANENT_JUMP, true); + } + if (e.getEffect().getType().equals(PotionEffectType.DAMAGE_RESISTANCE)) { + player.addPotionEffect(PERMANENT_RESISTANCE, true); + } + } + } + } + + @EventHandler(priority=EventPriority.MONITOR) + public void onEntityArrowHit(EntityDamageByEntityEvent event) { + if (event.isCancelled()) { + return; + } + + if (event.getDamager() instanceof Player && event.getEntity() instanceof Player) { + Player damager = (Player) event.getDamager(); + Player victim = (Player) event.getEntity(); + + if (damager.getItemInHand() != null && damager.getItemInHand().getType() == Material.GOLD_SWORD && PvPClassHandler.hasKitOn(damager, this)) { + if (backstabCooldown.containsKey(damager.getName()) && backstabCooldown.get(damager.getName()) > System.currentTimeMillis()) { + return; + } + + backstabCooldown.put(damager.getName(), System.currentTimeMillis() + 1500L); + + org.bukkit.util.Vector playerVector = damager.getLocation().getDirection(); + Vector entityVector = victim.getLocation().getDirection(); + + playerVector.setY(0F); + entityVector.setY(0F); + + double degrees = playerVector.angle(entityVector); + + if (Math.abs(degrees) < 1.4) { + damager.setItemInHand(new ItemStack(Material.AIR)); + + damager.playSound(damager.getLocation(), Sound.ITEM_BREAK, 1F, 1F); + damager.getWorld().playEffect(victim.getEyeLocation(), Effect.STEP_SOUND, Material.REDSTONE_BLOCK); + + if (victim.getHealth() - 7D <= 0) { + event.setCancelled(true); + } else { + event.setDamage(0D); + } + + victim.setHealth(Math.max(0D, victim.getHealth() - 7D)); + + damager.addPotionEffect(SLOW); + } else { + damager.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Backstab failed!"); + } + } + } + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/pvpclasses/pvpclasses/bard/BardEffect.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/pvpclasses/pvpclasses/bard/BardEffect.java new file mode 100644 index 0000000..63bee61 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/pvpclasses/pvpclasses/bard/BardEffect.java @@ -0,0 +1,44 @@ +package com.elevatemc.potpvp.pvpclasses.pvpclasses.bard; + +import lombok.Getter; +import org.bukkit.potion.PotionEffect; + +import java.util.HashMap; +import java.util.Map; + +public class BardEffect { + + @Getter private final PotionEffect potionEffect; + @Getter private final int energy; + @Getter private final String name; + + // For the message we send when you select the (de)buff in your hotbar. + @Getter private final Map lastMessageSent = new HashMap<>(); + + private BardEffect(PotionEffect potionEffect, int energy, String name) { + this.potionEffect = potionEffect; + this.energy = energy; + this.name = name; + } + + public static BardEffect fromPotion(PotionEffect potionEffect) { + return (new BardEffect(potionEffect, -1, "")); + } + + public static BardEffect fromPotionAndEnergy(PotionEffect potionEffect, int energy) { + return (new BardEffect(potionEffect, energy, "")); + } + + public static BardEffect fromPotionAndEnergyAndName(PotionEffect potionEffect, int energy, String name) { + return (new BardEffect(potionEffect, energy, name)); + } + + public static BardEffect fromEnergy(int energy) { + return (new BardEffect(null, energy, "")); + } + + public static BardEffect fromEnergyAndName(int energy, String name) { + return (new BardEffect(null, energy, name)); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/queue/MatchQueue.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/queue/MatchQueue.java new file mode 100644 index 0000000..edcbb33 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/queue/MatchQueue.java @@ -0,0 +1,140 @@ +package com.elevatemc.potpvp.queue; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import lombok.Getter; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.elo.EloHandler; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.util.PatchedPlayerUtils; +import org.bukkit.ChatColor; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +public final class MatchQueue { + + @Getter private final GameMode gameMode; + @Getter private final boolean competitive; + private final List entries = new CopyOnWriteArrayList<>(); + + MatchQueue(GameMode gameMode, boolean competitive) { + this.gameMode = Preconditions.checkNotNull(gameMode, "gameMode"); + this.competitive = competitive; + } + + void tick() { + // we clone so we can remove entries from our working set + // (sometimes matches fail to create [ex no maps open] and + // we should retry) + List entriesCopy = new ArrayList<>(entries); + EloHandler eloHandler = PotPvPSI.getInstance().getEloHandler(); + + // ranked match algorithm requires entries are in + // order by elo. There's no reason we only do this for ranked + // matches aside from performance + if (competitive) { + entriesCopy.sort(Comparator.comparing(e -> eloHandler.getElo(e.getMembers(), gameMode))); + } + + while (entriesCopy.size() >= 2) { + // remove from 0 both times because index shifts down + MatchQueueEntry a = entriesCopy.remove(0); + MatchQueueEntry b = entriesCopy.remove(0); + + // the algorithm for ranked and unranked queues is actually very similar, + // except for the fact ranked matches can't be made if the elo window for + // both players don't overlap + if (competitive) { + int aElo = eloHandler.getElo(a.getMembers(), gameMode); + int bElo = eloHandler.getElo(b.getMembers(), gameMode); + + int aEloWindow = a.getWaitSeconds() * QueueHandler.RANKED_WINDOW_GROWTH_PER_SECOND; + int bEloWindow = b.getWaitSeconds() * QueueHandler.RANKED_WINDOW_GROWTH_PER_SECOND; + + if (Math.abs(aElo - bElo) > Math.max(aEloWindow, bEloWindow)) { + continue; + } + } + + createMatchAndRemoveEntries(a, b); + } + } + + public int countPlayersQueued() { + int count = 0; + + for (MatchQueueEntry entry : entries) { + count += entry.getMembers().size(); + } + + return count; + } + + void addToQueue(MatchQueueEntry entry) { + entries.add(entry); + } + + void removeFromQueue(MatchQueueEntry entry) { + entries.remove(entry); + } + + private void createMatchAndRemoveEntries(MatchQueueEntry entryA, MatchQueueEntry entryB) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + QueueHandler queueHandler = PotPvPSI.getInstance().getQueueHandler(); + + MatchTeam teamA = new MatchTeam(entryA.getMembers()); + MatchTeam teamB = new MatchTeam(entryB.getMembers()); + + Match match = matchHandler.startMatch( + ImmutableList.of(teamA, teamB), + gameMode, + null, + competitive, + !competitive // allowRematches is the inverse of ranked + ); + + // only remove entries if match creation was successfull + if (match != null) { + queueHandler.removeFromQueueCache(entryA); + queueHandler.removeFromQueueCache(entryB); + + String teamAElo = ""; + String teamBElo = ""; + + if (competitive) { + EloHandler eloHandler = PotPvPSI.getInstance().getEloHandler(); + + teamAElo = " (" + eloHandler.getElo(teamA.getAliveMembers(), gameMode) + " Elo)"; + teamBElo = " (" + eloHandler.getElo(teamB.getAliveMembers(), gameMode) + " Elo)"; + } + + + String foundStart = ChatColor.DARK_AQUA.toString() + ChatColor.BOLD + (competitive ? "Competitive" : "Casual") + " Match"; + String ladder = "➥ Ladder: " + ChatColor.DARK_AQUA + gameMode.getName(); + String opponentStart = "➥ Opponent: " + ChatColor.DARK_AQUA; + String map = "➥ Map: " + ChatColor.DARK_AQUA + PotPvPSI.getInstance().getArenaHandler().getSchematic(match.getArena().getSchematic()).getDisplayName(); + teamA.messageAlive(""); + teamB.messageAlive(""); + teamA.messageAlive(foundStart); + teamB.messageAlive(foundStart); + teamA.messageAlive(ladder); + teamB.messageAlive(ladder); + teamA.messageAlive(opponentStart + Joiner.on(", ").join(PatchedPlayerUtils.mapToNames(teamB.getAllMembers())) + teamBElo); + teamB.messageAlive(opponentStart + Joiner.on(", ").join(PatchedPlayerUtils.mapToNames(teamA.getAllMembers())) + teamAElo); + teamA.messageAlive(map); + teamB.messageAlive(map); + teamA.messageAlive(""); + teamB.messageAlive(""); + entries.remove(entryA); + entries.remove(entryB); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/queue/MatchQueueEntry.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/queue/MatchQueueEntry.java new file mode 100644 index 0000000..f6645f6 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/queue/MatchQueueEntry.java @@ -0,0 +1,37 @@ +package com.elevatemc.potpvp.queue; + +import lombok.Getter; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Set; +import java.util.UUID; + +public abstract class MatchQueueEntry { + + /** + * {@link MatchQueue} this MatchQueueEntry is entered in + */ + @Getter private final MatchQueue queue; + + /** + * Time this MatchQueueEntry joined its {@link MatchQueue} + */ + @Getter private final Instant timeJoined; + + MatchQueueEntry(MatchQueue queue) { + this.queue = queue; + this.timeJoined = Instant.now(); + } + + public abstract Set getMembers(); + + /** + * Gets how long, in seconds, this MatchQueueEntry has been waiting in a queue + * @return the duration, in seconds, this entry has been waiting in a queue + */ + public int getWaitSeconds() { + return (int) ChronoUnit.SECONDS.between(timeJoined, Instant.now()); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/queue/PartyMatchQueueEntry.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/queue/PartyMatchQueueEntry.java new file mode 100644 index 0000000..0ff7509 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/queue/PartyMatchQueueEntry.java @@ -0,0 +1,29 @@ +package com.elevatemc.potpvp.queue; + +import com.google.common.base.Preconditions; +import lombok.Getter; +import com.elevatemc.potpvp.party.Party; + +import java.util.Set; +import java.util.UUID; + +/** + * Represents a {@link com.elevatemc.potpvp.party.Party} waiting + * in a {@link MatchQueue} + */ +public final class PartyMatchQueueEntry extends MatchQueueEntry { + + @Getter private final Party party; + + PartyMatchQueueEntry(MatchQueue queue, Party party) { + super(queue); + + this.party = Preconditions.checkNotNull(party, "party"); + } + + @Override + public Set getMembers() { + return party.getMembers(); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/queue/QueueHandler.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/queue/QueueHandler.java new file mode 100644 index 0000000..a1ce746 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/queue/QueueHandler.java @@ -0,0 +1,208 @@ +package com.elevatemc.potpvp.queue; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Table; +import lombok.Getter; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.potpvp.queue.listener.QueueGeneralListener; +import com.elevatemc.potpvp.queue.listener.QueueItemListener; +import com.elevatemc.potpvp.util.InventoryUtils; +import com.elevatemc.potpvp.validation.PotPvPValidation; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public final class QueueHandler { + + public static final int RANKED_WINDOW_GROWTH_PER_SECOND = 5; + + private static final String JOIN_SOLO_MESSAGE = ChatColor.DARK_AQUA + "⚔ " + ChatColor.AQUA + "You joined the " + ChatColor.DARK_AQUA + "%s %s" + ChatColor.AQUA + " queue" + "."; + private static final String LEAVE_SOLO_MESSAGE = ChatColor.DARK_AQUA + "⚠ " + ChatColor.AQUA + "You left the " + ChatColor.DARK_AQUA + "%s %s" + ChatColor.AQUA + " queue" + "."; + private static final String WAIT_SOLO_MESSAGE = ChatColor.DARK_GRAY + " ⌛" + ChatColor.GRAY + " Please wait while we find an opponent…"; + private static final String JOIN_PARTY_MESSAGE = ChatColor.DARK_AQUA + "⚔ " + ChatColor.AQUA + "Your party has queued for " + ChatColor.DARK_AQUA + "%s %s" + ChatColor.AQUA + "."; + private static final String LEAVE_PARTY_MESSAGE = ChatColor.DARK_AQUA + "⚠ " + ChatColor.AQUA + "Your party is no longer queued for " + ChatColor.DARK_AQUA + "%s %s" + ChatColor.AQUA + "."; + + private static final String WAIT_PARTY_MESSAGE = ChatColor.DARK_GRAY + " ⌛" + ChatColor.GRAY + " Please wait while we find an opponents…"; + + + // we never call .put outside of the constructor so no concurrency is needed + // (GameMode type, boolean ranked) -> MatchQueue + private final Table soloQueues = HashBasedTable.create(); + private final Table partyQueues = HashBasedTable.create(); + + // maps players (and parties) to their entry for fast O(1) lookup + private final Map soloQueueCache = new ConcurrentHashMap<>(); + private final Map partyQueueCache = new ConcurrentHashMap<>(); + + // because this is called very often (it's on the lobby scoreboard) + // we cache every second (per GameMode counts aren't cached, however) + @Getter private int queuedCount = 0; + + public QueueHandler() { + Bukkit.getPluginManager().registerEvents(new QueueGeneralListener(this), PotPvPSI.getInstance()); + Bukkit.getPluginManager().registerEvents(new QueueItemListener(this), PotPvPSI.getInstance()); + + Bukkit.getScheduler().runTaskTimer(PotPvPSI.getInstance(), () -> { + soloQueues.values().forEach(MatchQueue::tick); + partyQueues.values().forEach(MatchQueue::tick); + + int i = 0; + + for (MatchQueue queue : soloQueues.values()) { + i += queue.countPlayersQueued(); + } + + for (MatchQueue queue : partyQueues.values()) { + i += queue.countPlayersQueued(); + } + + queuedCount = i; + }, 20L, 20L); + } + + public void addQueues(GameMode gameMode) { + soloQueues.put(gameMode, true, new MatchQueue(gameMode, true)); + soloQueues.put(gameMode, false, new MatchQueue(gameMode, false)); + + partyQueues.put(gameMode, true, new MatchQueue(gameMode, true)); + partyQueues.put(gameMode, false, new MatchQueue(gameMode, false)); + } + + public void removeQueues(GameMode gameMode) { + soloQueues.remove(gameMode, true); + soloQueues.remove(gameMode, false); + + partyQueues.remove(gameMode, true); + partyQueues.remove(gameMode, false); + } + + public int countPlayersQueued(GameMode gameMode, boolean ranked) { + return soloQueues.get(gameMode, ranked).countPlayersQueued() + + partyQueues.get(gameMode, ranked).countPlayersQueued(); + } + + public boolean joinQueue(Player player, GameMode gameMode, boolean competitive) { + if (!PotPvPValidation.canJoinQueue(player)) { + return false; + } + + MatchQueue queue = soloQueues.get(gameMode, competitive); + SoloMatchQueueEntry entry = new SoloMatchQueueEntry(queue, player.getUniqueId()); + + queue.addToQueue(entry); + soloQueueCache.put(player.getUniqueId(), entry); + + player.sendMessage(String.format(JOIN_SOLO_MESSAGE, competitive ? "Competitive" : "Casual", gameMode.getName())); + player.sendMessage(String.format(WAIT_SOLO_MESSAGE)); + InventoryUtils.resetInventoryDelayed(player); + return true; + } + + public boolean leaveQueue(Player player, boolean silent) { + MatchQueueEntry entry = getQueueEntry(player.getUniqueId()); + + if (entry == null) { + return false; + } + + MatchQueue queue = entry.getQueue(); + + queue.removeFromQueue(entry); + soloQueueCache.remove(player.getUniqueId()); + + if (!silent) { + player.sendMessage(String.format(LEAVE_SOLO_MESSAGE, queue.isCompetitive() ? "Competitive" : "Casual", queue.getGameMode().getName())); + } + + InventoryUtils.resetInventoryDelayed(player); + return true; + } + + public boolean joinQueue(Party party, GameMode gameMode, boolean ranked) { + if (!PotPvPValidation.canJoinQueue(party)) { + return false; + } + + MatchQueue queue = partyQueues.get(gameMode, ranked); + PartyMatchQueueEntry entry = new PartyMatchQueueEntry(queue, party); + + queue.addToQueue(entry); + partyQueueCache.put(party, entry); + + party.message(String.format(JOIN_PARTY_MESSAGE, ranked ? "Competitive" : "Casual", gameMode.getName())); + party.resetInventoriesDelayed(); + return true; + } + + public boolean leaveQueue(Party party, boolean silent) { + MatchQueueEntry entry = getQueueEntry(party); + + if (entry == null) { + return false; + } + + MatchQueue queue = entry.getQueue(); + + queue.removeFromQueue(entry); + partyQueueCache.remove(party); + + if (!silent) { + party.message(String.format(LEAVE_PARTY_MESSAGE, queue.isCompetitive() ? "Competitive" : "Casual", queue.getGameMode().getName())); + } + + party.resetInventoriesDelayed(); + return true; + } + + public boolean isQueued(UUID player) { + return soloQueueCache.containsKey(player); + } + + public boolean isQueuedRanked(UUID player) { + SoloMatchQueueEntry entry = getQueueEntry(player); + return entry != null && entry.getQueue().isCompetitive(); + } + + public boolean isQueuedUnranked(UUID player) { + SoloMatchQueueEntry entry = getQueueEntry(player); + return entry != null && !entry.getQueue().isCompetitive(); + } + + public SoloMatchQueueEntry getQueueEntry(UUID player) { + return soloQueueCache.get(player); + } + + public boolean isQueued(Party party) { + return partyQueueCache.containsKey(party); + } + + public boolean isQueuedRanked(Party party) { + PartyMatchQueueEntry entry = getQueueEntry(party); + return entry != null && entry.getQueue().isCompetitive(); + } + + public boolean isQueuedUnranked(Party party) { + PartyMatchQueueEntry entry = getQueueEntry(party); + return entry != null && !entry.getQueue().isCompetitive(); + } + + + public PartyMatchQueueEntry getQueueEntry(Party party) { + return partyQueueCache.get(party); + } + + void removeFromQueueCache(MatchQueueEntry entry) { + if (entry instanceof SoloMatchQueueEntry) { + soloQueueCache.remove(((SoloMatchQueueEntry) entry).getPlayer()); + } else if (entry instanceof PartyMatchQueueEntry) { + partyQueueCache.remove(((PartyMatchQueueEntry) entry).getParty()); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/queue/QueueItems.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/queue/QueueItems.java new file mode 100644 index 0000000..075b51f --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/queue/QueueItems.java @@ -0,0 +1,41 @@ +package com.elevatemc.potpvp.queue; + +import lombok.experimental.UtilityClass; +import com.elevatemc.elib.util.ItemUtils; +import org.bukkit.DyeColor; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + + +import static org.bukkit.ChatColor.*; + +@UtilityClass +public final class QueueItems { + + public static final ItemStack JOIN_SOLO_UNRANKED_QUEUE_ITEM = new ItemStack(Material.IRON_SWORD); + public static final ItemStack LEAVE_SOLO_UNRANKED_QUEUE_ITEM = new ItemStack(Material.INK_SACK, 1, DyeColor.RED.getDyeData()); + + public static final ItemStack JOIN_SOLO_RANKED_QUEUE_ITEM = new ItemStack(Material.DIAMOND_SWORD); + public static final ItemStack LEAVE_SOLO_RANKED_QUEUE_ITEM = new ItemStack(Material.INK_SACK, 1, DyeColor.RED.getDyeData()); + + public static final ItemStack JOIN_PARTY_UNRANKED_QUEUE_ITEM = new ItemStack(Material.IRON_SWORD); + public static final ItemStack LEAVE_PARTY_UNRANKED_QUEUE_ITEM = new ItemStack(Material.INK_SACK, 1, DyeColor.RED.getDyeData()); + + public static final ItemStack JOIN_PARTY_RANKED_QUEUE_ITEM = new ItemStack(Material.DIAMOND_SWORD); + public static final ItemStack LEAVE_PARTY_RANKED_QUEUE_ITEM = new ItemStack(Material.INK_SACK, 1, DyeColor.RED.getDyeData()); + + static { + ItemUtils.setDisplayName(JOIN_SOLO_UNRANKED_QUEUE_ITEM, GRAY + "• " + AQUA + "Casual Queue" + GRAY + " •"); + ItemUtils.setDisplayName(LEAVE_SOLO_UNRANKED_QUEUE_ITEM, GRAY + "• " + RED + "Leave Queue" + GRAY + " •"); + + ItemUtils.setDisplayName(JOIN_SOLO_RANKED_QUEUE_ITEM, GRAY + "• " + AQUA + "Competitive Queue" + GRAY + " •"); + ItemUtils.setDisplayName(LEAVE_SOLO_RANKED_QUEUE_ITEM, GRAY + "• " + RED + "Leave Queue" + GRAY + " •"); + + ItemUtils.setDisplayName(JOIN_PARTY_UNRANKED_QUEUE_ITEM, GRAY + "• " + AQUA + "Casual 2v2 Queue" + GRAY + " •"); + ItemUtils.setDisplayName(LEAVE_PARTY_UNRANKED_QUEUE_ITEM, GRAY + "• " + RED + "Leave 2v2 Queue" + GRAY + " •"); + + ItemUtils.setDisplayName(JOIN_PARTY_RANKED_QUEUE_ITEM, GRAY + "• " + AQUA + "Ranked 2v2 Queue" + GRAY + " •"); + ItemUtils.setDisplayName(LEAVE_PARTY_RANKED_QUEUE_ITEM, GRAY + "• " + RED + "Leave 2v2 Queue" + GRAY + " •"); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/queue/SoloMatchQueueEntry.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/queue/SoloMatchQueueEntry.java new file mode 100644 index 0000000..0a46438 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/queue/SoloMatchQueueEntry.java @@ -0,0 +1,29 @@ +package com.elevatemc.potpvp.queue; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import lombok.Getter; + +import java.util.Set; +import java.util.UUID; + +/** + * Represents a single player waiting + * in a {@link MatchQueue} + */ +public final class SoloMatchQueueEntry extends MatchQueueEntry { + + @Getter private final UUID player; + + SoloMatchQueueEntry(MatchQueue queue, UUID player) { + super(queue); + + this.player = Preconditions.checkNotNull(player, "player"); + } + + @Override + public Set getMembers() { + return ImmutableSet.of(player); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/queue/listener/QueueGeneralListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/queue/listener/QueueGeneralListener.java new file mode 100644 index 0000000..2c16213 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/queue/listener/QueueGeneralListener.java @@ -0,0 +1,91 @@ +package com.elevatemc.potpvp.queue.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.match.event.MatchCountdownStartEvent; +import com.elevatemc.potpvp.match.event.MatchSpectatorJoinEvent; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.potpvp.party.PartyHandler; +import com.elevatemc.potpvp.party.event.*; +import com.elevatemc.potpvp.queue.QueueHandler; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.UUID; + +public final class QueueGeneralListener implements Listener { + + private final QueueHandler queueHandler; + + public QueueGeneralListener(QueueHandler queueHandler) { + this.queueHandler = queueHandler; + } + + @EventHandler + public void onPartyDisband(PartyDisbandEvent event) { + queueHandler.leaveQueue(event.getParty(), true); + } + + @EventHandler + public void onPartyCreate(PartyCreateEvent event) { + UUID leaderUuid = event.getParty().getLeader(); + Player leaderPlayer = Bukkit.getPlayer(leaderUuid); + + queueHandler.leaveQueue(leaderPlayer, false); + } + + @EventHandler + public void onPartyMemberJoin(PartyMemberJoinEvent event) { + queueHandler.leaveQueue(event.getMember(), false); + leaveQueue(event.getParty(), event.getMember(), "joined"); + } + + @EventHandler + public void onPartyMemberKick(PartyMemberKickEvent event) { + leaveQueue(event.getParty(), event.getMember(), "was kicked"); + } + + @EventHandler + public void onPartyMemberLeave(PartyMemberLeaveEvent event) { + leaveQueue(event.getParty(), event.getMember(), "left"); + } + + private void leaveQueue(Party party, Player member, String action) { + if (queueHandler.leaveQueue(party, true)) { + party.message(ChatColor.YELLOW + "Your party has been removed from the queue because " + ChatColor.AQUA + member.getName() + ChatColor.YELLOW + " " + action + "."); + } + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + queueHandler.leaveQueue(event.getPlayer(), true); + } + + @EventHandler + public void onMatchSpectatorJoin(MatchSpectatorJoinEvent event) { + queueHandler.leaveQueue(event.getSpectator(), true); + } + + @EventHandler + public void onMatchCountdownStart(MatchCountdownStartEvent event) { + PartyHandler partyHandler = PotPvPSI.getInstance().getPartyHandler(); + + for (MatchTeam team : event.getMatch().getTeams()) { + for (UUID member : team.getAllMembers()) { + Player memberBukkit = Bukkit.getPlayer(member); + Party memberParty = partyHandler.getParty(memberBukkit); + + queueHandler.leaveQueue(memberBukkit, true); + + if (memberParty != null && memberParty.isLeader(member)) { + queueHandler.leaveQueue(memberParty, true); + } + } + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/queue/listener/QueueItemListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/queue/listener/QueueItemListener.java new file mode 100644 index 0000000..08a43e2 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/queue/listener/QueueItemListener.java @@ -0,0 +1,117 @@ +package com.elevatemc.potpvp.queue.listener; + +import com.elevatemc.elib.eLib; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.gamemode.menu.queue.QueueGameModeMenu; +import com.elevatemc.potpvp.listener.RankedMatchQualificationListener; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.potpvp.queue.QueueHandler; +import com.elevatemc.potpvp.queue.QueueItems; +import com.elevatemc.potpvp.util.ItemListener; +import com.elevatemc.potpvp.validation.PotPvPValidation; +import com.elevatemc.elib.util.UUIDUtils; +import net.md_5.bungee.api.ChatColor; +import org.bukkit.entity.Player; + +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +// This class followes a different organizational style from other item listeners +// because we need seperate listeners for ranked/unranked, we have methods which +// we call which generate a Consumer designed for either ranked/unranked, +// based on the argument passed. Returning Consumers makes this code slightly +// harder to follow, but saves us from a lot of duplication +public final class QueueItemListener extends ItemListener { + private final QueueHandler queueHandler; + + public QueueItemListener(QueueHandler queueHandler) { + this.queueHandler = queueHandler; + + addHandler(QueueItems.JOIN_SOLO_UNRANKED_QUEUE_ITEM, joinSoloConsumer(false)); + addHandler(QueueItems.JOIN_SOLO_RANKED_QUEUE_ITEM, joinSoloConsumer(true)); + + addHandler(QueueItems.JOIN_PARTY_UNRANKED_QUEUE_ITEM, joinPartyConsumer(false)); + addHandler(QueueItems.JOIN_PARTY_RANKED_QUEUE_ITEM, joinPartyConsumer(true)); + + addHandler(QueueItems.LEAVE_SOLO_UNRANKED_QUEUE_ITEM, p -> queueHandler.leaveQueue(p, false)); + addHandler(QueueItems.LEAVE_SOLO_RANKED_QUEUE_ITEM, p -> queueHandler.leaveQueue(p, false)); + + Consumer leaveQueuePartyConsumer = player -> { + Party party = PotPvPSI.getInstance().getPartyHandler().getParty(player); + + // don't message, players who aren't leader shouldn't even get this item + if (party != null && party.isLeader(player.getUniqueId())) { + queueHandler.leaveQueue(party, false); + } + }; + + addHandler(QueueItems.LEAVE_PARTY_UNRANKED_QUEUE_ITEM, leaveQueuePartyConsumer); + addHandler(QueueItems.LEAVE_PARTY_RANKED_QUEUE_ITEM, leaveQueuePartyConsumer); + } + + private Consumer joinSoloConsumer(boolean ranked) { + return player -> { + if (ranked) { + if (rebootSoon()) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You can't join ranked queues with a reboot scheduled soon."); + return; + } + + if (!RankedMatchQualificationListener.isQualified(player.getUniqueId())) { + int needed = RankedMatchQualificationListener.getWinsNeededToQualify(player.getUniqueId()); + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You can't join ranked queues with less than " + RankedMatchQualificationListener.MIN_MATCH_WINS + " casual 1v1 wins. You need " + needed + " more wins!"); + return; + } + } + + if (PotPvPValidation.canJoinQueue(player)) { + new QueueGameModeMenu(gameMode -> { + queueHandler.joinQueue(player, gameMode, ranked); + player.closeInventory(); + }, ranked).openMenu(player); + } + }; + } + + private Consumer joinPartyConsumer(boolean competitive) { + return player -> { + Party party = PotPvPSI.getInstance().getPartyHandler().getParty(player); + + // just fail silently, players who aren't a leader + // of a party shouldn't even have this item + if (party == null || !party.isLeader(player.getUniqueId())) { + return; + } + + if (competitive) { + if (rebootSoon()) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You can't join competitive queues with a reboot scheduled soon."); + return; + } + + for (UUID member : party.getMembers()) { + if (!RankedMatchQualificationListener.isQualified(member)) { + int needed = RankedMatchQualificationListener.getWinsNeededToQualify(member); + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Your party can't join competitive queues because " + UUIDUtils.name(member) + " has less than " + RankedMatchQualificationListener.MIN_MATCH_WINS + " casual 1v1 wins. They need " + needed + " more wins!"); + return; + } + } + } + + // try to check validation issues in advance + // (will be called again in QueueHandler#joinQueue) + if (PotPvPValidation.canJoinQueue(party)) { + new QueueGameModeMenu(gameMode -> { + queueHandler.joinQueue(party, gameMode, competitive); + player.closeInventory(); + }, competitive).openMenu(player); + } + }; + } + + private boolean rebootSoon() { + return eLib.getInstance().getAutoRebootHandler().isRebooting() && eLib.getInstance().getAutoRebootHandler().getRebootSecondsRemaining() <= TimeUnit.MINUTES.toSeconds(5); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/scoreboard/GameScoreGetter.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/scoreboard/GameScoreGetter.java new file mode 100644 index 0000000..5941564 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/scoreboard/GameScoreGetter.java @@ -0,0 +1,21 @@ +package com.elevatemc.potpvp.scoreboard; + +import com.elevatemc.potpvp.events.game.Game; +import com.elevatemc.potpvp.events.game.GameQueue; +import org.bukkit.entity.Player; + +import java.util.LinkedList; +import java.util.function.BiConsumer; + +public class GameScoreGetter implements BiConsumer> { + + @Override + public void accept(Player player, LinkedList scores) { + final Game game = GameQueue.INSTANCE.getCurrentGame(player); + + if(game == null) return; + if(!game.getPlayers().contains(player)) return; + + scores.addAll(game.getEvent().getScoreboardScores(player, game)); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/scoreboard/LobbyScoreGetter.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/scoreboard/LobbyScoreGetter.java new file mode 100644 index 0000000..c256332 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/scoreboard/LobbyScoreGetter.java @@ -0,0 +1,129 @@ +package com.elevatemc.potpvp.scoreboard; + +import com.elevatemc.elib.util.Pair; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.potpvp.party.PartyHandler; +import com.elevatemc.potpvp.queue.MatchQueue; +import com.elevatemc.potpvp.queue.MatchQueueEntry; +import com.elevatemc.potpvp.queue.QueueHandler; +import com.elevatemc.potpvp.hctranked.game.RankedGame; +import com.elevatemc.potpvp.hctranked.game.RankedGameHandler; +import com.elevatemc.potpvp.hctranked.game.RankedGameTeam; +import com.elevatemc.potpvp.tournament.Tournament; +import com.elevatemc.potpvp.tournament.Tournament.TournamentStage; +import com.elevatemc.elib.util.TimeUtils; +import dev.apposed.prime.spigot.module.server.scoreboard.PrimeScoreboardStyle; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.LinkedList; +import java.util.function.BiConsumer; + +final class LobbyScoreGetter implements BiConsumer> { + private long lastUpdated = System.currentTimeMillis(); + + @Override + public void accept(Player player, LinkedList scores) { + PartyHandler partyHandler = PotPvPSI.getInstance().getPartyHandler(); + RankedGameHandler rankedGameHandler = PotPvPSI.getInstance().getHCTRankedHandler().getGameHandler(); + final Pair style = PrimeScoreboardStyle.getStyle(player); + + + scores.add(style.getValue() + "Online: " + style.getKey() + PotPvPSI.getInstance().getOnlineCount()); + scores.add(""); + scores.add(style.getValue() + "In Fights: " + style.getKey() + PotPvPSI.getInstance().getFightsCount()); + scores.add(style.getValue() + "In Queues: " + style.getKey() + PotPvPSI.getInstance().getQueueHandler().getQueuedCount()); + + + + Party playerParty = partyHandler.getParty(player); + if (playerParty != null) { + int size = playerParty.getMembers().size(); + + String privacy = ""; + switch (playerParty.getAccessRestriction()) { + case PUBLIC: + privacy = ChatColor.DARK_AQUA + "Open"; + break; + case INVITE_ONLY: + privacy = ChatColor.DARK_AQUA + "Invite Only"; + break; + case PASSWORD: + privacy = ChatColor.DARK_AQUA + "Password Only"; + break; + default: + break; + } + + scores.add(""); + scores.add(style.getKey() + ChatColor.BOLD.toString() + "Party"); + scores.add(style.getKey() + "❘ " + style.getValue() + "Leader&7: " + style.getKey() + Bukkit.getPlayer(playerParty.getLeader()).getName()); + scores.add(style.getKey() + "❘ " + style.getValue() + "Members&7: " + style.getKey() + playerParty.getMembers().size() + "/" + Party.MAX_SIZE); + scores.add(style.getKey() + "❘ " + style.getValue() + "Privacy&7: " + style.getKey() + privacy); + } + + RankedGame rankedGame = rankedGameHandler.getJoinedGame(player); + if (rankedGame != null) { + RankedGameTeam team1 = rankedGame.getTeam1(); + RankedGameTeam team2 = rankedGame.getTeam2(); + scores.add(""); + scores.add(style.getKey() + "Ranked Game"); + scores.add(style.getKey() + "❘ " + style.getValue() + "Entered&7: " + style.getKey() + rankedGame.getJoinedPlayers().size() + "/" + rankedGame.getAllPlayers().size()); + scores.add(style.getKey() + "❘ " + style.getValue() + "Team 1&7: " + style.getKey() + (team1.isReady() ? "&aReady" : "&cNot Ready")); + scores.add(style.getKey() + "❘ " + style.getValue() + "Team 2&7: " + style.getKey() + (team2.isReady() ? "&aReady" : "&cNot Ready")); + } + + + MatchQueueEntry entry = getQueueEntry(player); + + if (entry != null) { + MatchQueue queue = entry.getQueue(); + + String waitTimeFormatted = TimeUtils.formatIntoMMSS(entry.getWaitSeconds()); + + scores.add(""); + scores.add(style.getKey() + ChatColor.BOLD.toString() + "Queue"); + scores.add(style.getKey() + "❘ " + style.getValue() + "Ladder: " + style.getKey() + queue.getGameMode().getName()); + scores.add(style.getKey() + "❘ " + style.getValue() + "Time: " + style.getKey() + waitTimeFormatted); + + + } + + if(PotPvPSI.getInstance().getTournamentHandler() != null && PotPvPSI.getInstance().getTournamentHandler().getTournament() != null) { + Tournament tournament = PotPvPSI.getInstance().getTournamentHandler().getTournament(); + if (tournament != null) { + scores.add(" "); + scores.add(style.getKey().toString() + "Tournament"); + scores.add(style.getKey() + "❘ " + style.getValue() + "&rLadder: " + style.getKey() + tournament.getType().getName()); + if (tournament.getStage() == TournamentStage.WAITING_FOR_TEAMS) { + int teamSize = tournament.getRequiredPartySize(); + + scores.add(style.getKey() + "❘ " + style.getValue() + "Team Size: " + style.getKey() + teamSize + "v" + teamSize); + } else if (tournament.getStage() == TournamentStage.COUNTDOWN) { + if (tournament.getCurrentRound() == 0) { + scores.add(style.getKey() + "❘ " + style.getValue() + "Start: " + style.getKey() + TimeUtils.formatIntoMMSS(tournament.getBeginNextRoundIn())); + } else { + scores.add(style.getKey() + "❘ " + style.getValue() + "Next round: " + style.getKey() + TimeUtils.formatIntoMMSS(tournament.getBeginNextRoundIn())); + } + } else if (tournament.getStage() == TournamentStage.IN_PROGRESS) { + scores.add(style.getKey() + "❘ " + style.getValue() + "Duration: " + style.getKey() + TimeUtils.formatIntoMMSS((int) (System.currentTimeMillis() - tournament.getRoundStartedAt()) / 1000)); + scores.add(style.getKey() + "❘ " + style.getValue() + "Round: " + style.getKey() + tournament.getCurrentRound()); + } + } + } + } + + private MatchQueueEntry getQueueEntry(Player player) { + PartyHandler partyHandler = PotPvPSI.getInstance().getPartyHandler(); + QueueHandler queueHandler = PotPvPSI.getInstance().getQueueHandler(); + + Party playerParty = partyHandler.getParty(player); + if (playerParty != null) { + return queueHandler.getQueueEntry(playerParty); + } else { + return queueHandler.getQueueEntry(player.getUniqueId()); + } + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/scoreboard/MatchScoreGetter.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/scoreboard/MatchScoreGetter.java new file mode 100644 index 0000000..b10a9d8 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/scoreboard/MatchScoreGetter.java @@ -0,0 +1,478 @@ +package com.elevatemc.potpvp.scoreboard; + +import com.elevatemc.elib.scoreboard.construct.ScoreFunction; +import com.elevatemc.elib.util.Pair; +import com.elevatemc.elib.util.PlayerUtils; +import com.elevatemc.elib.util.UUIDUtils; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.elevatemc.potpvp.util.ClickTracker; +import com.google.common.collect.ImmutableMap; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.gamemode.HealingMethod; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.match.MatchState; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.pvpclasses.pvpclasses.ArcherClass; +import com.elevatemc.potpvp.pvpclasses.pvpclasses.BardClass; +import dev.apposed.prime.spigot.module.server.scoreboard.PrimeScoreboardStyle; +import org.apache.commons.lang.StringEscapeUtils; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import static org.bukkit.ChatColor.GREEN; +import static org.bukkit.ChatColor.RED; + +import java.util.*; +import java.util.function.BiConsumer; + +final class MatchScoreGetter implements BiConsumer> { + + private Map healsLeft = ImmutableMap.of(); + + MatchScoreGetter() { + Bukkit.getScheduler().runTaskTimer(PotPvPSI.getInstance(), () -> { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + Map newHealsLeft = new HashMap<>(); + + for (Player player : Bukkit.getOnlinePlayers()) { + Match playing = matchHandler.getMatchPlaying(player); + + if (playing == null) { + continue; + } + + HealingMethod healingMethod = playing.getGameMode().getHealingMethod(); + + if (healingMethod == null) { + continue; + } + + int count = healingMethod.count(player.getInventory().getContents()); + newHealsLeft.put(player.getUniqueId(), count); + } + + this.healsLeft = newHealsLeft; + }, 10L, 10L); + } + + @Override + public void accept(Player player, LinkedList scores) { + Match match = PotPvPSI.getInstance().getMatchHandler().getMatchPlayingOrSpectating(player); + final Pair style = PrimeScoreboardStyle.getStyle(player); + + boolean participant = match.getTeam(player.getUniqueId()) != null; + + if (participant) { + renderParticipantLines(scores, match, player, style); + } else { + MatchTeam previousTeam = match.getPreviousTeam(player.getUniqueId()); + renderSpectatorLines(scores, match, previousTeam, style); + } + } + + private void renderParticipantLines(List scores, Match match, Player player, Pair style) { + List teams = match.getTeams(); + GameMode gameMode = match.getGameMode(); + + // only render scoreboard if we have two teams + if (teams.size() != 2) { + + if (gameMode.equals(GameModes.BOXING)) { + scores.add("First to 100 hits wins"); + } else if (gameMode.equals(GameModes.PEARL_FIGHT)) { + scores.add("Everyone starts with 3 lives"); + } else { + scores.add("FFA Match"); + } + return; + } + + // this method won't be called if the player isn't a participant + MatchTeam ourTeam = match.getTeam(player.getUniqueId()); + if (ourTeam == null) { + return; + } + MatchTeam otherTeam = teams.get(0) == ourTeam ? teams.get(1) : teams.get(0); + + // we use getAllMembers instead of getAliveMembers to avoid + // mid-match scoreboard changes as players die / disconnect + int ourTeamSize = ourTeam.getAllMembers().size(); + int otherTeamSize = otherTeam.getAllMembers().size(); + + if (ourTeamSize == 1 && otherTeamSize == 1) { + render1v1MatchLines(scores, ourTeam, otherTeam, match.getState(), gameMode, style); + } else if (ourTeamSize <= 2 && otherTeamSize <= 2) { + render2v2MatchLines(scores, ourTeam, otherTeam, player, match.getGameMode().getHealingMethod(), gameMode, style); + } else if (ourTeamSize <= 4 && otherTeamSize <= 4) { + // We don't want to make the scoreboard too large if we also have boxing lines + if (!match.getGameMode().equals(GameModes.BOXING)) { + render4v4MatchLines(scores, ourTeam, otherTeam, style); + } else { + renderLargeMatchLines(scores, ourTeam, otherTeam, gameMode, style); + } + + } else if (ourTeam.getAllMembers().size() <= 9) { + renderLargeMatchLines(scores, ourTeam, otherTeam, gameMode, style); + } else { + renderJumboMatchLines(scores, ourTeam, otherTeam, gameMode, style); + } + + String archerMarkScore = getArcherMarkScore(player); + String bardEnergyScore = getBardEnergyScore(player); + + if (archerMarkScore != null) { + scores.add("&6Archer Mark: " + style.getValue() + archerMarkScore); + } + + if (bardEnergyScore != null) { + scores.add("&eBard Energy: " + style.getValue() + bardEnergyScore); + } + } + + + private void render1v1MatchLines(List scores, MatchTeam ourTeam, MatchTeam otherTeam, MatchState state, GameMode gameMode, Pair style) { + Player otherPlayer = Bukkit.getPlayer(otherTeam.getFirstMember()); + Player ourPlayer = Bukkit.getPlayer(ourTeam.getFirstMember()); + + String ping; + String cps; + if (otherPlayer != null) { + ping = String.valueOf(PlayerUtils.getPing(otherPlayer)); + cps = String.valueOf(ClickTracker.getCPS(otherPlayer)); + } else { + ping = "LOGGED"; + cps = "LOGGED"; + } + + scores.add(style.getKey() + ChatColor.BOLD.toString() + "Match"); + scores.add(style.getKey() + "❘" + style.getValue() + " Opponent: " + style.getKey() + UUIDUtils.name(otherTeam.getFirstMember())); + scores.add(style.getKey() + "❘" + style.getValue() + " CPS: " + GREEN + + ClickTracker.getCPS(ourPlayer) + " CPS" + ChatColor.GRAY + " ❘ " + RED + cps + " CPS"); + + scores.add(style.getKey() + "❘" + style.getValue() + " Ping: " + GREEN + + PlayerUtils.getPing(ourPlayer) + " ms" + ChatColor.GRAY + " ❘ " + RED + ping + " ms"); + + + + if (gameMode.equals(GameModes.BOXING)) { + int ourHits = ourTeam.getHits(); + int otherHits = otherTeam.getHits(); + String extra = (ourHits >= otherHits) ? "&a(+" + (ourHits - otherHits) + ")" : "&c(" + (ourHits - otherHits) + ")"; + scores.add(""); + scores.add(style.getKey() + ChatColor.BOLD.toString() + "Boxing " + extra); + scores.add(style.getKey() + "❘" + style.getValue() + " You: " + GREEN + ourHits); + scores.add(style.getKey() + "❘" + ChatColor.RESET + " Opponent: " + RED + otherHits); + + } else if (gameMode.equals(GameModes.PEARL_FIGHT)) { + int ourLives = ourTeam.getLives(); + int otherLives = otherTeam.getLives(); + scores.add(style.getKey() + ChatColor.BOLD.toString() + "Lives: "); + scores.add(style.getKey() + "❘" + style.getValue() + " You: " + GREEN + ourLives); + scores.add(style.getKey() + "❘" + ChatColor.RESET + " Opponent: " + RED + otherLives); + scores.add(""); + } + } + + private void render2v2MatchLines(List scores, MatchTeam ourTeam, MatchTeam otherTeam, Player player, HealingMethod healingMethod, GameMode gameMode, Pair style) { + // 2v2, but potentially 1v2 / 1v1 if players have died + UUID partnerUuid = null; + + scores.add("&aTeam"); + + for (UUID teamMember : ourTeam.getAllMembers()) { + if (teamMember != player.getUniqueId()) { + partnerUuid = teamMember; + break; + } + } + + if (partnerUuid != null) { + String healthStr; + String healsStr; + String namePrefix; + + if (ourTeam.isAlive(partnerUuid)) { + Player partnerPlayer = Bukkit.getPlayer(partnerUuid); // will never be null (or isAlive would've returned false) + double health = Math.round(partnerPlayer.getHealth()) / 2D; + int heals = healsLeft.getOrDefault(partnerUuid, 0); + + ChatColor healthColor; + ChatColor healsColor; + + if (health > 8) { + healthColor = ChatColor.GREEN; + } else if (health > 6) { + healthColor = ChatColor.YELLOW; + } else if (health > 4) { + healthColor = ChatColor.GOLD; + } else if (health > 1) { + healthColor = ChatColor.RED; + } else { + healthColor = ChatColor.DARK_RED; + } + + if (heals > 20) { + healsColor = ChatColor.GREEN; + } else if (heals > 12) { + healsColor = ChatColor.YELLOW; + } else if (heals > 8) { + healsColor = ChatColor.GOLD; + } else if (heals > 3) { + healsColor = ChatColor.RED; + } else { + healsColor = ChatColor.DARK_RED; + } + + namePrefix = "&f"; + healthStr = healthColor.toString() + health + " *❤*" + ChatColor.GRAY; + + if (healingMethod != null) { + healsStr = " &l⏐ " + healsColor + heals + " " + (heals == 1 ? healingMethod.getShortSingular() : healingMethod.getShortPlural()); + } else { + healsStr = ""; + } + } else { + namePrefix = "&7&m"; + healthStr = "&cDead"; + healsStr = ""; + } + + scores.add(namePrefix + UUIDUtils.name(partnerUuid)); + scores.add(healthStr + healsStr); + scores.add("&b"); + } + + scores.add("&cOpponents"); + scores.addAll(renderTeamMemberOverviewLines(otherTeam, style)); + + if (gameMode.equals(GameModes.BOXING)) { + scores.add(""); + int ourHits = ourTeam.getHits(); + int otherHits = otherTeam.getHits(); + String extra = (ourHits >= otherHits) ? "&a(+" + (ourHits - otherHits) + ")" : "&c(" + (ourHits - otherHits) + ")"; + scores.add(style.getKey() + ChatColor.BOLD.toString() + "Boxing " + extra); + scores.add(style.getKey() + "❘" + style.getValue() + " Team: " + GREEN + ourHits); + scores.add(style.getKey() + "❘" + ChatColor.RESET + " Opponent: " + RED + otherHits); + } else if (gameMode.equals(GameModes.PEARL_FIGHT)) { + int ourLives = ourTeam.getLives(); + int otherLives = otherTeam.getLives(); + scores.add(style.getKey() + ChatColor.BOLD.toString() + "Lives: "); + scores.add(style.getKey() + "❘" + style.getValue() + " Team: " + GREEN + ourLives); + scores.add(style.getKey() + "❘" + ChatColor.RESET + " Opponent: " + RED + otherLives); + scores.add(""); + } + } + + private void render4v4MatchLines(List scores, MatchTeam ourTeam, MatchTeam otherTeam, Pair style) { + // Above a 2v2, but up to a 4v4. + scores.add("&aTeam " + style.getValue() + "(" + ourTeam.getAliveMembers().size() + "/" + ourTeam.getAllMembers().size() + ")"); + scores.addAll(renderTeamMemberOverviewLinesWithHearts(ourTeam)); + scores.add(""); + scores.add("&cOpponents " + style.getValue() + "(" + otherTeam.getAliveMembers().size() + "/" + otherTeam.getAllMembers().size() + ")"); + scores.addAll(renderTeamMemberOverviewLines(otherTeam, style)); + + } + + private void renderLargeMatchLines(List scores, MatchTeam ourTeam, MatchTeam otherTeam, GameMode gameMode, Pair style) { + // We just display THEIR team's names, and the other team is a number. + scores.add("&aTeam " + style.getValue() + "(" + ourTeam.getAliveMembers().size() + "/" + ourTeam.getAllMembers().size() + ")"); + scores.addAll(renderTeamMemberOverviewLinesWithHearts(ourTeam)); + scores.add(""); + scores.add("&cOpponents: " + style.getValue() + otherTeam.getAliveMembers().size() + "/" + otherTeam.getAllMembers().size()); + if (gameMode.equals(GameModes.BOXING)) { + scores.add(""); + int ourHits = ourTeam.getHits(); + int otherHits = otherTeam.getHits(); + String extra = (ourHits >= otherHits) ? "&a(+" + (ourHits - otherHits) + ")" : "&c(" + (ourHits - otherHits) + ")"; + scores.add(style.getKey() + ChatColor.BOLD.toString() + "Boxing " + extra); + scores.add(style.getKey() + "❘" + style.getValue() + " Team: " + GREEN + ourHits); + scores.add(style.getKey() + "❘" + ChatColor.RESET + " Opponent: " + RED + otherHits); + } else if (gameMode.equals(GameModes.PEARL_FIGHT)) { + int ourLives = ourTeam.getLives(); + int otherLives = otherTeam.getLives(); + scores.add(style.getKey() + ChatColor.BOLD.toString() + "Lives: "); + scores.add(style.getKey() + "❘" + style.getValue() + " Team: " + GREEN + ourLives); + scores.add(style.getKey() + "❘" + ChatColor.RESET + " Opponent: " + RED + otherLives); + scores.add(""); + } + } + + private void renderJumboMatchLines(List scores, MatchTeam ourTeam, MatchTeam otherTeam, GameMode gameMode, Pair style) { + // We just display numbers. + scores.add("&aTeam: " + style.getValue() + ourTeam.getAliveMembers().size() + "/" + ourTeam.getAllMembers().size()); + scores.add("&cOpponents: " + style.getValue() + otherTeam.getAliveMembers().size() + "/" + otherTeam.getAllMembers().size()); + if (gameMode.equals(GameModes.BOXING)) { + scores.add(""); + int ourHits = ourTeam.getHits(); + int otherHits = otherTeam.getHits(); + String extra = (ourHits >= otherHits) ? "&a(+" + (ourHits - otherHits) + ")" : "&c(" + (ourHits - otherHits) + ")"; + scores.add(style.getKey() + ChatColor.BOLD.toString() + "Boxing " + extra); + scores.add(style.getKey() + "❘" + style.getValue() + " Team: " + GREEN + ourHits); + scores.add(style.getKey() + "❘" + ChatColor.RESET + " Opponent: " + RED + otherHits); + } else if (gameMode.equals(GameModes.PEARL_FIGHT)) { + int ourLives = ourTeam.getLives(); + int otherLives = otherTeam.getLives(); + scores.add(style.getKey() + ChatColor.BOLD.toString() + "Lives: "); + scores.add(style.getKey() + "❘" + style.getValue() + " Team: " + GREEN + ourLives); + scores.add(style.getKey() + "❘" + ChatColor.RESET + " Opponent: " + RED + otherLives); + scores.add(""); + } + } + + private void renderSpectatorLines(List scores, Match match, MatchTeam oldTeam, Pair style) { + List teams = match.getTeams(); + + // only render team overview if we have two teams + if (teams.size() == 2) { + MatchTeam teamOne = teams.get(0); + MatchTeam teamTwo = teams.get(1); + + if (teamOne.getAllMembers().size() != 1 && teamTwo.getAllMembers().size() != 1) { + // spectators who were on a team see teams as they releate + // to them, not just one/two. + if (oldTeam == null) { + scores.add("&aTeam One&7: " + style.getValue() + teamOne.getAliveMembers().size() + "/" + teamOne.getAllMembers().size()); + scores.add("&cTeam Two&7: " + style.getValue() + teamTwo.getAliveMembers().size() + "/" + teamTwo.getAllMembers().size()); + } else { + MatchTeam otherTeam = oldTeam == teamOne ? teamTwo : teamOne; + + scores.add("&aYour Team&7: " + style.getValue() + oldTeam.getAliveMembers().size() + "/" + oldTeam.getAllMembers().size()); + scores.add("&cTheir Team&7: " + style.getValue() + otherTeam.getAliveMembers().size() + "/" + otherTeam.getAllMembers().size()); + } + + if (match.getGameMode().equals(GameModes.BOXING)) { + scores.add(""); + int teamOneHitsHits = teamOne.getHits(); + int teamTwoHits = teamTwo.getHits(); + String extra = (teamOneHitsHits >= teamTwoHits) ? "&a(+" + (teamOneHitsHits - teamTwoHits) + ")" : "&c(" + (teamOneHitsHits - teamTwoHits) + ")"; + scores.add(style.getKey() + ChatColor.BOLD.toString() + "Boxing " + extra); + scores.add(style.getKey() + "❘" + " &aTeam One&7: " + style.getValue() + teamOneHitsHits); + scores.add(style.getKey() + "❘" + " &cTeam Two&7: " + style.getValue() + teamTwoHits); + } else if (match.getGameMode().equals(GameModes.PEARL_FIGHT)) { + int ourLives = teamOne.getLives(); + int otherLives = teamTwo.getLives(); + scores.add(style.getKey() + ChatColor.BOLD.toString() + "Lives: "); + scores.add(style.getKey() + "❘" + " &dTeam One&7: " + style.getKey() + ourLives); + scores.add(style.getKey() + "❘" + " &bTeam Two&7: " + style.getKey() + otherLives); + scores.add(""); + } + } else { + Player firstPlayer = Bukkit.getPlayer(teamOne.getFirstMember()); + Player otherPlayer = Bukkit.getPlayer(teamTwo.getFirstMember()); + if (firstPlayer != null) scores.add(firstPlayer.getName() + "'s Ping: " + style.getKey() + PlayerUtils.getPing(firstPlayer) + "ms"); + if (otherPlayer != null) scores.add(otherPlayer.getName() + "'s Ping: " + style.getKey() + PlayerUtils.getPing(otherPlayer) + "ms"); + } + } else { + scores.add("FFA Match"); + } + + } + + /* Returns the names of all alive players, colored + indented, followed + by the names of all dead players, colored + indented. */ + + private List renderTeamMemberOverviewLinesWithHearts(MatchTeam team) { + List aliveLines = new ArrayList<>(); + List deadLines = new ArrayList<>(); + + // seperate lists to sort alive players before dead + // + color differently + for (UUID teamMember : team.getAllMembers()) { + if (team.isAlive(teamMember)) { + aliveLines.add(" &f" + UUIDUtils.name(teamMember) + " " + getHeartString(team, teamMember)); + } else { + deadLines.add(" &7&m" + UUIDUtils.name(teamMember)); + } + } + + List result = new ArrayList<>(); + + result.addAll(aliveLines); + result.addAll(deadLines); + + return result; + } + + private List renderTeamMemberOverviewLines(MatchTeam team, Pair style) { + List aliveLines = new ArrayList<>(); + List deadLines = new ArrayList<>(); + + // seperate lists to sort alive players before dead + // + color differently + for (UUID teamMember : team.getAllMembers()) { + if (team.isAlive(teamMember)) { + aliveLines.add(" &f" + UUIDUtils.name(teamMember)); + } else { + deadLines.add(" &7&m" + UUIDUtils.name(teamMember)); + } + } + + List result = new ArrayList<>(); + + result.addAll(aliveLines); + result.addAll(deadLines); + + return result; + } + + private String getHeartString(MatchTeam ourTeam, UUID partnerUuid) { + if (partnerUuid != null) { + String healthStr; + + if (ourTeam.isAlive(partnerUuid)) { + Player partnerPlayer = Bukkit.getPlayer(partnerUuid); // will never be null (or isAlive would've returned false) + double health = Math.round(partnerPlayer.getHealth()) / 2D; + + ChatColor healthColor; + + if (health > 8) { + healthColor = ChatColor.GREEN; + } else if (health > 6) { + healthColor = ChatColor.YELLOW; + } else if (health > 4) { + healthColor = ChatColor.GOLD; + } else if (health > 1) { + healthColor = ChatColor.RED; + } else { + healthColor = ChatColor.DARK_RED; + } + + healthStr = healthColor + "(" + health + " ❤)"; + } else { + healthStr = "&c(Dead)"; + } + + return healthStr; + } else { + return "&c(Dead)"; + } + } + + public String getArcherMarkScore(Player player) { + if (ArcherClass.isMarked(player)) { + long diff = ArcherClass.getMarkedPlayers().get(player.getName()) - System.currentTimeMillis(); + + if (diff > 0) { + return (ScoreFunction.TIME_FANCY.apply(diff / 1000F)); + } + } + + return (null); + } + + public String getBardEnergyScore(Player player) { + if (BardClass.getEnergy().containsKey(player.getName())) { + float energy = BardClass.getEnergy().get(player.getName()); + + if (energy > 0) { + // No function here, as it's a "raw" value. + return (String.valueOf(BardClass.getEnergy().get(player.getName()))); + } + } + + return (null); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/scoreboard/MultiplexingScoreGetter.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/scoreboard/MultiplexingScoreGetter.java new file mode 100644 index 0000000..acf1595 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/scoreboard/MultiplexingScoreGetter.java @@ -0,0 +1,126 @@ +package com.elevatemc.potpvp.scoreboard; + +import com.elevatemc.elib.eLib; +import com.elevatemc.elib.scoreboard.construct.ScoreGetter; +import com.elevatemc.elib.util.Pair; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.events.game.Game; +import com.elevatemc.potpvp.events.game.GameQueue; +import com.elevatemc.potpvp.events.game.GameState; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.potpvp.party.PartyHandler; +import com.elevatemc.potpvp.queue.MatchQueue; +import com.elevatemc.potpvp.queue.MatchQueueEntry; +import com.elevatemc.potpvp.queue.QueueHandler; +import com.elevatemc.potpvp.setting.Setting; +import com.elevatemc.potpvp.setting.SettingHandler; + +import com.elevatemc.elib.util.TimeUtils; +import com.elevatemc.elib.util.UUIDUtils; +import dev.apposed.prime.spigot.module.server.scoreboard.PrimeScoreboardStyle; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.Date; +import java.util.LinkedList; +import java.util.Optional; +import java.util.UUID; +import java.util.function.BiConsumer; + +final class MultiplexingScoreGetter implements ScoreGetter { + + private final BiConsumer> matchScoreGetter; + private final BiConsumer> lobbyScoreGetter; + private final BiConsumer> gameScoreGetter; + + MultiplexingScoreGetter( + BiConsumer> matchScoreGetter, + BiConsumer> lobbyScoreGetter, + BiConsumer> gameScoreGetter + ) { + this.matchScoreGetter = matchScoreGetter; + this.lobbyScoreGetter = lobbyScoreGetter; + this.gameScoreGetter = gameScoreGetter; + } + + @Override + public void getScores(LinkedList scores, Player player) { + if (PotPvPSI.getInstance() == null) return; + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + SettingHandler settingHandler = PotPvPSI.getInstance().getSettingHandler(); + final Pair style = PrimeScoreboardStyle.getStyle(player); + + if (settingHandler.getSetting(player, Setting.SHOW_SCOREBOARD)) { + if (matchHandler.isPlayingOrSpectatingMatch(player) && !matchHandler.isPlayingEvent(player)) { + matchScoreGetter.accept(player, scores); + } else { + final Game game = GameQueue.INSTANCE.getCurrentGame(player); + if(game != null && game.getPlayers().contains(player) && game.getState() != GameState.ENDED) { + gameScoreGetter.accept(player, scores); + } else { + lobbyScoreGetter.accept(player, scores); + } + } + + Optional followingOpt = PotPvPSI.getInstance().getFollowHandler().getFollowing(player); + if (followingOpt.isPresent()) { + scores.add(""); + scores.add(style.getValue() + "Following&7: " + style.getKey() + UUIDUtils.name(followingOpt.get())); + if (player.hasPermission("core.staffteam")) { + Player following = Bukkit.getPlayer(followingOpt.get()); + MatchQueueEntry targetEntry = getQueueEntry(following); + + if (targetEntry != null) { + MatchQueue queue = targetEntry.getQueue(); + + scores.add(style.getValue() + "Target's Queue&7: " + style.getKey() + queue.getGameMode().getName() + (queue.isCompetitive() ? "&7(R)" : "&7(C)")); + } + } + } + + if (eLib.getInstance().getAutoRebootHandler().isRebooting()) { + String secondsStr = TimeUtils.formatIntoMMSS(eLib.getInstance().getAutoRebootHandler().getRebootSecondsRemaining()); + scores.add("&4&lReboot&7: &c" + secondsStr); + } + + + if (player.hasMetadata("modmode")) { + scores.add(""); + scores.add("&c&oStaff Mode"); + } + + if (scores.size() <= 13) { + scores.add(""); + } + + if (scores.size() <= 13) { + scores.add(style.getKey() + " elevatemc.com "); + } + + if (scores.size() <= 13) { + scores.addFirst(""); + } + + if (scores.size() <= 13) { + scores.addFirst( ChatColor.GRAY.toString() + ChatColor.ITALIC + " " + PotPvPScoreboardConfiguration.date + " "); + } + } + + return; + } + + private MatchQueueEntry getQueueEntry(Player player) { + PartyHandler partyHandler = PotPvPSI.getInstance().getPartyHandler(); + QueueHandler queueHandler = PotPvPSI.getInstance().getQueueHandler(); + + Party playerParty = partyHandler.getParty(player); + if (playerParty != null) { + return queueHandler.getQueueEntry(playerParty); + } else { + return queueHandler.getQueueEntry(player.getUniqueId()); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/scoreboard/PotPvPScoreboardConfiguration.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/scoreboard/PotPvPScoreboardConfiguration.java new file mode 100644 index 0000000..4375b2d --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/scoreboard/PotPvPScoreboardConfiguration.java @@ -0,0 +1,44 @@ +package com.elevatemc.potpvp.scoreboard; + +import com.elevatemc.elib.scoreboard.config.ScoreboardConfiguration; +import com.elevatemc.elib.scoreboard.construct.TitleGetter; +import com.elevatemc.elib.util.Pair; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.util.Color; +import dev.apposed.prime.spigot.module.server.scoreboard.PrimeScoreboardStyle; +import org.apache.commons.lang.StringEscapeUtils; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.text.SimpleDateFormat; +import java.util.Date; + +public final class PotPvPScoreboardConfiguration { + private final static SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yy"); + public static String date; + public static ScoreboardConfiguration create() { + ScoreboardConfiguration configuration = new ScoreboardConfiguration(); + + Bukkit.getScheduler().runTaskTimerAsynchronously(PotPvPSI.getInstance(), () -> { + date = dateFormat.format(new Date()); + }, 0L, 20 * 60 * 5); + + configuration.setTitleGetter(new TitleGetter() { + @Override + public String getTitle(Player player) { + final Pair style = PrimeScoreboardStyle.getStyle(player); + return Color.translate(style.getKey().toString() + ChatColor.BOLD + " Practice &7" + "❘" + style.getValue() + " Season 3"); + } + }); + + configuration.setScoreGetter(new MultiplexingScoreGetter( + new MatchScoreGetter(), + new LobbyScoreGetter(), + new GameScoreGetter() + )); + + return configuration; + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/Setting.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/Setting.java new file mode 100644 index 0000000..d9438c3 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/Setting.java @@ -0,0 +1,163 @@ +package com.elevatemc.potpvp.setting; + +import com.google.common.collect.ImmutableList; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.util.List; + +@AllArgsConstructor +public enum Setting { + + SHOW_SCOREBOARD( + "Toggle Scoreboard", + ImmutableList.of( + ChatColor.GRAY + "Enable or disable scoreboard" + ), + Material.SIGN, + ChatColor.GREEN + "Enabled", + ChatColor.RED + "Enabled", + true, + null // no permission required + ), + RECEIVE_DUELS( + "Toggle Duel Requests", + ImmutableList.of( + ChatColor.GRAY + "Enable or disable duel requests" + ), + Material.DIAMOND_SWORD, + ChatColor.GREEN + "Enabled", + ChatColor.RED + "Disabled", + true, + null // no permission required + ), + ALLOW_SPECTATORS( + "Allow Spectators", + ImmutableList.of( + ChatColor.GRAY + "Enable or disable spectators" + ), + Material.REDSTONE_COMPARATOR, + ChatColor.GREEN + "Enabled", + ChatColor.RED + "Disabled", + true, + null // no permission required + ), + VIEW_OTHER_SPECTATORS( + "Other Spectators", + ImmutableList.of( + ChatColor.GRAY + "If enabled, you can see spectators", + ChatColor.GRAY + "in the same match as you.", + "", + ChatColor.GRAY + "Disable to only see alive players in match." + ), + Material.GLASS_BOTTLE, + ChatColor.YELLOW + "Show other spectators", + ChatColor.YELLOW + "Hide other spectators", + true, + null // no permission required + ), + SHOW_SPECTATOR_JOIN_MESSAGES( + "Toggle Spectator Join Messages", + ImmutableList.of( + ChatColor.GRAY + "Enabled or disable spectator join messages" + ), + Material.PAPER, + ChatColor.GREEN + "Enabled", + ChatColor.RED + "Disabled", + true, + null// no permission required + ), + NIGHT_MODE( + "Toggle Time", + ImmutableList.of( + ChatColor.GRAY + "Set the time" + ), + Material.WATCH, + ChatColor.GREEN + "Night", + ChatColor.RED + "Day", + false, + null // no permission required + ), + SEE_TOURNAMENT_JOIN_MESSAGE( + "Tournament Join Messages", + ImmutableList.of( + ChatColor.GRAY + "If enabled, you will see messages", + ChatColor.GRAY + "when people join the tournament", + "", + ChatColor.GRAY + "Disable to only see your own party join messages." + ), + Material.IRON_DOOR, + ChatColor.YELLOW + "Tournament join messages are shown", + ChatColor.YELLOW + "Tournament join messages are hidden", + true, + null // no permission required + ), + SEE_TOURNAMENT_ELIMINATION_MESSAGES( + "Tournament Elimination Messages", + ImmutableList.of( + ChatColor.GRAY + "If enabled, you will see messages when", + ChatColor.GRAY + "people are eliminated the tournament", + "", + ChatColor.GRAY + "Disable to only see your own party elimination messages." + ), + Material.SKULL_ITEM, + ChatColor.YELLOW + "Tournament elimination messages are shown", + ChatColor.YELLOW + "Tournament elimination messages are hidden", + true, + null // no permission required + ); + + + /** + * Friendly (colored) display name for this setting + */ + @Getter private final String name; + + /** + * Friendly (colored) description for this setting + */ + @Getter private final List description; + + /** + * Material to be used when rendering an icon for this setting + * @see com.elevatemc.potpvp.setting.menu.SettingButton + */ + @Getter private final Material icon; + + /** + * Text to be shown when rendering an icon for this setting, while enabled + * @see com.elevatemc.potpvp.setting.menu.SettingButton + */ + @Getter private final String enabledText; + + /** + * Text to be shown when rendering an icon for this setting, while enabled + * @see com.elevatemc.potpvp.setting.menu.SettingButton + */ + @Getter private final String disabledText; + + /** + * Default value for this setting, will be used for players who haven't + * updated the setting and if a player's settings fail to load. + */ + private final boolean defaultValue; + + /** + * The permission required to be able to see and update this setting, + * null means no permission is required to update/see. + */ + private final String permission; + + // Using @Getter means the method would be 'isDefaultValue', + // which doesn't correctly represent this variable. + public boolean getDefaultValue() { + return defaultValue; + } + + public boolean canUpdate(Player player) { + return permission == null || player.hasPermission(permission); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/SettingHandler.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/SettingHandler.java new file mode 100644 index 0000000..dfae227 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/SettingHandler.java @@ -0,0 +1,89 @@ +package com.elevatemc.potpvp.setting; + +import com.google.common.collect.ImmutableMap; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.setting.event.SettingUpdateEvent; +import com.elevatemc.potpvp.setting.listener.SettingLoadListener; +import com.elevatemc.potpvp.setting.repository.MongoSettingRepository; +import com.elevatemc.potpvp.setting.repository.SettingRepository; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public final class SettingHandler { + + // we previously had a PlayerSettings class which acted as a type alias for + // a Map but the client interface is ultimately the same and + // this is slightly easier for us to work with + private final Map> settingsData = new ConcurrentHashMap<>(); + private final SettingRepository settingRepository; + + public SettingHandler() { + Bukkit.getPluginManager().registerEvents(new SettingLoadListener(), PotPvPSI.getInstance()); + + settingRepository = new MongoSettingRepository(); + } + + /** + * Retrieves the value of a setting for the player provided, falling back to the + * setting's default value if the player hasn't updated the setting or the player's + * settings failed to load. + * + * @param player The player to look up settings for + * @param setting The Setting to look up the value of + * @return If the setting is, after considered defaults and player customizations, enabled. + */ + public boolean getSetting(Player player, Setting setting) { + Map playerSettings = settingsData.getOrDefault(player.getUniqueId(), ImmutableMap.of()); + return playerSettings.getOrDefault(setting, setting.getDefaultValue()); + } + + /** + * Updates the value of a setting for the player provided. Automatically handles + * calling {@link SettingUpdateEvent}s and saving the changes in a database. + * + * @param player The player to update settings for + * @param setting The Setting to update the value of + * @param enabled If the setting should be enabled + */ + public void updateSetting(Player player, Setting setting, boolean enabled) { + Map playerSettings = settingsData.computeIfAbsent(player.getUniqueId(), i -> new HashMap<>()); + playerSettings.put(setting, enabled); + + Bukkit.getScheduler().runTaskAsynchronously(PotPvPSI.getInstance(), () -> { + try { + settingRepository.saveSettings(player.getUniqueId(), playerSettings); + } catch (IOException ex) { + // just log, nothing else to do. + ex.printStackTrace(); + } + }); + + Bukkit.getPluginManager().callEvent(new SettingUpdateEvent(player, setting, enabled)); + } + + public void loadSettings(UUID playerUuid) { + Map playerSettings; + + try { + playerSettings = new ConcurrentHashMap<>(settingRepository.loadSettings(playerUuid)); + } catch (IOException ex) { + // just print + return an empty map, this will cause us + // to fall back to default values. + ex.printStackTrace(); + playerSettings = new ConcurrentHashMap<>(); + } + + settingsData.put(playerUuid, playerSettings); + } + + public void unloadSettings(Player player) { + settingsData.remove(player.getUniqueId()); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/command/NightCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/command/NightCommand.java new file mode 100644 index 0000000..47255de --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/command/NightCommand.java @@ -0,0 +1,34 @@ +package com.elevatemc.potpvp.setting.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.setting.Setting; +import com.elevatemc.potpvp.setting.SettingHandler; +import com.elevatemc.elib.command.Command; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +/** + * /night command, allows players to toggle {@link Setting#NIGHT_MODE} setting + */ +public final class NightCommand { + + @Command(names = { "night", "nightMode" }, permission = "") + public static void night(Player sender) { + if (!Setting.NIGHT_MODE.canUpdate(sender)) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "No permission."); + return; + } + + SettingHandler settingHandler = PotPvPSI.getInstance().getSettingHandler(); + boolean enabled = !settingHandler.getSetting(sender, Setting.NIGHT_MODE); + + settingHandler.updateSetting(sender, Setting.NIGHT_MODE, enabled); + + if (enabled) { + sender.sendMessage(ChatColor.GREEN + "Night mode turned on."); + } else { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Night mode turned off."); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/command/SettingsCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/command/SettingsCommand.java new file mode 100644 index 0000000..53851d8 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/command/SettingsCommand.java @@ -0,0 +1,17 @@ +package com.elevatemc.potpvp.setting.command; + +import com.elevatemc.potpvp.setting.menu.SettingsMenu; +import com.elevatemc.elib.command.Command; +import org.bukkit.entity.Player; + +/** + * /settings, accessible by all users, opens a {@link SettingsMenu} + */ +public final class SettingsCommand { + + @Command(names = {"settings", "preferences", "prefs", "options"}, permission = "") + public static void settings(Player sender) { + new SettingsMenu().openMenu(sender); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/command/ToggleDuelCommand.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/command/ToggleDuelCommand.java new file mode 100644 index 0000000..aaf074e --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/command/ToggleDuelCommand.java @@ -0,0 +1,34 @@ +package com.elevatemc.potpvp.setting.command; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.setting.Setting; +import com.elevatemc.potpvp.setting.SettingHandler; +import com.elevatemc.elib.command.Command; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +/** + * /toggleduels command, allows players to toggle {@link Setting#RECEIVE_DUELS} setting + */ +public final class ToggleDuelCommand { + + @Command(names = { "toggleduels" }, permission = "") + public static void toggleDuel(Player sender) { + if (!Setting.RECEIVE_DUELS.canUpdate(sender)) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "No permission."); + return; + } + + SettingHandler settingHandler = PotPvPSI.getInstance().getSettingHandler(); + boolean enabled = !settingHandler.getSetting(sender, Setting.RECEIVE_DUELS); + + settingHandler.updateSetting(sender, Setting.RECEIVE_DUELS, enabled); + + if (enabled) { + sender.sendMessage(ChatColor.GREEN + "Your duel requests have been turned on."); + } else { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Your duel requests have been turned off."); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/event/SettingUpdateEvent.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/event/SettingUpdateEvent.java new file mode 100644 index 0000000..c32d68b --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/event/SettingUpdateEvent.java @@ -0,0 +1,39 @@ +package com.elevatemc.potpvp.setting.event; + +import com.google.common.base.Preconditions; +import lombok.Getter; +import com.elevatemc.potpvp.setting.Setting; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; + +/** + * Called when a player updates a setting value. + */ +public final class SettingUpdateEvent extends PlayerEvent { + + @Getter private static HandlerList handlerList = new HandlerList(); + + /** + * The setting that was updated + */ + @Getter private final Setting setting; + + /** + * The new state of the setting + */ + @Getter private final boolean enabled; + + public SettingUpdateEvent(Player player, Setting setting, boolean enabled) { + super(player); + + this.setting = Preconditions.checkNotNull(setting, "setting"); + this.enabled = enabled; + } + + @Override + public HandlerList getHandlers() { + return handlerList; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/listener/SettingLoadListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/listener/SettingLoadListener.java new file mode 100644 index 0000000..9ac8a9e --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/listener/SettingLoadListener.java @@ -0,0 +1,22 @@ +package com.elevatemc.potpvp.setting.listener; + +import com.elevatemc.potpvp.PotPvPSI; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerPreLoginEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +public final class SettingLoadListener implements Listener { + + @EventHandler(priority = EventPriority.LOWEST) // LOWEST runs first + public void onAsyncPlayerPreLogin(AsyncPlayerPreLoginEvent event) { + PotPvPSI.getInstance().getSettingHandler().loadSettings(event.getUniqueId()); + } + + @EventHandler(priority = EventPriority.MONITOR) // MONITOR runs last + public void onPlayerQuit(PlayerQuitEvent event) { + PotPvPSI.getInstance().getSettingHandler().unloadSettings(event.getPlayer()); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/menu/SettingButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/menu/SettingButton.java new file mode 100644 index 0000000..529153b --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/menu/SettingButton.java @@ -0,0 +1,71 @@ +package com.elevatemc.potpvp.setting.menu; + +import com.google.common.base.Preconditions; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.setting.Setting; +import com.elevatemc.potpvp.setting.SettingHandler; +import com.elevatemc.elib.menu.Button; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.ArrayList; +import java.util.List; + +/** + * Button used by {@link com.elevatemc.potpvp.setting.menu.SettingsMenu} to render a {@link Setting} + */ +final class SettingButton extends Button { + + private static final String ENABLED_ARROW = ChatColor.BLUE.toString() + ChatColor.BOLD + " ► "; + private static final String DISABLED_SPACER = " "; + + private final Setting setting; + + SettingButton(Setting setting) { + this.setting = Preconditions.checkNotNull(setting, "setting"); + } + + @Override + public String getName(Player player) { + return ChatColor.AQUA + setting.getName(); + } + + @Override + public List getDescription(Player player) { + List description = new ArrayList<>(); + + description.add(""); + description.addAll(setting.getDescription()); + description.add(""); + + if (PotPvPSI.getInstance().getSettingHandler().getSetting(player, setting)) { + description.add(ENABLED_ARROW + setting.getEnabledText()); + description.add(DISABLED_SPACER + setting.getDisabledText()); + } else { + description.add(DISABLED_SPACER + setting.getEnabledText()); + description.add(ENABLED_ARROW + setting.getDisabledText()); + } + + return description; + } + + @Override + public Material getMaterial(Player player) { + return setting.getIcon(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + if (!setting.canUpdate(player)) { + return; + } + + SettingHandler settingHandler = PotPvPSI.getInstance().getSettingHandler(); + + boolean enabled = !settingHandler.getSetting(player, setting); + settingHandler.updateSetting(player, setting, enabled); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/menu/SettingsMenu.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/menu/SettingsMenu.java new file mode 100644 index 0000000..53e7443 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/menu/SettingsMenu.java @@ -0,0 +1,50 @@ +package com.elevatemc.potpvp.setting.menu; + +import com.elevatemc.potpvp.setting.Setting; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; + +/** + * Menu used by /settings to let players toggle settings + */ +public final class SettingsMenu extends Menu { + + public SettingsMenu() { + setAutoUpdate(true); + setPlaceholder(true); + } + + @Override + public String getTitle(Player player) { + return ChatColor.BLUE + "Your Settings"; + } + + @Override + public Map getButtons(Player player) { + Map buttons = new HashMap<>(); + int index = 9; + + for (Setting setting : Setting.values()) { + if (setting.canUpdate(player)) { + index++; + if ((index + 1) % 9 == 0) index++; + if (index % 9 == 0) index++; + + buttons.put(index, new SettingButton(setting)); + } + } + + return buttons; + } + + @Override + public int size(Player player) { + Map buttons = getButtons(player); + return ((int)Math.ceil(((float)buttons.size() / 7)) + 2) * 9; + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/package-info.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/package-info.java new file mode 100644 index 0000000..15e7cd8 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/package-info.java @@ -0,0 +1,9 @@ +/** + * Handles accessing, saving, updating, and presentation of player settings. + * + * This includes the /settings command, a settings menu, persistence, etc. + * Clients using the settings API should only concern themselves with {@link com.elevatemc.potpvp.setting.event.SettingUpdateEvent}, + * {@link com.elevatemc.potpvp.setting.SettingHandler#getSetting(java.util.UUID, com.elevatemc.potpvp.setting.Setting)} and + * {@link com.elevatemc.potpvp.setting.SettingHandler#updateSetting(org.bukkit.entity.Player, com.elevatemc.potpvp.setting.Setting, boolean)}, + */ +package com.elevatemc.potpvp.setting; \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/repository/MongoSettingRepository.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/repository/MongoSettingRepository.java new file mode 100644 index 0000000..67abb1d --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/repository/MongoSettingRepository.java @@ -0,0 +1,72 @@ +package com.elevatemc.potpvp.setting.repository; + +import com.google.common.collect.ImmutableMap; +import com.mongodb.MongoException; +import com.mongodb.client.MongoCollection; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.setting.Setting; +import com.elevatemc.potpvp.util.MongoUtils; +import org.bson.Document; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public final class MongoSettingRepository implements SettingRepository { + + private static final String MONGO_COLLECTION_NAME = "playerSettings"; + + @Override + public Map loadSettings(UUID playerUuid) throws IOException { + MongoCollection settingsCollection = MongoUtils.getCollection(MONGO_COLLECTION_NAME); + Document settingsDocument; + + try { + settingsDocument = settingsCollection.find(buildQuery(playerUuid)).first(); + } catch (MongoException ex) { + throw new IOException(ex); + } + + // no settings is okay, just return an empty map + if (settingsDocument == null) { + return ImmutableMap.of(); + } + + Document rawSettings = settingsDocument.get("settings", Document.class); + Map parsedSettings = new HashMap<>(); + + rawSettings.forEach((rawSetting, value) -> { + try { + parsedSettings.put(Setting.valueOf(rawSetting), (Boolean) value); + } catch (Exception ex) { + PotPvPSI.getInstance().getLogger().info("Failed to load setting " + rawSetting + " (value=" + value + ") for " + playerUuid + "."); + } + }); + + return ImmutableMap.copyOf(parsedSettings); + } + + @Override + public void saveSettings(UUID playerUuid, Map settings) throws IOException { + MongoCollection settingsCollection = MongoUtils.getCollection(MONGO_COLLECTION_NAME); + Document settingsDocument = new Document(); + + settings.forEach((setting, value) -> { + settingsDocument.put(setting.name(), value); + }); + + Document update = new Document("$set", new Document("settings", settingsDocument)); + + try { + settingsCollection.updateOne(buildQuery(playerUuid), update, MongoUtils.UPSERT_OPTIONS); + } catch (MongoException ex) { + throw new IOException(ex); + } + } + + private Document buildQuery(UUID playerUuid) { + return new Document("_id", playerUuid.toString()); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/repository/SettingRepository.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/repository/SettingRepository.java new file mode 100644 index 0000000..8cb8410 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/setting/repository/SettingRepository.java @@ -0,0 +1,14 @@ +package com.elevatemc.potpvp.setting.repository; + +import com.elevatemc.potpvp.setting.Setting; + +import java.io.IOException; +import java.util.Map; +import java.util.UUID; + +public interface SettingRepository { + + Map loadSettings(UUID playerUuid) throws IOException; + void saveSettings(UUID playerUuid, Map settings) throws IOException; + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/statistics/StatisticsHandler.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/statistics/StatisticsHandler.java new file mode 100644 index 0000000..bc693f9 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/statistics/StatisticsHandler.java @@ -0,0 +1,225 @@ +package com.elevatemc.potpvp.statistics; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.google.common.base.MoreObjects; +import com.google.common.collect.Maps; +import com.mongodb.client.MongoCollection; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.event.MatchTerminateEvent; +import com.elevatemc.potpvp.util.MongoUtils; +import com.elevatemc.elib.util.UUIDUtils; +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableMap; +import org.bson.Document; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.Map; +import java.util.UUID; + +public class StatisticsHandler implements Listener { + + private static MongoCollection COLLECTION; + private Map>> statisticsMap; + + public StatisticsHandler() { + COLLECTION = MongoUtils.getCollection("playerStatistics"); + statisticsMap = Maps.newConcurrentMap(); + + Bukkit.getScheduler().runTaskTimerAsynchronously(PotPvPSI.getInstance(), () -> { + + long start = System.currentTimeMillis(); + statisticsMap.keySet().forEach(this::saveStatistics); + Bukkit.getLogger().info("Saved " + statisticsMap.size() + " statistics in " + (System.currentTimeMillis() - start) + "ms."); + + }, 30 * 20, 30 * 20); + } + + + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onJoin(PlayerJoinEvent event) { + Bukkit.getScheduler().runTaskAsynchronously(PotPvPSI.getInstance(), () -> { + loadStatistics(event.getPlayer().getUniqueId()); + }); + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onQuit(PlayerQuitEvent event) { + Bukkit.getScheduler().runTaskAsynchronously(PotPvPSI.getInstance(), () -> { + saveStatistics(event.getPlayer().getUniqueId()); + unloadStatistics(event.getPlayer().getUniqueId()); + }); + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onMatchEnd(MatchTerminateEvent event) { + Match match = event.getMatch(); + GameMode gameMode = match.getGameMode(); + + if (gameMode.equals(GameModes.TEAMFIGHT) || gameMode.equals(GameModes.TEAMFIGHT_DEBUFF)) return; + + match.getWinningPlayers().forEach(uuid -> { + incrementStat(uuid, Statistic.WINS, match.getGameMode()); + }); + + match.getLosingPlayers().forEach(uuid -> { + incrementStat(uuid, Statistic.LOSSES, match.getGameMode()); + }); + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onDeath(PlayerDeathEvent event) { + Player died = event.getEntity(); + Player killer = died.getKiller(); + + Match diedMatch = PotPvPSI.getInstance().getMatchHandler().getMatchPlayingOrSpectating(died); + + if (diedMatch == null) { + return; + } + + GameMode gameMode = diedMatch.getGameMode(); + if (gameMode.equals(GameModes.TEAMFIGHT) || gameMode.equals(GameModes.TEAMFIGHT_DEBUFF)) { + return; + } + + incrementStat(died.getUniqueId(), Statistic.DEATHS, diedMatch.getGameMode()); + + if (killer != null) { + incrementStat(killer.getUniqueId(), Statistic.KILLS, diedMatch.getGameMode()); + } + } + + public void loadStatistics(UUID uuid) { + Document document = COLLECTION.find(new Document("_id", uuid.toString())).first(); + + if (document == null) { + document = new Document(); + } + + document.put("lastUsername", UUIDUtils.name(uuid)); + + final Document finalDocument = document; + Map> subStatisticsMap = Maps.newHashMap(); + + GameMode.getAll().forEach(gameMode -> { + Document subStatisticsDocument = finalDocument.containsKey(gameMode.getId()) ? finalDocument.get(gameMode.getId(), Document.class) : new Document(); + + Map statsMap = Maps.newHashMap(); + for (Statistic statistic : Statistic.values()) { + Double value = Objects.firstNonNull(subStatisticsDocument.get(statistic.name(), Double.class), 0D); + statsMap.put(statistic, value); + } + + subStatisticsMap.put(gameMode.getId(), statsMap); + }); + + if (finalDocument.containsKey("GLOBAL")) { + Document subStatisticsDocument = finalDocument.containsKey("GLOBAL") ? finalDocument.get("GLOBAL", Document.class) : new Document(); + + Map statsMap = Maps.newHashMap(); + for (Statistic statistic : Statistic.values()) { + Double value = Objects.firstNonNull(subStatisticsDocument.get(statistic.name(), Double.class), 0D); + statsMap.put(statistic, value); + } + + subStatisticsMap.put("GLOBAL", statsMap); + } else { + subStatisticsMap.put("GLOBAL", Maps.newHashMap()); + } + + statisticsMap.put(uuid, subStatisticsMap); + } + + public void saveStatistics(UUID uuid) { + Map> subMap = statisticsMap.get(uuid); + if (subMap == null) { + return; + } + + Document toInsert = new Document(); + subMap.entrySet().forEach(entry -> { + Document typeStats = new Document(); + entry.getValue().entrySet().forEach(subEntry -> { + typeStats.put(subEntry.getKey().name(), subEntry.getValue()); + }); + + toInsert.put(entry.getKey(), typeStats); + }); + + toInsert.put("lastUsername", UUIDUtils.name(uuid)); + + COLLECTION.updateOne(new Document("_id", uuid.toString()), new Document("$set", toInsert), MongoUtils.UPSERT_OPTIONS); + } + + public void unloadStatistics(UUID uuid) { + statisticsMap.remove(uuid); + } + + public void incrementStat(UUID uuid, Statistic statistic, GameMode gameMode) { + boolean shouldUpdateWLR = statistic == Statistic.WINS || statistic == Statistic.LOSSES; + boolean shouldUpdateKDR = statistic == Statistic.KILLS || statistic == Statistic.DEATHS; + + if (!statisticsMap.containsKey(uuid)) return; // not loaded, so prob offline so it won't save anyway + + incrementEntry(uuid, gameMode.getId(), statistic); + incrementEntry(uuid, "GLOBAL", statistic); + + if (shouldUpdateWLR) { + recalculateWLR(uuid, gameMode); + } else if (shouldUpdateKDR) { + recalculateKDR(uuid, gameMode); + } + } + + private void recalculateWLR(UUID uuid, GameMode gameMode) { + double totalWins = getStat(uuid, Statistic.WINS, gameMode.getId()); + double totalLosses = getStat(uuid, Statistic.LOSSES, gameMode.getId()); + + double ratio = totalWins / Math.max(totalLosses, 1); + statisticsMap.get(uuid).get(gameMode.getId()).put(Statistic.WLR, ratio); + + totalWins = getStat(uuid, Statistic.WINS, "GLOBAL"); + totalLosses = getStat(uuid, Statistic.LOSSES, "GLOBAL"); + + ratio = totalWins / Math.max(totalLosses, 1); + statisticsMap.get(uuid).get("GLOBAL").put(Statistic.WLR, ratio); + } + + private void recalculateKDR(UUID uuid, GameMode gameMode) { + double totalKills = getStat(uuid, Statistic.KILLS, gameMode.getId()); + double totalDeaths = getStat(uuid, Statistic.DEATHS, gameMode.getId()); + + double ratio = totalKills / Math.max(totalDeaths, 1); + statisticsMap.get(uuid).get(gameMode.getId()).put(Statistic.KDR, ratio); + + totalKills = getStat(uuid, Statistic.KILLS, "GLOBAL"); + totalDeaths = getStat(uuid, Statistic.DEATHS, "GLOBAL"); + + ratio = totalKills / Math.max(totalDeaths, 1); + statisticsMap.get(uuid).get("GLOBAL").put(Statistic.KDR, ratio); + } + + private void incrementEntry(UUID uuid, String primaryKey, Statistic statistic) { + Map subMap = statisticsMap.get(uuid).get(primaryKey); + subMap.put(statistic, subMap.getOrDefault(statistic, 0D) + 1); + } + + public double getStat(UUID uuid, Statistic statistic, String gameMode) { + return Objects.firstNonNull(statisticsMap.getOrDefault(uuid, ImmutableMap.of()).getOrDefault(gameMode, ImmutableMap.of()).get(statistic), 0D); + } + + private enum Statistic { + WINS, LOSSES, WLR, KILLS, DEATHS, KDR + } + + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/tab/HeaderLayoutProvider.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/tab/HeaderLayoutProvider.java new file mode 100644 index 0000000..c4bab3c --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/tab/HeaderLayoutProvider.java @@ -0,0 +1,25 @@ +package com.elevatemc.potpvp.tab; + + +import com.elevatemc.potpvp.PotPvPSI; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.function.BiConsumer; + +final class HeaderLayoutProvider implements BiConsumer { + + @Override + public void accept(Player player, PotPvPLayoutProvider.TabLayout tabLayout) { + header: { + tabLayout.put(1, 0, ChatColor.DARK_AQUA.toString() + ChatColor.BOLD + "Practice"); + } + + status: { + tabLayout.put(0, 1, ChatColor.GRAY + "Online: " + PotPvPSI.getInstance().getOnlineCount()); +// tabLayout.put(1, 1, ChatColor.GRAY + "Your Connection", PotPvPLayoutProvider.getPingOrDefault(player.getUniqueId())); + tabLayout.put(2, 1, ChatColor.GRAY + "Fighting: " + PotPvPSI.getInstance().getFightsCount()); + } + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/tab/LobbyLayoutProvider.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/tab/LobbyLayoutProvider.java new file mode 100644 index 0000000..0a64c58 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/tab/LobbyLayoutProvider.java @@ -0,0 +1,184 @@ +package com.elevatemc.potpvp.tab; + +import com.elevatemc.elib.util.TimeUtils; +import com.elevatemc.potpvp.hctranked.game.RankedGame; +import com.elevatemc.potpvp.hctranked.game.RankedGameTeam; +import com.elevatemc.potpvp.tournament.Tournament; +import com.google.common.collect.Sets; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.elo.EloHandler; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.elib.util.UUIDUtils; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.LinkedHashMap; +import java.util.Set; +import java.util.UUID; +import java.util.function.BiConsumer; + +final class LobbyLayoutProvider implements BiConsumer { + + @Override + public void accept(Player player, PotPvPLayoutProvider.TabLayout tabLayout) { + EloHandler eloHandler = PotPvPSI.getInstance().getEloHandler(); + Party party = PotPvPSI.getInstance().getPartyHandler().getParty(player); + Tournament tournament = PotPvPSI.getInstance().getTournamentHandler().getTournament(); + RankedGame game = PotPvPSI.getInstance().getHCTRankedHandler().getGameHandler().getJoinedGame(player); + + int y = 3; + + rankings: { + tabLayout.put(1, y++, ChatColor.DARK_AQUA.toString() + ChatColor.BOLD + "Your Rankings"); + + int x = 0; + + for (GameMode gameMode : GameMode.getAll()) { + if (!gameMode.getSupportsCompetitive()) { + continue; + } + + tabLayout.put(x++, y, ChatColor.DARK_AQUA + gameMode.getName() + ChatColor.GRAY + " - " + ChatColor.WHITE + eloHandler.getElo(player, gameMode)); + + if (x == 3) { + x = 0; + y++; + } + } + } + + party: { + if (party == null) { + break party; + } + + y += 2; + + tabLayout.put(1, y++, ChatColor.DARK_AQUA.toString() + ChatColor.BOLD + "Your Party"); + + int x = 0; + + for (UUID member : getOrderedMembers(player, party)) { + int ping = PotPvPLayoutProvider.getPingOrDefault(member); + String suffix = member == party.getLeader() ? ChatColor.GRAY + "*" : ""; + String displayName = ChatColor.BLUE + UUIDUtils.name(member) + suffix; + + tabLayout.put(x++, y, displayName/*, ping*/); + + if (x == 3 && y == PotPvPLayoutProvider.MAX_TAB_Y) { + break; + } + + if (x == 3) { + x = 0; + y++; + } + } + } + + tournament: { + if (tournament == null) { + break tournament; + } + + y += 2; + + tabLayout.put(1, y++, ChatColor.DARK_AQUA.toString() + ChatColor.BOLD + "Tournament"); + if (tournament.getStage() == Tournament.TournamentStage.WAITING_FOR_TEAMS) { + tabLayout.put(1, y, ChatColor.GRAY + "Waiting..."); + } else if (tournament.getStage() == Tournament.TournamentStage.COUNTDOWN) { + if (tournament.getCurrentRound() == 0) { + tabLayout.put(1, y, ChatColor.GRAY + "Starts: &f" + TimeUtils.formatIntoMMSS(tournament.getBeginNextRoundIn())); + } else { + tabLayout.put(1, y, ChatColor.GRAY + "Next Round: " + TimeUtils.formatIntoMMSS(tournament.getBeginNextRoundIn())); + } + } else if (tournament.getStage() == Tournament.TournamentStage.IN_PROGRESS) { + int teamSize = tournament.getRequiredPartySize(); + int multiplier = teamSize < 3 ? teamSize : 1; + + tabLayout.put(0, y, ChatColor.GRAY + "Round" + ": " + tournament.getCurrentRound()); + tabLayout.put(1, y, ChatColor.GRAY + (teamSize < 3 ? "Players" : "Teams") + ": " + tournament.getActiveParties().size() * multiplier); + tabLayout.put(2, y, ChatColor.GRAY + "Duration: " + TimeUtils.formatIntoMMSS((int) (System.currentTimeMillis() - tournament.getRoundStartedAt()) / 1000)); + } + + } + + rankedgame: { + if (game == null) { + break rankedgame; + } + + y += 2; + + tabLayout.put(1, y++, ChatColor.DARK_AQUA.toString() + ChatColor.BOLD + "Ranked Team"); + + int x = 0; + + for (UUID member : getOrderedPlayers(player, game.getTeam(player))) { + int ping = PotPvPLayoutProvider.getPingOrDefault(member); + String suffix = member == game.getTeam(player).getCaptain() ? ChatColor.GRAY + "*" : ""; + String displayName = (game.getJoinedPlayers().contains(member) ? ChatColor.GREEN : ChatColor.GRAY) + UUIDUtils.name(member) + suffix; + + tabLayout.put(x++, y, displayName/*, ping*/); + + if (x == 3 && y == PotPvPLayoutProvider.MAX_TAB_Y) { + break; + } + + if (x == 3) { + x = 0; + y++; + } + } + } + } + + // player first, leader next, then all other members + private Set getOrderedMembers(Player viewer, Party party) { + Set orderedMembers = Sets.newSetFromMap(new LinkedHashMap<>()); + UUID leader = party.getLeader(); + + orderedMembers.add(viewer.getUniqueId()); + + // if they're the leader we don't display them twice + if (viewer.getUniqueId() != leader) { + orderedMembers.add(leader); + } + + for (UUID member : party.getMembers()) { + // don't display the leader or the viewer again + if (member == leader || member == viewer.getUniqueId()) { + continue; + } + + orderedMembers.add(member); + } + + return orderedMembers; + } + + private Set getOrderedPlayers(Player viewer, RankedGameTeam team) { + Set orderedMembers = Sets.newSetFromMap(new LinkedHashMap<>()); + UUID captain = team.getCaptain(); + + orderedMembers.add(viewer.getUniqueId()); + + // if they're the leader we don't display them twice + if (viewer.getUniqueId() != captain) { + orderedMembers.add(captain); + } + + for (UUID member : team.getPlayers()) { + // don't display the leader or the viewer again + if (member == captain || member == viewer.getUniqueId()) { + continue; + } + + orderedMembers.add(member); + } + + return orderedMembers; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/tab/MatchParticipantLayoutProvider.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/tab/MatchParticipantLayoutProvider.java new file mode 100644 index 0000000..17af3b4 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/tab/MatchParticipantLayoutProvider.java @@ -0,0 +1,210 @@ +package com.elevatemc.potpvp.tab; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.elib.util.UUIDUtils; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.*; +import java.util.function.BiConsumer; + +final class MatchParticipantLayoutProvider implements BiConsumer { + + @Override + public void accept(Player player, PotPvPLayoutProvider.TabLayout tabLayout) { + Match match = PotPvPSI.getInstance().getMatchHandler().getMatchPlaying(player); + List teams = match.getTeams(); + + // if it's one team versus another + if (teams.size() == 2) { + // this method won't be called if the player isn't a participant + MatchTeam ourTeam = match.getTeam(player.getUniqueId()); + MatchTeam otherTeam = teams.get(0) == ourTeam ? teams.get(1) : teams.get(0); + + boolean duel = ourTeam.getAllMembers().size() == 1 && otherTeam.getAllMembers().size() == 1; + + { + // Column 1 + // we handle duels a bit differently + if (!duel) { + tabLayout.put(0, 3, ChatColor.GREEN + ChatColor.BOLD.toString() + "Team " + ChatColor.GREEN + "(" + ourTeam.getAliveMembers().size() + "/" + ourTeam.getAllMembers().size() + ")"); + } else { + tabLayout.put(0, 3, ChatColor.GREEN + ChatColor.BOLD.toString() + "You"); + } + renderTeamMemberOverviewEntries(tabLayout, ourTeam, 0, 4, ChatColor.GREEN); + } + + { + // Column 3 + // we handle duels a bit differently + if (!duel) { + tabLayout.put(2, 3, ChatColor.RED + ChatColor.BOLD.toString() + "Enemies " + ChatColor.RED + "(" + otherTeam.getAliveMembers().size() + "/" + otherTeam.getAllMembers().size() + ")"); + } else { + tabLayout.put(2, 3, ChatColor.RED + ChatColor.BOLD.toString() + "Opponent"); + } + renderTeamMemberOverviewEntries(tabLayout, otherTeam, 2, 4, ChatColor.RED); + } + } else { // it's an FFA or something else like that + tabLayout.put(1, 3, ChatColor.BLUE + ChatColor.BOLD.toString() + "Party FFA"); + + int x = 0; + int y = 4; + + Map entries = new LinkedHashMap<>(); + + MatchTeam ourTeam = match.getTeam(player.getUniqueId()); + + { + // this is where we'll be adding our team members + + Map aliveLines = new LinkedHashMap<>(); + Map deadLines = new LinkedHashMap<>(); + + // separate lists to sort alive players before dead + // + color differently + for (UUID teamMember : ourTeam.getAllMembers()) { + if (ourTeam.isAlive(teamMember)) { + aliveLines.put(ChatColor.GREEN + UUIDUtils.name(teamMember), PotPvPLayoutProvider.getPingOrDefault(teamMember)); + } else { + deadLines.put("&7&m" + UUIDUtils.name(teamMember), PotPvPLayoutProvider.getPingOrDefault(teamMember)); + } + } + + entries.putAll(aliveLines); + entries.putAll(deadLines); + } + + { + // this is where we'll be adding everyone else + Map deadLines = new LinkedHashMap<>(); + + for (MatchTeam otherTeam : match.getTeams()) { + if (otherTeam == ourTeam) { + continue; + } + + // separate lists to sort alive players before dead + // + color differently + for (UUID enemy : otherTeam.getAllMembers()) { + if (otherTeam.isAlive(enemy)) { + entries.put(ChatColor.RED + UUIDUtils.name(enemy), PotPvPLayoutProvider.getPingOrDefault(enemy)); + } else { + deadLines.put("&7&m" + UUIDUtils.name(enemy), PotPvPLayoutProvider.getPingOrDefault(enemy)); + } + } + } + + entries.putAll(deadLines); + } + + List> result = new ArrayList<>(entries.entrySet()); + + // actually display our entries + for (int index = 0; index < result.size(); index++) { + Map.Entry entry = result.get(index); + + tabLayout.put(x++, y, entry.getKey()); + + if (x == 3 && y == PotPvPLayoutProvider.MAX_TAB_Y) { + // if we're at the last slot, we want to see if we still have alive players to show + int aliveLeft = 0; + + for (int i = index; i < result.size(); i++) { + String currentEntry = result.get(i).getKey(); + boolean dead = ChatColor.getLastColors(currentEntry).equals(ChatColor.GRAY + ChatColor.STRIKETHROUGH.toString()); + + if (!dead) { + aliveLeft++; + } + } + + if (aliveLeft != 0 && aliveLeft != 1) { + // if there are players we weren't able to show and if it's more than one + // (if it's only one they'll be shown as the last entry [see 17 lines above]), display the number + // of alive players we weren't able to show instead. + tabLayout.put(x, y, ChatColor.GREEN + "+" + aliveLeft); + } + + break; + } + + if (x == 3) { + x = 0; + y++; + } + } + } + } + + private void renderTeamMemberOverviewEntries(PotPvPLayoutProvider.TabLayout layout, MatchTeam team, int column, int start, ChatColor color) { + List> result = new ArrayList<>(renderTeamMemberOverviewLines(team, color).entrySet()); + + // how many spots we have left + int spotsLeft = PotPvPLayoutProvider.MAX_TAB_Y - start; + + // we could've used the 'start' variable, but we create a new one for readability. + int y = start; + + for (int index = 0; index < result.size(); index++) { + Map.Entry entry = result.get(index); + + // we check if we only have 1 more spot to show + if (spotsLeft == 1) { + // if so, count how many alive players we have left to show + int aliveLeft = 0; + + for (int i = index; i < result.size(); i++) { + String currentEntry = result.get(i).getKey(); + boolean dead = !ChatColor.getLastColors(currentEntry).equals(color.toString()); + + if (!dead) { + aliveLeft++; + } + } + + // if we have any + if (aliveLeft != 0) { + if (aliveLeft == 1) { + // if it's only one, we display them as the last entry + layout.put(column, y, entry.getKey()); + } else { + // if it's more than one, display a number of how many we couldn't display. + layout.put(column, y, color + "+" + aliveLeft); + } + } + + break; + } + + // if not, just display the entry. + layout.put(column, y, entry.getKey()); + y++; + spotsLeft--; + } + } + + private Map renderTeamMemberOverviewLines(MatchTeam team, ChatColor aliveColor) { + Map aliveLines = new LinkedHashMap<>(); + Map deadLines = new LinkedHashMap<>(); + + for (UUID member : team.getAllMembers()) { + int ping = PotPvPLayoutProvider.getPingOrDefault(member); + + if (team.isAlive(member)) { + aliveLines.put(aliveColor + UUIDUtils.name(member), ping); + } else { + deadLines.put("&7&m" + UUIDUtils.name(member), ping); + } + } + + Map result = new LinkedHashMap<>(); + + result.putAll(aliveLines); + result.putAll(deadLines); + + return result; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/tab/MatchSpectatorLayoutProvider.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/tab/MatchSpectatorLayoutProvider.java new file mode 100644 index 0000000..e038766 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/tab/MatchSpectatorLayoutProvider.java @@ -0,0 +1,238 @@ +package com.elevatemc.potpvp.tab; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.elib.util.UUIDUtils; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.*; +import java.util.function.BiConsumer; + +final class MatchSpectatorLayoutProvider implements BiConsumer { + + @Override + public void accept(Player player, PotPvPLayoutProvider.TabLayout tabLayout) { + Match match = PotPvPSI.getInstance().getMatchHandler().getMatchSpectating(player); + MatchTeam oldTeam = match.getTeam(player.getUniqueId()); + List teams = match.getTeams(); + + // if it's one team versus another + if (teams.size() == 2) { + MatchTeam teamOne = teams.get(0); + MatchTeam teamTwo = teams.get(1); + + boolean duel = teamOne.getAllMembers().size() == 1 && teamTwo.getAllMembers().size() == 1; + + // first, we want to check if they were a part of the match and died, and if so, render the tab differently. + if (oldTeam != null) { + // if they were, it means it couldn't have been a duel, so we don't check for that below. + MatchTeam ourTeam = teamOne == oldTeam ? teamOne : teamTwo; + MatchTeam otherTeam = teamOne == ourTeam ? teamTwo : teamOne; + + { + // Column 1 + if (!duel) { + tabLayout.put(0, 3, ChatColor.GREEN + ChatColor.BOLD.toString() + "Team " + ChatColor.GREEN + "(" + ourTeam.getAliveMembers().size() + "/" + ourTeam.getAllMembers().size() + ")"); + } else { + tabLayout.put(0, 3, ChatColor.GREEN + ChatColor.BOLD.toString() + "You"); + } + renderTeamMemberOverviewEntries(tabLayout, ourTeam, 0, 4, ChatColor.GREEN); + } + + { + // Column 3 + if (!duel) { + tabLayout.put(2, 3, ChatColor.RED + ChatColor.BOLD.toString() + "Enemies " + ChatColor.RED + "(" + otherTeam.getAliveMembers().size() + "/" + otherTeam.getAllMembers().size() + ")"); + } else { + tabLayout.put(2, 3, ChatColor.RED + ChatColor.BOLD.toString() + "Opponent"); + } + renderTeamMemberOverviewEntries(tabLayout, otherTeam, 2, 4, ChatColor.RED); + } + + } else { + + { + // Column 1 + // we handle duels a bit differently + if (!duel) { + tabLayout.put(0, 3, ChatColor.LIGHT_PURPLE + ChatColor.BOLD.toString() + "Team One (" + teamOne.getAliveMembers().size() + "/" + teamOne.getAllMembers().size() + ")"); + } else { + tabLayout.put(0, 3, ChatColor.LIGHT_PURPLE + ChatColor.BOLD.toString() + "Player One"); + } + renderTeamMemberOverviewEntries(tabLayout, teamOne, 0, 4, ChatColor.LIGHT_PURPLE); + } + + { + // Column 3 + // we handle duels a bit differently + if (!duel) { + tabLayout.put(2, 3, ChatColor.AQUA + ChatColor.BOLD.toString() + "Team Two (" + teamTwo.getAliveMembers().size() + "/" + teamTwo.getAllMembers().size() + ")"); + } else { + tabLayout.put(2, 3, ChatColor.AQUA + ChatColor.BOLD.toString() + "Player Two"); + } + renderTeamMemberOverviewEntries(tabLayout, teamTwo, 2, 4, ChatColor.AQUA); + } + + } + } else { // it's an FFA or something else like that + tabLayout.put(1, 3, ChatColor.BLUE + ChatColor.BOLD.toString() + "Party FFA"); + + int x = 0; + int y = 4; + + Map entries = new LinkedHashMap<>(); + + if (oldTeam != null) { + // if they were a part of this match, we want to render it like we would for an alive player, showing their team-mates first and in green. + entries = renderTeamMemberOverviewLines(oldTeam, ChatColor.GREEN); + + { + // this is where we'll be adding everyone else + Map deadLines = new LinkedHashMap<>(); + + for (MatchTeam otherTeam : match.getTeams()) { + if (otherTeam == oldTeam) { + continue; + } + + // separate lists to sort alive players before dead + // + color differently + for (UUID enemy : otherTeam.getAllMembers()) { + if (otherTeam.isAlive(enemy)) { + entries.put(ChatColor.RED + UUIDUtils.name(enemy), PotPvPLayoutProvider.getPingOrDefault(enemy)); + } else { + deadLines.put("&7&m" + UUIDUtils.name(enemy), PotPvPLayoutProvider.getPingOrDefault(enemy)); + } + } + } + + entries.putAll(deadLines); + } + } else { + // if they're just a random spectator, we'll pick different colors for each team. + Map deadLines = new LinkedHashMap<>(); + + for (MatchTeam team : match.getTeams()) { + for (UUID enemy : team.getAllMembers()) { + if (team.isAlive(enemy)) { + entries.put("&c" + UUIDUtils.name(enemy), PotPvPLayoutProvider.getPingOrDefault(enemy)); + } else { + deadLines.put("&7&m" + UUIDUtils.name(enemy), PotPvPLayoutProvider.getPingOrDefault(enemy)); + } + } + } + + entries.putAll(deadLines); + } + + List> result = new ArrayList<>(entries.entrySet()); + + // actually display our entries + for (int index = 0; index < result.size(); index++) { + Map.Entry entry = result.get(index); + + tabLayout.put(x++, y, entry.getKey()); + + if (x == 3 && y == PotPvPLayoutProvider.MAX_TAB_Y) { + // if we're at the last slot, we want to see if we still have alive players to show + int aliveLeft = 0; + + for (int i = index; i < result.size(); i++) { + String currentEntry = result.get(i).getKey(); + boolean dead = ChatColor.getLastColors(currentEntry).equals(ChatColor.GRAY + ChatColor.STRIKETHROUGH.toString()); + + if (!dead) { + aliveLeft++; + } + } + + if (aliveLeft != 0 && aliveLeft != 1) { + // if there are players we weren't able to show and if it's more than one + // (if it's only one they'll be shown as the last entry [see 17 lines above]), display the number + // of alive players we weren't able to show instead. + tabLayout.put(x, y, ChatColor.GREEN + "+" + aliveLeft); + } + + break; + } + + if (x == 3) { + x = 0; + y++; + } + } + } + } + + private void renderTeamMemberOverviewEntries(PotPvPLayoutProvider.TabLayout layout, MatchTeam team, int column, int start, ChatColor color) { + List> result = new ArrayList<>(renderTeamMemberOverviewLines(team, color).entrySet()); + + // how many spots we have left + int spotsLeft = PotPvPLayoutProvider.MAX_TAB_Y - start; + + // we could've used the 'start' variable, but we create a new one for readability. + int y = start; + + for (int index = 0; index < result.size(); index++) { + Map.Entry entry = result.get(index); + + // we check if we only have 1 more spot to show + if (spotsLeft == 1) { + // if so, count how many alive players we have left to show + int aliveLeft = 0; + + for (int i = index; i < result.size(); i++) { + String currentEntry = result.get(i).getKey(); + boolean dead = !ChatColor.getLastColors(currentEntry).equals(color.toString()); + + if (!dead) { + aliveLeft++; + } + } + + // if we have any + if (aliveLeft != 0) { + if (aliveLeft == 1) { + // if it's only one, we display them as the last entry + layout.put(column, y, entry.getKey()); + } else { + // if it's more than one, display a number of how many we couldn't display. + layout.put(column, y, color + "+" + aliveLeft); + } + } + + break; + } + + // if not, just display the entry. + layout.put(column, y, entry.getKey()); + y++; + spotsLeft--; + } + } + + private Map renderTeamMemberOverviewLines(MatchTeam team, ChatColor aliveColor) { + Map aliveLines = new LinkedHashMap<>(); + Map deadLines = new LinkedHashMap<>(); + + for (UUID member : team.getAllMembers()) { + int ping = PotPvPLayoutProvider.getPingOrDefault(member); + + if (team.isAlive(member)) { + aliveLines.put(aliveColor + UUIDUtils.name(member), ping); + } else { + deadLines.put("&7&m" + UUIDUtils.name(member), ping); + } + } + + Map result = new LinkedHashMap<>(); + + result.putAll(aliveLines); + result.putAll(deadLines); + + return result; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/tab/PotPvPLayoutProvider.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/tab/PotPvPLayoutProvider.java new file mode 100644 index 0000000..407d318 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/tab/PotPvPLayoutProvider.java @@ -0,0 +1,78 @@ +package com.elevatemc.potpvp.tab; + +import com.elevatemc.elib.tab.provider.TabProvider; +import com.elevatemc.elib.util.ChatUtils; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.elib.util.PlayerUtils; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Table; +import com.mojang.authlib.properties.Property; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.UUID; +import java.util.function.BiConsumer; + +public final class PotPvPLayoutProvider implements TabProvider { + + static final int MAX_TAB_Y = 20; + + private final BiConsumer headerLayoutProvider = new HeaderLayoutProvider(); + private final BiConsumer lobbyLayoutProvider = new LobbyLayoutProvider(); + private final BiConsumer matchSpectatorLayoutProvider = new MatchSpectatorLayoutProvider(); + private final BiConsumer matchParticipantLayoutProvider = new MatchParticipantLayoutProvider(); + + @Override + public Table provide(Player player) { + TabLayout tabLayout = new TabLayout(); + if (PotPvPSI.getInstance() == null) return tabLayout.build(); + + Match match = PotPvPSI.getInstance().getMatchHandler().getMatchPlayingOrSpectating(player); + headerLayoutProvider.accept(player, tabLayout); + + if (match != null) { + if (match.isSpectator(player.getUniqueId())) { + matchSpectatorLayoutProvider.accept(player, tabLayout); + } else { + matchParticipantLayoutProvider.accept(player, tabLayout); + } + } else { + lobbyLayoutProvider.accept(player, tabLayout); + } + + return tabLayout.build(); + } + + @Override + public String getFooter(Player player) { + return "e"; + } + + @Override + public String getHeader(Player player) { + return "e"; + } + + static int getPingOrDefault(UUID check) { + Player player = Bukkit.getPlayer(check); + return player != null ? PlayerUtils.getPing(player) : 0; + } + public static class TabLayout { + private final Table layout; + public TabLayout() { + layout = HashBasedTable.create(); + for(int r = 0; r < 20; ++r) { + for (int c = 0; c < 4; ++c) { + layout.put(r, c, " "); + } + } + } + public void put(Integer var1, Integer var2, String var3) { + layout.put(var2, var1, ChatUtils.colorize(var3)); + } + public Table build() { + return layout; + } + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/tournament/Tournament.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/tournament/Tournament.java new file mode 100644 index 0000000..f511ea1 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/tournament/Tournament.java @@ -0,0 +1,342 @@ +package com.elevatemc.potpvp.tournament; + +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.elevatemc.potpvp.setting.Setting; +import com.elevatemc.potpvp.util.Color; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import lombok.Getter; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchState; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.potpvp.setting.SettingHandler; +import com.elevatemc.potpvp.util.PatchedPlayerUtils; +import net.md_5.bungee.api.chat.*; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.*; +import java.util.stream.Collectors; + +public class Tournament { + + // TODO: EDIT WHOLE CLASS'S STYLING + + @Getter private int currentRound = -1; + + @Getter private List activeParties = Lists.newArrayList(); + private List lost = Lists.newArrayList(); + + @Getter private int requiredPartySize; + @Getter private GameMode type; + @Getter private int bards; + @Getter private int archers; + + @Getter private List matches = Lists.newArrayList(); + + @Getter private int beginNextRoundIn = 31; + + // We do this because players can leave a party or the server during the tournament + // We will need to ensure that at the end of the tournament we clear this + // (or make sure the Tournament object is unreachable) + private Map partyMap = Maps.newHashMap(); + + @Getter private TournamentStage stage = TournamentStage.WAITING_FOR_TEAMS; + + @Getter private long roundStartedAt; + + public Tournament(GameMode type, int partySize) { + this.type = type; + this.requiredPartySize = partySize; + this.bards = 0; + this.archers = 0; + } + + public Tournament(GameMode type, int partySize, int bards, int archers) { + this.type = type; + this.requiredPartySize = partySize; + this.bards = bards; + this.archers = archers; + } + + public void addParty(Party party) { + activeParties.add(party); + checkActiveParties(); + joinedTournament(party); + } + + public boolean isInTournament(Party party) { + return activeParties.contains(party); + } + + public void check() { + checkActiveParties(); + populatePartyMap(); + checkMatches(); + + if (matches.stream().anyMatch(s -> s != null && s.getState() != MatchState.TERMINATED)) return; // We don't want to advance to the next round if any matches are ongoing + matches.clear(); + + if (currentRound == -1) return; + + if (activeParties.isEmpty()) { + if (lost.isEmpty()) { + stage = TournamentStage.FINISHED; + PotPvPSI.getInstance().getTournamentHandler().setTournament(null); + return; + } + + // shouldn't happen, meant that the two last parties disconnected at the last second + Bukkit.broadcastMessage(Color.translate("&cThe tournament's last two teams forfeited. Winner by default: " + PatchedPlayerUtils.getFormattedName((lost.get(lost.size() - 1)).getLeader()) + "'s team!")); + PotPvPSI.getInstance().getTournamentHandler().setTournament(null); // Removes references to this tournament, will get cleaned up by GC + stage = TournamentStage.FINISHED; + return; + } + + if (activeParties.size() == 1) { + Party party = activeParties.get(0); + if (party.getMembers().size() == 1) { + repeatMessage(Color.translate("&3&l" + PatchedPlayerUtils.getFormattedName(party.getLeader()) + " &fwon the tournament!"), 4, 2); + } else if (party.getMembers().size() == 2) { + Iterator membersIterator = party.getMembers().iterator(); + UUID[] members = new UUID[] { membersIterator.next(), membersIterator.next() }; + repeatMessage(Color.translate("&3&l" + PatchedPlayerUtils.getFormattedName(members[0]) + " &fand &3&l" + PatchedPlayerUtils.getFormattedName(members[1]) + " &fwon the tournament!"), 4, 2); + } else { + repeatMessage(Color.translate("&3&l" + PatchedPlayerUtils.getFormattedName(party.getLeader()) + "&f's team won the tournament!"), 4, 2); + } + + activeParties.clear(); + PotPvPSI.getInstance().getTournamentHandler().setTournament(null); + stage = TournamentStage.FINISHED; + return; + } + + if (--beginNextRoundIn >= 1) { + switch (beginNextRoundIn) { + case 30: + case 15: + case 10: + case 5: + case 4: + case 3: + case 2: + case 1: + if (currentRound == 0) { + int teamSize = this.getRequiredPartySize(); + int multiplier = teamSize < 3 ? teamSize : 1; + + Bukkit.broadcastMessage(""); + Bukkit.broadcastMessage(Color.translate("&3&lElevate Tournament")); + Bukkit.broadcastMessage(""); + Bukkit.broadcastMessage(Color.translate("&fMode: &3" + type.getName())); + Bukkit.broadcastMessage(Color.translate("&fStarting: &3" + beginNextRoundIn + " &3second" + (beginNextRoundIn == 1 ? "" : "s") + ".")); + Bukkit.broadcastMessage(""); + } else { + Bukkit.broadcastMessage(Color.translate("&3&lRound " + (currentRound + 1) + " &fwill begin in &3" + beginNextRoundIn + " &fsecond" + (beginNextRoundIn == 1 ? "" : "s") + ".")); + } + } + + stage = TournamentStage.COUNTDOWN; + return; + } + + startRound(); + } + + private void checkActiveParties() { + Set realParties = PotPvPSI.getInstance().getPartyHandler().getParties().stream().map(Party::getPartyId).collect(Collectors.toSet()); + Iterator activePartyIterator = activeParties.iterator(); + while (activePartyIterator.hasNext()) { + Party activeParty = activePartyIterator.next(); + if (!realParties.contains(activeParty.getPartyId())) { + activePartyIterator.remove(); + + if (!lost.contains(activeParty)) { + lost.add(activeParty); + } + } + } + } + + private void repeatMessage(String message, int times, int interval) { + new BukkitRunnable() { + + private int runs = times; + + @Override + public void run() { + if (0 <= --runs) { + Bukkit.broadcastMessage(message); + } else { + cancel(); + } + } + }.runTaskTimer(PotPvPSI.getInstance(), 0, interval * 20); + } + + public void start() { + if (currentRound == -1) { + currentRound = 0; + } + } + + private void joinedTournament(Party party) { + broadcastJoinMessage(party); + } + + private void populatePartyMap() { + activeParties.forEach(p -> p.getMembers().forEach(u -> { + partyMap.put(u, p); + })); + } + + private void startRound() { + beginNextRoundIn = 31; + // Next round has begun... + + Bukkit.broadcastMessage(Color.translate("&3&lRound " + ++currentRound + " &fhas begun. Good luck!")); + Bukkit.broadcastMessage(Color.translate("&fUse &3/status &fto see who is fighting.")); + + List oldPartyList = Lists.newArrayList(activeParties); + Collections.shuffle(oldPartyList); + // Doing it this way will ensure that the tournament runs BUT if one party + // disconnects every round, the bottom party could get to the final round without + // winning a single duel. Could shuffle? But would remove the predictability & pseudo-bracket system + while (1 < oldPartyList.size()) { + Party firstParty = oldPartyList.remove(0); + Party secondParty = oldPartyList.remove(0); + + matches.add(PotPvPSI.getInstance().getMatchHandler().startMatch(ImmutableList.of(new MatchTeam(firstParty.getMembers()), new MatchTeam(secondParty.getMembers())), type, null, false, false)); + } + + if (oldPartyList.size() == 1) { + oldPartyList.get(0).message(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "There were an odd number of teams in this round - so your team has advanced to the next round."); + } + + stage = TournamentStage.IN_PROGRESS; + roundStartedAt = System.currentTimeMillis(); + } + + private void checkMatches() { + Iterator matchIterator = matches.iterator(); + while (matchIterator.hasNext()) { + Match match = matchIterator.next(); + if (match == null) { + matchIterator.remove(); + continue; + } + + if (match.getState() != MatchState.TERMINATED) continue; + MatchTeam winner = match.getWinner(); + List losers = Lists.newArrayList(match.getTeams()); + losers.remove(winner); + MatchTeam loser = losers.get(0); + Party loserParty = partyMap.get(loser.getFirstMember()); + if (loserParty != null) { + activeParties.remove(loserParty); + broadcastEliminationMessage(loserParty); + lost.add(loserParty); + matchIterator.remove(); + } + } + } + + public void broadcastJoinMessage() { + int teamSize = this.getRequiredPartySize(); + + if (this.getCurrentRound() != -1) return; + + Bukkit.broadcastMessage(""); + Bukkit.broadcastMessage(ChatColor.DARK_AQUA.toString() + ChatColor.BOLD + "Elevate Tournament"); + Bukkit.broadcastMessage(ChatColor.DARK_AQUA + "❘ " + ChatColor.WHITE + "Team Size: " + ChatColor.DARK_AQUA + teamSize + "v" + teamSize); + Bukkit.broadcastMessage(ChatColor.DARK_AQUA + "❘ " + ChatColor.WHITE + "Mode: " + ChatColor.DARK_AQUA + type.getName()); + if (type.equals(GameModes.TEAMFIGHT) || type.equals(GameModes.TEAMFIGHT_DEBUFF)) { + String kits; + if (bards == 0 && archers == 0) { + kits = "All diamonds"; + } else if (bards == 1 && archers == 0) { + kits = bards + " Bard & No Archers"; + } else if (bards > 0 && archers == 0) { + kits = bards + " Bards & No Archers"; + } else if (bards == 0 && archers == 1) { + kits = archers + " Archer & No Bards"; + } else if (bards == 0 && archers > 0) { + kits = archers + " Archers & No Bards"; + } else if (bards == 1 && archers == 1) { + kits = bards + " Bard " + archers + " Archer"; + } else { + kits = bards + " Bards " + archers + " Archers"; + } + Bukkit.broadcastMessage(ChatColor.GRAY + " » " + ChatColor.DARK_AQUA + "Kits: " + ChatColor.WHITE + kits); + } + + + TextComponent JOIN_TOURNAMENT = new TextComponent("[Click to Join]"); + + HoverEvent.Action showText = HoverEvent.Action.SHOW_TEXT; // readability + BaseComponent[] acceptTooltip = new ComponentBuilder("Click to join").color(net.md_5.bungee.api.ChatColor.GREEN).create(); + + JOIN_TOURNAMENT.setColor(net.md_5.bungee.api.ChatColor.GRAY); + JOIN_TOURNAMENT.setHoverEvent(new HoverEvent(showText, acceptTooltip)); + + ClickEvent.Action runCommand = ClickEvent.Action.RUN_COMMAND; + JOIN_TOURNAMENT.setClickEvent(new ClickEvent(runCommand, "/join")); + + Bukkit.spigot().broadcast(JOIN_TOURNAMENT); + Bukkit.broadcastMessage(""); + } + + private void broadcastJoinMessage(Party joiningParty) { + String message; + if (joiningParty.getMembers().size() == 1) { + message = "&3" + PatchedPlayerUtils.getFormattedName(joiningParty.getLeader()) + "&f has joined the &3tournament&f."; + } else if (joiningParty.getMembers().size() == 2) { + Iterator membersIterator = joiningParty.getMembers().iterator(); + message ="&3" + PatchedPlayerUtils.getFormattedName(membersIterator.next()) + "&f and &3" + PatchedPlayerUtils.getFormattedName(membersIterator.next()) + "&f have joined the &3tournament&7."; + } else { + message = "&3" + PatchedPlayerUtils.getFormattedName(joiningParty.getLeader()) + "&f's team has joined the &3tournament&f."; + } + + SettingHandler settingHandler = PotPvPSI.getInstance().getSettingHandler(); + + for (Player player : Bukkit.getOnlinePlayers()) { + if (joiningParty.isMember(player.getUniqueId()) || settingHandler.getSetting(player, Setting.SEE_TOURNAMENT_JOIN_MESSAGE)) { + player.sendMessage(Color.translate(message)); + } + } + } + + private void broadcastEliminationMessage(Party loserParty) { + String message; + + if (loserParty.getMembers().size() == 1) { + message = "&3" + PatchedPlayerUtils.getFormattedName(loserParty.getLeader()) + "&f has been eliminated."; + } else if (loserParty.getMembers().size() == 2) { + Iterator membersIterator = loserParty.getMembers().iterator(); + message = "&3" + PatchedPlayerUtils.getFormattedName(membersIterator.next()) + "&f and &3" + PatchedPlayerUtils.getFormattedName(membersIterator.next()) + " &fwere eliminated."; + } else { + message = "&3" + PatchedPlayerUtils.getFormattedName(loserParty.getLeader()) + "&f's team has been eliminated."; + } + + SettingHandler settingHandler = PotPvPSI.getInstance().getSettingHandler(); + + for (Player player : Bukkit.getOnlinePlayers()) { + if (loserParty.isMember(player.getUniqueId()) || settingHandler.getSetting(player, Setting.SEE_TOURNAMENT_ELIMINATION_MESSAGES)) { + player.sendMessage(Color.translate(message)); + } + } + } + + public enum TournamentStage { + WAITING_FOR_TEAMS, + COUNTDOWN, + IN_PROGRESS, + FINISHED + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/tournament/TournamentHandler.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/tournament/TournamentHandler.java new file mode 100644 index 0000000..a718011 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/tournament/TournamentHandler.java @@ -0,0 +1,257 @@ +package com.elevatemc.potpvp.tournament; + +import com.elevatemc.elib.eLib; +import com.elevatemc.potpvp.gamemode.GameModes; +import com.elevatemc.potpvp.gamemode.menu.extra.SelectNoDebuffOrDebuff; +import com.elevatemc.potpvp.gamemode.menu.select.SelectGameModeMenu; +import com.elevatemc.potpvp.match.MatchState; +import com.elevatemc.potpvp.pvpclasses.PvPClasses; +import com.elevatemc.potpvp.tournament.menu.StatusMenu; +import com.elevatemc.potpvp.util.Color; +import lombok.Getter; +import lombok.Setter; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.gamemode.GameMode; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.*; + +public class TournamentHandler implements Listener { + + @Getter @Setter private Tournament tournament = null; + private static TournamentHandler instance; + + public TournamentHandler() { + instance = this; + eLib.getInstance().getCommandHandler().registerClass(this.getClass()); + Bukkit.getScheduler().scheduleSyncRepeatingTask(PotPvPSI.getInstance(), () -> { + if (tournament != null) tournament.check(); + }, 20L, 20L); + } + + public boolean isInTournament(Party party) { + return tournament != null && tournament.isInTournament(party); + } + + public boolean isInTournament(Match match) { + return tournament != null && tournament.getMatches().contains(match); + } + + @Command(names = { "tournament createteamfight" }, permission = "tournament.create") + public static void tournamentCreateteamfight(Player sender, @Parameter(name = "team-size") int teamSize, @Parameter(name = "bards") int bards, @Parameter(name = "archers") int archers) { + if (instance.getTournament() != null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "There's already an ongoing tournament!"); + return; + } + + if (teamSize < 1 || teamSize > 50) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "The team size must be between 1 and 100."); + return; + } + + new SelectNoDebuffOrDebuff(isDebuff -> { + sender.closeInventory(); + Tournament tournament; + instance.setTournament(tournament = new Tournament(isDebuff ? GameModes.TEAMFIGHT_DEBUFF : GameModes.TEAMFIGHT, teamSize, bards, archers)); + + new BukkitRunnable() { + @Override + public void run() { + if (instance.getTournament() == tournament) { + tournament.broadcastJoinMessage(); + } else { + cancel(); + } + } + }.runTaskTimer(PotPvPSI.getInstance(), 0, 60 * 20); + }).openMenu(sender); + } + + @Command(names = { "tournament create" }, permission = "tournament.create") + public static void tournamentCreate(Player sender, @Parameter(name = "team-size") int teamSize) { + if (instance.getTournament() != null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "There's already an ongoing tournament!"); + return; + } + + if (teamSize < 1 || teamSize > 50) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "The team size must be between 1 and 50."); + return; + } + + new SelectGameModeMenu(gameMode -> { + sender.closeInventory(); + Tournament tournament; + instance.setTournament(tournament = new Tournament(gameMode, teamSize)); + + new BukkitRunnable() { + @Override + public void run() { + if (instance.getTournament() == tournament) { + tournament.broadcastJoinMessage(); + } else { + cancel(); + } + } + }.runTaskTimer(PotPvPSI.getInstance(), 0, 60 * 20); + }, "Select a kit type").openMenu(sender); + } + + @Command(names = { "tournament join", "join", "jointournament" }, description = "Join a running tournament.", permission = "") + public static void tournamentJoin(Player sender) { + if (instance.getTournament() == null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "There is no running tournament to join."); + return; + } + + int tournamentTeamSize = instance.getTournament().getRequiredPartySize(); + + if ((instance.getTournament().getCurrentRound() != -1 || instance.getTournament().getBeginNextRoundIn() != 31) && (instance.getTournament().getCurrentRound() != 0 || !sender.hasPermission("tournaments.joinduringcountdown"))) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "This tournament is already in progress."); + return; + } + + Party senderParty = PotPvPSI.getInstance().getPartyHandler().getParty(sender); + if (senderParty == null) { + if (tournamentTeamSize == 1) { + senderParty = PotPvPSI.getInstance().getPartyHandler().getOrCreateParty(sender); // Will auto put them in a party + } else { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You don't have a team to join the tournament with!"); + return; + } + } + + int notInLobby = 0; + int queued = 0; + for (UUID member : senderParty.getMembers()) { + if (!PotPvPSI.getInstance().getLobbyHandler().isInLobby(Bukkit.getPlayer(member))) { + notInLobby++; + } + + if (PotPvPSI.getInstance().getQueueHandler().getQueueEntry(member) != null) { + queued++; + } + } + + if (notInLobby != 0) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED.toString() + notInLobby + "member" + (notInLobby == 1 ? "" : "s") + " of your team aren't in the lobby."); + return; + } + + if (queued != 0) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED.toString() + notInLobby + "member" + (notInLobby == 1 ? "" : "s") + " of your team are currently queued."); + return; + } + + if (!senderParty.getLeader().equals(sender.getUniqueId())) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You must be the leader of your team to join the tournament."); + return; + } + + if (senderParty.getMembers().size() != instance.getTournament().getRequiredPartySize()) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You need exactly " + instance.getTournament().getRequiredPartySize() + " members in your party to join the tournament."); + return; + } + + if (instance.isInTournament(senderParty)) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Your team is already in the tournament!"); + return; + } + + if (PotPvPSI.getInstance().getQueueHandler().getQueueEntry(senderParty) != null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You can't join the tournament if your party is currently queued."); + return; + } + + Collection kits = senderParty.getKits().values(); + GameMode gameMode = instance.getTournament().getType(); + if (gameMode.equals(GameModes.TEAMFIGHT) || gameMode.equals(GameModes.TEAMFIGHT_DEBUFF)) { + int bards = Collections.frequency(kits, PvPClasses.BARD); + int archers = Collections.frequency(kits, PvPClasses.ARCHER); + int rogues = Collections.frequency(kits, PvPClasses.ROGUE); + + int tournBards = instance.getTournament().getBards(); + int tournArchers = instance.getTournament().getArchers(); + + if (rogues > 0) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "Rogues are not allowed during a tournament."); + return; + } + + if (tournBards == 0 && tournArchers == 0 && bards > 0 && archers > 0) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "This tournament is all diamond only."); + return; + } + + if (bards != tournBards) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You need exactly " + tournBards + " bard" + ((tournBards == 1) ? "" : "s") + " for this tournament."); + return; + } + + if (archers != tournArchers) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You need exactly " + tournArchers + " archer" + ((tournArchers == 1) ? "" : "s") + " for this tournament."); + return; + } + } else if (kits.size() > 0) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "This tournament does not support teamfight kits."); + return; + } + + senderParty.message(ChatColor.GREEN + "Joined the tournament."); + instance.getTournament().addParty(senderParty); + } + + @Command(names = { "tournament status", "tstatus", "status" }, description = "Check the tournament status.", permission = "") + public static void tournamentStatus(Player sender) { + if (instance.getTournament() == null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "There is no ongoing tournament to get the status of."); + return; + } + + long matches = PotPvPSI.getInstance().getTournamentHandler().getTournament().getMatches().stream().filter(m -> m.getState() != MatchState.TERMINATED && m.getState() != MatchState.ENDING).count(); + + if (matches > 0) { + new StatusMenu().openMenu(sender); + } else { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "There are no matches to spectate at this moment."); + } + } + + @Command(names = { "tournament cancel"}, permission = "tournament.cancel") + public static void tournamentCancel(CommandSender sender) { + if (instance.getTournament() == null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "There is no running tournament to cancel."); + return; + } + + Bukkit.broadcastMessage(""); + Bukkit.broadcastMessage(Color.translate("&fThe &3tournament&f was cancelled.")); + Bukkit.broadcastMessage(""); + instance.setTournament(null); + } + + @Command(names = { "tournament start"}, permission = "tournament.start") + public static void tournamentForceStart(CommandSender sender) { + if (instance.getTournament() == null) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "There is no tournament to force start."); + return; + } + + if (instance.getTournament().getCurrentRound() != -1 || instance.getTournament().getBeginNextRoundIn() != 31) { + sender.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "This tournament is already in progress."); + return; + } + + instance.getTournament().start(); + sender.sendMessage(ChatColor.GREEN + "Force started tournament."); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/tournament/menu/StatusButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/tournament/menu/StatusButton.java new file mode 100644 index 0000000..25071cc --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/tournament/menu/StatusButton.java @@ -0,0 +1,84 @@ +package com.elevatemc.potpvp.tournament.menu; + +import com.google.common.base.Preconditions; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchTeam; +import com.elevatemc.potpvp.validation.PotPvPValidation; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.util.UUIDUtils; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +final class StatusButton extends Button { + + private final Match match; + + StatusButton(Match match) { + this.match = Preconditions.checkNotNull(match, "match"); + } + + @Override + public String getName(Player player) { + return ChatColor.YELLOW.toString() + ChatColor.BOLD + match.getSimpleDescription(false); + } + + @Override + public List getDescription(Player player) { + List description = new ArrayList<>(); + MatchTeam teamA = match.getTeams().get(0); + MatchTeam teamB = match.getTeams().get(1); + description.add(ChatColor.DARK_AQUA + "Arena: " + ChatColor.WHITE + PotPvPSI.getInstance().getArenaHandler().getSchematic(match.getArena().getSchematic()).getDisplayName()); + + List spectators = new ArrayList<>(match.getSpectators()); + // don't count actual players and players in silent mode. + spectators.removeIf(uuid -> Bukkit.getPlayer(uuid) != null && Bukkit.getPlayer(uuid).hasMetadata("modmode") || match.getPreviousTeam(uuid) != null); + + if (teamA.getAliveMembers().size() != 1 || teamB.getAliveMembers().size() != 1) { + description.add(""); + + for (UUID member : teamA.getAliveMembers()) { + description.add(ChatColor.AQUA + UUIDUtils.name(member)); + } + + description.add(ChatColor.DARK_AQUA + " vs."); + + for (UUID member : teamB.getAliveMembers()) { + description.add(ChatColor.AQUA + UUIDUtils.name(member)); + } + } + + description.add(""); + description.add(ChatColor.GREEN + "» Click to spectate «"); + + return description; + } + + @Override + public Material getMaterial(Player player) { + return match.getGameMode().getIcon().getItemType(); + } + + @Override + public void clicked(Player player, int i, ClickType clickType) { + if (!PotPvPValidation.canUseSpectateItemIgnoreMatchSpectating(player)) { + return; + } + + Match currentlySpectating = PotPvPSI.getInstance().getMatchHandler().getMatchSpectating(player); + + if (currentlySpectating != null) { + currentlySpectating.removeSpectator(player, false); + } + + match.addSpectator(player, null); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/tournament/menu/StatusMenu.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/tournament/menu/StatusMenu.java new file mode 100644 index 0000000..bfbcde5 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/tournament/menu/StatusMenu.java @@ -0,0 +1,55 @@ +package com.elevatemc.potpvp.tournament.menu; + +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchState; +import com.elevatemc.potpvp.setting.SettingHandler; +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.pagination.PaginatedMenu; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public final class StatusMenu extends PaginatedMenu { + + public StatusMenu() { + setAutoUpdate(true); + } + + @Override + public String getPrePaginatedTitle(Player player) { + return "Tournament Status"; + } + + @Override + public Map getAllPagesButtons(Player player) { + Map buttons = new HashMap<>(); + int i = 0; + + List matches = PotPvPSI.getInstance().getTournamentHandler().getTournament().getMatches().stream().filter(m -> m.getState() != MatchState.TERMINATED && m.getState() != MatchState.ENDING).collect(Collectors.toList()); + + for (Match match : matches) { + // players can view this menu while spectating + if (match.isSpectator(player.getUniqueId())) { + continue; + } + + buttons.put(i++, new StatusButton(match)); + } + + return buttons; + } + + // we lock the size of this inventory at full, otherwise we'll have + // issues if it 'grows' into the next line while it's open (say we open + // the menu with 8 entries, then it grows to 11 [and onto the second row] + // - this breaks things) + @Override + public int size(Player buttons) { + return 9 * 6; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/AlphanumComparator.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/AlphanumComparator.java new file mode 100644 index 0000000..627db9b --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/AlphanumComparator.java @@ -0,0 +1,80 @@ +package com.elevatemc.potpvp.util; + +import java.util.Comparator; + +public class AlphanumComparator implements Comparator { + private final boolean isDigit(char ch) { + return ((ch >= 48) && (ch <= 57)); + } + + /** + * Length of string is passed in for improved efficiency (only need to calculate it once) + **/ + private final String getChunk(String s, int slength, int marker) { + StringBuilder chunk = new StringBuilder(); + char c = s.charAt(marker); + chunk.append(c); + marker++; + if (isDigit(c)) { + while (marker < slength) { + c = s.charAt(marker); + if (!isDigit(c)) + break; + chunk.append(c); + marker++; + } + } else { + while (marker < slength) { + c = s.charAt(marker); + if (isDigit(c)) + break; + chunk.append(c); + marker++; + } + } + return chunk.toString(); + } + + public int compare(String s1, String s2) { + if ((s1 == null) || (s2 == null)) { + return 0; + } + + int thisMarker = 0; + int thatMarker = 0; + int s1Length = s1.length(); + int s2Length = s2.length(); + + while (thisMarker < s1Length && thatMarker < s2Length) { + String thisChunk = getChunk(s1, s1Length, thisMarker); + thisMarker += thisChunk.length(); + + String thatChunk = getChunk(s2, s2Length, thatMarker); + thatMarker += thatChunk.length(); + + // If both chunks contain numeric characters, sort them numerically + int result = 0; + if (isDigit(thisChunk.charAt(0)) && isDigit(thatChunk.charAt(0))) { + // Simple chunk comparison by length. + int thisChunkLength = thisChunk.length(); + result = thisChunkLength - thatChunk.length(); + // If equal, the first different number counts + if (result == 0) { + for (int i = 0; i < thisChunkLength; i++) { + result = thisChunk.charAt(i) - thatChunk.charAt(i); + if (result != 0) { + return result; + } + } + } + } else { + result = thisChunk.compareTo(thatChunk); + } + + if (result != 0) + return result; + } + + return s1Length - s2Length; + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/AngleUtils.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/AngleUtils.java new file mode 100644 index 0000000..bd7109a --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/AngleUtils.java @@ -0,0 +1,49 @@ +package com.elevatemc.potpvp.util; + +import lombok.experimental.UtilityClass; +import org.bukkit.block.BlockFace; + +import java.util.EnumMap; +import java.util.Map; + +@UtilityClass +public final class AngleUtils { + + private static final Map NOTCHES = new EnumMap<>(BlockFace.class); + + static { + BlockFace[] radials = { + BlockFace.WEST, + BlockFace.NORTH_WEST, + BlockFace.NORTH, + BlockFace.NORTH_EAST, + BlockFace.EAST, + BlockFace.SOUTH_EAST, + BlockFace.SOUTH, + BlockFace.SOUTH_WEST + }; + + for (int i = 0; i < radials.length; i++) { + NOTCHES.put(radials[i], i); + } + } + + public static int faceToYaw(BlockFace face) { + return wrapAngle(45 * NOTCHES.getOrDefault(face, 0)); + } + + private static int wrapAngle(int angle) { + int wrappedAngle = angle; + + while (wrappedAngle <= -180) { + wrappedAngle += 360; + } + + while (wrappedAngle > 180) { + wrappedAngle -= 360; + } + + return wrappedAngle; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/ClickTracker.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/ClickTracker.java new file mode 100644 index 0000000..1a76b3c --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/ClickTracker.java @@ -0,0 +1,109 @@ +package com.elevatemc.potpvp.util; + +import com.elevatemc.elib.util.TaskUtil; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.spigot.eSpigot; +import com.elevatemc.spigot.handler.PacketHandler; +import com.google.common.collect.Sets; +import net.minecraft.server.v1_8_R3.*; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.craftbukkit.v1_8_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_8_R3.block.CraftBlock; +import org.bukkit.craftbukkit.v1_8_R3.util.CraftMagicNumbers; +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.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class ClickTracker implements PacketHandler, Listener { + + private final PotPvPSI plugin; + + // This is concurrent to make sure that scoreboard won't throw a CME + private static final Map cpsCount = new ConcurrentHashMap<>(); + private static final Set mining = new HashSet<>(); + + private static final Set cancelNextSwing = new HashSet<>(); + + public ClickTracker(PotPvPSI plugin) { + this.plugin = plugin; + plugin.getServer().getPluginManager().registerEvents(this, plugin); + eSpigot.getInstance().addPacketHandler(this); + } + + @Override + public void handleReceivedPacket(PlayerConnection connection, Packet packet) { + if (packet instanceof PacketPlayInArmAnimation) { + UUID uuid = connection.getPlayer().getUniqueId(); + if (mining.contains(uuid)) { + return; + } + if (cancelNextSwing.contains(uuid)) { + cancelNextSwing.remove(uuid); + return; + } + + cpsCount.put(uuid, cpsCount.get(uuid) + 1); + plugin.getServer().getScheduler().runTaskLater(plugin, () -> { + if (cpsCount.containsKey(uuid)) { + cpsCount.put(uuid, Math.max(0, cpsCount.get(uuid) - 1)); + } + }, 20L); + } + if (packet instanceof PacketPlayInBlockDig) { + UUID uuid = connection.getPlayer().getUniqueId(); + PacketPlayInBlockDig digPacket = ((PacketPlayInBlockDig) packet); + BlockPosition blockPosition = digPacket.a(); + PacketPlayInBlockDig.EnumPlayerDigType digType = digPacket.c(); + TaskUtil.runSync(() -> { + switch (digType) { + case START_DESTROY_BLOCK: + // Checks for hardness -> the abort/stop won't be sent to the player when it's a block like tnt + float hardness = CraftMagicNumbers.getBlock(connection.getPlayer().getWorld().getBlockAt(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ())).g(null, null); + if (hardness > 0.0F) { + mining.add(uuid); + } else { + cancelNextSwing.add(uuid); + Bukkit.getScheduler().runTaskLater(plugin, () -> { + cancelNextSwing.remove(uuid); + }, 1l); + } + break; + case ABORT_DESTROY_BLOCK: + mining.remove(uuid); + break; + case STOP_DESTROY_BLOCK: + // Some delay remove to prevent swing packets from the time between the end of the mining and the start of mining for the same block + Bukkit.getScheduler().runTaskLater(plugin, () -> { + mining.remove(uuid); + }, 6l); // 6 ticks some magic value which probs isn't that good for laggy connects + break; + } + }); + } + } + + @EventHandler + public void onJoin(PlayerJoinEvent event) { + cpsCount.putIfAbsent(event.getPlayer().getUniqueId(), 0); + } + @EventHandler + public void onQuit(PlayerQuitEvent event) { + cpsCount.remove(event.getPlayer().getUniqueId()); + } + + public static int getCPS(Player player) { + return cpsCount.get(player.getUniqueId()); + } +} + diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/Color.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/Color.java new file mode 100644 index 0000000..b8e35fa --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/Color.java @@ -0,0 +1,18 @@ +package com.elevatemc.potpvp.util; + +import org.bukkit.ChatColor; + +import java.util.List; +import java.util.stream.Collectors; + +public class Color { + + public static String translate(String toTranslate) { + return ChatColor.translateAlternateColorCodes('&', toTranslate); + } + + public static List translate(List toTranslate) { + return toTranslate.stream().map(Color::translate).collect(Collectors.toList()); + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/Cooldown.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/Cooldown.java new file mode 100644 index 0000000..9f60c83 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/Cooldown.java @@ -0,0 +1,27 @@ +package com.elevatemc.potpvp.util; + +/** + * Copyright (c) 2022 - Tranquil, LLC. + * + * @author ImHacking + * @date 6/6/2022 + */ +public class Cooldown { + Long endTime; + + public Cooldown(long time) { + this.endTime = System.currentTimeMillis() + time; + } + + public long getRemaining () { + return this.endTime - System.currentTimeMillis(); + } + + public boolean hasExpired () { + if (this.endTime < System.currentTimeMillis()) { + return true; + } else { + return false; + } + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/Elevator.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/Elevator.java new file mode 100644 index 0000000..0b19946 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/Elevator.java @@ -0,0 +1,137 @@ +package com.elevatemc.potpvp.util; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Sign; + +public enum Elevator { + UP, + DOWN; + + public Location getCalculatedLocation(final Location location, Type type) { + + if (this == UP) { + + for (int yLevel = location.getBlockY(); yLevel < 256; yLevel++) { + + if (location.getBlockY() == yLevel) { + continue; + } + + final Location first = new Location(location.getWorld(),location.getX(),yLevel,location.getBlockZ()); + final Location second = new Location(location.getWorld(),location.getX(),(yLevel+1),location.getBlockZ()); + + if (type == Type.MINE_CART && this.isValidForMineCart(first,second)) { + return new Location(location.getWorld(),location.getBlockX(),yLevel,location.getBlockZ()); + } else if (type == Type.CARPET && this.isValidForCarpet(this,first,second) == Cause.ALLOWED) { + return new Location(location.getWorld(),location.getBlockX(),yLevel,location.getBlockZ()); + } else if (type == Type.SIGN && this.isValidForSign(this,first,second) == Cause.ALLOWED) { + return new Location(location.getWorld(),location.getBlockX(),yLevel,location.getBlockZ()); + } + + } + + return null; + } else { + + for (int yLevel = location.getBlockY(); yLevel > 0; yLevel--) { + + if (location.getBlockY() == yLevel) { + continue; + } + + + final Location first = new Location(location.getWorld(),location.getX(),yLevel,location.getBlockZ()); + final Location second = new Location(location.getWorld(),location.getX(),(yLevel+1),location.getBlockZ()); + + if (type == Type.MINE_CART && this.isValidForMineCart(first,second)) { + return first; + } else if (type == Type.CARPET && this.isValidForCarpet(DOWN,first,second) == Cause.ALLOWED) { + return first; + } else if (type == Type.SIGN && this.isValidForSign(DOWN,first,second) == Cause.ALLOWED) { + return first; + } + + + } + + return null; + } + } + + private boolean isValidForMineCart(Location first,Location second) { + return (first.getBlock().getType() == Material.AIR && second.getBlock().getType() == Material.AIR) || (first.getBlock().getType() == Material.FENCE_GATE && second.getBlock().getType() == Material.FENCE_GATE); + } + + private Cause isValidForCarpet(Elevator direction,Location first,Location second) { + + if (second.getBlock().getType() != null && second.getBlock().getType() != Material.AIR) { + return Cause.SECOND_BLOCK_NOT_AIR; + } + + if (first.getBlock().getType() != Material.CARPET && first.getBlock().getType() == Material.AIR) { + return Cause.ALLOWED; + } + + if (first.getBlock().getType() != Material.CARPET) { + return Cause.FIRST_BLOCK_NOT_REQUIRED_ITEM; + } + + byte data = 0; + + if (direction == UP) { + data = 14; + } else if (direction == DOWN) { + data = 13; + } + + if (first.getBlock().getData() != data) { + return Cause.INVALID_DIRECTION; + } + + return Cause.ALLOWED; + } + + private Cause isValidForSign(Elevator elevator,Location first,Location second) { + + if (second.getBlock().getType() != null && second.getBlock().getType() != Material.AIR) { + return Cause.SECOND_BLOCK_NOT_AIR; + } + if (!first.getBlock().getType().name().contains("SIGN") && first.getBlock().getType() == Material.AIR) { + return Cause.ALLOWED; + } + + if (!first.getBlock().getType().name().contains("SIGN")) { + return Cause.FIRST_BLOCK_NOT_REQUIRED_ITEM; + } + + final Sign sign = (Sign)first.getBlock().getState(); + + if (!sign.getLine(0).contains("Elevator")) { + return Cause.NOT_ELEVATOR; + } + + if (elevator == UP ? !sign.getLine(1).equalsIgnoreCase("Down"):!sign.getLine(1).equalsIgnoreCase("Up")) { + return Cause.INVALID_DIRECTION; + } + + return Cause.ALLOWED; + } + + private enum Cause { + FIRST_BLOCK_NOT_REQUIRED_ITEM, + SECOND_BLOCK_NOT_AIR, + NOT_ELEVATOR, + INVALID_DIRECTION, + ALLOWED + } + + public enum Type { + + SIGN, + CARPET, + MINE_CART + + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/FancyPlayerInventory.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/FancyPlayerInventory.java new file mode 100644 index 0000000..16edf1f --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/FancyPlayerInventory.java @@ -0,0 +1,285 @@ +package com.elevatemc.potpvp.util; + +import com.elevatemc.potpvp.PotPvPSI; +import net.minecraft.server.v1_8_R3.EntityHuman; +import net.minecraft.server.v1_8_R3.ItemStack; +import net.minecraft.server.v1_8_R3.PlayerInventory; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftHumanEntity; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_8_R3.inventory.CraftInventory; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; + +import java.util.*; + +public class FancyPlayerInventory extends PlayerInventory { + + private static final Map storage = new HashMap<>(); + private static final Set open = new HashSet<>(); + + private CraftPlayer owner; + private boolean playerOnline = false; + private ItemStack[] extra = new ItemStack[5]; + private CraftInventory inventory = new CraftInventory(this); + + private FancyPlayerInventory(Player player) { + super(((CraftPlayer) player).getHandle()); + this.owner = ((CraftPlayer) player); + this.playerOnline = player.isOnline(); + this.items = this.player.inventory.items; + this.armor = this.player.inventory.armor; + + storage.put(owner.getUniqueId(), this); + } + + private Inventory getBukkitInventory() { + return inventory; + } + + private void removalCheck() { + Bukkit.getScheduler().runTaskAsynchronously(PotPvPSI.getInstance(), () -> owner.saveData()); + + if (transaction.isEmpty() && !playerOnline) { + storage.remove(owner.getUniqueId()); + } + } + + private void onJoin(Player joined) { + if (!playerOnline) { + CraftPlayer player = (CraftPlayer) joined; + player.getHandle().inventory.items = this.items; + player.getHandle().inventory.armor = this.armor; + playerOnline = true; + + Bukkit.getScheduler().runTaskAsynchronously(PotPvPSI.getInstance(), () -> owner.saveData()); + } + } + + private void onQuit() { + playerOnline = false; + + this.removalCheck(); + } + + @Override + public void onClose(CraftHumanEntity who) { + super.onClose(who); + open.remove(who.getUniqueId()); + this.removalCheck(); + } + + @Override + public ItemStack[] getContents() { + ItemStack[] contents = new ItemStack[getSize()]; + + System.arraycopy(items, 0, contents, 0, items.length); + System.arraycopy(items, 0, contents, items.length, armor.length); + + return contents; + } + + @Override + public int getSize() { + return super.getSize() + 5; + } + + @Override + public ItemStack getItem(int i) { + ItemStack[] is; + + if (i >= 0 && i <= 4) { + i = getReversedArmorSlotNum(i); + is = this.armor; + } else if (i >= 5 && i <= 8) { + i -= 4; + is = this.extra; + } else { + i -= 9; + is = this.items; + + i = getReversedItemSlotNum(i); + } + + if (i >= is.length) { + i -= is.length; + is = this.extra; + } + else if (is == this.armor) { + i = getReversedArmorSlotNum(i); + } + + return is[i]; + } + + @Override + public ItemStack splitStack(int i, int j) { + ItemStack[] is; + + if (i >= 0 && i <= 4) { + i = getReversedArmorSlotNum(i); + is = this.armor; + } else if (i >= 5 && i <= 8) { + i -= 4; + is = this.extra; + } else { + i -= 9; + is = this.items; + + i = getReversedItemSlotNum(i); + } + + if (i >= is.length) { + i -= is.length; + is = this.extra; + } + else if (is == this.armor) { + i = getReversedArmorSlotNum(i); + } + + if (is[i] != null) { + ItemStack itemstack; + + if (is[i].count <= j) { + itemstack = is[i]; + is[i] = null; + return itemstack; + } + else { + itemstack = is[i].cloneAndSubtract(j); + if (is[i].count == 0) { + is[i] = null; + } + + return itemstack; + } + } + else { + return null; + } + } + + @Override + public ItemStack splitWithoutUpdate(int i) { + ItemStack[] is; + + if (i >= 0 && i <= 4) { + i = getReversedArmorSlotNum(i); + is = this.armor; + } else if (i >= 5 && i <= 8) { + i -= 4; + is = this.extra; + } else { + i -= 9; + is = this.items; + + i = getReversedItemSlotNum(i); + } + + if (i >= is.length) { + i -= is.length; + is = this.extra; + } + else if (is == this.armor) { + i = getReversedArmorSlotNum(i); + } + + if (is[i] != null) { + ItemStack itemstack = is[i]; + + is[i] = null; + return itemstack; + } + else { + return null; + } + } + + @Override + public void setItem(int i, ItemStack itemstack) { + ItemStack[] is; + + if (i >= 0 && i <= 4) { + i = getReversedArmorSlotNum(i); + is = this.armor; + } else if (i >= 5 && i <= 8) { + i -= 4; + is = this.extra; + } else { + i -= 9; + is = this.items; + + i = getReversedItemSlotNum(i); + } + + if (i >= is.length) { + i -= is.length; + is = this.extra; + } + else if (is == this.armor) { + i = getReversedArmorSlotNum(i); + } + + if (is == this.extra) { + owner.getHandle().drop(itemstack, true); + itemstack = null; + } + + is[i] = itemstack; + + owner.getHandle().defaultContainer.b(); + } + + private int getReversedItemSlotNum(int i) { + return i >= 27 ? i - 27 : i + 9; + } + + private int getReversedArmorSlotNum(int i) { + switch (i) { + case 0: + return 3; + case 1: + return 2; + case 2: + return 1; + case 3: + return 0; + default: + return i; + } + } + + @Override + public String getName() { + return "Inventory: " + owner.getDisplayName(); + } + + @Override + public boolean a(EntityHuman entityhuman) { + return true; + } + + public static void open(Player owner, Player openFor) { + FancyPlayerInventory inventory = storage.containsKey(owner.getUniqueId()) ? storage.get(owner.getUniqueId()) : new FancyPlayerInventory(owner); + openFor.openInventory(inventory.getBukkitInventory()); + + open.add(openFor.getUniqueId()); + } + + public static boolean isViewing(Player player) { + return open.contains(player.getUniqueId()); + } + + public static void join(Player player) { + if (storage.containsKey(player.getUniqueId())) { + storage.get(player.getUniqueId()).onJoin(player); + } + } + + public static void quit(Player player) { + if (storage.containsKey(player.getUniqueId())) { + storage.get(player.getUniqueId()).onQuit(); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/FireworkEffectPlayer.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/FireworkEffectPlayer.java new file mode 100644 index 0000000..f646d03 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/FireworkEffectPlayer.java @@ -0,0 +1,111 @@ +package com.elevatemc.potpvp.util; + +import org.bukkit.FireworkEffect; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Firework; +import org.bukkit.inventory.meta.FireworkMeta; + +import java.lang.reflect.Method; + +/** + * FireworkEffectPlayer v1.0 + * + * FireworkEffectPlayer provides a thread-safe and (reasonably) version independant way to instantly explode a FireworkEffect at a given location. + * You are welcome to use, redistribute, modify and destroy your own copies of this source with the following conditions: + * + * 1. No warranty is given or implied. + * 2. All damage is your own responsibility. + * 3. You provide credit publicly to the original source should you release the plugin. + * + * @author codename_B + */ +public class FireworkEffectPlayer { + + /* + * Example use: + * + * public class FireWorkPlugin implements Listener { + * + * FireworkEffectPlayer fplayer = new FireworkEffectPlayer(); + * + * @EventHandler + * public void onPlayerLogin(PlayerLoginEvent event) { + * fplayer.playFirework(event.getPlayer().getWorld(), event.getPlayer.getLocation(), Util.getRandomFireworkEffect()); + * } + * + * } + */ + + // internal references, performance improvements + private Method world_getHandle = null; + private Method nms_world_broadcastEntityEffect = null; + private Method firework_getHandle = null; + + /** + * Internal method, used as shorthand to grab our method in a nice friendly manner + * @param cl + * @param method + * @return Method (or null) + */ + private static Method getMethod(Class cl, String method) { + for(Method m : cl.getMethods()) { + if(m.getName().equals(method)) { + return m; + } + } + return null; + } + + /** + * Play a pretty firework at the location with the FireworkEffect when called + * @param world + * @param loc + * @param fe + * @throws Exception + */ + public void playFirework(World world, Location loc, FireworkEffect fe) throws Exception { + // Bukkity load (CraftFirework) + Firework fw = world.spawn(loc, Firework.class); + // the net.minecraft.server.World + Object nms_world = null; + Object nms_firework = null; + /* + * The reflection part, this gives us access to funky ways of messing around with things + */ + if(world_getHandle == null) { + // get the methods of the craftbukkit objects + world_getHandle = getMethod(world.getClass(), "getHandle"); + firework_getHandle = getMethod(fw.getClass(), "getHandle"); + } + // invoke with no arguments + nms_world = world_getHandle.invoke(world, (Object[]) null); + nms_firework = firework_getHandle.invoke(fw, (Object[]) null); + // null checks are fast, so having this seperate is ok + if(nms_world_broadcastEntityEffect == null) { + // get the method of the nms_world + nms_world_broadcastEntityEffect = getMethod(nms_world.getClass(), "broadcastEntityEffect"); + } + /* + * Now we mess with the metadata, allowing nice clean spawning of a pretty firework (look, pretty lights!) + */ + // metadata load + FireworkMeta data = fw.getFireworkMeta(); + // clear existing + data.clearEffects(); + // power of one + data.setPower(1); + // add the effect + data.addEffect(fe); + // set the meta + fw.setFireworkMeta(data); + /* + * Finally, we broadcast the entity effect then kill our fireworks object + */ + // invoke with arguments + nms_world_broadcastEntityEffect.invoke(nms_world, new Object[] {nms_firework, (byte) 17}); + // remove from the game + fw.remove(); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/InventoryUtils.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/InventoryUtils.java new file mode 100644 index 0000000..29b742c --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/InventoryUtils.java @@ -0,0 +1,57 @@ +package com.elevatemc.potpvp.util; + +import lombok.experimental.UtilityClass; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.lobby.LobbyUtils; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.match.MatchUtils; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +@UtilityClass +public final class InventoryUtils { + + public static final long RESET_DELAY_TICKS = 2L; + + public static void resetInventoryDelayed(Player player) { + Runnable task = () -> resetInventoryNow(player); + Bukkit.getScheduler().runTaskLater(PotPvPSI.getInstance(), task, RESET_DELAY_TICKS); + } + + public static void resetInventoryNow(Player player) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + + if (matchHandler.isPlayingOrSpectatingMatch(player)) { + MatchUtils.resetInventory(player); + } else { + LobbyUtils.resetInventory(player); + } + } + + public static void removeAmountFromInventory(Inventory inventory, ItemStack item, int amount) { + for(ItemStack invItem : inventory.getContents()) { + if(invItem != null) { + if(invItem.isSimilar(item)) { + int preAmount = invItem.getAmount(); + int newAmount = Math.max(0, preAmount - amount); + amount = Math.max(0, amount - preAmount); + invItem.setAmount(newAmount); + if(amount == 0) { + break; + } + } + } + } + } + + public static boolean isEmpty(Player player) { + for(ItemStack content : player.getInventory().getContents()) { + if(content != null) return false; + } + + return true; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/ItemListener.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/ItemListener.java new file mode 100644 index 0000000..3678d06 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/ItemListener.java @@ -0,0 +1,70 @@ +package com.elevatemc.potpvp.util; + +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; +import java.util.function.Predicate; + +public abstract class ItemListener implements Listener { + + // we add a 500ms silent cooldown between all button uses + protected static final Map canUseButton = new ConcurrentHashMap<>(); + + private final Map> handlers = new HashMap<>(); + private Predicate preProcessPredicate = null; + + protected final void addHandler(ItemStack stack, Consumer handler) { + this.handlers.put(stack, handler); + } + + protected final void setPreProcessPredicate(Predicate preProcessPredicate) { + this.preProcessPredicate = preProcessPredicate; + } + + public static void addButtonCooldown(Player player, int ms) { + canUseButton.put(player.getUniqueId(), System.currentTimeMillis() + ms); + } + + @EventHandler + public void onPlayerInteract(PlayerInteractEvent event) { + if (!event.hasItem() || !event.getAction().name().contains("RIGHT_")) { + return; + } + + Player player = event.getPlayer(); + ItemStack item = event.getItem(); + + if (preProcessPredicate != null && !preProcessPredicate.test(player)) { + return; + } + + for (Map.Entry> entry : handlers.entrySet()) { + if (item.isSimilar(entry.getKey())) { + boolean permitted = canUseButton.getOrDefault(player.getUniqueId(), 0L) < System.currentTimeMillis(); + + if (permitted) { + entry.getValue().accept(player); + canUseButton.put(player.getUniqueId(), System.currentTimeMillis() + 500); + } + + event.setCancelled(true); + return; + } + } + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + canUseButton.remove(event.getPlayer().getUniqueId()); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/ItemUtils.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/ItemUtils.java new file mode 100644 index 0000000..5b4ba4c --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/ItemUtils.java @@ -0,0 +1,71 @@ +package com.elevatemc.potpvp.util; + +import lombok.experimental.UtilityClass; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.Potion; +import org.bukkit.potion.PotionType; + +import java.util.function.Predicate; + +@UtilityClass +public final class ItemUtils { + + /** + * Checks if a {@link ItemStack} is an instant heal potion (if its type is {@link PotionType#INSTANT_HEAL}) + */ + public static final Predicate INSTANT_HEAL_POTION_PREDICATE = item -> { + if (item.getType() != Material.POTION || item.getDurability() == 0) { + return false; + } + PotionType potionType = Potion.fromItemStack(item).getType(); + return potionType == PotionType.INSTANT_HEAL; + }; + + /** + * Checks if a {@link ItemStack} is a bowl of mushroom soup (if its type is {@link Material#MUSHROOM_SOUP}) + */ + public static final Predicate SOUP_PREDICATE = item -> item.getType() == Material.MUSHROOM_SOUP; + + /** + * Checks if a {@link ItemStack} is a debuff potion + */ + public static final Predicate DEBUFF_POTION_PREDICATE = item -> { + if (item.getType() == Material.POTION) { + PotionType type = Potion.fromItemStack(item).getType(); + return type == PotionType.WEAKNESS || type == PotionType.SLOWNESS + || type == PotionType.POISON || type == PotionType.INSTANT_DAMAGE; + } else { + return false; + } + }; + + /** + * Checks if a {@link ItemStack} is edible (if its type passes {@link Material#isEdible()}) + */ + public static final Predicate EDIBLE_PREDICATE = item -> item.getType().isEdible(); + + /** + * Returns the number of stacks of items matching the predicate provided. + * + * @param items ItemStack array to scan + * @param predicate The predicate which will be applied to each non-null temStack. + * @return The amount of ItemStacks which matched the predicate, or 0 if {@code items} was null. + */ + public static int countStacksMatching(ItemStack[] items, Predicate predicate) { + if (items == null) { + return 0; + } + + int amountMatching = 0; + + for (ItemStack item : items) { + if (item != null && predicate.test(item)) { + amountMatching++; + } + } + + return amountMatching; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/ListWrapper.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/ListWrapper.java new file mode 100644 index 0000000..2ee8408 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/ListWrapper.java @@ -0,0 +1,17 @@ +package com.elevatemc.potpvp.util; + +import java.util.LinkedList; +import java.util.List; + +public class ListWrapper { + + private List backingList; + + public List ensure() { + return isPresent() ? backingList : (backingList = new LinkedList<>()); + } + + public boolean isPresent() { + return backingList != null; + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/LocationUtils.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/LocationUtils.java new file mode 100644 index 0000000..c75ed89 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/LocationUtils.java @@ -0,0 +1,13 @@ +package com.elevatemc.potpvp.util; + +import lombok.experimental.UtilityClass; +import org.bukkit.Location; + +@UtilityClass +public final class LocationUtils { + + public static String locToStr(Location loc) { + return "(" + loc.getBlockX() + ", " + loc.getBlockY() + ", " + loc.getBlockZ() + ")"; + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/MongoUtils.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/MongoUtils.java new file mode 100644 index 0000000..3f164e4 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/MongoUtils.java @@ -0,0 +1,28 @@ +package com.elevatemc.potpvp.util; + +import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.UpdateOptions; +import lombok.experimental.UtilityClass; +import com.elevatemc.potpvp.PotPvPSI; +import org.bson.Document; + +@UtilityClass +public final class MongoUtils { + + /** + * UpdateOptions used to perform an upsert (see https://docs.mongodb.com/manual/reference/method/db.collection.update/) + */ + public static final UpdateOptions UPSERT_OPTIONS = new UpdateOptions().upsert(true); + + /** + * Convenience method to retrieve a MongoCollection object from singleton + * MongoDatabase. + * + * @param collectionId Id of collection to retrieve + * @return MongoCollection for specified collection id. + */ + public static MongoCollection getCollection(String collectionId) { + return PotPvPSI.getInstance().getMongoDatabase().getCollection(collectionId); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/PatchedPlayerUtils.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/PatchedPlayerUtils.java new file mode 100644 index 0000000..39a2f0d --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/PatchedPlayerUtils.java @@ -0,0 +1,76 @@ +package com.elevatemc.potpvp.util; + +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.elib.util.UUIDUtils; +import org.bukkit.GameMode; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.potion.PotionEffect; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +// we mess with fly mode in PotPvP, so we need to reset that with PlayerUtils (in qLib) +// unfortunately, that class doesn't reset fly mode - and plugins like qHub, which use doublejump +// (implemented with fly mode if you're not familiar) have already started using that method. +public class PatchedPlayerUtils { + + private static final ProfileHandler profileHandler = PotPvPSI.getInstance().getPrime().getModuleHandler().getModule(ProfileHandler.class); + + public static void resetInventory(Player player) { + resetInventory(player, null); + } + + public static void resetInventory(Player player, GameMode gameMode) { + resetInventory(player, gameMode, false); + } + + public static void resetInventory(Player player, GameMode gameMode, boolean skipInvReset) { + player.setHealth(player.getMaxHealth()); + player.setFallDistance(0F); + player.setFoodLevel(20); + player.setSaturation(10F); + player.setLevel(0); + player.setExp(0F); + + if (!skipInvReset) { + player.getInventory().clear(); + player.getInventory().setArmorContents(null); + + Inventory inv = player.getOpenInventory().getTopInventory(); + if (inv.getType().equals(InventoryType.CRAFTING)) { + inv.clear(); + } + player.setItemOnCursor(null); + } + + player.setFireTicks(0); + + for (PotionEffect potionEffect : player.getActivePotionEffects()) { + player.removePotionEffect(potionEffect.getType()); + } + + if (gameMode != null && player.getGameMode() != gameMode) { + player.setGameMode(gameMode); + } + + player.setAllowFlight(false); + player.setFlying(false); + } + + public static List mapToNames(Collection uuids) { + return uuids.stream().map(UUIDUtils::name).collect(Collectors.toList()); + } + + public static String getFormattedName(UUID uuid) { + Optional profile = profileHandler.getProfile(uuid); + if(!profile.isPresent()) return UUIDUtils.name(uuid); + return profile.get().getColoredName(); + } +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/PotionUtil.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/PotionUtil.java new file mode 100644 index 0000000..82db0f0 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/PotionUtil.java @@ -0,0 +1,24 @@ +package com.elevatemc.potpvp.util; + +import lombok.experimental.UtilityClass; +import net.minecraft.server.v1_8_R3.Entity; +import net.minecraft.server.v1_8_R3.EntityPlayer; +import net.minecraft.server.v1_8_R3.EntityPotion; +import net.minecraft.server.v1_8_R3.World; +import org.bukkit.craftbukkit.v1_8_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_8_R3.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +@UtilityClass +public final class PotionUtil { + + // Using regular Player#shootProjectile api from spigot doesn't work great because we are not able to change the appearance of the potion. The potion would already spawn on the player side before the appearance has been set. Love spigot... + public void splashPotion(Player player, ItemStack item) { + World world = ((CraftWorld) player.getWorld()).getHandle(); + EntityPlayer entityPlayer = ((CraftPlayer) player).getHandle(); + Entity launch = new EntityPotion(world, entityPlayer, CraftItemStack.asNMSCopy(item)); + world.addEntity(launch); + } +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/VisibilityUtils.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/VisibilityUtils.java new file mode 100644 index 0000000..f9ee18e --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/VisibilityUtils.java @@ -0,0 +1,82 @@ +package com.elevatemc.potpvp.util; + +import com.elevatemc.potpvp.hctranked.game.RankedGame; +import com.elevatemc.potpvp.hctranked.game.RankedGameHandler; +import com.elevatemc.potpvp.setting.Setting; +import lombok.experimental.UtilityClass; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.follow.FollowHandler; +import com.elevatemc.potpvp.match.Match; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.potpvp.party.PartyHandler; +import com.elevatemc.potpvp.setting.SettingHandler; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.Optional; +import java.util.UUID; + +@UtilityClass +public final class VisibilityUtils { + + public static void updateVisibilityFlicker(Player target) { + for (Player otherPlayer : Bukkit.getOnlinePlayers()) { + target.hidePlayer(otherPlayer); + otherPlayer.hidePlayer(target); + } + + Bukkit.getScheduler().runTaskLater(PotPvPSI.getInstance(), () -> updateVisibility(target), 10L); + } + + public static void updateVisibility(Player target) { + for (Player otherPlayer : Bukkit.getOnlinePlayers()) { + if (shouldSeePlayer(otherPlayer, target)) { + otherPlayer.showPlayer(target); + } else { + otherPlayer.hidePlayer(target); + } + + if (shouldSeePlayer(target, otherPlayer)) { + target.showPlayer(otherPlayer); + } else { + target.hidePlayer(otherPlayer); + } + } + } + + private static boolean shouldSeePlayer(Player viewer, Player target) { + SettingHandler settingHandler = PotPvPSI.getInstance().getSettingHandler(); + FollowHandler followHandler = PotPvPSI.getInstance().getFollowHandler(); + PartyHandler partyHandler = PotPvPSI.getInstance().getPartyHandler(); + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + RankedGameHandler rankedGameHandler = PotPvPSI.getInstance().getHCTRankedHandler().getGameHandler(); + + Match targetMatch = matchHandler.getMatchPlayingOrSpectating(target); + + if (targetMatch == null) { + // we're not in a match so we hide other players based on their party/match + Party targetParty = partyHandler.getParty(target); + RankedGame rankedGame = rankedGameHandler.getJoinedGame(viewer); + Optional following = followHandler.getFollowing(viewer); + + boolean viewerPlayingMatch = matchHandler.isPlayingOrSpectatingMatch(viewer); + boolean viewerSameParty = targetParty != null && targetParty.isMember(viewer.getUniqueId()); + boolean viewerSameRankedGame = rankedGame != null && rankedGame.getJoinedPlayers().contains(target.getUniqueId()); + boolean viewerFollowingTarget = following.isPresent() && following.get().equals(target.getUniqueId()); + + boolean targetLobbyVisibility = target.hasPermission("potpvp.lobby.visibility"); + boolean targetNotVanishedAndVisible = !target.hasMetadata("modmode") && targetLobbyVisibility; + + return viewerPlayingMatch || viewerSameParty || viewerFollowingTarget || viewerSameRankedGame || targetNotVanishedAndVisible; + } else { + // we're in a match so we only hide other spectators (if our settings say so) + boolean targetIsSpectator = targetMatch.isSpectator(target.getUniqueId()); + boolean viewerSpecSetting = settingHandler.getSetting(viewer, Setting.VIEW_OTHER_SPECTATORS); + boolean viewerIsSpectator = matchHandler.isSpectatingMatch(viewer); + + return !targetIsSpectator || (viewerSpecSetting && viewerIsSpectator && !target.hasMetadata("modmode")); + } + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/VoidChunkGenerator.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/VoidChunkGenerator.java new file mode 100644 index 0000000..4aef95a --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/VoidChunkGenerator.java @@ -0,0 +1,38 @@ +package com.elevatemc.potpvp.util; + +import java.util.Collections; +import java.util.List; +import java.util.Random; + +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Biome; +import org.bukkit.generator.BlockPopulator; +import org.bukkit.generator.ChunkGenerator; + +public class VoidChunkGenerator extends ChunkGenerator { + + public List getDefaultPopulators(World world) { + return Collections.emptyList(); + } + + public boolean canSpawn(World world, int x, int z) { + return true; + } + + public ChunkData generateChunkData(World world, Random random, int chunkX, int chunkZ, BiomeGrid biomeGrid) { + ChunkData chunkData = this.createChunkData(world); + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + biomeGrid.setBiome(x, z, Biome.PLAINS); + } + } + + return chunkData; + } + + public Location getFixedSpawnLocation(World world, Random random) { + return new Location(world, 0, 128, 0); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/menu/BooleanTraitButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/menu/BooleanTraitButton.java new file mode 100644 index 0000000..30484ff --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/menu/BooleanTraitButton.java @@ -0,0 +1,70 @@ +package com.elevatemc.potpvp.util.menu; + +import com.google.common.collect.ImmutableList; +import com.elevatemc.elib.menu.Button; +import org.bukkit.ChatColor; +import org.bukkit.DyeColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +public final class BooleanTraitButton extends Button { + + private final T target; + private final String trait; + private final BiConsumer writeFunction; + private final Function readFunction; + private final Consumer saveFunction; + + public BooleanTraitButton(T target, String trait, BiConsumer writeFunction, Function readFunction) { + this(target, trait, writeFunction, readFunction, (i) -> {}); + } + + public BooleanTraitButton(T target, String trait, BiConsumer writeFunction, Function readFunction, Consumer saveFunction) { + this.target = target; + this.trait = trait; + this.writeFunction = writeFunction; + this.readFunction = readFunction; + this.saveFunction = saveFunction; + } + + @Override + public String getName(Player player) { + return ChatColor.GOLD + "Edit " + trait; + } + + @Override + public List getDescription(Player player) { + return ImmutableList.of( + ChatColor.YELLOW + "Current: " + ChatColor.WHITE + (readFunction.apply(target) ? "On" : "Off"), + "", + ChatColor.GREEN.toString() + ChatColor.BOLD + "Click to toggle" + ); + } + + @Override + public Material getMaterial(Player player) { + return Material.INK_SACK; + } + + @Override + public byte getDamageValue(Player player) { + return readFunction.apply(target) ? DyeColor.LIME.getDyeData() : DyeColor.GRAY.getDyeData(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + boolean current = readFunction.apply(target); + + writeFunction.accept(target, !current); + saveFunction.accept(target); + + player.sendMessage(ChatColor.GREEN + "Set " + trait + " trait to " + (current ? "off" : "on")); + } + +} diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/menu/IntegerTraitButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/menu/IntegerTraitButton.java new file mode 100644 index 0000000..9c8ccb1 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/menu/IntegerTraitButton.java @@ -0,0 +1,78 @@ +package com.elevatemc.potpvp.util.menu; + +import com.google.common.collect.ImmutableList; +import com.elevatemc.elib.menu.Button; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +public final class IntegerTraitButton extends Button { + + private final T target; + private final String trait; + private final BiConsumer writeFunction; + private final Function readFunction; + private final Consumer saveFunction; + + public IntegerTraitButton(T target, String trait, BiConsumer writeFunction, Function readFunction) { + this(target, trait, writeFunction, readFunction, (i) -> {}); + } + + public IntegerTraitButton(T target, String trait, BiConsumer writeFunction, Function readFunction, Consumer saveFunction) { + this.target = target; + this.trait = trait; + this.writeFunction = writeFunction; + this.readFunction = readFunction; + this.saveFunction = saveFunction; + } + + @Override + public String getName(Player player) { + return ChatColor.GOLD + "Edit " + trait; + } + + @Override + public List getDescription(Player player) { + return ImmutableList.of( + ChatColor.YELLOW + "Current: " + ChatColor.WHITE + readFunction.apply(target), + "", + ChatColor.GREEN.toString() + ChatColor.BOLD + "LEFT-CLICK " + ChatColor.GREEN + "to increase by 1", + ChatColor.GREEN.toString() + ChatColor.BOLD + "SHIFT LEFT-CLICK " + ChatColor.GREEN + "to increase by 10", + "", + ChatColor.RED.toString() + ChatColor.BOLD + "RIGHT-CLICK " + ChatColor.GREEN + "to decrease by 1", + ChatColor.RED.toString() + ChatColor.BOLD + "SHIFT RIGHT-CLICK " + ChatColor.GREEN + "to decrease by 10" + ); + } + + @Override + public Material getMaterial(Player player) { + return Material.GHAST_TEAR; + } + + @Override + public int getAmount(Player player) { + return readFunction.apply(target); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + int current = readFunction.apply(target); + int change = clickType.isShiftClick() ? 10 : 1; + + if (clickType.isRightClick()) { + change = -change; + } + + writeFunction.accept(target, current + change); + saveFunction.accept(target); + + player.sendMessage(ChatColor.GREEN + "Set " + trait + " trait to " + (current + change)); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/menu/MenuBackButton.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/menu/MenuBackButton.java new file mode 100644 index 0000000..34fd4f8 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/util/menu/MenuBackButton.java @@ -0,0 +1,47 @@ +package com.elevatemc.potpvp.util.menu; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.elevatemc.elib.menu.Button; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.List; +import java.util.function.Consumer; + +public final class MenuBackButton extends Button { + + private final Consumer openPreviousMenuConsumer; + + public MenuBackButton(Consumer openPreviousMenuConsumer) { + this.openPreviousMenuConsumer = Preconditions.checkNotNull(openPreviousMenuConsumer, "openPreviousMenuConsumer"); + } + + @Override + public String getName(Player player) { + return ChatColor.RED.toString() + ChatColor.BOLD + "Back"; + } + + @Override + public List getDescription(Player player) { + return ImmutableList.of( + "", + ChatColor.RED + "Click here to return to", + ChatColor.RED + "the previous menu." + ); + } + + @Override + public Material getMaterial(Player player) { + return Material.REDSTONE; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + player.closeInventory(); + openPreviousMenuConsumer.accept(player); + } + +} \ No newline at end of file diff --git a/Network/ePractice/src/main/java/com/elevatemc/potpvp/validation/PotPvPValidation.java b/Network/ePractice/src/main/java/com/elevatemc/potpvp/validation/PotPvPValidation.java new file mode 100644 index 0000000..b825048 --- /dev/null +++ b/Network/ePractice/src/main/java/com/elevatemc/potpvp/validation/PotPvPValidation.java @@ -0,0 +1,552 @@ +package com.elevatemc.potpvp.validation; + +import com.elevatemc.potpvp.tournament.Tournament; +import lombok.experimental.UtilityClass; +import com.elevatemc.potpvp.PotPvPSI; +import com.elevatemc.potpvp.follow.FollowHandler; +import com.elevatemc.potpvp.lobby.LobbyHandler; +import com.elevatemc.potpvp.match.MatchHandler; +import com.elevatemc.potpvp.party.Party; +import com.elevatemc.potpvp.party.PartyHandler; +import com.elevatemc.potpvp.queue.QueueHandler; +import com.elevatemc.potpvp.setting.Setting; +import com.elevatemc.potpvp.setting.SettingHandler; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +@UtilityClass +public final class PotPvPValidation { + + private static final String CANNOT_DUEL_SELF = ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You can't duel yourself!"; + private static final String CANNOT_DUEL_OWN_PARTY = ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You can't duel your own party!"; + + private static final String CANNOT_DO_THIS_WHILE_IN_RANKED_GAME = ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You can't do this while in a ranked game!"; + private static final String CANNOT_DO_THIS_WHILE_IN_PARTY = ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You can't do this while in a party!"; + private static final String CANNOT_DO_THIS_WHILE_QUEUED = ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You can't do this while queued!"; + private static final String CANNOT_DO_THIS_WHILE_NOT_IN_LOBBY = ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You can't do this while you're not in the lobby!"; + private static final String CANNOT_DO_THIS_WHILE_IN_MATCH = ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You can't do this while participating in or spectating a match!"; + private static final String CANNOT_DO_THIS_WHILE_IN_EVENT = ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You can't do this while participating in or spectating an event!"; + private static final String CANNOT_DO_THIS_WHILE_FOLLOWING = ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You cannot do this while following someone! Type /unfollow to exit."; + private static final String CANNOT_DO_THIS_IN_SILENT_MODE = ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You cannot do this while in staff mode!"; + private static final String CANNOT_DO_THIS_WHILST_IN_TOURNAMENT = ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You cannot do this whilst in the tournament!"; + private static final String CANNOT_DO_THIS_TOO_FEW_PEOPLE = ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You need more members to do that!"; + + private static final String TARGET_PARTY_NOT_IN_LOBBY = ChatColor.DARK_RED + "✖ " + ChatColor.RED + "That party is not in the lobby!"; + private static final String TARGET_PLAYER_NOT_IN_LOBBY = ChatColor.DARK_RED + "✖ " + ChatColor.RED + "That player is not in the lobby!"; + private static final String TARGET_PLAYER_FOLLOWING_SOMEONE = ChatColor.DARK_RED + "✖ " + ChatColor.RED + "That player is currently following someone!"; + private static final String TARGET_PLAYER_HAS_DUELS_DISABLED = ChatColor.DARK_RED + "✖ " + ChatColor.RED + "The player has duels disabled!"; + private static final String TARGET_IN_PARTY = ChatColor.DARK_RED + "✖ " + ChatColor.RED + "That player is in a party!"; + private static final String TARGET_PARTY_HAS_DUELS_DISABLED = ChatColor.DARK_RED + "✖ " + ChatColor.RED + "The party has duels disabled!"; + private static final String TARGET_PARTY_REACHED_MAXIMUM_SIZE = ChatColor.DARK_RED + "✖ " + ChatColor.RED + "The party is full."; + private static final String TARGET_PARTY_REACHED_MAXIMUM_SIZE_FOR_TOURNAMENT = ChatColor.DARK_RED + "✖ " + ChatColor.RED + "That party is full for the tournament."; + + public static boolean canJoinRankedGame(Player player) { + if (isInParty(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_IN_PARTY); + return false; + } + + if (!isInLobby(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_NOT_IN_LOBBY); + return false; + } + + if (isFollowingSomeone(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_FOLLOWING); + return false; + } + + if (isInMatch(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_IN_MATCH); + return false; + } + + if(isInEvent(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_IN_EVENT); + return false; + } + + if (isInQueue(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_QUEUED); + return false; + } + + return true; + } + + public static boolean canJoinRankedGameSilent(Player player) { + if (isInParty(player)) { + return false; + } + + if (!isInLobby(player)) { + return false; + } + + if (isFollowingSomeone(player)) { + return false; + } + + if (isInMatch(player)) { + return false; + } + + if(isInEvent(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_IN_EVENT); + return false; + } + + if (isInQueue(player)) { + return false; + } + + return true; + } + + public static boolean canSendDuel(Player sender, Player target) { + if (isInRankedGame(sender)) { + sender.sendMessage(CANNOT_DO_THIS_WHILE_IN_RANKED_GAME); + return false; + } + + if (isInSilentMode(sender)) { + sender.sendMessage(CANNOT_DO_THIS_IN_SILENT_MODE); + return false; + } + + if (sender == target) { + sender.sendMessage(CANNOT_DUEL_SELF); + return false; + } + + if (!isInLobby(sender)) { + sender.sendMessage(CANNOT_DO_THIS_WHILE_NOT_IN_LOBBY); + return false; + } + + if(isInEvent(sender)) { + sender.sendMessage(CANNOT_DO_THIS_WHILE_IN_EVENT); + return false; + } + + if (!isInLobby(target)) { + sender.sendMessage(TARGET_PLAYER_NOT_IN_LOBBY); + return false; + } + + if (isFollowingSomeone(sender)) { + sender.sendMessage(CANNOT_DO_THIS_WHILE_FOLLOWING); + return false; + } + + if (!getSetting(target, Setting.RECEIVE_DUELS)) { + sender.sendMessage(TARGET_PLAYER_HAS_DUELS_DISABLED); + return false; + } + + return true; + } + + // sender = the one who typed /accept + public static boolean canAcceptDuel(Player sender, Player duelSentBy) { + if (isInSilentMode(sender)) { + sender.sendMessage(CANNOT_DO_THIS_IN_SILENT_MODE); + return false; + } + + if (!isInLobby(sender)) { + sender.sendMessage(CANNOT_DO_THIS_WHILE_NOT_IN_LOBBY); + return false; + } + + if (!isInLobby(duelSentBy)) { + sender.sendMessage(TARGET_PLAYER_NOT_IN_LOBBY); + return false; + } + + if (isFollowingSomeone(sender)) { + sender.sendMessage(CANNOT_DO_THIS_WHILE_FOLLOWING); + return false; + } + + if (isFollowingSomeone(duelSentBy)) { + sender.sendMessage(TARGET_PLAYER_FOLLOWING_SOMEONE); + return false; + } + + if(isInEvent(sender)) { + sender.sendMessage(CANNOT_DO_THIS_WHILE_IN_EVENT); + return false; + } + + if (isInParty(sender)) { + sender.sendMessage(CANNOT_DO_THIS_WHILE_IN_PARTY); + return false; + } + + if (isInParty(duelSentBy)) { + sender.sendMessage(TARGET_IN_PARTY); + return false; + } + + return true; + } + + public static boolean canSendDuel(Party sender, Party target, Player initiator) { + if (isInRankedGame(initiator)) { + initiator.sendMessage(CANNOT_DO_THIS_WHILE_IN_RANKED_GAME); + return false; + } + + if (isInTournament(sender)) { + initiator.sendMessage(CANNOT_DO_THIS_WHILST_IN_TOURNAMENT); + return false; + } + + if (sender == target) { + initiator.sendMessage(CANNOT_DUEL_OWN_PARTY); + return false; + } + + if (!isInLobby(initiator)) { + initiator.sendMessage(CANNOT_DO_THIS_WHILE_NOT_IN_LOBBY); + return false; + } + + if (!isInLobby(Bukkit.getPlayer(target.getLeader()))) { + initiator.sendMessage(TARGET_PARTY_NOT_IN_LOBBY); + return false; + } + + if(isInEvent(initiator)) { + initiator.sendMessage(CANNOT_DO_THIS_WHILE_IN_EVENT); + return false; + } + + if (!getSetting(Bukkit.getPlayer(target.getLeader()), Setting.RECEIVE_DUELS)) { + initiator.sendMessage(TARGET_PARTY_HAS_DUELS_DISABLED); + return false; + } + + /*if (isInTournament(sender)) { + initiator.sendMessage(CANNOT_DO_THIS_WHILST_IN_TOURNAMENT); + return false; + }*/ + + return true; + } + + public static boolean canAcceptDuel(Party target, Party sender, Player initiator) { + if (isInRankedGame(initiator)) { + initiator.sendMessage(CANNOT_DO_THIS_WHILE_IN_RANKED_GAME); + return false; + } + + if (!isInLobby(initiator)) { + initiator.sendMessage(CANNOT_DO_THIS_WHILE_NOT_IN_LOBBY); + return false; + } + + if(isInEvent(initiator)) { + initiator.sendMessage(CANNOT_DO_THIS_WHILE_IN_EVENT); + return false; + } + + if (!isInLobby(Bukkit.getPlayer(target.getLeader()))) { + initiator.sendMessage(TARGET_PLAYER_NOT_IN_LOBBY); + return false; + } + + /*if (isInTournament(target)) { + initiator.sendMessage(CANNOT_DO_THIS_WHILST_IN_TOURNAMENT); + return false; + }*/ + + return true; + } + + public static boolean canJoinParty(Player player, Party party) { + if (isInRankedGame(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_IN_RANKED_GAME); + return false; + } + + if (isInParty(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_IN_PARTY); + return false; + } + + if (!isInLobby(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_NOT_IN_LOBBY); + return false; + } + + if(isInEvent(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_IN_EVENT); + return false; + } + + if (isFollowingSomeone(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_FOLLOWING); + return false; + } + + if (party.getMembers().size() >= Party.MAX_SIZE && !Bukkit.getPlayer(party.getLeader()).isOp()) { + player.sendMessage(TARGET_PARTY_REACHED_MAXIMUM_SIZE); + return false; + } + + Tournament tournament = PotPvPSI.getInstance().getTournamentHandler().getTournament(); + if (tournament != null && isInTournament(party) && party.getMembers().size() >= tournament.getRequiredPartySize()) { + player.sendMessage(TARGET_PARTY_REACHED_MAXIMUM_SIZE_FOR_TOURNAMENT); + return false; + } + + return true; + } + + public static boolean canUseSpectateItem(Player player) { + if (!isInLobby(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_NOT_IN_LOBBY); + return false; + } + + return canUseSpectateItemIgnoreMatchSpectating(player); + } + + public static boolean canUseSpectateItemIgnoreMatchSpectating(Player player) { + if (isInParty(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_IN_PARTY); + return false; + } + + if (isInRankedGame(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_IN_RANKED_GAME); + return false; + } + + if (isInQueue(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_QUEUED); + return false; + } + + if (isInMatch(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_IN_MATCH); + return false; + } + + if(isInEvent(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_IN_EVENT); + return false; + } + + if (isFollowingSomeone(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_FOLLOWING); + return false; + } + + return true; + } + + public static boolean canFollowSomeone(Player player) { + if (isInParty(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_IN_PARTY); + return false; + } + + if (isInQueue(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_QUEUED); + return false; + } + + if (isInMatch(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_IN_MATCH); + return false; + } + + if(isInEvent(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_IN_EVENT); + return false; + } + + if (isInRankedGame(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_IN_RANKED_GAME); + return false; + } + + if (!(isInLobby(player))) { + player.sendMessage(ChatColor.DARK_RED + "✖ " + ChatColor.RED + "You can't do that here!"); + return false; + } + + return isInLobby(player); + } + + public static boolean canJoinQueue(Player player) { + if (isInRankedGame(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_IN_RANKED_GAME); + return false; + } + if (isInSilentMode(player)) { + player.sendMessage(CANNOT_DO_THIS_IN_SILENT_MODE); + return false; + } + + if (isInParty(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_IN_PARTY); + return false; + } + + if (isInQueue(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_QUEUED); + return false; + } + + if (!isInLobby(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_NOT_IN_LOBBY); + return false; + } + + if(isInEvent(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_IN_EVENT); + return false; + } + + if (isFollowingSomeone(player)) { + player.sendMessage(CANNOT_DO_THIS_WHILE_FOLLOWING); + return false; + } + + return true; + } + + public static boolean canJoinQueue(Party party) { + if (isInQueue(party)) { + // we shouldn't really message the whole party + // here, but players should never really be able to click + // this item while in a queue anyway (and it takes a lot of work + // to rework this validation to include an initiator) + party.message(CANNOT_DO_THIS_WHILE_QUEUED); + return false; + } + + if (isInTournament(party)) { + party.message(CANNOT_DO_THIS_WHILST_IN_TOURNAMENT); + return false; + } + + return true; + } + + public static boolean canStartTeamSplit(Party party, Player initiator) { + if (isInQueue(party)) { + initiator.sendMessage(CANNOT_DO_THIS_WHILE_QUEUED); + return false; + } + + if (!isInLobby(initiator)) { + initiator.sendMessage(CANNOT_DO_THIS_WHILE_NOT_IN_LOBBY); + return false; + } + + if(isInEvent(initiator)) { + initiator.sendMessage(CANNOT_DO_THIS_WHILE_IN_EVENT); + return false; + } + + if (isInTournament(party)) { + initiator.sendMessage(CANNOT_DO_THIS_WHILST_IN_TOURNAMENT); + return false; + } + + if (party.getMembers().size() < 2) { + initiator.sendMessage(CANNOT_DO_THIS_TOO_FEW_PEOPLE); + return false; + } + + return true; + } + + public static boolean canStartFfa(Party party, Player initiator) { + if (isInQueue(party)) { + initiator.sendMessage(CANNOT_DO_THIS_WHILE_QUEUED); + return false; + } + + if (!isInLobby(initiator)) { + initiator.sendMessage(CANNOT_DO_THIS_WHILE_NOT_IN_LOBBY); + return false; + } + + if(isInEvent(initiator)) { + initiator.sendMessage(CANNOT_DO_THIS_WHILE_IN_EVENT); + return false; + } + + if (isInTournament(party)) { + initiator.sendMessage(CANNOT_DO_THIS_WHILST_IN_TOURNAMENT); + return false; + } + + if (party.getMembers().size() < 2) { + initiator.sendMessage(CANNOT_DO_THIS_TOO_FEW_PEOPLE); + return false; + } + + return true; + } + + private static boolean getSetting(Player player, Setting setting) { + SettingHandler settingHandler = PotPvPSI.getInstance().getSettingHandler(); + return settingHandler.getSetting(player, setting); + } + + public static boolean isInRankedGame (Player player) { + return PotPvPSI.getInstance().getHCTRankedHandler().getGameHandler().getJoinedGame(player) != null; + } + + private static boolean isInParty(Player player) { + PartyHandler partyHandler = PotPvPSI.getInstance().getPartyHandler(); + return partyHandler.hasParty(player); + } + + private static boolean isInQueue(Player player) { + QueueHandler queueHandler = PotPvPSI.getInstance().getQueueHandler(); + return queueHandler.isQueued(player.getUniqueId()); + } + + private static boolean isInQueue(Party party) { + QueueHandler queueHandler = PotPvPSI.getInstance().getQueueHandler(); + return queueHandler.isQueued(party); + } + + private boolean isInMatch(Player player) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + return matchHandler.isPlayingMatch(player); + } + + private boolean isInLobby(Player player) { + LobbyHandler lobbyHandler = PotPvPSI.getInstance().getLobbyHandler(); + return lobbyHandler.isInLobby(player); + } + + private boolean isInEvent(Player player) { + MatchHandler matchHandler = PotPvPSI.getInstance().getMatchHandler(); + return matchHandler.isPlayingEvent(player); + } + + private boolean isFollowingSomeone(Player player) { + FollowHandler followHandler = PotPvPSI.getInstance().getFollowHandler(); + return followHandler.getFollowing(player).isPresent(); + } + + private boolean isInTournament(Party party) { + return PotPvPSI.getInstance().getTournamentHandler().isInTournament(party); + } + + private boolean isInSilentMode(Player player) { + return player.hasMetadata("modmode"); + } + +} diff --git a/Network/ePractice/src/main/resources/config.yml b/Network/ePractice/src/main/resources/config.yml new file mode 100644 index 0000000..048ca4e --- /dev/null +++ b/Network/ePractice/src/main/resources/config.yml @@ -0,0 +1,3 @@ +mongo: + uri: "mongodb://127.0.0.1:27017" + database: "practice" \ No newline at end of file diff --git a/Network/ePractice/src/main/resources/plugin.yml b/Network/ePractice/src/main/resources/plugin.yml new file mode 100644 index 0000000..5c1478f --- /dev/null +++ b/Network/ePractice/src/main/resources/plugin.yml @@ -0,0 +1,6 @@ +name: ePractice +version: '${version}' +author: ElevateMC Development Team +main: com.elevatemc.potpvp.PotPvPSI +depend: [ eLib, WorldEdit ] +softdepend: [ Prime ] \ No newline at end of file diff --git a/Network/eQueue/build.gradle b/Network/eQueue/build.gradle new file mode 100644 index 0000000..fb3ef23 --- /dev/null +++ b/Network/eQueue/build.gradle @@ -0,0 +1,39 @@ +plugins { + id 'java' +} + +group 'com.elevatemc' +version '1.0' + +sourceSets { + main.java.srcDirs = ['src/main/java'] + main.resources.srcDirs = ['src/main/resources'] +} + +compileJava.options.encoding = 'UTF-8' + +repositories { + mavenCentral() + maven { + url 'https://papermc.io/repo/repository/maven-public/' + } +} + + +dependencies { + compileOnly files('../lib/primevelocity.jar') + compileOnly 'com.velocitypowered:velocity-api:3.0.1' + annotationProcessor 'com.velocitypowered:velocity-api:3.0.1' + + compileOnly 'org.projectlombok:lombok:1.18.22' + annotationProcessor 'org.projectlombok:lombok:1.18.22' +} + +processResources { + def props = [version: 'git rev-parse --verify --short HEAD'.execute().text.trim()] + inputs.properties props + filteringCharset 'UTF-8' + filesMatching('bungee.yml') { + expand props + } +} diff --git a/Network/eQueue/src/main/java/com/elevatemc/equeue/command/eQueueCommand.java b/Network/eQueue/src/main/java/com/elevatemc/equeue/command/eQueueCommand.java new file mode 100644 index 0000000..ef7fc43 --- /dev/null +++ b/Network/eQueue/src/main/java/com/elevatemc/equeue/command/eQueueCommand.java @@ -0,0 +1,53 @@ +package com.elevatemc.equeue.command; + +import com.elevatemc.equeue.eQueue; +import com.elevatemc.equeue.queue.Queue; +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.command.SimpleCommand; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; + +public class eQueueCommand implements SimpleCommand { + + @Override + public void execute(final Invocation invocation) { + CommandSource source = invocation.source(); + // Get the arguments after the command alias + String[] args = invocation.arguments(); + + if (args.length == 0) { + sendDefaultHelpMessage(source); + } else { + String subcmd = args[0]; + switch (subcmd) { + case "reload": + eQueue.getInstance().reloadConfigAndQueues(); + source.sendMessage(Component.text("Reloaded the config & queues.!", NamedTextColor.GREEN)); + break; + case "list": + sendQueueList(source); + break; + default: + sendDefaultHelpMessage(source); + } + } + } + + @Override + public boolean hasPermission(final Invocation invocation) { + return invocation.source().hasPermission("equeue.admin"); + } + + private void sendQueueList(CommandSource commandSender) { + for (Queue queue : eQueue.getInstance().getQueueHandler().getQueues().values()) { + commandSender.sendMessage(Component.text(queue.getServer(), NamedTextColor.YELLOW)); + commandSender.sendMessage(Component.text(" * Size: " + queue.getQueueEntries().size(), NamedTextColor.YELLOW)); + } + } + + private void sendDefaultHelpMessage(CommandSource commandSender) { + commandSender.sendMessage(Component.text("All eQueue Commands:", NamedTextColor.RED)); + commandSender.sendMessage(Component.text("/equeue reload - Reload the config (ALL QUEUES WILL BE RESET)", NamedTextColor.RED)); + commandSender.sendMessage(Component.text("/equeue list - Get a list of all queues + status", NamedTextColor.RED)); + } +} diff --git a/Network/eQueue/src/main/java/com/elevatemc/equeue/eQueue.java b/Network/eQueue/src/main/java/com/elevatemc/equeue/eQueue.java new file mode 100644 index 0000000..4d901fa --- /dev/null +++ b/Network/eQueue/src/main/java/com/elevatemc/equeue/eQueue.java @@ -0,0 +1,102 @@ +package com.elevatemc.equeue; + +import com.elevatemc.equeue.command.eQueueCommand; +import com.elevatemc.equeue.listener.GeneralListener; +import com.elevatemc.equeue.queue.QueueHandler; +import com.google.inject.Inject; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; +import com.velocitypowered.api.plugin.Dependency; +import com.velocitypowered.api.plugin.Plugin; +import com.velocitypowered.api.plugin.annotation.DataDirectory; +import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; +import dev.apposed.prime.proxy.PrimeProxy; +import dev.apposed.prime.proxy.module.profile.ProfileHandler; +import lombok.Getter; +import ninja.leaping.configurate.ConfigurationNode; +import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; +import org.slf4j.Logger; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; + +@Plugin( + id = "equeue", + name = "eQueue", + description = "A simple single instance queue plugin.", + version = "RELEASE", + url = "https://elevatemc.com", + authors = {"ElevateMC Development Team"}, + dependencies = { + @Dependency(id = "primeproxy") + } +) +public class eQueue { + @Getter private static eQueue instance; + @Getter private QueueHandler queueHandler; + @Getter private ConfigurationNode config; + + private Path dataDirectory; + + @Getter private ProfileHandler profileHandler; + + @Getter private final ProxyServer server; + @Getter private final Logger logger; + + @Getter MinecraftChannelIdentifier channel = MinecraftChannelIdentifier.create("equeue", "main"); + + @Inject + public eQueue(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) { + instance = this; + this.server = server; + this.logger = logger; + this.dataDirectory = dataDirectory; + + // profileHandler = prime.getModuleHandler().getModule(ProfileHandler.class); + } + + @Subscribe + public void onInitialize(ProxyInitializeEvent e) { + loadConfig(); + + profileHandler = PrimeProxy.getInstance().getModuleHandler().getModule(ProfileHandler.class); + queueHandler = new QueueHandler(); + getQueueHandler().loadQueues(); + + server.getChannelRegistrar().register(channel); + server.getEventManager().register(this, new GeneralListener()); + server.getCommandManager().register(server.getCommandManager().metaBuilder("equeue").build(), new eQueueCommand()); + } + + public void reloadConfigAndQueues() { + this.loadConfig(); + getQueueHandler().loadQueues(); + } + + private void loadConfig() { + if (!dataDirectory.toFile().exists()) + dataDirectory.toFile().mkdir(); + + File file = dataDirectory.resolve("config.yml").toFile(); + + + if (!file.exists()) { + try (InputStream in = getClass().getClassLoader().getResourceAsStream("config.yml")) { + Files.copy(in, file.toPath()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + YAMLConfigurationLoader configurationLoader = YAMLConfigurationLoader.builder().setFile(file).build(); + try { + config = configurationLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/Network/eQueue/src/main/java/com/elevatemc/equeue/listener/GeneralListener.java b/Network/eQueue/src/main/java/com/elevatemc/equeue/listener/GeneralListener.java new file mode 100644 index 0000000..ccecafc --- /dev/null +++ b/Network/eQueue/src/main/java/com/elevatemc/equeue/listener/GeneralListener.java @@ -0,0 +1,47 @@ +package com.elevatemc.equeue.listener; + +import com.elevatemc.equeue.eQueue; +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteStreams; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.DisconnectEvent; +import com.velocitypowered.api.event.connection.PluginMessageEvent; +import com.velocitypowered.api.event.player.ServerConnectedEvent; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ServerConnection; + +public class GeneralListener { + @Subscribe + public void onLeave(DisconnectEvent e){ + Player player = e.getPlayer(); + eQueue.getInstance().getQueueHandler().removeFromQueue(player.getUniqueId()); + } + + @Subscribe + public void onServerSwitch(ServerConnectedEvent e) { + if (e.getPreviousServer().isPresent()) { + // TODO: do not remove the player if its from hub to hub + Player player = e.getPlayer(); + eQueue.getInstance().getQueueHandler().removeFromQueue(player.getUniqueId()); + } + } + + @Subscribe + public void onPluginMessage(PluginMessageEvent e) { + if (e.getIdentifier().getId().equals(eQueue.getInstance().getChannel().getId())) { + ByteArrayDataInput in = ByteStreams.newDataInput( e.getData() ); + String subChannel = in.readUTF(); + if ( subChannel.equalsIgnoreCase( "joinqueue" )) { + // the receiver is a ProxiedPlayer when a server talks to the proxy + if ( e.getSource() instanceof ServerConnection) + { + + Player player = ((ServerConnection) e.getSource()).getPlayer(); + String queueName = in.readUTF(); + eQueue.getInstance().getQueueHandler().joinQueue(player.getUniqueId(), queueName); + } + } + } + + } +} diff --git a/Network/eQueue/src/main/java/com/elevatemc/equeue/queue/Queue.java b/Network/eQueue/src/main/java/com/elevatemc/equeue/queue/Queue.java new file mode 100644 index 0000000..3e58d7f --- /dev/null +++ b/Network/eQueue/src/main/java/com/elevatemc/equeue/queue/Queue.java @@ -0,0 +1,13 @@ +package com.elevatemc.equeue.queue; + +import com.elevatemc.equeue.util.SortedList; +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class Queue { + @Getter @NonNull private String server; + @Getter + SortedList queueEntries = new SortedList<>(new QueueEntryComparator()); +} \ No newline at end of file diff --git a/Network/eQueue/src/main/java/com/elevatemc/equeue/queue/QueueEntry.java b/Network/eQueue/src/main/java/com/elevatemc/equeue/queue/QueueEntry.java new file mode 100644 index 0000000..c5ae834 --- /dev/null +++ b/Network/eQueue/src/main/java/com/elevatemc/equeue/queue/QueueEntry.java @@ -0,0 +1,17 @@ +package com.elevatemc.equeue.queue; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.UUID; + +@AllArgsConstructor +public class QueueEntry implements Comparable { + @Getter private UUID UUID; + @Getter private int priority; + + @Override + public int compareTo(QueueEntry o) { + return this.getPriority() - o.getPriority(); + } +} diff --git a/Network/eQueue/src/main/java/com/elevatemc/equeue/queue/QueueEntryComparator.java b/Network/eQueue/src/main/java/com/elevatemc/equeue/queue/QueueEntryComparator.java new file mode 100644 index 0000000..09b83e7 --- /dev/null +++ b/Network/eQueue/src/main/java/com/elevatemc/equeue/queue/QueueEntryComparator.java @@ -0,0 +1,24 @@ +package com.elevatemc.equeue.queue; + +import com.elevatemc.equeue.eQueue; +import dev.apposed.prime.proxy.module.profile.Profile; + +import java.util.Comparator; +import java.util.Optional; + +public class QueueEntryComparator implements Comparator { + @Override + public int compare(QueueEntry entry1, QueueEntry entry2) { + Optional profile1 = eQueue.getInstance().getProfileHandler().getProfile(entry1.getUUID()); + Optional profile2 = eQueue.getInstance().getProfileHandler().getProfile(entry2.getUUID()); + int firstEntryWeight = 0; + int secondEntryWeight = 0; + if (profile1.isPresent()) { + firstEntryWeight = profile1.get().getHighestActiveGrant().getRank().getWeight(); + } + if (profile2.isPresent()) { + secondEntryWeight = profile2.get().getHighestActiveGrant().getRank().getWeight(); + } + return secondEntryWeight - firstEntryWeight; + } +} diff --git a/Network/eQueue/src/main/java/com/elevatemc/equeue/queue/QueueHandler.java b/Network/eQueue/src/main/java/com/elevatemc/equeue/queue/QueueHandler.java new file mode 100644 index 0000000..8c03bcb --- /dev/null +++ b/Network/eQueue/src/main/java/com/elevatemc/equeue/queue/QueueHandler.java @@ -0,0 +1,138 @@ +package com.elevatemc.equeue.queue; + +import com.elevatemc.equeue.eQueue; +import com.elevatemc.equeue.util.SortedList; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ServerConnection; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import lombok.Getter; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import ninja.leaping.configurate.ConfigurationNode; +import ninja.leaping.configurate.commented.CommentedConfigurationNode; + +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; + +public class QueueHandler { + + @Getter private final ConcurrentHashMap queues = new ConcurrentHashMap<>(); + + public QueueHandler () { + eQueue.getInstance().getServer().getScheduler().buildTask(eQueue.getInstance(), this::runQueueUpdate).repeat(eQueue.getInstance().getConfig().getNode("speed").getInt(), TimeUnit.SECONDS).schedule(); + } + + public void loadQueues() { + queues.clear(); + ConfigurationNode config = eQueue.getInstance().getConfig(); + for (ConfigurationNode node : config.getNode("queues").getChildrenMap().values()) { + String server = node.getNode("server").getString(); + if (server.equals("")) { + eQueue.getInstance().getLogger().warn("The server name for queue " + server + " is undefined."); + return; + } + Optional info = eQueue.getInstance().getServer().getServer(server); + if (!info.isPresent()) { + eQueue.getInstance().getLogger().warn("No server exists with name " + server); + return; + } + + boolean enabled = node.getNode("enabled").getBoolean(); + if (enabled) { + Queue queue = new Queue(server); + queues.put(server, queue); + } else { + eQueue.getInstance().getLogger().info("Queue for server " + server + " is disabled."); + } + + } + } + + public void runQueueUpdate() { + for (Queue queue : queues.values()) { + Optional info = eQueue.getInstance().getServer().getServer(queue.getServer()); + if (!info.isPresent()) { + eQueue.getInstance().getLogger().warn("No server exists with name " + queue.getServer()); + return; + } + + SortedList queueEntries = queue.getQueueEntries(); + if (queueEntries.size() == 0) { + return; + } + QueueEntry entry = queueEntries.get(0); + UUID playerUUID = entry.getUUID(); + Optional player = eQueue.getInstance().getServer().getPlayer(playerUUID); + if (!player.isPresent()) { // Somehow a player is still in queue + eQueue.getInstance().getLogger().error("this shouldnt happen"); + queueEntries.remove(entry); + sendQueuePositions(queue); + return; + } + player.get().createConnectionRequest(info.get()).connect(); + } + } + + private void sendQueuePositions(Queue queue) { + for (int i = 0; i < queue.getQueueEntries().size(); i ++) { + QueueEntry entry = queue.getQueueEntries().get(i); + UUID playerUUID = entry.getUUID(); + Optional playerOpt = eQueue.getInstance().getServer().getPlayer(playerUUID); + if (!playerOpt.isPresent()) { + queue.getQueueEntries().remove(entry); + return; + } + + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + out.writeUTF( "position" ); + out.writeUTF(playerOpt.get().getUniqueId().toString()); + out.writeUTF(queue.getServer()); + out.writeInt(i + 1); // Array is zero indexed + out.writeInt(queue.getQueueEntries().size()); + Optional serverConnectionOpt = playerOpt.get().getCurrentServer(); + if (serverConnectionOpt.isPresent()) { + serverConnectionOpt.get().sendPluginMessage(eQueue.getInstance().getChannel(), out.toByteArray()); + } + } + } + + public void joinQueue(UUID uuid, String queueName) { + boolean alreadyInQueue = false; + for (Queue q : queues.values()) { + alreadyInQueue = q.getQueueEntries().stream().anyMatch(entry -> entry.getUUID().equals(uuid)); + if (alreadyInQueue) break; + } + Optional playerOpt = eQueue.getInstance().getServer().getPlayer(uuid); + if (alreadyInQueue) { + playerOpt.ifPresent(player -> player.sendMessage(Component.text("You are already in queue.", NamedTextColor.RED))); + return; + } + Queue queue = queues.get(queueName); + if (queue == null) { + playerOpt.ifPresent(player -> player.sendMessage(Component.text("The queue does not exists.", NamedTextColor.RED))); + return; + } + queue.getQueueEntries().add(new QueueEntry(uuid, 0)); + sendQueuePositions(queue); + } + + // This checks all queues because we do not keep track of PLAYER -> QUEUE but only QUEUE -> PLAYER + public void removeFromQueue(UUID uuid) { + Queue leftQueue = null; + for (Queue q : queues.values()) { + boolean result = q.getQueueEntries().removeIf(entry -> entry.getUUID().equals(uuid)); + if (result) leftQueue = q; + } + if (leftQueue != null) { + sendQueuePositions(leftQueue); + Optional playerOpt = eQueue.getInstance().getServer().getPlayer(uuid); + playerOpt.ifPresent(player -> player.sendMessage(Component.text("You left the queue", NamedTextColor.RED))); + } + + } +} diff --git a/Network/eQueue/src/main/java/com/elevatemc/equeue/util/SortedList.java b/Network/eQueue/src/main/java/com/elevatemc/equeue/util/SortedList.java new file mode 100644 index 0000000..2291b10 --- /dev/null +++ b/Network/eQueue/src/main/java/com/elevatemc/equeue/util/SortedList.java @@ -0,0 +1,43 @@ +package com.elevatemc.equeue.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; + +public class SortedList extends ArrayList +{ + private Comparator comparator; + + public SortedList(final Comparator comparator) + { + this.comparator = comparator; + } + + @Override + public boolean add(E e) { + super.add(e); + Collections.sort(this, comparator); + return true; + } + + @Override + public void add(int index, E element) { + super.add(index, element); + Collections.sort(this, comparator); + } + + @Override + public boolean addAll(Collection c) { + boolean returned = super.addAll(c); + Collections.sort(this, comparator); + return returned; + } + + @Override + public boolean addAll(int index, Collection c) { + boolean returned = super.addAll(index, c); + Collections.sort(this, comparator); + return returned; + } +} \ No newline at end of file diff --git a/Network/eQueue/src/main/resources/config.yml b/Network/eQueue/src/main/resources/config.yml new file mode 100644 index 0000000..4aefa1c --- /dev/null +++ b/Network/eQueue/src/main/resources/config.yml @@ -0,0 +1,8 @@ +# The speed of the queue system defined in seconds +speed: 1 + +# The queues +queues: + practice: + server: "Practice" + enabled: true \ No newline at end of file diff --git a/Network/gradle/wrapper/gradle-wrapper.jar b/Network/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7454180 Binary files /dev/null and b/Network/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Network/gradle/wrapper/gradle-wrapper.properties b/Network/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..69a9715 --- /dev/null +++ b/Network/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/Network/gradlew b/Network/gradlew new file mode 100644 index 0000000..744e882 --- /dev/null +++ b/Network/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MSYS* | MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/Network/gradlew.bat b/Network/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/Network/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/Network/lib/NachoSpigot.jar b/Network/lib/NachoSpigot.jar new file mode 100644 index 0000000..54153cd Binary files /dev/null and b/Network/lib/NachoSpigot.jar differ diff --git a/Network/lib/espigot.jar b/Network/lib/espigot.jar new file mode 100644 index 0000000..60be6b6 Binary files /dev/null and b/Network/lib/espigot.jar differ diff --git a/Network/lib/lcapi.jar b/Network/lib/lcapi.jar new file mode 100644 index 0000000..d4a7bcf Binary files /dev/null and b/Network/lib/lcapi.jar differ diff --git a/Network/lib/nuvotifier.jar b/Network/lib/nuvotifier.jar new file mode 100644 index 0000000..7a59a1d Binary files /dev/null and b/Network/lib/nuvotifier.jar differ diff --git a/Network/lib/primespigot.jar b/Network/lib/primespigot.jar new file mode 100644 index 0000000..64d50f7 Binary files /dev/null and b/Network/lib/primespigot.jar differ diff --git a/Network/lib/primevelocity.jar b/Network/lib/primevelocity.jar new file mode 100644 index 0000000..250d651 Binary files /dev/null and b/Network/lib/primevelocity.jar differ diff --git a/Network/lib/server.jar b/Network/lib/server.jar new file mode 100644 index 0000000..0b739e5 Binary files /dev/null and b/Network/lib/server.jar differ diff --git a/Network/lib/universespigot.jar b/Network/lib/universespigot.jar new file mode 100644 index 0000000..fe82f1a Binary files /dev/null and b/Network/lib/universespigot.jar differ diff --git a/Network/settings.gradle b/Network/settings.gradle new file mode 100644 index 0000000..4e1bae4 --- /dev/null +++ b/Network/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'ElevateNetwork' +include 'eLib', 'ePractice', 'eHub', 'eQueue' \ No newline at end of file diff --git a/Prime/.gitignore b/Prime/.gitignore new file mode 100644 index 0000000..caa32e6 --- /dev/null +++ b/Prime/.gitignore @@ -0,0 +1,2 @@ +.idea/ +*.iml \ No newline at end of file diff --git a/Prime/pom.xml b/Prime/pom.xml new file mode 100644 index 0000000..58ae274 --- /dev/null +++ b/Prime/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + dev.apposed + prime-parent + pom + 1.0-BETA + + Prime + + + prime-spigot + prime-velocity + + + + + UTF-8 + + + + + md_5-releases + https://repo.md-5.net/content/repositories/releases/ + + + bungeecord-releases + https://oss.sonatype.org/content/repositories/snapshots + + + + \ No newline at end of file diff --git a/Prime/prime-bungee/dependency-reduced-pom.xml b/Prime/prime-bungee/dependency-reduced-pom.xml new file mode 100644 index 0000000..bc283c3 --- /dev/null +++ b/Prime/prime-bungee/dependency-reduced-pom.xml @@ -0,0 +1,78 @@ + + + + prime-parent + dev.apposed + 1.0-BETA + + 4.0.0 + prime-bungee + + + + true + src/main/resources + + plugin.yml + + + + src/main/resources + + plugin.yml + + + + + + maven-compiler-plugin + + 1.8 + 1.8 + + + + maven-shade-plugin + 3.2.0 + + + package + + shade + + + + + org.apache.commons.pool2 + org.shaded.apache.commons.pool2 + + + org.mongodb + org.shaded.mongodb + + + com.google.code.gson + com.shaded.google.code.gson + + + + + + + + + + + bungeecord-repo + https://oss.sonatype.org/content/repositories/snapshots + + + + + org.projectlombok + lombok + LATEST + provided + + + diff --git a/Prime/prime-bungee/pom.xml b/Prime/prime-bungee/pom.xml new file mode 100644 index 0000000..952e33d --- /dev/null +++ b/Prime/prime-bungee/pom.xml @@ -0,0 +1,126 @@ + + + + prime-parent + dev.apposed + 1.0-BETA + + 4.0.0 + + prime-bungee + + + + bungeecord-repo + https://oss.sonatype.org/content/repositories/snapshots + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.0 + + + package + + shade + + + + + org.apache.commons.pool2 + org.shaded.apache.commons.pool2 + + + org.mongodb + org.shaded.mongodb + + + com.google.code.gson + com.shaded.google.code.gson + + + + + + + + + + src/main/resources + true + + plugin.yml + + + + src/main/resources + false + + plugin.yml + + + + + + + + net.md-5 + bungeecord-api + 1.8-SNAPSHOT + + + org.projectlombok + lombok + LATEST + provided + + + org.mongodb + mongo-java-driver + 3.12.5 + + + org.apache.commons + commons-pool2 + 2.8.0 + + + redis.clients + jedis + 2.7.2 + jar + compile + + + com.google.code.gson + gson + 2.8.6 + + + com.googlecode.json-simple + json-simple + 1.1 + + + dev.apposed + prime-spigot + 1.0-BETA + compile + + + + \ No newline at end of file diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/ConsoleCommandPacket.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/ConsoleCommandPacket.java new file mode 100644 index 0000000..33058a2 --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/ConsoleCommandPacket.java @@ -0,0 +1,16 @@ +package dev.apposed.prime.packet; + +import dev.apposed.prime.proxy.module.database.redis.packet.Packet; + +public class ConsoleCommandPacket extends Packet { + + @Override + public void onReceive() { + + } + + @Override + public void onSend() { + + } +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/PasswordCreatePacket.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/PasswordCreatePacket.java new file mode 100644 index 0000000..4403bef --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/PasswordCreatePacket.java @@ -0,0 +1,16 @@ +package dev.apposed.prime.packet; + +import dev.apposed.prime.proxy.module.database.redis.packet.Packet; + +public class PasswordCreatePacket extends Packet { + + @Override + public void onReceive() { + + } + + @Override + public void onSend() { + + } +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/ProfileRefreshPacket.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/ProfileRefreshPacket.java new file mode 100644 index 0000000..4850074 --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/ProfileRefreshPacket.java @@ -0,0 +1,34 @@ +package dev.apposed.prime.packet; + +import dev.apposed.prime.packet.type.RefreshType; +import dev.apposed.prime.proxy.PrimeProxy; +import dev.apposed.prime.proxy.module.database.redis.packet.Packet; +import dev.apposed.prime.proxy.module.profile.Profile; +import dev.apposed.prime.proxy.module.profile.ProfileHandler; +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class ProfileRefreshPacket extends Packet { + + private Profile profile; + private RefreshType type; + + @Override + public void onSend() { + } + + @Override + public void onReceive() { + final ProfileHandler profileHandler = PrimeProxy.getInstance().getModuleHandler().getModule(ProfileHandler.class); + switch(type) { + case UPDATE: { + profileHandler.updateProfile(profile); + break; + } + case REMOVE: { + // do nothing, no point + break; + } + } + } +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/PunishmentPacket.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/PunishmentPacket.java new file mode 100644 index 0000000..cdc9c03 --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/PunishmentPacket.java @@ -0,0 +1,16 @@ +package dev.apposed.prime.packet; + +import dev.apposed.prime.proxy.module.database.redis.packet.Packet; + +public class PunishmentPacket extends Packet { + + @Override + public void onReceive() { + + } + + @Override + public void onSend() { + + } +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/RankRefreshPacket.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/RankRefreshPacket.java new file mode 100644 index 0000000..179dbef --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/RankRefreshPacket.java @@ -0,0 +1,34 @@ +package dev.apposed.prime.packet; + +import dev.apposed.prime.packet.type.RefreshType; +import dev.apposed.prime.proxy.PrimeProxy; +import dev.apposed.prime.proxy.module.database.redis.packet.Packet; +import dev.apposed.prime.proxy.module.rank.Rank; +import dev.apposed.prime.proxy.module.rank.RankHandler; +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class RankRefreshPacket extends Packet { + + private Rank rank; + private RefreshType type; + + @Override + public void onSend() { + } + + @Override + public void onReceive() { + final RankHandler rankHandler = PrimeProxy.getInstance().getModuleHandler().getModule(RankHandler.class); + switch(type) { + case UPDATE: { + rankHandler.updateRank(rank); + break; + } + case REMOVE: { + rankHandler.getCache().remove(rank); + break; + } + } + } +} \ No newline at end of file diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/ServerHeartbeatPacket.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/ServerHeartbeatPacket.java new file mode 100644 index 0000000..e2e00fa --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/ServerHeartbeatPacket.java @@ -0,0 +1,26 @@ +package dev.apposed.prime.packet; + +import dev.apposed.prime.proxy.PrimeProxy; +import dev.apposed.prime.proxy.module.database.redis.packet.Packet; +import dev.apposed.prime.proxy.module.server.Server; +import dev.apposed.prime.proxy.module.server.ServerHandler; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class ServerHeartbeatPacket extends Packet { + + private Server server; + + @Override + public void onSend() { + + } + + @Override + public void onReceive() { + final ServerHandler serverHandler = PrimeProxy.getInstance().getModuleHandler().getModule(ServerHandler.class); + serverHandler.updateServer(server); + } +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/ServerUpdatePacket.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/ServerUpdatePacket.java new file mode 100644 index 0000000..8d018e3 --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/ServerUpdatePacket.java @@ -0,0 +1,20 @@ +package dev.apposed.prime.packet; + +import dev.apposed.prime.proxy.module.database.redis.packet.Packet; +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class ServerUpdatePacket extends Packet { + + private String serverName; + + @Override + public void onReceive() { + + } + + @Override + public void onSend() { + + } +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/StaffMessagePacket.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/StaffMessagePacket.java new file mode 100644 index 0000000..75692d0 --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/StaffMessagePacket.java @@ -0,0 +1,24 @@ +package dev.apposed.prime.packet; + +import dev.apposed.prime.proxy.module.database.redis.packet.Packet; +import dev.apposed.prime.packet.type.StaffMessageType; +import lombok.AllArgsConstructor; + +import java.util.UUID; + +@AllArgsConstructor +public class StaffMessagePacket extends Packet { + + private StaffMessageType type; + private UUID uuid; + private String prevServer, server; + + @Override + public void onReceive() { + + } + + @Override + public void onSend() { + } +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/type/RefreshType.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/type/RefreshType.java new file mode 100644 index 0000000..c7fb586 --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/type/RefreshType.java @@ -0,0 +1,6 @@ +package dev.apposed.prime.packet.type; + +public enum RefreshType { + UPDATE, + REMOVE +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/type/StaffMessageType.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/type/StaffMessageType.java new file mode 100644 index 0000000..0544784 --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/packet/type/StaffMessageType.java @@ -0,0 +1,13 @@ +package dev.apposed.prime.packet.type; + +public enum StaffMessageType { + JOIN, + LEAVE, + SWITCH, + CHAT, + HELPOP, + REPORT, + FREEZE, + UNFREEZE, + ADMIN_CHAT +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/PrimeConstants.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/PrimeConstants.java new file mode 100644 index 0000000..2c757a6 --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/PrimeConstants.java @@ -0,0 +1,9 @@ +package dev.apposed.prime.proxy; + +import java.util.UUID; + +public class PrimeConstants { + + public static final UUID CONSOLE_UUID = UUID.fromString("285cda74-7eb6-4756-b3e4-86f4f9b0be6e"); + +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/PrimeProxy.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/PrimeProxy.java new file mode 100644 index 0000000..0d3ea3f --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/PrimeProxy.java @@ -0,0 +1,86 @@ +package dev.apposed.prime.proxy; + +import com.google.common.io.ByteStreams; +import dev.apposed.prime.proxy.module.ModuleHandler; +import dev.apposed.prime.proxy.module.bungee.BungeeHandler; +import dev.apposed.prime.proxy.module.bungee.listener.BungeeListener; +import dev.apposed.prime.proxy.module.command.ServerCommand; +import dev.apposed.prime.proxy.module.database.mongo.MongoModule; +import dev.apposed.prime.proxy.module.database.redis.JedisModule; +import dev.apposed.prime.proxy.module.profile.ProfileHandler; +import dev.apposed.prime.proxy.module.profile.listener.ProfileListener; +import dev.apposed.prime.proxy.module.rank.RankHandler; +import dev.apposed.prime.proxy.module.server.ServerHandler; +import lombok.Getter; +import lombok.SneakyThrows; +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.config.Configuration; +import net.md_5.bungee.config.ConfigurationProvider; +import net.md_5.bungee.config.YamlConfiguration; + +import java.io.*; +import java.util.Arrays; + +@Getter +public class PrimeProxy extends Plugin { + + @Getter private static PrimeProxy instance; + + private ModuleHandler moduleHandler; + private Configuration config; + + @Override + public void onEnable() { + instance = this; + + loadHelpers(); + loadHandlers(); + registerCommands(); + } + + @SneakyThrows + @Override + public void onDisable() { + ConfigurationProvider.getProvider(YamlConfiguration.class).save(config, new File(getDataFolder(), "config.yml")); + } + + private void loadHandlers() { + this.moduleHandler = new ModuleHandler(); + + Arrays.asList( + new MongoModule(), + new JedisModule(), + new RankHandler(), + new ProfileHandler(), + new ServerHandler(), + new BungeeHandler() + ).forEach(module -> this.moduleHandler.registerModule(module)); + } + + @SneakyThrows + private void loadHelpers() { + if (!getDataFolder().exists()) { + getDataFolder().mkdir(); + } + File configFile = new File(getDataFolder(), "config.yml"); + if (!configFile.exists()) { + try { + configFile.createNewFile(); + try (InputStream is = getResourceAsStream("config.yml"); + OutputStream os = new FileOutputStream(configFile)) { + ByteStreams.copy(is, os); + } + } catch (IOException e) { + throw new RuntimeException("Unable to create configuration file", e); + } + } + + this.config = ConfigurationProvider.getProvider(YamlConfiguration.class).load(configFile); + } + + private void registerCommands() { + getProxy().getPluginManager().registerListener(this, new ProfileListener(this)); + getProxy().getPluginManager().registerCommand(this, new ServerCommand(this)); + getProxy().getPluginManager().registerListener(this, new BungeeListener()); + } +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/Module.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/Module.java new file mode 100644 index 0000000..d2dbfed --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/Module.java @@ -0,0 +1,26 @@ +package dev.apposed.prime.proxy.module; + +import dev.apposed.prime.proxy.PrimeProxy; +import lombok.Getter; +import lombok.Setter; +import net.md_5.bungee.config.Configuration; + +@Getter @Setter +public abstract class Module { + + private String name = this.getClass().getSimpleName(); + private String version = "1.0.0"; + + private PrimeProxy plugin = PrimeProxy.getInstance(); + + private Configuration config = plugin.getConfig(); + private ModuleHandler moduleHandler = plugin.getModuleHandler(); + + public void onEnable() { + System.out.println("Module " + name + " v" + version + " has been enabled."); + } + + public void onDisable() { + System.out.println("Module " + name + " v" + version + " has been disabled."); + } +} \ No newline at end of file diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/ModuleHandler.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/ModuleHandler.java new file mode 100644 index 0000000..35ea984 --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/ModuleHandler.java @@ -0,0 +1,36 @@ +package dev.apposed.prime.proxy.module; + +import java.util.HashMap; +import java.util.Map; + +public class ModuleHandler { + + private Map, Module> modules; + + public ModuleHandler() { + this.modules = new HashMap<>(); + } + + public void registerModule(Module module) { + this.modules.put(module.getClass(), module); + module.onEnable(); + } + + public void disableModule(Module module) { + this.modules.remove(module.getClass()); + module.onDisable(); + } + + public void disableModule(Class module) { + this.disableModule(getModule(module)); + } + + public void disableModules() { + this.modules.values().forEach(Module::onDisable); + this.modules.clear(); + } + + public T getModule(Class clazz) { + return clazz.cast(this.modules.get(clazz)); + } +} \ No newline at end of file diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/bungee/BungeeHandler.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/bungee/BungeeHandler.java new file mode 100644 index 0000000..6057aac --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/bungee/BungeeHandler.java @@ -0,0 +1,30 @@ +package dev.apposed.prime.proxy.module.bungee; + +import dev.apposed.prime.proxy.module.Module; +import lombok.Getter; +import net.md_5.bungee.api.connection.ProxiedPlayer; + +import java.util.*; + +@Getter +public class BungeeHandler extends Module { + + private Map prevServer; + + @Override + public void onEnable() { + this.prevServer = new HashMap<>(); + } + public String getPreviousServer(ProxiedPlayer player) { + return this.prevServer.getOrDefault(player, null); + } + + public void setPrevServer(ProxiedPlayer player, String server) { + if(server == null) { + this.prevServer.remove(player); + return; + } + + prevServer.put(player, server); + } +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/bungee/listener/BungeeListener.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/bungee/listener/BungeeListener.java new file mode 100644 index 0000000..c177dca --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/bungee/listener/BungeeListener.java @@ -0,0 +1,41 @@ +package dev.apposed.prime.proxy.module.bungee.listener; + +import dev.apposed.prime.proxy.PrimeProxy; +import dev.apposed.prime.proxy.module.database.redis.JedisModule; +import dev.apposed.prime.proxy.util.time.DurationUtils; +import net.md_5.bungee.api.ServerPing; +import net.md_5.bungee.api.event.ProxyPingEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.event.EventHandler; + +import java.util.concurrent.TimeUnit; + +public class BungeeListener implements Listener { + + private final JedisModule jedisModule; + private volatile String lineOne = "Database Error", lineTwo = "Failed to fetch MOTD"; + private volatile long countdown = 0; + + { + this.jedisModule = PrimeProxy.getInstance().getModuleHandler().getModule(JedisModule.class); + + PrimeProxy.getInstance().getProxy().getScheduler().schedule(PrimeProxy.getInstance(), () -> this.jedisModule.runCommand(jedis -> { + this.lineOne = jedis.hget("Prime:MOTD", "1"); + this.lineTwo = jedis.hget("Prime:MOTD", "2"); + this.countdown = Long.parseLong(jedis.hget("Prime:MOTD", "countdown")); + }), 0, 10, TimeUnit.SECONDS); + } + + @EventHandler + public void onProxyPing(ProxyPingEvent event) { + ServerPing response = event.getResponse(); + + response.setDescription((this.lineOne != null ? this.lineOne.replace("%countdown%", DurationUtils.formatIntoDetailedString( + (int)((countdown - System.currentTimeMillis()) / 1000) + )) : "") + "\n" + (this.lineTwo != null ? this.lineTwo.replace("%countdown%", DurationUtils.formatIntoDetailedString( + (int)((countdown - System.currentTimeMillis()) / 1000) + )) : "")); + event.setResponse(response); +// event.setResponse(new LunarServerPing(event.getResponse())); + } +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/bungee/listener/LunarServerPing.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/bungee/listener/LunarServerPing.java new file mode 100644 index 0000000..b465141 --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/bungee/listener/LunarServerPing.java @@ -0,0 +1,14 @@ +package dev.apposed.prime.proxy.module.bungee.listener; + +import net.md_5.bungee.api.ServerPing; + +public class LunarServerPing extends ServerPing { + private final String lcServer = "hugecock"; + + public LunarServerPing(ServerPing parent) { + this.setDescription(parent.getDescription()); + this.setFavicon(parent.getFaviconObject()); + this.setPlayers(parent.getPlayers()); + this.setVersion(parent.getVersion()); + } +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/command/ServerCommand.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/command/ServerCommand.java new file mode 100644 index 0000000..c300981 --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/command/ServerCommand.java @@ -0,0 +1,77 @@ +package dev.apposed.prime.proxy.module.command; + +import dev.apposed.prime.proxy.PrimeProxy; +import dev.apposed.prime.proxy.module.profile.Profile; +import dev.apposed.prime.proxy.module.profile.ProfileHandler; +import dev.apposed.prime.proxy.module.server.ServerHandler; +import dev.apposed.prime.proxy.util.Color; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Command; + +import java.util.Optional; + + +public class ServerCommand extends Command { + + private final PrimeProxy primeProxy; + private final ProfileHandler profileHandler; + private final ServerHandler serverHandler; + + public ServerCommand(PrimeProxy primeProxy) { + super("server"); + + this.primeProxy = primeProxy; + this.profileHandler = primeProxy.getModuleHandler().getModule(ProfileHandler.class); + this.serverHandler = primeProxy.getModuleHandler().getModule(ServerHandler.class); + } + + @Override + public void execute(CommandSender sender, String[] args) { + if(!(sender instanceof ProxiedPlayer)) return; + ProxiedPlayer player = (ProxiedPlayer) sender; + + final Optional profileOptional = profileHandler.getProfile(player.getUniqueId()); + if(!profileOptional.isPresent()) { + player.sendMessage(Color.translate("&cProfile not loaded properly.")); + return; + } + + final Profile profile = profileOptional.get(); + + if(!profile.hasServerPerm()) { + player.sendMessage(Color.translate("&cNo Permission.")); + return; + } + + if(args.length == 0) { + player.sendMessage(Color.translate("&6You are currently connected to &f" + player.getServer().getInfo().getName() + "&6.")); + String servers = Color.translate("&6Servers: &f"); + boolean first = true; + + for(String name : this.primeProxy.getProxy().getServers().keySet()) { + if(first) { + servers += name; + } else { + servers += Color.translate("&7, &f" + name); + } + + first = false; + } + + player.sendMessage(servers); + player.sendMessage(Color.translate("&6Connect to a server with &e/server ")); + return; + } + + ServerInfo server = this.primeProxy.getProxy().getServerInfo(args[0]); + if(server == null) { + player.sendMessage(Color.translate("&cNo server by the name &f" + args[0] + "&c found!")); + return; + } + + player.connect(server); + player.sendMessage(Color.translate("&6Connecting to &f" + server.getName() + "&6!")); + } +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/database/mongo/MongoModule.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/database/mongo/MongoModule.java new file mode 100644 index 0000000..e0681e9 --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/database/mongo/MongoModule.java @@ -0,0 +1,51 @@ +package dev.apposed.prime.proxy.module.database.mongo; + +import com.mongodb.MongoClient; +import com.mongodb.MongoCredential; +import com.mongodb.ServerAddress; +import com.mongodb.client.MongoDatabase; +import dev.apposed.prime.proxy.module.Module; +import lombok.Getter; + +import java.util.Arrays; + +@Getter +public class MongoModule extends Module { + + private String host, database, username, password, authdb; + private int port; + private boolean auth; + + private MongoDatabase mongoDatabase; + private MongoClient mongoClient; + + @Override + public void onEnable() { + this.host = getPlugin().getConfig().getString("mongo.host"); + this.port = getPlugin().getConfig().getInt("mongo.port"); + this.database = getPlugin().getConfig().getString("mongo.database"); + this.auth = getPlugin().getConfig().getBoolean("mongo.auth.enabled"); + this.username = getPlugin().getConfig().getString("mongo.auth.username"); + this.password = getPlugin().getConfig().getString("mongo.auth.password"); + this.authdb = getPlugin().getConfig().getString("mongo.auth.auth-db"); + + this.connect(); + } + + private void connect() { + if(auth) { + char[] passArr = this.password.toCharArray(); + MongoCredential credential = MongoCredential.createCredential( + this.username, + this.database, + passArr + ); + + this.mongoClient = new MongoClient(new ServerAddress(host, port), Arrays.asList(credential)); + } else { + this.mongoClient = new MongoClient(this.host, this.port); + } + + this.mongoDatabase = this.mongoClient.getDatabase(this.database); + } +} \ No newline at end of file diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/database/redis/JedisModule.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/database/redis/JedisModule.java new file mode 100644 index 0000000..31fa684 --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/database/redis/JedisModule.java @@ -0,0 +1,112 @@ +package dev.apposed.prime.proxy.module.database.redis; + +import com.google.gson.Gson; +import dev.apposed.prime.proxy.module.Module; +import dev.apposed.prime.proxy.module.database.redis.packet.Packet; +import dev.apposed.prime.proxy.util.json.JsonHelper; +import lombok.Getter; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPubSub; + +import java.util.function.Consumer; + +@Getter +public class JedisModule extends Module { + + private String host; + private String password; + private int port; + private boolean auth; + + private JedisPool jedisPool; + + private String channel; + + private Gson gson; + + @Override + public void onEnable() { + this.host = getPlugin().getConfig().getString("redis.host"); + this.port = getPlugin().getConfig().getInt("redis.port"); + this.channel = getPlugin().getConfig().getString("redis.channel"); + this.auth = getPlugin().getConfig().getBoolean("redis.auth"); + this.password = getPlugin().getConfig().getString("redis.password"); + + this.gson = JsonHelper.GSON; + connect(); + } + + /** + * Attempts to make a connection to the + * redis database with the specified credentials and + * starts a thread for receiving messages + */ + public void connect() { + this.jedisPool = new JedisPool(host, port); + if(this.auth) { + this.jedisPool.getResource().auth(this.password); + } + + new Thread(() -> this.runCommand(redis -> { + if(this.auth) { + redis.auth(this.password); + } + redis.subscribe(new JedisPubSub() { + + @Override + public void onMessage(String channel, String message) { + try { + // Create the packet + String[] strings = message.split("/split/"); + Object jsonObject = gson.fromJson(strings[1], Class.forName(strings[0])); + if(jsonObject == null) return; + + Packet packet = (Packet) jsonObject; + + packet.onReceive(); + + } catch (Exception ex) { + // do nothing + } + } + }, channel); + })).start(); + + } + + /** + * sends a packet through redis + * + * @Parameter packet the packet to get sent + */ + + public void sendPacket(Packet packet) { + packet.onSend(); + + new Thread(() -> + runCommand(redis -> { + if(this.auth) { + redis.auth(this.password); + } + + redis.publish(channel, packet.getClass().getName() + "/split/" + gson.toJson(packet)); + })).start(); + } + + /** + * sends a packet through redis + * + * @Parameter consumer the callback to be executed + */ + public void runCommand(Consumer consumer) { + Jedis jedis = jedisPool.getResource(); + if (jedis != null) { + if(this.auth) { + jedis.auth(this.password); + } + consumer.accept(jedis); + jedisPool.returnResource(jedis); + } + } +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/database/redis/packet/Packet.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/database/redis/packet/Packet.java new file mode 100644 index 0000000..393eca4 --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/database/redis/packet/Packet.java @@ -0,0 +1,8 @@ +package dev.apposed.prime.proxy.module.database.redis.packet; + +public abstract class Packet { + + public abstract void onReceive(); + public abstract void onSend(); + +} \ No newline at end of file diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/profile/Profile.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/profile/Profile.java new file mode 100644 index 0000000..c1a1fa1 --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/profile/Profile.java @@ -0,0 +1,94 @@ +package dev.apposed.prime.proxy.module.profile; + +import com.google.gson.annotations.SerializedName; +import dev.apposed.prime.proxy.PrimeProxy; +import dev.apposed.prime.proxy.module.profile.grant.Grant; +import dev.apposed.prime.proxy.module.rank.Rank; +import dev.apposed.prime.proxy.module.rank.meta.RankMeta; +import lombok.AllArgsConstructor; +import lombok.Data; +import net.md_5.bungee.api.connection.ProxiedPlayer; + +import java.util.*; +import java.util.stream.Collectors; + +@Data +@AllArgsConstructor +public class Profile { + + @SerializedName("_id") + private UUID uuid; + + private String username; + + private Set grants; + private Set permissions; + + private boolean online; + + public Profile(ProxiedPlayer player) { + this.uuid = player.getUniqueId(); + this.username = player.getName(); + + this.grants = new HashSet<>(); + this.permissions = new HashSet<>(); + } + + public Profile(UUID uuid, String username) { + this.uuid = uuid; + this.username = username; + + this.grants = new HashSet<>(); + this.permissions = new HashSet<>(); + } + + public List getActiveGrants() { + return this.grants.stream().filter(Grant::isActive).collect(Collectors.toList()); + } + + public Grant getHighestActiveGrant() { + return this.getActiveGrants().stream().max(Comparator.comparingInt(a -> a.getRank().getWeight())).get(); + } + + public Grant highestGrantOnScope(String scope) { + return this.getActiveGrants().stream().filter(grant -> !grant.getRank().hasMeta(RankMeta.HIDDEN, true)).filter(grant -> grant.getScopes().contains("Global") || grant.getScopes().contains(scope)).max(Comparator.comparingInt(a -> a.getRank().getWeight())).get(); + } + + public boolean hasPermission(String permission) { + final List permissions = new ArrayList<>(); + + permissions.addAll(this.permissions); + this.getActiveGrants().stream().map(Grant::getRank).forEach(rank -> permissions.addAll(rank.getFullPermissions())); + + return permissions.contains(permission); + } + + public boolean hasRank(Rank rank) { + return this.grants.stream().anyMatch(grant -> grant.getRank().getName().equalsIgnoreCase(rank.getName())); + } + + public boolean hasMeta(RankMeta meta) { + return this.getActiveGrants().stream().anyMatch(grant -> grant.getRank().hasMeta(meta, true)); + } + + public String getColoredName() { + return this.getHighestActiveGrant().getRank() + this.getUsername(); + } + + + public String getColoredName(String scope) { + return this.highestGrantOnScope(scope).getRank().getColor() + this.getUsername(); + } + + public boolean isStaff() { + return this.getActiveGrants().stream().anyMatch(grant -> grant.getRank().hasMeta(RankMeta.STAFF, true)); + } + + public boolean hasServerPerm() { + return this.getActiveGrants().stream().anyMatch(grant -> grant.getRank().hasMeta(RankMeta.SERVER, true)); + } + + public ProxiedPlayer getPlayer() { + return PrimeProxy.getInstance().getProxy().getPlayer(uuid); + } +} \ No newline at end of file diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/profile/ProfileHandler.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/profile/ProfileHandler.java new file mode 100644 index 0000000..cc7b2f4 --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/profile/ProfileHandler.java @@ -0,0 +1,161 @@ +package dev.apposed.prime.proxy.module.profile; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.RemovalListener; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.model.Collation; +import com.mongodb.client.model.CollationStrength; +import com.mongodb.client.model.Filters; +import dev.apposed.prime.proxy.module.Module; +import dev.apposed.prime.proxy.module.database.mongo.MongoModule; +import dev.apposed.prime.proxy.util.json.JsonHelper; +import dev.apposed.prime.proxy.util.mojang.MojangUtils; +import lombok.Getter; +import lombok.SneakyThrows; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import org.bson.Document; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Pattern; + +@Getter +public class ProfileHandler extends Module { + + private Cache cache; + private MongoCollection collection; + + @Override + public void onEnable() { + this.cache = CacheBuilder.newBuilder() + .expireAfterWrite(10, TimeUnit.MINUTES) + .removalListener((RemovalListener) n -> { + if(!n.wasEvicted()) return; + if(n.getKey() != null && n.getValue() != null && n.getValue().getPlayer() != null && n.getValue().getPlayer().isConnected()) { + cache.put(n.getKey(), n.getValue()); + } + }) + .build(); + this.collection = this.getModuleHandler().getModule(MongoModule.class).getMongoDatabase().getCollection("profiles"); + } + + @SneakyThrows + public Profile load(Document document) { + return JsonHelper.GSON.fromJson(JsonHelper.GSON.toJson(document), Profile.class); + } + + public CompletableFuture load(UUID uuid) { + return CompletableFuture.supplyAsync(() -> { + Optional profile = getProfile(uuid); + + if(profile.isPresent()) return profile.get(); + + // In database + profile = this.fetchFromDatabase(uuid); + if(profile.isPresent()) { + add(profile.get()); + return profile.get(); + } + + ProxiedPlayer player = getPlugin().getProxy().getPlayer(uuid); + if(player == null || !player.isConnected()) return null; + return add(new Profile(player)); + }); + } + + public void updateProfile(Profile updatedProfile) { + Profile profile = this.getProfile(updatedProfile.getUuid()).orElse(null); + if(profile == null) { + profile = updatedProfile; + add(profile); + } + + profile.setOnline(updatedProfile.isOnline()); + profile.setGrants(updatedProfile.getGrants()); + profile.setPermissions(updatedProfile.getPermissions()); + profile.setUsername(updatedProfile.getUsername()); + profile.setUuid(updatedProfile.getUuid()); + this.setupPlayer(profile); + } + + public void setupPlayer(Profile profile) { + ProxiedPlayer player = this.getPlugin().getProxy().getPlayer(profile.getUuid()); + if(player == null || !player.isConnected()) return; + + profile.getActiveGrants().forEach(grant -> grant.getRank().getFullPermissions().forEach(permission -> { + if(permission.startsWith("-")) { + player.setPermission(permission.replaceFirst("-", ""), false); + } else { + player.setPermission(permission, true); + } + })); + } + + public Profile add(Profile profile) { + this.cache.put(profile.getUuid(), profile); + return profile; + } + + public Optional fetchFromDatabase(UUID uuid) { + final MongoCursor profile = this.collection.find(Filters.eq("_id", uuid.toString())).iterator(); + if(profile.hasNext()) { + Profile loadedProfile = load(profile.next()); + if(loadedProfile != null) { + return Optional.of(loadedProfile); + } + } + + return Optional.empty(); + } + + public Profile fetchFromDatabase(String username) { + String patternString = "(?i)^" + username + "$"; + Pattern pattern = Pattern.compile(patternString, Pattern.CASE_INSENSITIVE); + final MongoCursor profile = this.collection.find(Filters.regex("username", pattern)).collation(Collation.builder().locale("en").collationStrength(CollationStrength.SECONDARY).build()).iterator(); + if(profile.hasNext()) { + Profile loadedProfile = load(profile.next()); + if(loadedProfile != null) { + return loadedProfile; + } + } + + // create new profile | fetch uuid of player + + final AtomicReference uuidRef = new AtomicReference<>(); + MojangUtils.getUUIDForPlayerName(username, uuidString-> {; + if(uuidString == null) return; + uuidRef.set(UUID.fromString(uuidString)); + }); + + final Optional profileOptional = getProfile(uuidRef.get()); + return profileOptional.orElseGet(() -> add(new Profile(uuidRef.get(), username))); + + } + + public Optional getProfile(UUID uuid) { + return Optional.ofNullable(cache.getIfPresent(uuid)); + } + + public CompletableFuture getProfile(String name) { + return CompletableFuture.supplyAsync(() -> { + final Optional profile = getProfiles().stream().filter(p -> p.getUsername().equalsIgnoreCase(name)).findFirst(); + return profile.orElseGet(() -> fetchFromDatabase(name)); + }); + } + + public Profile getProfile(String name, UUID uuid) { + final Optional profile = this.getProfile(uuid); + return profile.orElseGet(() -> this.add(new Profile(uuid, name) + )); + + } + + public Collection getProfiles() { + return cache.asMap().values(); + } + +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/profile/grant/Grant.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/profile/grant/Grant.java new file mode 100644 index 0000000..e78a16e --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/profile/grant/Grant.java @@ -0,0 +1,64 @@ +package dev.apposed.prime.proxy.module.profile.grant; + +import com.google.gson.annotations.SerializedName; +import dev.apposed.prime.proxy.module.rank.Rank; +import dev.apposed.prime.proxy.util.time.DurationUtils; +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.List; +import java.util.UUID; + +@Data @AllArgsConstructor +public class Grant { + + @SerializedName("_id") + private UUID id; + + private final Rank rank; + + private final UUID addedBy; + private final long addedAt; + private final String addedReason; + private final long duration; + private final List scopes; + + private UUID removedBy; + private long removedAt; + private String removedReason; + + private boolean removed; + + public Grant(Rank rank, UUID addedBy, long addedAt, String addedReason, long duration, List scopes) { + this.id = UUID.randomUUID(); + this.rank = rank; + + this.addedBy = addedBy; + this.addedAt = addedAt; + this.addedReason = addedReason; + this.duration = duration; + this.scopes = scopes; + } + + public boolean isActive() { + if(!removed) { + if(this.duration == Long.MAX_VALUE) return true; + return System.currentTimeMillis() <= (this.addedAt + this.duration); + } + + return false; + } + + public long getRemaining() { + if(removed) return 0L; + if(duration == Long.MAX_VALUE) return Long.MAX_VALUE; + if(!isActive()) return 0L; + + return (addedAt + duration) - System.currentTimeMillis(); + } + + public String formatDuration() { + if(this.duration == Long.MAX_VALUE) return "Permanent"; + return DurationUtils.toString(this.addedAt + this.duration); + } +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/profile/listener/ProfileListener.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/profile/listener/ProfileListener.java new file mode 100644 index 0000000..2f52433 --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/profile/listener/ProfileListener.java @@ -0,0 +1,100 @@ +package dev.apposed.prime.proxy.module.profile.listener; + +import dev.apposed.prime.proxy.PrimeProxy; +import dev.apposed.prime.proxy.module.bungee.BungeeHandler; +import dev.apposed.prime.proxy.module.database.redis.JedisModule; +import dev.apposed.prime.packet.StaffMessagePacket; +import dev.apposed.prime.packet.type.StaffMessageType; +import dev.apposed.prime.proxy.module.profile.Profile; +import dev.apposed.prime.proxy.module.profile.ProfileHandler; +import dev.apposed.prime.proxy.module.rank.meta.RankMeta; +import dev.apposed.prime.proxy.util.request.SimpleRequest; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.event.PlayerDisconnectEvent; +import net.md_5.bungee.api.event.ServerConnectEvent; +import net.md_5.bungee.api.event.ServerConnectedEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.event.EventHandler; +import org.json.simple.JSONObject; + +// TODO: Do profile online/offline with last server and last online in this class instead of on the spigot instances? Not sure +public class ProfileListener implements Listener { + + private final PrimeProxy plugin; + + private final ProfileHandler profileHandler; + private final BungeeHandler bungeeHandler; + private final JedisModule jedisModule; + + public ProfileListener(PrimeProxy plugin) { + this.plugin = plugin; + + this.profileHandler = plugin.getModuleHandler().getModule(ProfileHandler.class); + this.bungeeHandler = plugin.getModuleHandler().getModule(BungeeHandler.class); + this.jedisModule = plugin.getModuleHandler().getModule(JedisModule.class); + } + + @EventHandler + public void onLeave(PlayerDisconnectEvent event) { + ProxiedPlayer player = event.getPlayer(); + profileHandler.load(player.getUniqueId()).thenAccept(profile -> { + if(profile.isStaff() && player.getServer() != null) { + bungeeHandler.setPrevServer(player, null); + this.jedisModule.sendPacket(new StaffMessagePacket(StaffMessageType.LEAVE, player.getUniqueId(), player.getServer().getInfo().getName(), "")); + } + }); + } + + @EventHandler + public void onJoin(ServerConnectedEvent event) { + ProxiedPlayer player = event.getPlayer(); + profileHandler.load(player.getUniqueId()).thenAccept(profile -> { + this.profileHandler.setupPlayer(profile); + if(profile.isStaff()) { + if(this.bungeeHandler.getPreviousServer(player) == null) { + this.bungeeHandler.setPrevServer(player, event.getServer().getInfo().getName()); + this.jedisModule.sendPacket(new StaffMessagePacket(StaffMessageType.JOIN, player.getUniqueId(), event.getServer().getInfo().getName(), "")); + } else { + if(this.bungeeHandler.getPreviousServer(player) != null) { + String prev = this.bungeeHandler.getPreviousServer(player); + this.jedisModule.sendPacket(new StaffMessagePacket(StaffMessageType.SWITCH, player.getUniqueId(), prev, event.getServer().getInfo().getName())); + } + } + + this.bungeeHandler.setPrevServer(player, event.getServer().getInfo().getName()); + } + }); + } + + @EventHandler + public void onJoinCheckVpn(ServerConnectEvent event) { + ProxiedPlayer player = event.getPlayer(); + profileHandler.load(player.getUniqueId()).thenAccept(profile -> { + if(player.getServer() == null && !profile.hasMeta(RankMeta.VPN_BYPASS)) { + String ip = player.getAddress().getAddress().getHostAddress(); + SimpleRequest.getRequest("http://ip-api.com/json/" + ip + "?fields=status,proxy,hosting").thenAccept(response -> { + if(response.get("status") == null || response.get("proxy") == null || response.get("hosting") == null) { + TextComponent component = new TextComponent("There was an issue whilst checking your provider for a VPN."); + component.setColor(ChatColor.RED); + + player.disconnect(component); + event.setCancelled(true); + return; + } + + if((Boolean) response.get("proxy") || (Boolean) response.get("hosting")) { + TextComponent component = new TextComponent("You may not join the network whilst using a VPN."); + component.setColor(ChatColor.RED); + + player.disconnect(component); + event.setCancelled(true); + return; + } + }); + } + }); + } + +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/rank/Rank.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/rank/Rank.java new file mode 100644 index 0000000..8c88519 --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/rank/Rank.java @@ -0,0 +1,71 @@ +package dev.apposed.prime.proxy.module.rank; + +import com.google.gson.annotations.SerializedName; +import dev.apposed.prime.proxy.module.rank.meta.RankMeta; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; +import net.md_5.bungee.api.ChatColor; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Data @AllArgsConstructor @Getter +public class Rank { + + @SerializedName("_id") + private String name; + + private List meta; + private List inherits; + private List permissions; + + private String prefix = ""; + private String color = ChatColor.WHITE.toString(); + private int weight; + + public Rank(String name) { + this.name = name; + + this.meta = new ArrayList<>(); + this.inherits = new ArrayList<>(); + this.permissions = new ArrayList<>(); + } + + public List getFullPermissions() { + final List permissions = new ArrayList<>(); + + permissions.addAll(this.permissions); + + for(Rank rank : inherits) { + permissions.addAll(rank.getPermissions()); + } + + return permissions; + } + + public boolean hasPermission(String permission) { + return this.getFullPermissions().contains(permission); + } + + public boolean inherits(Rank rank) { + return this.inherits.contains(rank); + } + + public boolean hasMeta(RankMeta meta, boolean individual) { + if(individual) + return this.meta.contains(meta); + + Optional rankedMeta = this.inherits.stream().filter(rank -> rank.hasMeta(meta, true)).findAny(); + return rankedMeta.isPresent() || this.meta.contains(meta); + } + + public String getColoredDisplay() { + return this.color + this.name; + } + + public int addWeight(int weight) { + return this.weight += weight; + } +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/rank/RankHandler.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/rank/RankHandler.java new file mode 100644 index 0000000..1730796 --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/rank/RankHandler.java @@ -0,0 +1,64 @@ +package dev.apposed.prime.proxy.module.rank; + +import com.mongodb.client.MongoCollection; +import dev.apposed.prime.proxy.module.Module; +import dev.apposed.prime.proxy.module.database.mongo.MongoModule; +import dev.apposed.prime.proxy.module.profile.ProfileHandler; +import dev.apposed.prime.proxy.module.rank.meta.RankMeta; +import dev.apposed.prime.proxy.util.json.JsonHelper; +import lombok.Getter; +import org.bson.Document; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +@Getter +public class RankHandler extends Module { + + private final ProfileHandler profileHandler = this.getPlugin().getModuleHandler().getModule(ProfileHandler.class); + + private Set cache; + private MongoCollection collection; + + @Override + public void onEnable() { + this.cache = new HashSet<>(); + this.collection = this.getModuleHandler().getModule(MongoModule.class).getMongoDatabase().getCollection("ranks"); + this.collection.find().iterator().forEachRemaining(document -> add(JsonHelper.GSON.fromJson(JsonHelper.GSON.toJson(document), Rank.class))); + } + + public Rank getDefaultRank() { + return this.cache.stream().filter(rank -> rank.hasMeta(RankMeta.DEFAULT, true)).findFirst().orElse(null); + } + + public Rank add(Rank rank) { + this.cache.add(rank); + return rank; + } + + public Optional getRank(String name) { + return this.cache.stream().filter(rank -> rank.getName().equalsIgnoreCase(name)).findFirst(); + } + + public void updateRank(Rank updatedRank) { + Rank rank = this.getRank(updatedRank.getName()).orElse(null); + if(rank == null) { + rank = updatedRank; + this.cache.add(rank); + } + + rank.setPrefix(updatedRank.getPrefix()); + rank.setColor(updatedRank.getColor()); + rank.setWeight(updatedRank.getWeight()); + rank.setPrefix(updatedRank.getPrefix()); + rank.setInherits(updatedRank.getInherits()); + rank.setMeta(updatedRank.getMeta()); + rank.setPermissions(updatedRank.getPermissions()); + + //todo: npe +// this.profileHandler.getProfiles().stream().filter(Objects::nonNull).filter(profile -> profile.hasRank(updatedRank)).forEach(this.profileHandler::setupPlayer); + } + +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/rank/meta/RankMeta.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/rank/meta/RankMeta.java new file mode 100644 index 0000000..78ff022 --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/rank/meta/RankMeta.java @@ -0,0 +1,21 @@ +package dev.apposed.prime.proxy.module.rank.meta; + +import lombok.Getter; + +@Getter +public enum RankMeta { + DEFAULT("Default Rank"), + STAFF("Staff Rank"), + SERVER("Bungee /server"), + DONATOR("Donator Rank"), + VPN_BYPASS("VPN Bypass"), + IP_BYPASS("IP Bypass"), + HIDDEN("Hidden Rank"); + + private String display; + + RankMeta(String display) { + this.display = display; + } + +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/server/Server.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/server/Server.java new file mode 100644 index 0000000..4158410 --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/server/Server.java @@ -0,0 +1,24 @@ +package dev.apposed.prime.proxy.module.server; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.concurrent.TimeUnit; + +@Data @AllArgsConstructor +public class Server { + + private final String name, group; + private boolean whitelisted; + private int players, maxPlayers; + private long lastHeartbeat; + + public boolean isAlive() { + return (System.currentTimeMillis() - lastHeartbeat) <= TimeUnit.SECONDS.toMillis(15L); + } + + public Server(String name, String group) { + this.name = name; + this.group = group; + } +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/server/ServerGroup.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/server/ServerGroup.java new file mode 100644 index 0000000..4dfbb45 --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/server/ServerGroup.java @@ -0,0 +1,9 @@ +package dev.apposed.prime.proxy.module.server; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter @AllArgsConstructor +public class ServerGroup { + private String id; +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/server/ServerHandler.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/server/ServerHandler.java new file mode 100644 index 0000000..b3ba6f1 --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/module/server/ServerHandler.java @@ -0,0 +1,65 @@ +package dev.apposed.prime.proxy.module.server; + +import dev.apposed.prime.proxy.module.Module; +import lombok.Getter; + +import java.util.*; +import java.util.stream.Collectors; + +@Getter +public class ServerHandler extends Module { + + private Set servers; + private Set serverGroups; + + @Override + public void onEnable() { + this.servers = new HashSet<>(); + this.serverGroups = new HashSet<>(); + } + + public void updateServer(Server receivedServer) { + Server server = this.findByName(receivedServer.getName()).orElse(null); + if(server == null) { + server = new Server(receivedServer.getName(), receivedServer.getGroup()); + this.servers.add(server); + } + + server.setLastHeartbeat(receivedServer.getLastHeartbeat()); + server.setMaxPlayers(receivedServer.getMaxPlayers()); + server.setPlayers(receivedServer.getPlayers()); + server.setWhitelisted(receivedServer.isWhitelisted()); + + ServerGroup group = this.getGroupById(server.getGroup()).orElse(null); + if(group == null) this.serverGroups.add(new ServerGroup(server.getGroup())); + } + + public void createServerGroup(String name) { + this.serverGroups.add(new ServerGroup(name)); + } + + public Optional resolveServerGroup(Server server) { + return this.serverGroups.stream().filter(group -> group.getId().equalsIgnoreCase(server.getGroup())).findFirst(); + } + + public Optional getGroupById(String id) { + return this.serverGroups.stream().filter(group -> group.getId().equalsIgnoreCase(id)).findFirst(); + } + + public List getServersWithGroup(ServerGroup group) { + if(group.getId().equalsIgnoreCase("Global")) return new ArrayList<>(servers); + + return this.servers.stream().filter(server -> { + final Optional serverGroup = this.resolveServerGroup(server); + + if(serverGroup.isPresent()) { + return serverGroup.get().getId().equalsIgnoreCase(group.getId()); + } + return false; + }).collect(Collectors.toList()); + } + + public Optional findByName(String name) { + return this.servers.stream().filter(server -> server.getName().equalsIgnoreCase(name)).findFirst(); + } +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/util/Color.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/util/Color.java new file mode 100644 index 0000000..b208ff3 --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/util/Color.java @@ -0,0 +1,17 @@ +package dev.apposed.prime.proxy.util; + +import net.md_5.bungee.api.ChatColor; + +import java.util.List; +import java.util.stream.Collectors; + +public class Color { + + public static String translate(String text) { + return ChatColor.translateAlternateColorCodes('&', text); + } + + public static List translate(List text) { + return text.stream().map(Color::translate).collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/util/json/JsonHelper.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/util/json/JsonHelper.java new file mode 100644 index 0000000..64dc996 --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/util/json/JsonHelper.java @@ -0,0 +1,26 @@ +package dev.apposed.prime.proxy.util.json; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.LongSerializationPolicy; +import com.mongodb.client.model.ReplaceOptions; +import dev.apposed.prime.proxy.module.profile.Profile; +import dev.apposed.prime.proxy.util.json.serialization.ProfileSerializer; + +public class JsonHelper { + + public static Gson GSON = new GsonBuilder() + // Register adapters + .registerTypeAdapter(Profile.class, new ProfileSerializer()) + + // Stuffs + .setPrettyPrinting() + .setLongSerializationPolicy(LongSerializationPolicy.STRING) + .serializeNulls() + .serializeSpecialFloatingPointValues() + + // Create + .create(); + + public static ReplaceOptions REPLACE_OPTIONS = new ReplaceOptions().upsert(true); +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/util/json/serialization/ProfileSerializer.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/util/json/serialization/ProfileSerializer.java new file mode 100644 index 0000000..a33f25c --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/util/json/serialization/ProfileSerializer.java @@ -0,0 +1,73 @@ +package dev.apposed.prime.proxy.util.json.serialization; + +import com.google.gson.*; +import dev.apposed.prime.proxy.PrimeProxy; +import dev.apposed.prime.proxy.module.profile.Profile; +import dev.apposed.prime.proxy.module.profile.grant.Grant; +import dev.apposed.prime.proxy.module.rank.Rank; +import dev.apposed.prime.proxy.module.rank.RankHandler; + +import java.lang.reflect.Type; +import java.util.*; + +public class ProfileSerializer implements JsonDeserializer { + + @Override + public Profile deserialize(JsonElement element, Type t, JsonDeserializationContext context) throws JsonParseException { + JsonObject object = element.getAsJsonObject(); + + UUID uuid = UUID.fromString(object.get("_id").getAsString()); + String username = object.get("username").getAsString(); + + Set grants = new HashSet<>(); + + JsonArray grantsArray = object.get("grants").getAsJsonArray(); + grantsArray.forEach(grantElement -> { + JsonObject grant = grantElement.getAsJsonObject(); + + UUID id = UUID.fromString(grant.get("_id").getAsString()); + final RankHandler rankHandler = PrimeProxy.getInstance().getModuleHandler().getModule(RankHandler.class); + Optional rank = rankHandler.getRank(grant.get("rank").getAsString()); + Rank grantRank = rank.orElseGet(rankHandler::getDefaultRank); + + UUID addedBy = UUID.fromString(grant.get("addedBy").getAsString()); + long addedAt = grant.get("addedAt").getAsLong(); + String addedReason = grant.get("addedReason").getAsString(); + long duration = grant.get("duration").getAsLong(); + boolean removed = grant.get("removed").getAsBoolean(); + + List scopes = new ArrayList<>(); + JsonArray scopesArr = grant.get("scopes").getAsJsonArray(); + scopesArr.forEach(scopeElement -> scopes.add(scopeElement.getAsString())); + + if(removed) { + UUID removedBy = UUID.fromString(grant.get("removedBy").getAsString()); + long removedAt = grant.get("removedAt").getAsLong(); + String removedReason = grant.get("removedReason").getAsString(); + + grants.add( + new Grant(id, grantRank, addedBy, addedAt, addedReason, duration, scopes, removedBy, removedAt, removedReason, true) + ); + } else { + grants.add( + new Grant(id, grantRank, addedBy, addedAt, addedReason, duration, scopes, null, 0L, null, false) + ); + } + }); + + Set permissions = new HashSet<>(); + + JsonArray permissionsArray = object.get("permissions").getAsJsonArray(); + permissionsArray.forEach(permissionElement -> permissions.add(permissionElement.getAsString())); + + boolean online = object.get("online").getAsBoolean(); + + return new Profile( + uuid, + username, + grants, + permissions, + online + ); + } +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/util/mojang/MojangUtils.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/util/mojang/MojangUtils.java new file mode 100644 index 0000000..0033c64 --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/util/mojang/MojangUtils.java @@ -0,0 +1,118 @@ +package dev.apposed.prime.proxy.util.mojang; + +import dev.apposed.prime.proxy.PrimeProxy; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +public class MojangUtils { + + private static final Map cachedSkinResponses = new HashMap(); + + private interface JSONResponseCallback { + void handle(JSONObject response); + } + + public interface UUIDResponseCallback { + void handle(String uuid); + } + + public interface GetTextureResponse { + void handle(String texture, String signature); + } + + public static void getTextureAndSignature(String playerName, GetTextureResponse response) { + String[] previousResponse = cachedSkinResponses.get(playerName); + if (previousResponse != null) { + response.handle(previousResponse[0], previousResponse[1]); + return; + } + + getUUIDForPlayerName(playerName, (uuid -> { + if (uuid == null) { + response.handle(null, null); + return; + } + + getTextureAndSignatureFromUUID(uuid, ((texture, signature) -> { + cachedSkinResponses.put(playerName, new String[]{texture, signature}); + response.handle(texture, signature); + })); + })); + } + + public static void getUUIDForPlayerName(String playerName, UUIDResponseCallback response) { + get("https://api.mojang.com/users/profiles/minecraft/" + playerName, (uuidReply) -> { + if (uuidReply == null) { + response.handle(null); + return; + } + + String uuidString = (String) uuidReply.get("id"); + if (uuidString == null) { + response.handle(null); + return; + } + + response.handle(formatUUIDWithHyphens(uuidString)); + }); + } + + public static String formatUUIDWithHyphens(String uuid) { + return uuid.substring(0, 8) + "-" + uuid.substring(8, 12) + "-" + uuid.substring(12, 16) + "-" + uuid.substring(16, 20) + "-" + uuid.substring(20, 32); + } + + public static void getTextureAndSignatureFromUUID(String uuidString, GetTextureResponse response) { + get("https://sessionserver.mojang.com/session/minecraft/profile/" + uuidString + "?unsigned=false", (profileReply) -> { + if (!profileReply.containsKey("properties")) { + response.handle(null, null); + return; + } + + JSONArray propertiesArray = (JSONArray) profileReply.get("properties"); + JSONObject properties = (JSONObject) propertiesArray.get(0); + String texture = (String) properties.get("value"); + String signature = (String) properties.get("signature"); + response.handle(texture, signature); + }); + } + + private static void get(String url, JSONResponseCallback callback) { + PrimeProxy.getInstance().getProxy().getScheduler().runAsync(PrimeProxy.getInstance(), () -> { + try { + URL rawURL = new URL(url); + HttpURLConnection connection = (HttpURLConnection) rawURL.openConnection(); + connection.setRequestMethod("GET"); + + BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream())); + String inputLine; + StringBuilder content = new StringBuilder(); + while ((inputLine = in.readLine()) != null) { + content.append(inputLine); + } + in.close(); + connection.disconnect(); + + if (content.toString().isEmpty()) { // Mojang API 204 fix + callback.handle(null); + return; + } + + JSONObject jsonObject = (JSONObject) new JSONParser().parse(content.toString()); + callback.handle(jsonObject); + } catch (IOException | ParseException e) { + e.printStackTrace(); + callback.handle(null); + } + }); + } +} \ No newline at end of file diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/util/request/SimpleRequest.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/util/request/SimpleRequest.java new file mode 100644 index 0000000..dac79dc --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/util/request/SimpleRequest.java @@ -0,0 +1,37 @@ +package dev.apposed.prime.proxy.util.request; + +import lombok.SneakyThrows; +import lombok.experimental.UtilityClass; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.concurrent.CompletableFuture; + +@UtilityClass +public class SimpleRequest { + + @SneakyThrows + public static CompletableFuture getRequest(String urlString) { + return CompletableFuture.supplyAsync(() -> { + JSONObject object = new JSONObject(); + try { + URL url = new URL(urlString); + object = (JSONObject) new JSONParser().parse(new InputStreamReader(url.openStream())); + + } catch (MalformedURLException e) { + e.printStackTrace(); + } catch(IOException e) { + e.printStackTrace(); + } catch(ParseException e) { + e.printStackTrace(); + } + + return object; + }); + } +} diff --git a/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/util/time/DurationUtils.java b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/util/time/DurationUtils.java new file mode 100644 index 0000000..c0d510c --- /dev/null +++ b/Prime/prime-bungee/src/main/java/dev/apposed/prime/proxy/util/time/DurationUtils.java @@ -0,0 +1,103 @@ +package dev.apposed.prime.proxy.util.time; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class DurationUtils { + + public static String formatIntoDetailedString(int secs) { + if (secs == 0) { + return "0 seconds"; + } else { + int remainder = secs % 86400; + int days = secs / 86400; + int hours = remainder / 3600; + int minutes = remainder / 60 - hours * 60; + int seconds = remainder % 3600 - minutes * 60; + String fDays = days > 0 ? " " + days + " day" + (days > 1 ? "s" : "") : ""; + String fHours = hours > 0 ? " " + hours + " hour" + (hours > 1 ? "s" : "") : ""; + String fMinutes = minutes > 0 ? " " + minutes + " minute" + (minutes > 1 ? "s" : "") : ""; + String fSeconds = seconds > 0 ? " " + seconds + " second" + (seconds > 1 ? "s" : "") : ""; + return (fDays + fHours + fMinutes + fSeconds).trim(); + } + } + + public static String toString(long millis) { + if(millis == Long.MAX_VALUE) { + return "Permament"; + } + millis -= System.currentTimeMillis(); + if(millis <= 0) { + return "Expired"; + } + long days = TimeUnit.MILLISECONDS.toDays(millis); + long hours = TimeUnit.MILLISECONDS.toHours(millis) + - TimeUnit.DAYS.toHours(days); + long minutes = TimeUnit.MILLISECONDS.toMinutes(millis) + - TimeUnit.HOURS.toMinutes(hours) + - TimeUnit.DAYS.toMinutes(days); + long seconds = TimeUnit.MILLISECONDS.toSeconds(millis) + - TimeUnit.HOURS.toSeconds(hours) + - TimeUnit.DAYS.toSeconds(days) + - TimeUnit.MINUTES.toSeconds(minutes); + List format = new ArrayList<>(); + if(days > 0) { + format.add(days + " days"); + } + if(hours > 0) { + format.add(hours + " hours"); + } + if(minutes > 0) { + format.add(minutes + " minutes"); + } + if(seconds > 0) { + format.add(seconds + " seconds"); + } + return String.join(", ", format); + } + + public static long fromString(String string) { + if(string.equalsIgnoreCase("perm") || string.equalsIgnoreCase("permanent")) { + return Long.MAX_VALUE; + } + long total = 0; + Matcher matcher = Pattern.compile("\\d+\\D+").matcher(string); + while(matcher.find()) { + String s = matcher.group(); + Long value = Long.parseLong(s.split("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)")[0]); + String type = s.split("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)")[1]; + switch (type) { + case "y": + total += value * 60 * 60 * 24 * 365; + case "M": + total += value * 60 * 60 * 24 * 30; + case "w": + total += value * 60 * 60 * 24 * 7; + case "d": + total += value * 60 * 60 * 24; + case "h": + total += value * 60 * 60; + case "m": + total += value * 60; + break; + case "s": + total += value; + break; + } + } + return total * 1000; + } + + public static String scoreboardFormat(long millis) { + return String.format("%02d:%02d:%02d", + TimeUnit.MILLISECONDS.toHours(millis), + TimeUnit.MILLISECONDS.toMinutes(millis) - + TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(millis)), // The change is in this line + TimeUnit.MILLISECONDS.toSeconds(millis) - + TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis))); + } + +} \ No newline at end of file diff --git a/Prime/prime-bungee/src/main/resources/config.yml b/Prime/prime-bungee/src/main/resources/config.yml new file mode 100644 index 0000000..c150eb7 --- /dev/null +++ b/Prime/prime-bungee/src/main/resources/config.yml @@ -0,0 +1,20 @@ +id: 'Proxy-NA-01' + +# database credentials +mongo: + host: '127.0.0.1' + port: 27017 + database: 'Prime' + auth: + enabled: false + username: '' + password: '' + auth-db: 'admin' + +redis: + host: '127.0.0.1' + port: 6379 + channel: '' + auth: false + password: '' + diff --git a/Prime/prime-bungee/src/main/resources/plugin.yml b/Prime/prime-bungee/src/main/resources/plugin.yml new file mode 100644 index 0000000..45a09a6 --- /dev/null +++ b/Prime/prime-bungee/src/main/resources/plugin.yml @@ -0,0 +1,5 @@ +main: dev.apposed.prime.proxy.PrimeProxy +name: Prime +description: All in one Essentials core +version: 1.0-BETA +author: Apposed \ No newline at end of file diff --git a/Prime/prime-spigot/dependency-reduced-pom.xml b/Prime/prime-spigot/dependency-reduced-pom.xml new file mode 100644 index 0000000..39e8411 --- /dev/null +++ b/Prime/prime-spigot/dependency-reduced-pom.xml @@ -0,0 +1,146 @@ + + + + prime-parent + dev.apposed + 1.0-BETA + + 4.0.0 + prime-spigot + + + + true + src/main/resources + + plugin.yml + + + + src/main/resources + + plugin.yml + + + + + + maven-compiler-plugin + + 1.8 + 1.8 + + + + maven-shade-plugin + 3.2.0 + + + package + + shade + + + + + com.google.guava:* + redis.clients:* + org.mongodb:* + org.apache.commons:* + + + + + org.apache.commons.pool2 + org.shaded.apache.commons.pool2 + + + org.mongodb + org.shaded.mongodb + + + com.google.code.gson + com.shaded.google.code.gson + + + redis.clients.jedis + redis.shaded.clients.jedis + + + com.google.common + com.shaded.google.common + + + + + + + + + + + jitpack.io + https://jitpack.io + + + aikar-repo + https://repo.aikar.co/nexus/content/repositories/aikar/ + + + + + org.spigotmc + spigot-api + 1.8.8-R0.1-SNAPSHOT + provided + + + commons-lang + commons-lang + + + json-simple + com.googlecode.json-simple + + + ebean + org.avaje + + + snakeyaml + org.yaml + + + bungeecord-chat + net.md-5 + + + + + org.projectlombok + lombok + LATEST + provided + + + com.squareup.okhttp3 + okhttp + 4.9.3 + compile + + + com.elevatemc + eLib + 1.0 + system + ${project.basedir}/lib/eLib-1.0.jar + + + com.elevatemc + spigot-server + 1.0 + system + ${project.basedir}/lib/espigot.jar + + + diff --git a/Prime/prime-spigot/lib/eLib-1.0.jar b/Prime/prime-spigot/lib/eLib-1.0.jar new file mode 100644 index 0000000..9d2f343 Binary files /dev/null and b/Prime/prime-spigot/lib/eLib-1.0.jar differ diff --git a/Prime/prime-spigot/lib/spigot.jar b/Prime/prime-spigot/lib/spigot.jar new file mode 100644 index 0000000..b78ae12 Binary files /dev/null and b/Prime/prime-spigot/lib/spigot.jar differ diff --git a/Prime/prime-spigot/pom.xml b/Prime/prime-spigot/pom.xml new file mode 100644 index 0000000..d296779 --- /dev/null +++ b/Prime/prime-spigot/pom.xml @@ -0,0 +1,156 @@ + + + + prime-parent + dev.apposed + 1.0-BETA + + 4.0.0 + + prime-spigot + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.0 + + + package + + shade + + + + + com.google.guava:* + redis.clients:* + org.mongodb:* + org.apache.commons:* + + + + + org.apache.commons.pool2 + org.shaded.apache.commons.pool2 + + + org.mongodb + org.shaded.mongodb + + + com.google.code.gson + com.shaded.google.code.gson + + + redis.clients.jedis + redis.shaded.clients.jedis + + + com.google.common + com.shaded.google.common + + + + + + + + + + src/main/resources + true + + plugin.yml + + + + src/main/resources + false + + plugin.yml + + + + + + + + jitpack.io + https://jitpack.io + + + aikar-repo + https://repo.aikar.co/nexus/content/repositories/aikar/ + + + + + + com.google.guava + guava + 31.1-jre + + + org.spigotmc + spigot-api + 1.8.8-R0.1-SNAPSHOT + provided + + + org.projectlombok + lombok + LATEST + provided + + + org.mongodb + mongo-java-driver + 3.12.10 + + + org.apache.commons + commons-pool2 + 2.11.1 + + + redis.clients + jedis + 4.1.1 + jar + compile + + + com.elevatemc + eLib + 1.0 + system + ${project.basedir}/lib/eLib-1.0.jar + + + com.elevatemc + spigot-server + 1.0 + system + ${project.basedir}/lib/espigot.jar + + + com.squareup.okhttp3 + okhttp + 4.9.3 + + + + \ No newline at end of file diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/ConsoleCommandPacket.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/ConsoleCommandPacket.java new file mode 100644 index 0000000..a887887 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/ConsoleCommandPacket.java @@ -0,0 +1,24 @@ +package dev.apposed.prime.packet; + +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.database.redis.packet.Packet; +import lombok.AllArgsConstructor; +import org.bukkit.Bukkit; + +@AllArgsConstructor +public class ConsoleCommandPacket extends Packet { + + private final String command; + + @Override + public void onReceive() { + Bukkit.getScheduler().runTask(Prime.getInstance(), () -> { + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command + " -c"); + }); + } + + @Override + public void onSend() { + + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/PasswordCreatePacket.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/PasswordCreatePacket.java new file mode 100644 index 0000000..a5a2fd8 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/PasswordCreatePacket.java @@ -0,0 +1,32 @@ +package dev.apposed.prime.packet; + +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.database.redis.packet.Packet; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import lombok.AllArgsConstructor; + +import java.util.Optional; +import java.util.UUID; + +@AllArgsConstructor +public class PasswordCreatePacket extends Packet { + + private final String uuid; + private final String password; + + @Override + public void onReceive() { + final ProfileHandler profileHandler = Prime.getInstance().getModuleHandler().getModule(ProfileHandler.class); + final Optional profileOptional = profileHandler.getProfile(UUID.fromString(uuid)); + if(!profileOptional.isPresent()) return; + final Profile profile = profileOptional.get(); + profile.setPassword(password); + profileHandler.sendSync(profile); + } + + @Override + public void onSend() { + + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/ProfileRefreshPacket.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/ProfileRefreshPacket.java new file mode 100644 index 0000000..970d5ac --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/ProfileRefreshPacket.java @@ -0,0 +1,36 @@ +package dev.apposed.prime.packet; + +import dev.apposed.prime.packet.type.RefreshType; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.database.redis.packet.Packet; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import lombok.AllArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.plugin.java.JavaPlugin; + +@AllArgsConstructor +public class ProfileRefreshPacket extends Packet { + + private Profile profile; + private RefreshType type; + + @Override + public void onSend() { + } + + @Override + public void onReceive() { + final ProfileHandler profileHandler = Prime.getInstance().getModuleHandler().getModule(ProfileHandler.class); + switch(type) { + case UPDATE: { + profileHandler.updateProfile(profile); + break; + } + case REMOVE: { + profileHandler.getProfiles().remove(profile); + break; + } + } + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/PunishmentPacket.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/PunishmentPacket.java new file mode 100644 index 0000000..1302b70 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/PunishmentPacket.java @@ -0,0 +1,97 @@ +package dev.apposed.prime.packet; + +import com.elevatemc.elib.util.UUIDUtils; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.PrimeConstants; +import dev.apposed.prime.spigot.module.database.redis.packet.Packet; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import dev.apposed.prime.spigot.module.profile.punishment.Punishment; +import dev.apposed.prime.spigot.module.webhook.DiscordWebhook; +import dev.apposed.prime.spigot.util.Color; +import lombok.AllArgsConstructor; +import net.md_5.bungee.api.chat.ComponentBuilder; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.TextComponent; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.UUID; + +@AllArgsConstructor +public class PunishmentPacket extends Packet { + + private final String PUNISHMENT_WEBHOOK = "https://discord.com/api/webhooks/993223760213717102/uEbyMMb5VQCCQi2ewigSYKNrJj4IHQ27NaPX9WQwVT_Z98sHaCttsKRiTVMPi4kyikc1"; + + private UUID player, executor; + private String actingServer; + private Punishment punishment; + private boolean publicPunishment; + private boolean undo; + + @Override + public void onReceive() { + final Prime plugin = JavaPlugin.getPlugin(Prime.class); + final ProfileHandler profileHandler = plugin.getModuleHandler().getModule(ProfileHandler.class); + String networkName = plugin.getConfig().getString("network.name"); + String appealLink = plugin.getConfig().getString("network.appeal"); + + + String message = Color.translate("&r%player% &ahas been&e%silent% &a" + (this.undo ? "un" : "") + punishment.getType().getDisplay() + " &aby &r%executor%&a.") + .replace("%player%", Color.translate(player.equals(PrimeConstants.CONSOLE_UUID) ? "&4&lConsole" : profileHandler.isCached(player) ? profileHandler.getProfile(player).get().getColoredName() : UUIDUtils.name(player))) + .replace("%silent%", Color.translate((publicPunishment ? "" : " &esilently"))) + .replace("%executor%", Color.translate(executor.equals(PrimeConstants.CONSOLE_UUID) ? "&4&lConsole" : profileHandler.isCached(player) ? profileHandler.getProfile(executor).get().getColoredName() : UUIDUtils.name(executor))); + + TextComponent component = new TextComponent(message); + if(!publicPunishment) { + component.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, + new ComponentBuilder("§eReason: §c" + (undo ? punishment.getRemovedReason() : punishment.getAddedReason()) + "\n§eServer: §c" + actingServer + (undo ? "" : "\n§eDuration: §c" + punishment.formatDuration())).create())); + } + + if(!publicPunishment) { + profileHandler.getStaffProfiles().forEach(staffProfile -> { + Player player = Bukkit.getPlayer(staffProfile.getUuid()); + if (player != null && player.isOnline()) { + player.spigot().sendMessage(component); + } + }); + } else { + Bukkit.broadcastMessage(message); + } + + Player player = Bukkit.getPlayer(this.player); + if(player != null && player.isOnline()) { + if(punishment.getRemaining() == Long.MAX_VALUE) { + player.kickPlayer( + Color.translate("&cYour account has been permanently suspended from the " + networkName + ".\n\n&cAppeal on " + appealLink + ".") + ); + } else { + player.kickPlayer( + Color.translate("&cYour account has been suspended from the " + networkName + " for " + punishment.formatDuration() + ".\n\n&cAppeal on " + appealLink + ".") + ); + } + } + } + + @Override + public void onSend() { + final Prime plugin = JavaPlugin.getPlugin(Prime.class); + final DiscordWebhook webhook = new DiscordWebhook(PUNISHMENT_WEBHOOK); + webhook.addEmbed( + new DiscordWebhook.EmbedObject() + .setTitle(UUIDUtils.name(player) + " has been " + (this.undo ? "un" : "") + punishment.getType().getDisplay()) + .addField("Executor", (executor.equals(PrimeConstants.CONSOLE_UUID) ? "Console" : UUIDUtils.name(executor)), false) + .addField("Reason", (this.undo ? punishment.getRemovedReason() : punishment.getAddedReason()), false) + .addField("Duration", (this.undo ? "N/A" : punishment.formatDuration()), false) + .setColor(java.awt.Color.cyan) + .setFooter("Prime Punishments", null) + ); + new Thread(() -> { + try { + webhook.execute(); + }catch(Exception ex) { + ex.printStackTrace(); + } + }, "punishment-log-" + executor).start(); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/RankRefreshPacket.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/RankRefreshPacket.java new file mode 100644 index 0000000..46eb86e --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/RankRefreshPacket.java @@ -0,0 +1,41 @@ +package dev.apposed.prime.packet; + +import dev.apposed.prime.packet.type.RefreshType; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.database.redis.packet.Packet; +import dev.apposed.prime.spigot.module.rank.Rank; +import dev.apposed.prime.spigot.module.rank.RankHandler; +import dev.apposed.prime.spigot.util.Color; +import lombok.AllArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.permissions.ServerOperator; +import org.bukkit.plugin.java.JavaPlugin; + +@AllArgsConstructor +public class RankRefreshPacket extends Packet { + + private Rank rank; + private RefreshType type; + + @Override + public void onSend() { + } + + @Override + public void onReceive() { + final RankHandler rankHandler = JavaPlugin.getPlugin(Prime.class).getModuleHandler().getModule(RankHandler.class); + switch(type) { + case UPDATE: { + Bukkit.getOnlinePlayers().stream().filter(ServerOperator::isOp).forEach(player -> { + player.sendMessage(Color.translate("&9[Monitor] &f" + rank.getColoredDisplay() + " &f -> Updated")); + }); + rankHandler.updateRank(rank); + break; + } + case REMOVE: { + rankHandler.getCache().remove(rank); + break; + } + } + } +} \ No newline at end of file diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/ServerHeartbeatPacket.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/ServerHeartbeatPacket.java new file mode 100644 index 0000000..d08e2d2 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/ServerHeartbeatPacket.java @@ -0,0 +1,26 @@ +package dev.apposed.prime.packet; + +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.database.redis.packet.Packet; +import dev.apposed.prime.spigot.module.server.Server; +import dev.apposed.prime.spigot.module.server.ServerHandler; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.bukkit.plugin.java.JavaPlugin; + +@AllArgsConstructor @Getter +public class ServerHeartbeatPacket extends Packet { + + private Server server; + + @Override + public void onSend() { + + } + + @Override + public void onReceive() { + final ServerHandler serverHandler = JavaPlugin.getPlugin(Prime.class).getModuleHandler().getModule(ServerHandler.class); + serverHandler.updateServer(server); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/ServerUpdatePacket.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/ServerUpdatePacket.java new file mode 100644 index 0000000..963dc56 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/ServerUpdatePacket.java @@ -0,0 +1,30 @@ +package dev.apposed.prime.packet; + +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.database.redis.packet.Packet; +import dev.apposed.prime.spigot.module.server.ServerHandler; +import dev.apposed.prime.spigot.module.server.filter.ChatFilterHandler; +import lombok.AllArgsConstructor; +import org.bukkit.plugin.java.JavaPlugin; + +@AllArgsConstructor +public class ServerUpdatePacket extends Packet { + + private String serverName; + + @Override + public void onReceive() { + final Prime plugin = JavaPlugin.getPlugin(Prime.class); + final ServerHandler serverHandler = plugin.getModuleHandler().getModule(ServerHandler.class); + + if(this.serverName.equalsIgnoreCase(serverHandler.getCurrentName())) { + plugin.reloadConfig(); + plugin.getModuleHandler().getModule(ChatFilterHandler.class).loadFilters(); + } + } + + @Override + public void onSend() { + + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/StaffMessagePacket.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/StaffMessagePacket.java new file mode 100644 index 0000000..6fe98a8 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/StaffMessagePacket.java @@ -0,0 +1,204 @@ +package dev.apposed.prime.packet; + +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.database.redis.packet.Packet; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import dev.apposed.prime.packet.type.StaffMessageType; +import dev.apposed.prime.spigot.util.Color; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.Optional; +import java.util.UUID; + +@AllArgsConstructor @RequiredArgsConstructor @Data +public class StaffMessagePacket extends Packet { + + private final StaffMessageType type; + private final UUID uuid; + private final String prevServer, server; + + private String message; + private UUID player; + + @Override + public void onReceive() { + final Prime plugin = JavaPlugin.getPlugin(Prime.class); + final ProfileHandler profileHandler = plugin.getModuleHandler().getModule(ProfileHandler.class); + profileHandler.load(uuid).thenAccept(profile -> { + switch(type) { + case JOIN: { + String message = Color.translate(plugin.getConfig().getString("staff.join")) + .replace("%player%", profile.getColoredName()) + .replace("%prev_server%", this.prevServer); + + profileHandler.getStaffProfiles().forEach(staffProfile -> { + Player player = Bukkit.getPlayer(staffProfile.getUuid()); + if(player != null && player.isOnline() && !player.hasMetadata("togglestaff")) { + player.sendMessage(message); + } + }); + break; + } + case LEAVE: { + String message = Color.translate(plugin.getConfig().getString("staff.leave")) + .replace("%player%", profile.getColoredName()) + .replace("%prev_server%", this.prevServer); + + profileHandler.getStaffProfiles().forEach(staffProfile -> { + Player player = Bukkit.getPlayer(staffProfile.getUuid()); + if(player != null && player.isOnline() && !player.hasMetadata("togglestaff")) { + player.sendMessage(message); + } + }); + break; + } + case SWITCH: { + String message = Color.translate(plugin.getConfig().getString("staff.switch")) + .replace("%player%", profile.getColoredName()) + .replace("%prev_server%", this.prevServer) + .replace("%server%", this.server); + + profileHandler.getStaffProfiles().forEach(staffProfile -> { + Player player = Bukkit.getPlayer(staffProfile.getUuid()); + if(player != null && player.isOnline() && !player.hasMetadata("togglestaff")) { + player.sendMessage(message); + } + }); + break; + } + case CHAT: { + String message = Color.translate(plugin.getConfig().getString("staff.chat")) + .replace("%player%", profile.getColoredName()) + .replace("%prefix%", profile.getHighestActiveNonHiddenGrant().getRank().getPrefix()) + .replace("%server%", this.prevServer) + .replace("%message%", this.server); + + profileHandler.getStaffProfiles().forEach(staffProfile -> { + Player player = Bukkit.getPlayer(staffProfile.getUuid()); + if(player != null && player.isOnline() && !player.hasMetadata("togglestaff")) { + player.sendMessage(message); + } + }); + break; + } + case ADMIN_CHAT: { + String message = Color.translate(plugin.getConfig().getString("staff.admin")) + .replace("%player%", profile.getColoredName()) + .replace("%prefix%", profile.getHighestActiveNonHiddenGrant().getRank().getPrefix()) + .replace("%server%", this.prevServer) + .replace("%message%", this.server); + + profileHandler.getStaffProfiles().forEach(staffProfile -> { + Player player = Bukkit.getPlayer(staffProfile.getUuid()); + if(player != null && player.isOnline() && !player.hasMetadata("togglestaff") && player.hasPermission("prime.command.adminchat")) { + player.sendMessage(message); + } + }); + break; + } + case MANAGER_CHAT: { + String message = Color.translate(plugin.getConfig().getString("staff.manager")) + .replace("%player%", profile.getColoredName()) + .replace("%prefix%", profile.getHighestActiveNonHiddenGrant().getRank().getPrefix()) + .replace("%server%", this.prevServer) + .replace("%message%", this.server); + + profileHandler.getStaffProfiles().forEach(staffProfile -> { + Player player = Bukkit.getPlayer(staffProfile.getUuid()); + if(player != null && player.isOnline() && !player.hasMetadata("togglestaff") && player.hasPermission("prime.command.managerchat")) { + player.sendMessage(message); + } + }); + break; + } + case HELPOP: { + final String message = Color.translate(String.format("&9[Request]&b[%s&b] &r%s &7has requested assistance: &f%s", + getServer(), + profile.getColoredName(), + getMessage())); + + profileHandler.getStaffProfiles().forEach(staffProfile -> { + Player player = Bukkit.getPlayer(staffProfile.getUuid()); + if(player != null && player.isOnline() && !player.hasMetadata("togglestaff")) { + player.sendMessage(message); + } + }); + break; + } + case REPORT: { + profileHandler.load(player).thenAccept(target -> { + final String message = Color.translate(String.format("&9[Report]&b[%s&b] &r%s &7has reported &r%s&7: &f%s", + getServer(), + profile.getColoredName(), + target.getColoredName(), + getMessage())); + + profileHandler.getStaffProfiles().forEach(staffProfile -> { + Player player = Bukkit.getPlayer(staffProfile.getUuid()); + if(player != null && player.isOnline() && !player.hasMetadata("togglestaff")) { + player.sendMessage(message); + } + }); + }).exceptionally(throwable -> { + Bukkit.getConsoleSender().sendMessage(Color.translate("&cStaffMessagePacket, failed to fetch target profile with uuid " + player.toString())); + return null; + }); + break; + } + case FREEZE: { + profileHandler.load(player).thenAccept(target -> { + final String message = Color.translate(String.format("&b[S] &7(%s&7) &r%s &7has frozen &r%s&7.", + getServer(), + profile.getColoredName(), + target.getColoredName())); + + profileHandler.getStaffProfiles().forEach(staffProfile -> { + Player player = Bukkit.getPlayer(staffProfile.getUuid()); + if(player != null && player.isOnline() && !player.hasMetadata("togglestaff")) { + player.sendMessage(message); + } + }); + }).exceptionally(throwable -> { + Bukkit.getConsoleSender().sendMessage(Color.translate("&cStaffMessagePacket, failed to fetch target profile with uuid " + player.toString())); + return null; + }); + break; + } + case UNFREEZE: { + profileHandler.load(player).thenAccept(target -> { + final String message = Color.translate(String.format("&b[S] &7(%s&7) &r%s &7has unfrozen &r%s&7.", + getServer(), + profile.getColoredName(), + target.getColoredName())); + + profileHandler.getStaffProfiles().forEach(staffProfile -> { + Player player = Bukkit.getPlayer(staffProfile.getUuid()); + if(player != null && player.isOnline() && !player.hasMetadata("togglestaff")) { + player.sendMessage(message); + } + }); + }).exceptionally(throwable -> { + Bukkit.getConsoleSender().sendMessage(Color.translate("&cStaffMessagePacket, failed to fetch target profile with uuid " + player.toString())); + return null; + }); + + break; + } + } + }).exceptionally(throwable -> { + Bukkit.getConsoleSender().sendMessage(Color.translate("&cStaffMessagePacket, failed to load profile with uuid " + uuid.toString())); + return null; + }); + } + + @Override + public void onSend() { + + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/type/RefreshType.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/type/RefreshType.java new file mode 100644 index 0000000..c7fb586 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/type/RefreshType.java @@ -0,0 +1,6 @@ +package dev.apposed.prime.packet.type; + +public enum RefreshType { + UPDATE, + REMOVE +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/type/StaffMessageType.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/type/StaffMessageType.java new file mode 100644 index 0000000..828f4c5 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/packet/type/StaffMessageType.java @@ -0,0 +1,14 @@ +package dev.apposed.prime.packet.type; + +public enum StaffMessageType { + JOIN, + LEAVE, + SWITCH, + CHAT, + HELPOP, + REPORT, + FREEZE, + UNFREEZE, + ADMIN_CHAT, + MANAGER_CHAT +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/Prime.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/Prime.java new file mode 100644 index 0000000..cbbb870 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/Prime.java @@ -0,0 +1,92 @@ +package dev.apposed.prime.spigot; + +import com.elevatemc.elib.eLib; +import dev.apposed.prime.spigot.module.ModuleHandler; +import dev.apposed.prime.spigot.module.database.mongo.MongoModule; +import dev.apposed.prime.spigot.module.database.redis.JedisModule; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import dev.apposed.prime.spigot.module.profile.target.ProfileTarget; +import dev.apposed.prime.spigot.module.profile.target.ProfileTypeAdapter; +import dev.apposed.prime.spigot.module.profile.listener.ProfileListener; +import dev.apposed.prime.spigot.module.profile.punishment.listener.PunishmentListener; +import dev.apposed.prime.spigot.module.profile.skin.SkinHandler; +import dev.apposed.prime.spigot.module.rank.Rank; +import dev.apposed.prime.spigot.module.rank.RankHandler; +import dev.apposed.prime.spigot.module.rank.adapter.RankTypeAdapter; +import dev.apposed.prime.spigot.module.server.ServerHandler; +import dev.apposed.prime.spigot.module.server.filter.ChatFilterHandler; +import dev.apposed.prime.spigot.module.server.filter.listener.ChatFilterListener; +import dev.apposed.prime.spigot.module.tag.Tag; +import dev.apposed.prime.spigot.module.tag.TagHandler; +import dev.apposed.prime.spigot.module.tag.adapter.TagTypeAdapter; +import dev.apposed.prime.spigot.module.webhook.listener.StaffGriefListener; +import lombok.Getter; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.Arrays; + +@Getter +public class Prime extends JavaPlugin { + + private ModuleHandler moduleHandler; + private static Prime instance; + + @Override + public void onEnable() { + instance = this; + + loadHandlers(); + loadHelpers(); + loadModules(); + } + + @Override + public void onDisable() { + this.moduleHandler.disableModules(); + + instance = null; + } + + private void loadHandlers() { + this.moduleHandler = new ModuleHandler(); + + Arrays.asList( + new MongoModule(), + new JedisModule(), + new RankHandler(), + new ProfileHandler(), + new ServerHandler(), + new ChatFilterHandler(), + new TagHandler(), + new SkinHandler() + ).forEach(module -> this.moduleHandler.registerModule(module)); + } + + private void loadHelpers() { + saveDefaultConfig(); + this.getConfig().options().copyDefaults(true); + + eLib.getInstance().getCommandHandler().registerAll(this); + + eLib.getInstance().getCommandHandler().registerParameterType(ProfileTarget.class, new ProfileTypeAdapter()); + eLib.getInstance().getCommandHandler().registerParameterType(Rank.class, new RankTypeAdapter(this.moduleHandler.getModule(RankHandler.class))); + eLib.getInstance().getCommandHandler().registerParameterType(Tag.class, new TagTypeAdapter(this.moduleHandler.getModule(TagHandler.class))); + } + + private void loadModules() { + Arrays.asList( + /* + Listeners, Commands, Modules, etc. + The module handler should be able to handle them all + */ + new ProfileListener(), + new PunishmentListener(), + new StaffGriefListener(), + new ChatFilterListener() + ).forEach(module -> this.moduleHandler.registerModule(module)); + } + + public static Prime getInstance() { + return instance; + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/PrimeConstants.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/PrimeConstants.java new file mode 100644 index 0000000..ce6e295 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/PrimeConstants.java @@ -0,0 +1,8 @@ +package dev.apposed.prime.spigot; + +import java.util.UUID; + +public class PrimeConstants { + + public static final UUID CONSOLE_UUID = UUID.fromString("285cda74-7eb6-4756-b3e4-86f4f9b0be6e"); +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/Module.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/Module.java new file mode 100644 index 0000000..697aa08 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/Module.java @@ -0,0 +1,25 @@ +package dev.apposed.prime.spigot.module; + +import dev.apposed.prime.spigot.Prime; +import lombok.Getter; +import lombok.Setter; +import org.bukkit.plugin.java.JavaPlugin; + +@Getter @Setter +public abstract class Module { + + private String name = this.getClass().getSimpleName(); + private String version = "1.0.0"; + + private Prime plugin = JavaPlugin.getPlugin(Prime.class); + + private ModuleHandler moduleHandler = plugin.getModuleHandler(); + + public void onEnable() { + System.out.println("Module " + name + " v" + version + " has been enabled."); + } + + public void onDisable() { + System.out.println("Module " + name + " v" + version + " has been disabled."); + } +} \ No newline at end of file diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/ModuleHandler.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/ModuleHandler.java new file mode 100644 index 0000000..1c2862e --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/ModuleHandler.java @@ -0,0 +1,36 @@ +package dev.apposed.prime.spigot.module; + +import java.util.HashMap; +import java.util.Map; + +public class ModuleHandler { + + private Map, Module> modules; + + public ModuleHandler() { + this.modules = new HashMap<>(); + } + + public void registerModule(Module module) { + this.modules.put(module.getClass(), module); + module.onEnable(); + } + + public void disableModule(Module module) { + this.modules.remove(module.getClass()); + module.onDisable(); + } + + public void disableModule(Class module) { + this.disableModule(getModule(module)); + } + + public void disableModules() { + this.modules.values().forEach(Module::onDisable); + this.modules.clear(); + } + + public T getModule(Class clazz) { + return clazz.cast(this.modules.get(clazz)); + } +} \ No newline at end of file diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/database/mongo/MongoModule.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/database/mongo/MongoModule.java new file mode 100644 index 0000000..9d78f4c --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/database/mongo/MongoModule.java @@ -0,0 +1,51 @@ +package dev.apposed.prime.spigot.module.database.mongo; + +import com.mongodb.MongoClient; +import com.mongodb.MongoCredential; +import com.mongodb.ServerAddress; +import com.mongodb.client.MongoDatabase; +import dev.apposed.prime.spigot.module.Module; +import lombok.Getter; + +import java.util.Arrays; + +@Getter +public class MongoModule extends Module { + + private String host, database, username, password, authdb; + private int port; + private boolean auth; + + private MongoDatabase mongoDatabase; + private MongoClient mongoClient; + + @Override + public void onEnable() { + this.host = getPlugin().getConfig().getString("mongo.host"); + this.port = getPlugin().getConfig().getInt("mongo.port"); + this.database = getPlugin().getConfig().getString("mongo.database"); + this.auth = getPlugin().getConfig().getBoolean("mongo.auth.enabled"); + this.username = getPlugin().getConfig().getString("mongo.auth.username"); + this.password = getPlugin().getConfig().getString("mongo.auth.password"); + this.authdb = getPlugin().getConfig().getString("mongo.auth.auth-db"); + + this.connect(); + } + + private void connect() { + if(auth) { + char[] passArr = this.password.toCharArray(); + MongoCredential credential = MongoCredential.createCredential( + this.username, + this.authdb, + passArr + ); + + this.mongoClient = new MongoClient(new ServerAddress(host, port), Arrays.asList(credential)); + } else { + this.mongoClient = new MongoClient(this.host, this.port); + } + + this.mongoDatabase = this.mongoClient.getDatabase(this.database); + } +} \ No newline at end of file diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/database/redis/JedisModule.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/database/redis/JedisModule.java new file mode 100644 index 0000000..1e343ca --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/database/redis/JedisModule.java @@ -0,0 +1,116 @@ +package dev.apposed.prime.spigot.module.database.redis; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import dev.apposed.prime.spigot.module.Module; +import dev.apposed.prime.spigot.module.database.redis.packet.Packet; +import dev.apposed.prime.spigot.util.json.JsonHelper; +import lombok.Getter; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPubSub; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Consumer; + +@Getter +public class JedisModule extends Module { + + private String host; + private String password; + private int port; + private boolean auth; + + private JedisPool jedisPool; + + private String channel; + + private Gson gson; + + private ExecutorService executorService = Executors.newFixedThreadPool(2); + + @Override + public void onEnable() { + this.host = getPlugin().getConfig().getString("redis.host"); + this.port = getPlugin().getConfig().getInt("redis.port"); + this.channel = getPlugin().getConfig().getString("redis.channel"); + this.auth = getPlugin().getConfig().getBoolean("redis.auth"); + this.password = getPlugin().getConfig().getString("redis.password"); + + this.gson = JsonHelper.GSON; + connect(); + } + + /** + * Attempts to make a connection to the + * redis database with the specified credentials and + * starts a thread for receiving messages + */ + public void connect() { + this.jedisPool = new JedisPool(host, port); + if(this.auth) { + this.jedisPool.getResource().auth(this.password); + } + + new Thread(() -> this.runCommand(redis -> { + if(this.auth) { + redis.auth(this.password); + } + redis.subscribe(new JedisPubSub() { + + @Override + public void onMessage(String channel, String message) { + try { + // Create the packet + String[] strings = message.split("/split/"); + Object jsonObject = gson.fromJson(strings[1], Class.forName(strings[0])); + if(jsonObject == null) return; + + Packet packet = (Packet) jsonObject; + + packet.onReceive(); + + } catch (Exception ex) { + // do nothing + } + } + }, channel); + })).start(); + + } + + /** + * sends a packet through redis + * + * @Param packet the packet to get sent + */ + + public void sendPacket(Packet packet) { + packet.onSend(); + + executorService.execute(() -> runCommand(redis -> { + if(this.auth) { + redis.auth(this.password); + } + + redis.publish(channel, packet.getClass().getName() + "/split/" + gson.toJson(packet)); + })); + } + + /** + * sends a packet through redis + * + * @Param consumer the callback to be executed + */ + public void runCommand(Consumer consumer) { + Jedis jedis = jedisPool.getResource(); + if (jedis != null) { + if(this.auth) { + jedis.auth(this.password); + } + consumer.accept(jedis); + jedisPool.returnResource(jedis); + } + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/database/redis/packet/Packet.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/database/redis/packet/Packet.java new file mode 100644 index 0000000..23be653 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/database/redis/packet/Packet.java @@ -0,0 +1,8 @@ +package dev.apposed.prime.spigot.module.database.redis.packet; + +public abstract class Packet { + + public abstract void onReceive(); + public abstract void onSend(); + +} \ No newline at end of file diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/listener/ListenerModule.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/listener/ListenerModule.java new file mode 100644 index 0000000..8e6ce62 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/listener/ListenerModule.java @@ -0,0 +1,23 @@ +package dev.apposed.prime.spigot.module.listener; + +import dev.apposed.prime.spigot.module.Module; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; + +public abstract class ListenerModule extends Module implements Listener { + + @Override + public void onEnable() { + getPlugin().getServer().getPluginManager().registerEvents(this, getPlugin()); + } + + @Override + public void onDisable() { + HandlerList.unregisterAll(this); + } + + @Override + public String getVersion() { + return "0.0.1"; + } +} \ No newline at end of file diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/Profile.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/Profile.java new file mode 100644 index 0000000..390c080 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/Profile.java @@ -0,0 +1,312 @@ +package dev.apposed.prime.spigot.module.profile; + +import com.elevatemc.elib.util.TimeUtils; +import com.google.gson.annotations.SerializedName; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.PrimeConstants; +import dev.apposed.prime.spigot.module.rank.Rank; +import dev.apposed.prime.spigot.module.tag.Tag; +import dev.apposed.prime.spigot.module.profile.grant.Grant; +import dev.apposed.prime.spigot.module.profile.identity.ProfileIdentity; +import dev.apposed.prime.spigot.module.profile.punishment.Punishment; +import dev.apposed.prime.spigot.module.profile.punishment.type.PunishmentType; +import dev.apposed.prime.spigot.module.rank.meta.RankMeta; +import dev.apposed.prime.spigot.module.tag.TagHandler; +import dev.apposed.prime.spigot.util.Color; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.metadata.FixedMetadataValue; + +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.*; +import java.util.stream.Collectors; + +@Data @AllArgsConstructor +public class Profile { + + @SerializedName("_id") + private UUID uuid; + + private String username; + private String email; + + private Set grants; + private Set punishments; + private Set permissions; + private String tag; + private Set identities; + + private boolean messagesToggled; + private boolean online; + private String lastServer; + private long lastOnline; + private ProfileIdentity lastIdentity; + private long lastChattedAt; + private long firstJoin; + private long playtime; + + private int syncCode; + private String password; + private ChatColor chatColor = ChatColor.WHITE; + private String nickname = ""; + private String style = ""; + + public Profile(Player player) { + this.uuid = player.getUniqueId(); + this.username = player.getName(); + + this.grants = new HashSet<>(); + this.punishments = new HashSet<>(); + this.permissions = new HashSet<>(); + this.identities = new HashSet<>(); + } + + public Profile(UUID uuid, String username) { + this.uuid = uuid; + this.username = username; + + this.grants = new HashSet<>(); + this.punishments = new HashSet<>(); + this.permissions = new HashSet<>(); + this.identities = new HashSet<>(); + } + + public Profile(UUID uuid, String username, String email, Set grants, Set punishments, Set permissions, String tag, Set identities, boolean messagesToggled, boolean online, String lastServer, long lastOnline, ProfileIdentity lastIdentity, long lastChattedAt) { + this.uuid = uuid; + this.username = username; + this.email = email; + this.grants = grants; + this.punishments = punishments; + this.permissions = permissions; + this.tag = tag; + this.identities = identities; + this.messagesToggled = messagesToggled; + this.online = online; + this.lastServer = lastServer; + this.lastOnline = lastOnline; + this.lastIdentity = lastIdentity; + this.lastChattedAt = lastChattedAt; + } + + public List getActiveGrants() { + return this.grants.stream().filter(Grant::isActive).collect(Collectors.toList()); + } + + public Grant getHighestActiveGrant() { + return this.getActiveGrants().stream().max(Comparator.comparingInt(a -> a.getRank().getWeight())).get(); + } + + public Grant getHighestActiveNonHiddenGrant() { + return this.getActiveGrants().stream().filter(grant -> !grant.getRank().hasMeta(RankMeta.HIDDEN, true)).max(Comparator.comparingInt(a -> a.getRank().getWeight())).get(); + } + + public Grant highestGrantOnScope(String scope) { + return this.getActiveGrants().stream().filter(grant -> !grant.getRank().hasMeta(RankMeta.HIDDEN, true)).filter(grant -> grant.getScopes().contains("Global") || grant.getScopes().contains(scope)).max(Comparator.comparingInt(a -> a.getRank().getWeight())).get(); + } + + public List getActivePunishments() { + return this.punishments.stream().filter(Punishment::isActive).collect(Collectors.toList()); + } + + public List getActivePunishments(PunishmentType type) { + return this.getActivePunishments().stream().filter(punishment -> punishment.getType() == type).collect(Collectors.toList()); + } + + public Optional getActivePunishment(PunishmentType type) { + return this.getActivePunishments(type).stream().max(Comparator.comparingLong(Punishment::getDuration)); + } + + public boolean hasActivePunishment(PunishmentType type) { + return this.getActivePunishments().stream().filter(punishment -> punishment.getType() == type).count() > 0; + } + + public boolean checkGrants() { + boolean updated = false; + + // fix ConcurrentModificationException + final List grants = new ArrayList<>(this.grants); + for(int i=0; i { + if(!punishment.isActive() && !punishment.isRemoved()) { + punishment.setRemoved(true); + punishment.setRemovedAt(punishment.getAddedAt() + punishment.getDuration()); + punishment.setRemovedBy(PrimeConstants.CONSOLE_UUID); + punishment.setRemovedReason("Expired"); + } + }); + } + + public Player getPlayer() { + return Bukkit.getPlayer(this.uuid); + } + + public boolean hasPermission(String permission) { + final List permissions = new ArrayList<>(this.permissions); + this.getActiveGrants().stream().map(Grant::getRank).forEach(rank -> permissions.addAll(rank.getFullPermissions())); + + return permissions.contains(permission); + } + + public String getColoredName() { + return this.getHighestActiveNonHiddenGrant().getRank().getColor() + getActiveName(); + } + + public String getColoredName(String scope) { + return this.highestGrantOnScope(scope).getRank().getColor() + getActiveName(); + } + + public String getActiveName() { + if(hasNickname()) return nickname; + return username; + } + + public String getSpecialPrefix() { + Rank rank = this.getActiveGrants() + .stream() + .map(Grant::getRank) + .filter(grantRank -> grantRank.hasMeta(RankMeta.PREFIX, true)) + .findFirst() + .orElse(null); + + if(rank == null) return ""; + return rank.getPrefix(); + } + + public boolean isStaff() { + return this.getActiveGrants().stream().anyMatch(grant -> grant.getRank().hasMeta(RankMeta.STAFF, true)); + } + + public boolean hasMeta(RankMeta meta) { + return this.getActiveGrants().stream().anyMatch(grant -> grant.getRank().hasMeta(meta, true)); + } + + public boolean requiresProof(PunishmentType meta) { + return this.getActiveGrants().stream().anyMatch(grant -> grant.getRank().requiresProof(meta)); + } + + public boolean hasIdentity(String address) { + return this.getIdentity(address) != null; + } + + public ProfileIdentity getIdentity(String address) { + return this.identities.stream().filter(identity -> identity.getIp().equalsIgnoreCase(address)).findFirst().orElse(null); + } + + public boolean isMessagesToggled() { + return this.getPlayer() != null && this.getPlayer().hasMetadata("messagesToggled"); + } + + public void setMessagesToggled(boolean messagesToggled) { + this.messagesToggled = messagesToggled; + if(messagesToggled) { + getPlayer().setMetadata("messagesToggled", new FixedMetadataValue(Prime.getInstance(), true)); + return; + } + + getPlayer().removeMetadata("messagesToggled", Prime.getInstance()); + } + + public boolean hasActiveTag() { + return this.tag != null; + } + + public Tag getActiveTag() { + return Prime.getInstance().getModuleHandler().getModule(TagHandler.class).getTag(this.tag.toUpperCase()); + } + + public boolean hasStyle() { + return this.style != null && this.style.length() > 0; + } + + public boolean hasNickname() { + return this.nickname.length() > 0; + } + + public String getHashedIp(String ip) { + if(ip == null) return null; + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] messageDigest = md.digest(ip.getBytes()); + + BigInteger no = new BigInteger(1, messageDigest); + + StringBuilder hashtext = new StringBuilder(no.toString(16)); + while (hashtext.length() < 32) { + hashtext.insert(0, "0"); + } + return hashtext.toString(); + } + + catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + public String getLastHashedIp() { + return getHashedIp(lastIdentity.getIp()); + } + + public List getAllHashedIps() { + return this.identities + .stream() + .map(identity -> getHashedIp(identity.getIp())) + .collect(Collectors.toList()); + } + + public String getPlaytimeString() { + final int playtimeSec = (int) playtime / 1000; + return TimeUtils.formatIntoDetailedString(playtimeSec); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Profile profile = (Profile) o; + return com.google.common.base.Objects.equal(uuid, profile.uuid); + } + + @Override + public int hashCode() { + return com.google.common.base.Objects.hashCode(uuid); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/ProfileHandler.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/ProfileHandler.java new file mode 100644 index 0000000..5b34318 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/ProfileHandler.java @@ -0,0 +1,468 @@ +package dev.apposed.prime.spigot.module.profile; + +import com.elevatemc.elib.util.Callback; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.RemovalListener; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.model.Collation; +import com.mongodb.client.model.CollationStrength; +import com.mongodb.client.model.Filters; +import dev.apposed.prime.packet.ProfileRefreshPacket; +import dev.apposed.prime.packet.type.RefreshType; +import dev.apposed.prime.spigot.PrimeConstants; +import dev.apposed.prime.spigot.module.Module; +import dev.apposed.prime.spigot.module.database.mongo.MongoModule; +import dev.apposed.prime.spigot.module.database.redis.JedisModule; +import dev.apposed.prime.spigot.module.profile.grant.Grant; +import dev.apposed.prime.spigot.module.profile.grant.task.GrantExpiryTask; +import dev.apposed.prime.spigot.module.profile.identity.ProfileIdentity; +import dev.apposed.prime.spigot.module.profile.permission.PermissionHandler; +import dev.apposed.prime.spigot.module.profile.punishment.Punishment; +import dev.apposed.prime.spigot.module.profile.punishment.task.PunishmentCheckerTask; +import dev.apposed.prime.spigot.module.profile.punishment.type.PunishmentType; +import dev.apposed.prime.spigot.module.profile.task.ProfileSaveTask; +import dev.apposed.prime.spigot.module.rank.Rank; +import dev.apposed.prime.spigot.module.rank.RankHandler; +import dev.apposed.prime.spigot.module.server.ServerHandler; +import dev.apposed.prime.spigot.util.Color; +import dev.apposed.prime.spigot.util.json.JsonHelper; +import dev.apposed.prime.spigot.util.mojang.MojangUtils; +import lombok.Getter; +import org.bson.Document; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + + +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +@Getter +public class ProfileHandler extends Module { + +// private Map cache; + private Cache cache; + private MongoCollection collection; + + private JedisModule jedisModule; + private ServerHandler serverHandler; + private RankHandler rankHandler; + private PermissionHandler permissionHandler; + + private ExecutorService executorService = Executors.newFixedThreadPool(15); + + @Override + public void onEnable() { +// this.cache = new HashMap<>(); + this.cache = CacheBuilder.newBuilder() + .expireAfterWrite(10, TimeUnit.MINUTES) + .removalListener((RemovalListener) n -> { + if(!n.wasEvicted()) return; + if(n.getKey() != null && n.getValue() != null && n.getValue().getPlayer() != null) { + cache.put(n.getKey(), n.getValue()); + } else if(n.getValue() != null){ + save(n.getValue()); + } + }) + .build(); + this.collection = this.getModuleHandler().getModule(MongoModule.class).getMongoDatabase().getCollection("profiles"); + this.jedisModule = this.getModuleHandler().getModule(JedisModule.class); + this.serverHandler = this.getModuleHandler().getModule(ServerHandler.class); + this.rankHandler = this.getModuleHandler().getModule(RankHandler.class); + this.permissionHandler = new PermissionHandler(); + + // Once every 15 seconds + new GrantExpiryTask(this).runTaskTimerAsynchronously(getPlugin(), 0L, 15 * 20L); + // Once every 3 minutes + new ProfileSaveTask(this).runTaskTimerAsynchronously(getPlugin(), 0L, 60 * 3 * 20L); + // Once every 10 seconds + new PunishmentCheckerTask(this).runTaskTimerAsynchronously(getPlugin(), 0L, 10 * 20L); + +// this.collection.find().iterator().forEachRemaining(document -> add(JsonHelper.GSON.fromJson(JsonHelper.GSON.toJson(document), Profile.class))); + } + + @Override + public void onDisable() { + this.cache.asMap().values().forEach(profile -> this.collection.replaceOne(Filters.eq("_id", profile.getUuid().toString()), Document.parse(JsonHelper.GSON.toJson(profile)), JsonHelper.REPLACE_OPTIONS)); + } + + // TODO: make another method for syncing profiles so i don't have to make so many database calls + public void save(Profile profile) { + executorService.execute(() -> { + this.collection.replaceOne(Filters.eq("_id", profile.getUuid().toString()), Document.parse(JsonHelper.GSON.toJson(profile)), JsonHelper.REPLACE_OPTIONS); + sendSync(profile); + }); + } + + public void sendSync(Profile profile) { + executorService.execute(() -> this.jedisModule.sendPacket( + new ProfileRefreshPacket(profile, RefreshType.UPDATE) + )); + } + + public Profile load(Document document) { + Profile profile = JsonHelper.GSON.fromJson(JsonHelper.GSON.toJson(document), Profile.class); + if(profile != null) { + profile.checkGrants(); + this.setupPlayer(profile); + } + return profile; + } + + public void updateProfile(Profile updatedProfile) { + this.cache.put(updatedProfile.getUuid(), updatedProfile); + } + + public void setupPlayer(Profile profile) { + Bukkit.getScheduler().runTask(getPlugin(), () -> { + Player player = Bukkit.getPlayer(profile.getUuid()); + if (player == null || !player.isOnline()) return; + + Map perms = new HashMap<>(); + + List grants = profile.getActiveGrants(); + Set permissions = profile.getPermissions(); + // TODO: scoped permissions + grants.forEach(grant -> grant.getRank().getFullPermissions().forEach(permission -> { + // scoped permission +// if (permission.contains(":")) { +// final String[] permissionData = permission.split(":"); +// final String scope = permissionData[0]; +// if (!scope.equalsIgnoreCase(this.serverHandler.getCurrentServer().getGroup().toLowerCase())) +// return; +// permission = permissionData[1]; +// } + + if (permission.startsWith("-")) { + perms.put(permission.replaceFirst("-", ""), false); + } else { + perms.put(permission, true); + } + })); + + permissions.forEach(permission -> { +// if (permission.contains(":")) { +// final String[] permissionData = permission.split(":"); +// final String scope = permissionData[0]; +// if (!scope.equalsIgnoreCase(this.serverHandler.getCurrentServer().getGroup().toLowerCase())) +// return; +// permission = permissionData[1]; +// } + + if (permission.startsWith("-")) { + perms.put(permission.replaceFirst("-", ""), false); + } else { + perms.put(permission, true); + } + }); + + permissionHandler.update(player, perms); + + if (getPlugin().getConfig().getBoolean("nametag")) { + player.setPlayerListName(profile.getColoredName(getModuleHandler().getModule(ServerHandler.class).getCurrentScope().getId())); + } + }); + } + + public CompletableFuture load(UUID uuid) { + return CompletableFuture.supplyAsync(() -> { + Optional profile = getProfile(uuid); + + if(profile.isPresent()) { + profile.get().checkGrants(); + this.setupPlayer(profile.get()); + return profile.get(); + } + + profile = this.fetchFromDatabase(uuid); + if(profile.isPresent()) { + profile.get().checkGrants(); + add(profile.get()); + this.setupPlayer(profile.get()); + return profile.get(); + } + + Player player = Bukkit.getPlayer(uuid); + if(player == null || !player.isOnline()) return null; + return create(new Profile(player)); + }); + } + + public Profile create(Profile profile) { + profile.getGrants().add( + new Grant( + this.getModuleHandler().getModule(RankHandler.class).getDefaultRank(), + PrimeConstants.CONSOLE_UUID, + System.currentTimeMillis(), + "Default Rank", + Long.MAX_VALUE, + Arrays.asList("Global") + ) + ); + profile.setFirstJoin(System.currentTimeMillis()); + + save(profile); + add(profile); + return profile; + } + + // need to make sure to remove all grants with this rank from profiles + public void delete(Profile profile) { + executorService.execute(() -> { + this.collection.deleteOne(Filters.eq("_id", profile.getUuid().toString())); + this.jedisModule.sendPacket(new ProfileRefreshPacket(profile, RefreshType.REMOVE)); + }); + } + + public boolean isCached(UUID uuid) { + return cache.asMap().containsKey(uuid); + } + + public Collection getProfiles() { + return this.cache.asMap().values(); + } + + public List getStaffProfiles() { + return this.getProfiles().stream().filter(Profile::isStaff).collect(Collectors.toList()); + } + + public Profile add(Profile profile) { + this.cache.put(profile.getUuid(), profile); + return profile; + } + + public Optional fetchFromDatabase(UUID uuid) { + if(Bukkit.isPrimaryThread()) { + Bukkit.getOnlinePlayers() + .stream() + .filter(player -> player.hasPermission("prime.staff")) + .forEach(player -> { + player.sendMessage(Color.translate("&c&l Database uuid query running on main thread. Contact a developer immediately.")); + }); + } + final MongoCursor profile = this.collection.find(Filters.eq("_id", uuid.toString())).iterator(); + if(profile.hasNext()) { + Profile loadedProfile = load(profile.next()); + if(loadedProfile != null) { + return Optional.of(loadedProfile); + } + } + + return Optional.empty(); + } + + public Profile fetchFromDatabase(String username) { + if(Bukkit.isPrimaryThread()) { + Bukkit.getOnlinePlayers() + .stream() + .filter(player -> player.hasPermission("prime.staff")) + .forEach(player -> { + player.sendMessage(Color.translate("&c&l Database username query running on main thread. Contact a developer immediately.")); + }); + } + String patternString = "(?i)^" + username + "$"; + Pattern pattern = Pattern.compile(patternString, Pattern.CASE_INSENSITIVE); + final MongoCursor profile = this.collection.find(Filters.regex("username", pattern)).collation(Collation.builder().locale("en").collationStrength(CollationStrength.SECONDARY).build()).iterator(); + if(profile.hasNext()) { + Profile loadedProfile = load(profile.next()); + if(loadedProfile != null) { + return loadedProfile; + } + } + + // create new profile | fetch uuid of player + + final AtomicReference uuidRef = new AtomicReference<>(); + MojangUtils.getUUIDForPlayerName(username, uuidString-> {; + if(uuidString == null) return; + uuidRef.set(UUID.fromString(uuidString)); + }); + + final Optional profileOptional = getProfile(uuidRef.get()); + return profileOptional.orElseGet(() -> create(new Profile(uuidRef.get(), username))); + + } + + public Optional getProfile(UUID uuid) { + return Optional.ofNullable(cache.getIfPresent(uuid)); +// return this.getProfiles().stream().filter(p -> p.getUuid().equals(uuid)).findFirst(); + } + + public CompletableFuture getProfile(String name) { + return CompletableFuture.supplyAsync(() -> { + final Optional profile = getProfiles().stream().filter(p -> p.getUsername().equalsIgnoreCase(name)).findFirst(); + return profile.orElseGet(() -> fetchFromDatabase(name)); + }); + } + + public Profile getProfile(String name, UUID uuid) { + final Optional profile = this.getProfile(uuid); + return profile.orElseGet(() -> this.create(new Profile(uuid, name) + )); + + } + + public List getPunishments(UUID uuid) { + final List punishments = new ArrayList<>(); + this.getProfiles().forEach(profile -> punishments.addAll(profile.getPunishments().stream().filter(punishment -> punishment.getAddedBy().equals(uuid)).collect(Collectors.toList()))); + return punishments; + } + + public List getPunishmentsByStaff(UUID staffUUID) { + try { + final CompletableFuture> futurePunishments = CompletableFuture.supplyAsync(() -> { + final List punishments = new ArrayList<>(); + + // db.profiles.find({"grants": {$elemMatch: {"addedBy": ""}}}) +// final MongoCursor profiles = this.collection.find(Filters.expr(Document.parse("{\"punishments\": {$elemMatch: {\"addedBy\": \"" + staffUUID.toString() + "\"}}}"))).cursor(); + final MongoCursor profiles = this.collection.find().cursor(); + + while(profiles.hasNext()) { + final Document document = profiles.next(); + if(!document.containsKey("punishments")) continue; + + final UUID uuid = UUID.fromString(document.getString("_id")); + final List documents = document.getList("punishments", Document.class); + if(documents.size() == 0) continue; + + documents.forEach(punishment -> { + if(!UUID.fromString(punishment.getString("addedBy")).equals(staffUUID)) return; + PunishmentType type = PunishmentType.valueOf(punishment.getString("type")); + UUID addedBy = UUID.fromString(punishment.getString("addedBy")); + long addedAt = punishment.getLong("addedAt"); + String addedReason = punishment.getString("addedReason"); + long duration = ((Number) punishment.get("duration")).longValue(); + boolean removed = punishment.getBoolean("removed"); + + Punishment newPunishment = new Punishment(type, addedBy, addedAt, addedReason, duration); + newPunishment.setRemoved(removed); + + if(removed) { + UUID removedBy = UUID.fromString(punishment.getString("removedBy")); + long removedAt = punishment.getLong("removedAt"); + String removedReason = punishment.getString("removedReason"); + + newPunishment.setRemovedBy(removedBy); + newPunishment.setRemovedAt(removedAt); + newPunishment.setRemovedReason(removedReason); + } + + newPunishment.setPlayer(uuid); + punishments.add(newPunishment); + }); + } + + return punishments; + }); + + return futurePunishments.get(); + } catch(Exception ex) { + ex.printStackTrace(); + return null; + } + } + + public List getActivePunishments(UUID uuid) { + return this.getPunishments(uuid).stream().filter(Punishment::isActive).collect(Collectors.toList()); + } + + public List punishmentsWithoutProof(Profile profile) { + return this.getPunishments(profile.getUuid()).stream().filter(punishment -> profile.requiresProof(punishment.getType())).filter(punishment -> punishment.getEvidence().size() == 0).collect(Collectors.toList()); + } + + public List getGrantsByStaff(UUID staffUUID) { + try { + final CompletableFuture> futureGrants = CompletableFuture.supplyAsync(() -> { + final List grants = new ArrayList<>(); + +// final MongoCursor profiles = this.collection.find(Filters.expr(Document.parse("{\"grants\": {$elemMatch: {\"addedBy\": \"" + staffUUID.toString() + "\"}}}"))).cursor(); + final MongoCursor profiles = this.collection.find().cursor(); + + while(profiles.hasNext()) { + final Document document = profiles.next(); + if(!document.containsKey("grants")) continue; + + final UUID uuid = UUID.fromString(document.getString("_id")); + final List documents = document.getList("grants", Document.class); + if(documents.size() == 0) continue; + + documents.forEach(grant -> { + if(!UUID.fromString(grant.getString("addedBy")).equals(staffUUID)) return; + UUID id = UUID.fromString(grant.getString("_id")); + Optional rank = rankHandler.getRank(grant.getString("rank")); + Rank grantRank = rank.orElseGet(rankHandler::getDefaultRank); + UUID addedBy = UUID.fromString(grant.getString("addedBy")); + long addedAt = grant.getLong("addedAt"); + String addedReason = grant.getString("addedReason"); + long duration = ((Number) grant.get("duration")).longValue(); + boolean removed = grant.getBoolean("removed"); + List scopes = grant.getList("scopes", String.class); + + Grant newGrant = new Grant(id, grantRank, addedBy, addedAt, addedReason, duration, scopes, null, 0L, null, false); + newGrant.setRemoved(removed); + + if(removed) { + UUID removedBy = UUID.fromString(grant.getString("removedBy")); + long removedAt = grant.getLong("removedAt"); + String removedReason = grant.getString("removedReason"); + + newGrant.setRemovedBy(removedBy); + newGrant.setRemovedAt(removedAt); + newGrant.setRemovedReason(removedReason); + } + + newGrant.setPlayer(uuid); + grants.add(newGrant); + }); + } + + return grants; + }); + + return futureGrants.get(); + } catch(Exception ex) { + ex.printStackTrace(); + return null; + } + } + + public Optional identityIsPunished(ProfileIdentity identity) { + return this.fetchByIdentity(identity.getIp()).stream().filter(profile -> profile.hasActivePunishment(PunishmentType.BAN) || profile.hasActivePunishment(PunishmentType.BLACKLIST)).findAny(); + } + + public List fetchByIdentity(String address) { + final List profiles = new ArrayList<>(); + + for(Profile profile : this.getProfiles()) { + for(ProfileIdentity profileIdentity : profile.getIdentities()) { + if(profileIdentity.getIp().equalsIgnoreCase(address)) { + profiles.add(profile); + } + } + } + + executorService.execute(() -> { + final MongoCursor profile = this.collection.find(Filters.eq("lastIdentity", address)).iterator(); + if(profile.hasNext()) { + Profile loadedProfile = load(profile.next()); + if(loadedProfile != null && profiles.stream().noneMatch(p -> p.getUuid().equals(loadedProfile.getUuid()))) { + profiles.add(loadedProfile); + } + } + }); + + return profiles; + } + + public Profile getProfileFromNickname(String nickname) { + return this.cache.asMap().values().stream().filter(profile -> profile.getNickname().equalsIgnoreCase(nickname)).findFirst().orElse(null); + } + + public boolean canChangeNickname(String nickname) { + return getProfileFromNickname(nickname) == null && this.cache.asMap().values().stream().noneMatch(profile -> profile.getUsername().equalsIgnoreCase(nickname)); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/adapter/ProfileTypeAdapter.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/adapter/ProfileTypeAdapter.java new file mode 100644 index 0000000..9810811 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/adapter/ProfileTypeAdapter.java @@ -0,0 +1,44 @@ +package dev.apposed.prime.spigot.module.profile.adapter; + +import com.elevatemc.elib.command.ParameterType; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +public class ProfileTypeAdapter implements ParameterType { + + private ProfileHandler profileHandler; + + public ProfileTypeAdapter(ProfileHandler profileHandler) { + this.profileHandler = profileHandler; + } + + @Override + public Profile transform(CommandSender sender, String s) { + Optional profile = profileHandler.getProfile(s); + if(!profile.isPresent()) { + final Profile nicknameProfile = profileHandler.getProfileFromNickname(s); + if(nicknameProfile.getPlayer() != null) { + return nicknameProfile; + } + + sender.sendMessage(Color.translate("&cFailed to fetch " + s + "'s profile.")); + return null; + } + + return profile.get(); + } + + @Override + public List tabComplete(Player player, Set set, String s) { + return Bukkit.getOnlinePlayers().stream().filter(player::canSee).map(Player::getName).collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/command/ProfileCommands.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/command/ProfileCommands.java new file mode 100644 index 0000000..fcdea78 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/command/ProfileCommands.java @@ -0,0 +1,880 @@ +package dev.apposed.prime.spigot.module.profile.command; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.flag.Flag; +import com.elevatemc.elib.command.param.Parameter; +import com.elevatemc.elib.util.TimeUtils; +import com.elevatemc.elib.util.UUIDUtils; +import com.google.common.collect.Maps; +import dev.apposed.prime.packet.StaffMessagePacket; +import dev.apposed.prime.packet.type.StaffMessageType; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.PrimeConstants; +import dev.apposed.prime.spigot.module.ModuleHandler; +import dev.apposed.prime.spigot.module.database.redis.JedisModule; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import dev.apposed.prime.spigot.module.profile.grant.Grant; +import dev.apposed.prime.spigot.module.profile.menu.ChatColorMenu; +import dev.apposed.prime.spigot.module.profile.punishment.type.PunishmentType; +import dev.apposed.prime.spigot.module.profile.target.ProfileTarget; +import dev.apposed.prime.spigot.module.rank.Rank; +import dev.apposed.prime.spigot.module.rank.RankHandler; +import dev.apposed.prime.spigot.module.rank.meta.RankMeta; +import dev.apposed.prime.spigot.module.server.ServerHandler; +import dev.apposed.prime.spigot.module.server.filter.ChatFilter; +import dev.apposed.prime.spigot.module.server.filter.ChatFilterHandler; +import dev.apposed.prime.spigot.module.webhook.DiscordWebhook; +import dev.apposed.prime.spigot.util.Color; +import dev.apposed.prime.spigot.util.time.DurationUtils; +import net.md_5.bungee.api.chat.ComponentBuilder; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.TextComponent; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.metadata.FixedMetadataValue; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +public class ProfileCommands { + + private static final Prime plugin = Prime.getInstance(); + private static final ModuleHandler moduleHandler = plugin.getModuleHandler(); + + private static final ProfileHandler profileHandler = moduleHandler.getModule(ProfileHandler.class); + private static final RankHandler rankHandler = moduleHandler.getModule(RankHandler.class); + private static final JedisModule jedisModule = moduleHandler.getModule(JedisModule.class); + private static final ServerHandler serverHandler = moduleHandler.getModule(ServerHandler.class); + private static final ChatFilterHandler filterHandler = moduleHandler.getModule(ChatFilterHandler.class); + + private static final Map lastRequest = Maps.newHashMap(); + private static final Map lastConversation = Maps.newHashMap(); + + private static final String GRANT_WEBHOOK = "https://discord.com/api/webhooks/993224084060115004/Rpd7FcojkkgLlbWJQg938NkwlarCA0Kuw_9uNfxyVEMMwBzlbaxkNnA7gdPkHKcHm6P3"; + private static final DateFormat format = new SimpleDateFormat("MMMM d, yyyy", Locale.ENGLISH); + + @Command(names = {"sc", "staffchat"}, description = "Use the staff chat", permission = "prime.command.staffchat") + public static void executeStaffChat(Player player, @Parameter(name = "message", defaultValue = "|toggle|", wildcard = true) String message) { + if(message.equalsIgnoreCase("|toggle|")) { + // toggle + if(player.hasMetadata("staffchat")) { + player.removeMetadata("staffchat", plugin); + } else { + player.setMetadata("staffchat", new FixedMetadataValue(plugin, true)); + } + + player.sendMessage(Color.translate("&9[SC] " + (player.hasMetadata("staffchat") ? "&aEnabled" : "&cDisabled") + "&7.")); + return; + } + + jedisModule.sendPacket(new StaffMessagePacket( + StaffMessageType.CHAT, + player.getUniqueId(), + serverHandler.getCurrentName(), + message + )); + } + + @Command(names = {"ac", "adminchat"}, description = "Use the admin chat", permission = "prime.command.adminchat") + public static void adminChat(Player player, @Parameter(name = "message", defaultValue = "|toggle|", wildcard = true) String message) { + if(message.equalsIgnoreCase("|toggle|")) { + // toggle + if(player.hasMetadata("adminchat")) { + player.removeMetadata("adminchat", plugin); + } else { + player.setMetadata("adminchat", new FixedMetadataValue(plugin, true)); + } + + player.sendMessage(Color.translate("&c[AC] " + (player.hasMetadata("adminchat") ? "&aEnabled" : "&cDisabled") + "&7.")); + return; + } + + jedisModule.sendPacket(new StaffMessagePacket( + StaffMessageType.ADMIN_CHAT, + player.getUniqueId(), + serverHandler.getCurrentName(), + message + )); + } + + @Command(names = {"mc", "managerchat"}, description = "Use the manager chat", permission = "prime.command.managerchat") + public static void managerChat(Player player, @Parameter(name = "message", defaultValue = "|toggle|", wildcard = true) String message) { + if(message.equalsIgnoreCase("|toggle|")) { + // toggle + if(player.hasMetadata("managerchat")) { + player.removeMetadata("managerchat", plugin); + } else { + player.setMetadata("managerchat", new FixedMetadataValue(plugin, true)); + } + + player.sendMessage(Color.translate("&5[MC] " + (player.hasMetadata("managerchat") ? "&aEnabled" : "&cDisabled") + "&7.")); + return; + } + + jedisModule.sendPacket(new StaffMessagePacket( + StaffMessageType.MANAGER_CHAT, + player.getUniqueId(), + serverHandler.getCurrentName(), + message + )); + } + + @Command(names = {"helpop", "request"}, permission = "prime.command.helpop") + public static void helpop(Player player, @Parameter(name = "message", wildcard = true) String message) { + final long lastRequestTime = lastRequest.getOrDefault(player.getUniqueId(), 0L); + if((System.currentTimeMillis() - lastRequestTime) < TimeUnit.MINUTES.toMillis(2)) { + player.sendMessage(Color.translate("&cYou may only send a request once every two minutes.")); + return; + } + + final StaffMessagePacket requestPacket = new StaffMessagePacket( + StaffMessageType.HELPOP, + player.getUniqueId(), + "", + serverHandler.getCurrentName() + ); + + requestPacket.setMessage(message); + + jedisModule.sendPacket(requestPacket); + + lastRequest.put(player.getUniqueId(), System.currentTimeMillis()); + player.sendMessage(Color.translate("&aYour request has been received.")); + } + + @Command(names = {"report"}, permission = "prime.command.report") + public static void report(Player player, @Parameter(name = "player") Player targetPlayer, @Parameter(name = "message", wildcard = true) String message) { + final long lastRequestTime = lastRequest.getOrDefault(player.getUniqueId(), 0L); + if((System.currentTimeMillis() - lastRequestTime) < TimeUnit.MINUTES.toMillis(2)) { + player.sendMessage(Color.translate("&cYou may only send a request once every two minutes.")); + return; + } + + final StaffMessagePacket requestPacket = new StaffMessagePacket( + StaffMessageType.REPORT, + player.getUniqueId(), + "", + serverHandler.getCurrentName() + ); + + final Profile target = profileHandler.getProfile(targetPlayer.getUniqueId()).orElse(null); + if(target == null) { + targetPlayer.sendMessage(Color.translate("&cFailed to fetch target player's profile.")); + return; + } + + requestPacket.setMessage(message); + requestPacket.setPlayer(target.getUuid()); + + jedisModule.sendPacket(requestPacket); + + lastRequest.put(player.getUniqueId(), System.currentTimeMillis()); + player.sendMessage(Color.translate("&aYour request has been received.")); + } + + @Command(names = {"list"}, permission = "prime.command.list") + public static void executeList(CommandSender sender) { + final List vanishedStaff = Bukkit.getOnlinePlayers().stream().filter(player -> player.hasMetadata("modmode")).map(Player::getUniqueId).collect(Collectors.toList()); + if(sender.hasPermission("prime.staff")) { + final String onlinePlayers = "(" + Bukkit.getOnlinePlayers().size() + "/" + Bukkit.getMaxPlayers() + ")"; + final String playerList = profileHandler.getProfiles().stream().filter(profile -> profile.getPlayer() != null).sorted((a, b) -> Integer.compare(b.getHighestActiveNonHiddenGrant().getRank().getWeight(), a.getHighestActiveNonHiddenGrant().getRank().getWeight())).map(profile -> { + if(vanishedStaff.contains(profile.getUuid())) { + return ChatColor.WHITE + "*" + profile.getColoredName(); + } + return profile.getColoredName(); + }).collect(Collectors.joining(Color.translate("&f, "))); + sender.sendMessage(new String[] { + rankHandler.getCache().stream().filter(rank -> !rank.hasMeta(RankMeta.HIDDEN, true)).sorted((a, b) -> Integer.compare(b.getWeight(), a.getWeight())).map(Rank::getColoredDisplay).collect(Collectors.joining(Color.translate("&f, "))), + onlinePlayers + " [" + playerList + ChatColor.WHITE + "]" + }); + } else { + final String onlinePlayers = "(" + (Bukkit.getOnlinePlayers().size() - vanishedStaff.size()) + "/" + Bukkit.getMaxPlayers() + ")"; final String playerList = profileHandler.getProfiles().stream().filter(profile -> profile.getPlayer() != null).sorted((a, b) -> Integer.compare(b.getHighestActiveNonHiddenGrant().getRank().getWeight(), a.getHighestActiveNonHiddenGrant().getRank().getWeight())).map(profile -> { + if(vanishedStaff.contains(profile.getUuid())) { + return null; + } + return profile.getColoredName(); + }).filter(Objects::nonNull).collect(Collectors.joining(Color.translate("&f, "))); + sender.sendMessage(new String[] { + rankHandler.getCache().stream().filter(rank -> !rank.hasMeta(RankMeta.HIDDEN, true)).sorted((a, b) -> Integer.compare(b.getWeight(), a.getWeight())).map(Rank::getColoredDisplay).collect(Collectors.joining(Color.translate("&f, "))), + onlinePlayers + " [" + playerList + ChatColor.WHITE + "]" + }); + } + } + + @Command(names = {"tpm", "togglemessages", "toggleprivatemessages"}, permission = "prime.command.tpm") + public static void executeTpm(Player player) { + final Optional profileOptional = profileHandler.getProfile(player.getUniqueId()); + if(!profileOptional.isPresent()) { + player.sendMessage(Color.translate("&cCould not load your profile.")); + return; + } + + profileOptional.get().setMessagesToggled(!profileOptional.get().isMessagesToggled()); + player.sendMessage(Color.translate("&eYou have toggled " + (profileOptional.get().isMessagesToggled() ? "&coff" : "&aon") + " &eprivate messages.")); + } + + @Command(names = {"seen"}, permission = "prime.command.seen") + public static void executeSeen(CommandSender sender, @Parameter(name = "player") ProfileTarget profileTarget) { + profileTarget.resolve(profile -> { + if (profile == null) { + profileTarget.sendError(sender); + return; + } + + if(profile.getLastServer() == null || profile.getLastOnline() == 0L) { + sender.sendMessage(Color.translate("&c" + profile.getUsername() + " has never joined the server.")); + return; + } + + final Player player = profile.getPlayer(); + if(!profile.isOnline() || (player != null && player.hasMetadata("modmode")) && !player.hasPermission("prime.staff")) { + sender.sendMessage(Color.translate(profile.getColoredName() + " &ewas last seen " + DurationUtils.formatAgo(System.currentTimeMillis() - profile.getLastOnline()) + " &eon &7" + profile.getLastServer())); + return; + } + + sender.sendMessage(Color.translate(profile.getColoredName() + " &eis currently &aonline &eon &7" + profile.getLastServer())); + }); + } + + @Command(names = {"message", "msg"}, permission = "prime.command.message") + public static void executeMessage(Player player, @Parameter(name = "player") Player targetPlayer, @Parameter(name = "message", wildcard = true) String message) { + final Optional profileOptional = profileHandler.getProfile(player.getUniqueId()); + if(!profileOptional.isPresent()) { + player.sendMessage(Color.translate("&cCould not load your profile.")); + return; + } + + final Profile profile = profileOptional.get(); + if(profile.hasActivePunishment(PunishmentType.MUTE)) { + player.sendMessage(Color.translate("&cYou cannot message players while muted.")); + return; + } + + if(profile.isMessagesToggled()) { + player.sendMessage(Color.translate("&cYou have messages toggled.")); + return; + } + + final Profile target = profileHandler.getProfile(targetPlayer.getUniqueId()).orElse(null); + if(target == null) { + targetPlayer.sendMessage(Color.translate("&cFailed to fetch target player's profile.")); + return; + } + + if(target.isMessagesToggled() && !player.hasPermission("prime.message.bypass")) { + player.sendMessage(Color.translate("&c" + target.getUsername() + " has messages toggled.")); + return; + } + + player.sendMessage(Color.translate("&7(To " + target.getColoredName() + "&7) ") + message); + + final ChatFilter filter = filterHandler.filterMessage(message); + if(filter != null) { + final TextComponent component = new TextComponent(Color.translate("&c&l[Filtered] ")); + component.addExtra(Color.translate("&7(" + profile.getColoredName() + " &e-> " + target.getColoredName() + "&7) &f" + message)); + component.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, + new ComponentBuilder("§eThis message was hidden from " + target.getUsername() + ".\n§cFilter: " + filter.getDescription()).create())); + + Bukkit.getOnlinePlayers().stream().filter(staff -> staff.hasPermission("prime.staff")).forEach(staff -> { + staff.spigot().sendMessage(component); + }); + return; + } + + target.getPlayer().sendMessage(Color.translate("&7(From " + profile.getColoredName() + "&7) ") + message); + lastConversation.put(player.getUniqueId(), target.getUuid()); + lastConversation.put(target.getUuid(), player.getUniqueId()); + } + + @Command(names = {"reply", "r"}, permission = "prime.command.message") + public static void executeReply(Player player, @Parameter(name = "message", wildcard = true) String message) { + final Optional profileOptional = profileHandler.getProfile(player.getUniqueId()); + if(!profileOptional.isPresent()) { + player.sendMessage(Color.translate("&cCould not load your profile.")); + return; + } + + final Profile profile = profileOptional.get(); + if(profile.hasActivePunishment(PunishmentType.MUTE)) { + player.sendMessage(Color.translate("&cYou cannot message players while muted.")); + return; + } + + if(profile.isMessagesToggled()) { + player.sendMessage(Color.translate("&cYou have messages toggled.")); + return; + } + + if(!lastConversation.containsKey(player.getUniqueId())) { + player.sendMessage(Color.translate("&cYou have not messaged anybody recently.")); + return; + } + + final Optional targetOptional = profileHandler.getProfile(lastConversation.get(player.getUniqueId())); + if(!targetOptional.isPresent()) { + player.sendMessage(Color.translate("&cCould not load the target's profile.")); + return; + } + + final Profile target = targetOptional.get(); + + if(target.isMessagesToggled() && !player.hasPermission("prime.message.bypass")) { + player.sendMessage(Color.translate("&c" + target.getUsername() + " has messages toggled.")); + return; + } + + final Player targetPlayer = target.getPlayer(); + + if (targetPlayer == null) { + lastConversation.remove(player.getUniqueId()); + player.sendMessage(Color.translate("&cThe player is no longer online.")); + return; + } + + player.sendMessage(Color.translate("&7(To " + target.getColoredName() + "&7) ") + message); + + final ChatFilter filter = filterHandler.filterMessage(message); + if(filter != null) { + final TextComponent component = new TextComponent(Color.translate("&c&l[Filtered] ")); + component.addExtra(Color.translate("&7(" + profile.getColoredName() + " &e-> " + target.getColoredName() + "&7) &f" + message)); + component.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, + new ComponentBuilder("§eThis message was hidden from " + target.getUsername() + ".\n§cFilter: " + filter.getDescription()).create())); + + Bukkit.getOnlinePlayers().stream().filter(staff -> staff.hasPermission("prime.staff")).forEach(staff -> { + staff.spigot().sendMessage(component); + }); + return; + } + targetPlayer.sendMessage(Color.translate("&7(From " + profile.getColoredName() + "&7) ") + message); + lastConversation.put(player.getUniqueId(), target.getUuid()); + lastConversation.put(target.getUuid(), player.getUniqueId()); + } + + @Command(names = {"permission add"}, permission = "prime.command.permission.add") + public static void permissionAdd(CommandSender sender, @Parameter(name = "player")ProfileTarget profileTarget, @Parameter(name = "permission", wildcard = true) String permission) { + profileTarget.resolve(profile -> { + if(profile == null) { + profileTarget.sendError(sender); + return; + } + + if(profile.getPermissions().contains(permission)) { + sender.sendMessage(Color.translate(profile.getColoredName() + " &calready has &e" + permission + " &cassigned to them.")); + return; + } + + UUID addedBy; + + if(sender instanceof Player) { + Player player = (Player) sender; + addedBy = player.getUniqueId(); + } else { + addedBy = PrimeConstants.CONSOLE_UUID; + } + + profile.getPermissions().add(permission); + profileHandler.sendSync(profile); + + final DiscordWebhook webhook = new DiscordWebhook(GRANT_WEBHOOK); + webhook.addEmbed( + new DiscordWebhook.EmbedObject() + .setTitle(profile.getUsername() + " has been assigned " + permission) + .addField("Added By", (addedBy.equals(PrimeConstants.CONSOLE_UUID) ? "Console" : UUIDUtils.name(addedBy)), false) + .addField("Current Server", serverHandler.getCurrentName(), false) + .setColor(java.awt.Color.cyan) + .setFooter("Prime Grants", null) + ); + new Thread(() -> { + try { + webhook.execute(); + }catch(Exception ex) { + ex.printStackTrace(); + } + }).start(); + + sender.sendMessage(Color.translate("&aAdded &e" + permission + " &ato " + profile.getColoredName() + "'s &aprofile.")); + }); + } + + @Command(names = {"permission remove"}, permission = "prime.command.permission.remove") + public static void permissionRemove(CommandSender sender, @Parameter(name = "player") ProfileTarget profileTarget, @Parameter(name = "permission", wildcard = true) String permission) { + profileTarget.resolve(profile -> { + if(profile == null) { + profileTarget.sendError(sender); + return; + } + + if(!profile.getPermissions().contains(permission)) { + sender.sendMessage(Color.translate(profile.getColoredName() + " &cdoes not have &e" + permission + " &cassigned to them.")); + return; + } + + UUID addedBy; + + if(sender instanceof Player) { + Player player = (Player) sender; + addedBy = player.getUniqueId(); + } else { + addedBy = PrimeConstants.CONSOLE_UUID; + } + + profile.getPermissions().remove(permission); + profileHandler.sendSync(profile); + + final DiscordWebhook webhook = new DiscordWebhook(GRANT_WEBHOOK); + webhook.addEmbed( + new DiscordWebhook.EmbedObject() + .setTitle(profile.getUsername() + " has been unassigned " + permission) + .addField("Added By", (addedBy.equals(PrimeConstants.CONSOLE_UUID) ? "Console" : UUIDUtils.name(addedBy)), false) + .addField("Current Server", serverHandler.getCurrentName(), false) + .setColor(java.awt.Color.cyan) + .setFooter("Prime Grants", null) + ); + new Thread(() -> { + try { + webhook.execute(); + }catch(Exception ex) { + ex.printStackTrace(); + } + }).start(); + + sender.sendMessage(Color.translate("&aRemoved &e" + permission + " &afrom " + profile.getColoredName() + "'s &aprofile.")); + }); + } + + @Command(names = {"permission list"}, permission = "prime.command.permission.list") + public static void permissionList(CommandSender sender, @Parameter(name = "player") ProfileTarget profileTarget) { + profileTarget.resolve(profile -> { + if(profile == null) { + profileTarget.sendError(sender); + return; + } + + sender.sendMessage(Color.translate(profile.getColoredName() + "'s &aIndividual Permissions:")); + sender.sendMessage(profile.getPermissions().stream() + .map(permission -> Color.translate("&f- " + permission)) + .collect(Collectors.joining("\n")) + ); + }); + } + + @Command(names = {"gamemode c", "gamemode creative", "creative", "gamemode 1", "gmc"}, permission = "prime.command.gamemode") + public static void executeGamemodeCreative(Player sender, @Parameter(name = "player", defaultValue = "self") Player player) { + player.setGameMode(GameMode.CREATIVE); + player.sendMessage(Color.translate("&6Gamemode: &fCREATIVE")); + if(!sender.getUniqueId().equals(player.getUniqueId())) { + sender.sendMessage(Color.translate("&6" + player.getName() + "'s Gamemode: &fCREATIVE")); + } + } + + @Command(names = {"gamemode s", "gamemode survival", "survival", "gamemode 0", "gms"}, permission = "prime.command.gamemode") + public static void executeGamemodeSurvival(Player sender, @Parameter(name = "player", defaultValue = "self") Player player) { + player.setGameMode(GameMode.SURVIVAL); + player.sendMessage(Color.translate("&6Gamemode: &fSURVIVAL")); + if(!sender.getUniqueId().equals(player.getUniqueId())) { + sender.sendMessage(Color.translate("&6" + player.getName() + "'s Gamemode: &fSURVIVAL")); + } + } + + @Command(names = {"fly"}, permission = "prime.command.fly") + public static void executeFly(Player sender, @Parameter(name = "player", defaultValue = "self") Player player) { + player.setAllowFlight(!player.getAllowFlight()); + player.sendMessage(Color.translate("&6Fly: " + (player.getAllowFlight() ? "&aEnabled" : "&cDisabled"))); + if(!sender.getUniqueId().equals(player.getUniqueId())) { + sender.sendMessage(Color.translate("&6" + player.getName() + "'s Fly:" + (player.getAllowFlight() ? "&aEnabled" : "&cDisabled"))); + } + } + + @Command(names = {"ss", "freeze"}, permission = "prime.command.freeze") + public static void freeze(Player player, @Parameter(name = "player") Player target) { + if(target.hasMetadata("frozen")) { + target.removeMetadata("frozen", plugin); + target.sendMessage(Color.translate("&aYou have been unfrozen.")); + jedisModule.sendPacket(new StaffMessagePacket( + StaffMessageType.UNFREEZE, + player.getUniqueId(), + "", + serverHandler.getCurrentName(), + "", + target.getUniqueId() + )); + } else { + target.setMetadata("frozen", new FixedMetadataValue(plugin, true)); + target.sendMessage(Color.translate("&cYou have been frozen by a staff member.")); + jedisModule.sendPacket(new StaffMessagePacket( + StaffMessageType.FREEZE, + player.getUniqueId(), + "", + serverHandler.getCurrentName(), + "", + target.getUniqueId() + )); + } + } + + @Command(names = {"invsee"}, permission = "prime.command.invsee") + public static void invsee(Player player, @Parameter(name = "player") Player target) { + final Profile profile = profileHandler.getProfile(target.getUniqueId()).orElse(null); + if(profile == null) return; + player.sendMessage(Color.translate("&eOpening " + profile.getColoredName() + "'s &einventory.")); + player.openInventory(target.getInventory()); + } + + @Command(names = {"tp", "teleport"}, permission = "prime.command.teleport") + public static void teleport(Player player, @Parameter(name = "player") Player target, @Parameter(name = "player", defaultValue = "self") Player other) { + if(!other.getUniqueId().equals(player.getUniqueId())) { + // teleport target to other + target.teleport(other); + final Optional targetProfOpt = profileHandler.getProfile(target.getUniqueId()), + otherProfOpt = profileHandler.getProfile(other.getUniqueId()); + if(!targetProfOpt.isPresent() || !otherProfOpt.isPresent()) return; + final Profile targetProfile = targetProfOpt.get(), + otherProfile = otherProfOpt.get(); + + player.sendMessage(Color.translate(String.format("&eTeleporting %s &eto %s&e.", targetProfile.getColoredName(), otherProfile.getColoredName()))); + return; + } + + final Profile profile = profileHandler.getProfile(target.getUniqueId()).orElse(null); + if(profile == null) return; + player.sendMessage(Color.translate("&eTeleporting to " + profile.getColoredName() + "&e.")); + player.teleport(target); + } + + @Command(names = {"tppos"}, permission = "prime.command.tppos") + public static void tppos(Player player, @Parameter(name = "x") double x, @Parameter(name = "y") double y, @Parameter(name = "z") double z) { + player.teleport(new Location(player.getWorld(), x, y, z)); + player.sendMessage(Color.translate("&6Teleporting...")); + } + + @Command(names = {"tphere", "s"}, permission = "prime.command.teleport.here") + public static void teleportHere(Player player, @Parameter(name = "player") Player target) { + final Profile profile = profileHandler.getProfile(target.getUniqueId()).orElse(null); + if(profile == null) return; + player.sendMessage(Color.translate("&eTeleporting " + profile.getColoredName() + " &eto yourself.")); + target.teleport(player); + } + + // TODO: Add flags (-a | no armor, -i | no inventory) + @Command(names = {"clear", "ci", "clearinventory"}, permission = "prime.command.clear") + public static void clear(Player player) { + player.getInventory().clear(); + player.getInventory().setArmorContents(null); + player.sendMessage(Color.translate("&eCleared your inventory.")); + } + + @Command(names = {"stafftimeline", "timeline"}, permission = "prime.command.timeline") + public static void timeline(Player self, @Parameter(name = "player", defaultValue = "self") Player player) { + if(!self.isOp() && !(self.getUniqueId().equals(player.getUniqueId()))) { + self.sendMessage(Color.translate("&cLol what are you trying to do buddy")); + return; + } + final Optional profileOptional = profileHandler.getProfile(player.getUniqueId()); + if(!profileOptional.isPresent()) return; + final Profile profile = profileOptional.get(); + + final List staffGrants = profile.getGrants() + .stream() + .filter(grant -> grant.getRank().hasMeta(RankMeta.STAFF, true)) + .sorted((a, b) -> b.getRank().getWeight() - a.getRank().getWeight()) + .collect(Collectors.toList()); + + if(staffGrants.size() != 0) { + final Grant firstGrant = staffGrants.get(staffGrants.size()-1); + final long firstStaffRank = firstGrant.getAddedAt(); + final long difference = System.currentTimeMillis() - firstStaffRank; + final int differenceSec = (int) (difference/1000); + + self.sendMessage(Color.SPACER_LONG); + staffGrants.forEach(grant -> { + Profile added = profileHandler.getProfile(grant.getAddedBy()).orElse(null); + String addedBy = added == null ? "Console" : added.getUsername(); + if(firstGrant.equals(grant)) { + self.sendMessage(Color.translate("&6" + profile.getUsername() + " &ejoined the staff team as " + grant.getRank().getColoredDisplay() + " &eon &d" + format.format(grant.getAddedAt()) + "&e.")); + } else { + self.sendMessage(Color.translate("&6" + profile.getUsername() + " &ewas &apromoted &eto &r" + grant.getRank().getColoredDisplay() + " &eon &d" + format.format(grant.getAddedAt()) + "&e by &6" + addedBy + "&e.")); + } + }); + self.sendMessage(" "); + + final int differenceWeeks = (int) Math.ceil(differenceSec / 60 / 60 / 24 / 7); + + Grant lastGrant = null; + final StringBuilder builder = new StringBuilder(); + for(int i=0; i grants, long time) { + Grant closest = null; + long closetDiff = Long.MAX_VALUE; + + for(Grant grant : grants) { + long diff = Math.abs(time - grant.getAddedAt()); + if(diff < closetDiff) { + closest = grant; + closetDiff = diff; + } + } + + return closest; + } + + @Command(names = {"playtime", "pt"}, permission = "prime.command.playtime") + public static void playtime(CommandSender sender, @Parameter(name = "player") ProfileTarget target) { + target.resolve(profile -> { + if(profile == null) { + target.sendError(sender); + return; + } + + sender.sendMessage(Color.translate(profile.getColoredName() + " &ehas been playing for &d" + profile.getPlaytimeString() + "&e.")); + }); + } + + @Command(names = {"togglestaff", "togglestaffchat"}, permission = "prime.command.togglestaff") + public static void togglestaff(Player player) { + if(player.hasMetadata("togglestaff")) { + player.removeMetadata("togglestaff", plugin); + player.sendMessage(Color.translate("&aYou are now viewing staff related messages")); + return; + } + + player.setMetadata("togglestaff", new FixedMetadataValue(plugin, true)); + player.sendMessage(Color.translate("&cYou are no longer viewing staff related messages")); + } + + @Command(names = {"sudo"}, permission = "op") + public static void sudo(CommandSender sender, @Flag(value = {"f"}) boolean force, @Parameter(name = "player") Player player, @Parameter(name = "command", wildcard = true) String command) { + sender.sendMessage(Color.translate("&6Forcing &f" + player.getName() + " &6to run &f" + command)); + boolean wasOp = player.isOp(); + if(force && !player.isOp()) player.setOp(true); + player.performCommand(command); + if(force && !wasOp) player.setOp(false); + } + + @Command(names = {"masssay"}, permission = "op") + public static void masssay(Player sender, @Parameter(name = "message", wildcard = true) String message) { + Bukkit.getOnlinePlayers().stream().filter(Player::isOp).forEach(op -> op.sendMessage(Color.translate("&c&l" + sender.getName() + " HAS USED MASS SAY"))); + Bukkit.getOnlinePlayers().forEach(player -> { + player.chat(message); + }); + } + + @Command(names = {"link", "sync", "register"}, permission = "prime.command.register") + public static void sync(Player player) { + final Optional profileOptional = profileHandler.getProfile(player.getUniqueId()); + if(!profileOptional.isPresent()) return; + final Profile profile = profileOptional.get(); + + if(profile.getPassword() != null) { + player.sendMessage(Color.translate("&cYou are already registered. If you forgot your password, use /resetpassword")); + return; + } + + player.sendMessage(Color.translate("&aGenerating sync code...")); + int code = profile.getSyncCode(); + if(code == 0) { + code = ThreadLocalRandom.current().nextInt(100000, 999999); + profile.setSyncCode(code); + profileHandler.save(profile); + } + player.sendMessage(Color.translate("&cTo finish registering your account, follow the final steps on https://elevatemc.com/register?code=" + code)); + } + + @Command(names = {"resetpassword", "forgotpassword"}, permission = "prime.command.resetpassword") + public static void resetPassword(Player player) { + final Optional profileOptional = profileHandler.getProfile(player.getUniqueId()); + if(!profileOptional.isPresent()) return; + final Profile profile = profileOptional.get(); + + if(profile.getPassword() == null) { + player.sendMessage(Color.translate("&cYou have not registered an account on the website. To do so, use /register.")); + return; + } + + profile.setSyncCode(0); + profile.setPassword(null); + player.sendMessage(Color.translate("&aSuccessfully reset your password. To create your new password use /register.")); + profileHandler.save(profile); + } + + @Command(names = {"chatcolor"}, permission = "prime.command.chatcolor") + public static void chatcolor(Player player) { + new ChatColorMenu().openMenu(player); + } + + @Command(names = {"nick", "nickname", "disguise"}, permission = "prime.command.nick") + public static void nick(Player player, @Parameter(name = "name", defaultValue = "off") String name) { + final Optional profileOptional = profileHandler.getProfile(player.getUniqueId()); + if(!profileOptional.isPresent()) return; + final Profile profile = profileOptional.get(); + + if(name.equalsIgnoreCase("off")) { + profile.setNickname(""); + player.sendMessage(Color.translate("&aSuccessfully reset your nickname.")); + } else { + profile.setNickname(name); + player.sendMessage(Color.translate("&aSuccessfully set your nickname to " + profile.getNickname() + ".")); + } + + profileHandler.sendSync(profile); + } + + @Command(names = {"realname", "whois"}, permission = "prime.command.realname") + public static void realname(CommandSender sender, @Parameter(name = "nickname") String nickname) { + final Profile profile = profileHandler.getProfileFromNickname(nickname); + if(profile == null) { + sender.sendMessage(Color.translate("&cThere are no players online with that nickname.")); + return; + } + + sender.sendMessage(Color.translate(profile.getColoredName() + "'s &areal name is &e" + profile.getUsername())); + } + + @Command(names = {"say", "bc", "broadcast"}, permission = "prime.command.say") + public static void say(CommandSender sender, @Parameter(name = "message", wildcard = true) String message) { + String name = ""; + if(sender instanceof Player) { + final Player player = (Player) sender; + final Optional profileOptional = profileHandler.getProfile(player.getUniqueId()); + if(!profileOptional.isPresent()) return; + final Profile profile = profileOptional.get(); + + name = profile.getColoredName(); + }else { + name = Color.translate("&4&lServer"); + } + + Bukkit.broadcastMessage(Color.translate("&d[" + name + "&d] ") + message); + } + +// @Command(names = {"setpin"}, permission = "prime.command.setpin") +// public static void setpin(CommandSender sender, @Parameter(name = "pin") int pin, @Parameter(name = "player", defaultValue = "self") ProfileTarget profileTarget) { +// profileTarget.resolve(profile -> { +// if(profile == null) { +// profileTarget.sendError(sender); +// return; +// } +// +// if(sender instanceof ConsoleCommandSender) { +// // setting pin of other player +// profile.setPin(pin); +// if(profile.getPlayer() != null) profile.getPlayer().sendMessage(Color.translate("&aYour pin has been updated.")); +// sender.sendMessage(Color.translate("&aSuccessfully updated pin.")); +// profileHandler.sendSync(profile); +// return; +// } +// +// if(!((Player) sender).getUniqueId().equals(profile.getUuid())) { +// sender.sendMessage(Color.translate("&cYou cannot set another player's pin.")); +// return; +// } +// +// if(profile.getPin() != 0) { +// sender.sendMessage(Color.translate("&cYour pin has already been set. If you forgot your pin, please contact the management team.")); +// return; +// } +// +// profile.setPin(pin); +// sender.sendMessage(Color.translate("&aSuccessfully set your pin.")); +// profileHandler.save(profile); +// }); +// } +// +// @Command(names = {"resetpin"}, permission = "op") +// public static void resetpin(CommandSender sender, @Parameter(name = "player") ProfileTarget profileTarget) { +// profileTarget.resolve(profile -> { +// if(profile == null) { +// profileTarget.sendError(sender); +// return; +// } +// +// if(!(sender instanceof ConsoleCommandSender)) { +// sender.sendMessage(Color.translate("&cThis command must be executed through console.")); +// return; +// } +// +// profile.setPin(0); +// sender.sendMessage(Color.translate("&aSuccessfully reset pin.")); +// profileHandler.save(profile); +// }); +// } +// +// @Command(names = {"forceauth"}, permission = "op") +// public static void forceauth(CommandSender sender, @Parameter(name = "player") Player player) { +// if(!(sender instanceof ConsoleCommandSender)) { +// sender.sendMessage(Color.translate("&cThis command must be executed through console.")); +// return; +// } +// +// player.setMetadata("authed", new FixedMetadataValue(Prime.getInstance(), true)); +// sender.sendMessage(Color.translate("&aForcefully authenticated the provided player.")); +// player.sendMessage(Color.translate("&aYou have been forcefully authenticated.")); +// } +// +// @Command(names = {"auth", "2fa", "pin"}, permission = "prime.staff") +// public static void pin(Player player, @Parameter(name = "pin") int pin) { +// final Optional profileOptional = profileHandler.getProfile(player.getUniqueId()); +// if(!profileOptional.isPresent()) { +// player.sendMessage(Color.translate("&cYour profile is not loaded.")); +// return; +// } +// +// if(player.hasMetadata("authed")) { +// player.sendMessage(Color.translate("&cYou are already authenticated.")); +// return; +// } +// +// final Profile profile = profileOptional.get(); +// +// if(profile.getPin() == 0) { +// player.sendMessage(Color.translate("&cYou have not yet set a pin. Use /setpin to set your pin.")); +// return; +// } +// +// if(pin != profile.getPin()) { +// player.sendMessage(Color.translate("&cInvalid pin. Try again.")); +// if(player.hasMetadata("pin_failure")) { +// // player already failed once before +// Bukkit.dispatchCommand(Bukkit.getConsoleSender(), String.format("ban %s Potentially Compromised Account (Failed authentication twice in a row)", player.getName())); +// return; +// } +// player.setMetadata("pin_failure", new FixedMetadataValue(Prime.getInstance(), true)); +// return; +// } +// +// player.setMetadata("authed", new FixedMetadataValue(Prime.getInstance(), true)); +// player.removeMetadata("pin_failure", Prime.getInstance()); +// profile.setLastUsedPin(System.currentTimeMillis()); +// profileHandler.sendSync(profile); +// +// player.sendMessage(Color.translate("&aYou have successfully authenticated.")); +// } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/grant/Grant.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/grant/Grant.java new file mode 100644 index 0000000..63029f4 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/grant/Grant.java @@ -0,0 +1,116 @@ +package dev.apposed.prime.spigot.module.profile.grant; + +import com.google.gson.annotations.SerializedName; +import dev.apposed.prime.spigot.module.rank.Rank; +import dev.apposed.prime.spigot.util.ItemBuilder; +import dev.apposed.prime.spigot.util.time.DurationUtils; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +@Data @AllArgsConstructor +public class Grant { + + @SerializedName("_id") + private UUID id; + + private final Rank rank; + + private final UUID addedBy; + private final long addedAt; + private final String addedReason; + private final long duration; + private final List scopes; + + private UUID removedBy; + private long removedAt; + private String removedReason; + + private boolean removed; + private UUID player; + + public Grant(Rank rank, UUID addedBy, long addedAt, String addedReason, long duration, List scopes) { + this.id = UUID.randomUUID(); + this.rank = rank; + + this.addedBy = addedBy; + this.addedAt = addedAt; + this.addedReason = addedReason; + this.duration = duration; + this.scopes = scopes; + } + + public Grant(UUID id, Rank rank, UUID addedBy, long addedAt, String addedReason, long duration, List scopes, UUID removedBy, long removedAt, String removedReason, boolean removed) { + this.id = id; + this.rank = rank; + this.addedBy = addedBy; + this.addedAt = addedAt; + this.addedReason = addedReason; + this.duration = duration; + this.scopes = scopes; + this.removedBy = removedBy; + this.removedAt = removedAt; + this.removedReason = removedReason; + this.removed = removed; + } + + // not using this idk why. ill change the old code to use method later on + public void removeGrant(UUID removedBy, long removedAt, String removedReason) { + this.removedBy = removedBy; + this.removedAt = removedAt; + this.removedReason = removedReason; + + this.removed = true; + } + + public boolean isActive() { + if(!removed) { + if(this.duration == Long.MAX_VALUE) return true; + return System.currentTimeMillis() <= (this.addedAt + this.duration); + } + + return false; + } + + public long getRemaining() { + if(removed) return 0L; + if(duration == Long.MAX_VALUE) return Long.MAX_VALUE; + if(!isActive()) return 0L; + + return (addedAt + duration) - System.currentTimeMillis(); + } + + public String formatDuration() { + if(this.duration == Long.MAX_VALUE) return "Permanent"; + return DurationUtils.toString(this.addedAt + this.duration); + } + + public ItemStack getItemStack() { + ItemBuilder builder = new ItemBuilder(Material.WOOL); + if(removed && !isActive()) { + builder.dur(14); + } else { + builder.dur(13); + } + + return builder.build(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Grant grant = (Grant) o; + return addedAt == grant.addedAt && duration == grant.duration && removedAt == grant.removedAt && removed == grant.removed && Objects.equals(id, grant.id) && Objects.equals(rank, grant.rank) && Objects.equals(addedBy, grant.addedBy) && Objects.equals(addedReason, grant.addedReason) && Objects.equals(scopes, grant.scopes) && Objects.equals(removedBy, grant.removedBy) && Objects.equals(removedReason, grant.removedReason) && Objects.equals(player, grant.player); + } + + @Override + public int hashCode() { + return Objects.hash(id, rank, addedBy, addedAt, addedReason, duration, scopes, removedBy, removedAt, removedReason, removed, player); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/grant/command/GrantCommands.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/grant/command/GrantCommands.java new file mode 100644 index 0000000..4e9d05c --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/grant/command/GrantCommands.java @@ -0,0 +1,223 @@ +package dev.apposed.prime.spigot.module.profile.grant.command; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import com.elevatemc.elib.util.UUIDUtils; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.PrimeConstants; +import dev.apposed.prime.spigot.module.ModuleHandler; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import dev.apposed.prime.spigot.module.profile.grant.Grant; +import dev.apposed.prime.spigot.module.profile.grant.menu.GrantMenu; +import dev.apposed.prime.spigot.module.profile.grant.menu.GrantsMenu; +import dev.apposed.prime.spigot.module.profile.target.ProfileTarget; +import dev.apposed.prime.spigot.module.rank.Rank; +import dev.apposed.prime.spigot.module.rank.RankHandler; +import dev.apposed.prime.spigot.module.webhook.DiscordWebhook; +import dev.apposed.prime.spigot.util.Color; +import dev.apposed.prime.spigot.util.time.DurationUtils; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +public class GrantCommands { + + private static final Prime plugin = Prime.getInstance(); + private static final ModuleHandler moduleHandler = plugin.getModuleHandler(); + + private static final ProfileHandler profileHandler = moduleHandler.getModule(ProfileHandler.class); + private static final RankHandler rankHandler = moduleHandler.getModule(RankHandler.class); + + private static final String GRANT_WEBHOOK = "https://discord.com/api/webhooks/993224084060115004/Rpd7FcojkkgLlbWJQg938NkwlarCA0Kuw_9uNfxyVEMMwBzlbaxkNnA7gdPkHKcHm6P3"; + + @Command(names = {"grant"}, permission = "prime.command.grant", description = "Grant a player a rank") + public static void executeGrant(Player player, @Parameter(name = "player") ProfileTarget profileTarget) { + profileTarget.resolve(profile -> { + if (profile == null) { + profileTarget.sendError(player); + return; + } + + Prime.getInstance().getServer().getScheduler().runTask(Prime.getInstance(), () -> { + new GrantMenu(profile).openMenu(player); + }); + }); + } + + @Command(names = {"ogrant"}, async = true, permission = "prime.command.grant", description = "Grant a player a rank") + public static void executeOGrant(CommandSender sender, @Parameter(name = "player") ProfileTarget profileTarget, @Parameter(name = "rank") Rank rank, @Parameter(name = "duration") String durationString, @Parameter(name = "scopes") String scopesString, @Parameter(name = "reason", wildcard = true) String reason) { + profileTarget.resolve(profile -> { + if (profile == null) { + profileTarget.sendError(sender); + return; + } + + if(!(sender.hasPermission("prime.grant.create." + rank.getName()))) { + sender.sendMessage(Color.translate("&cYou do not have permission to create a grant for this rank.")); + return; + } + + long duration = DurationUtils.fromString(durationString); + + UUID addedBy; + + if(sender instanceof Player) { + Player player = (Player) sender; + addedBy = player.getUniqueId(); + } else { + addedBy = PrimeConstants.CONSOLE_UUID; + } + + List scopes = Arrays.asList(scopesString.split(",").clone()); + + Grant grant = new Grant( + rank, + addedBy, + System.currentTimeMillis(), + reason, + duration, + scopes + ); + profile.getGrants().add(grant); + + profileHandler.save(profile); + + sender.sendMessage(Color.translate("&aYou have granted &r" + profile.getColoredName() + " &athe &r" + rank.getColoredDisplay() + " &arank for &f" + (duration == Long.MAX_VALUE ? "forever" : DurationUtils.toString(System.currentTimeMillis() + duration)) + "&a.")); + + Player targetPlayer = Bukkit.getPlayer(profile.getUuid()); + if(targetPlayer != null && targetPlayer.isOnline()) { + targetPlayer.sendMessage(Color.translate("&aYou have been granted &r" + rank.getColoredDisplay()+ " &afor &f" + (duration == Long.MAX_VALUE ? "forever" : DurationUtils.toString(System.currentTimeMillis() + duration)) + "&a.")); + } + + final DiscordWebhook webhook = new DiscordWebhook(GRANT_WEBHOOK); + webhook.addEmbed( + new DiscordWebhook.EmbedObject() + .setTitle(profile.getUsername() + " has been granted " + rank.getName()) + .addField("Added By", (grant.getAddedBy().equals(PrimeConstants.CONSOLE_UUID) ? "Console" : UUIDUtils.name(grant.getAddedBy())), false) + .addField("Reason", grant.getAddedReason(), false) + .addField("Duration", grant.formatDuration(), false) + .addField("Scopes", String.join(", ", grant.getScopes()), false) + .setColor(java.awt.Color.cyan) + .setFooter("Prime Grants", null) + ); + new Thread(() -> { + try { + webhook.execute(); + }catch(Exception ex) { + ex.printStackTrace(); + } + }, "grant-log-" + addedBy).start(); + }); + } + + @Command(names = {"ipbypass add", "sibling add"}, description = "Allow a player to bypass alt ip checks", permission = "prime.command.ipbypass") + public static void sibling(CommandSender sender, @Parameter(name = "player") ProfileTarget profileTarget, @Parameter(name = "reason", wildcard = true, defaultValue = "No reason provided") String reason) { + profileTarget.resolve(profile -> { + if (profile == null) { + profileTarget.sendError(sender); + return; + } + + UUID addedBy; + if(sender instanceof Player) { + Player player = (Player) sender; + addedBy = player.getUniqueId(); + } else { + addedBy = PrimeConstants.CONSOLE_UUID; + } + + final Rank bypass = rankHandler.getRank("IP Bypass").orElse(null); + if(bypass == null) { + sender.sendMessage(Color.translate("&cCould not fetch the bypass rank.")); + return; + } + + Grant grant = new Grant( + bypass, + addedBy, + System.currentTimeMillis(), + reason, + Long.MAX_VALUE, + Collections.singletonList("Global") + ); + profile.getGrants().add(grant); + + profileHandler.save(profile); + sender.sendMessage(Color.translate("&aYou have granted &r" + profile.getColoredName() + " &aip bypass.")); + }); + } + + @Command(names = {"grants"}, async = true, description = "Check a player's grants", permission = "prime.command.grants") + public static void executeGrants(Player player, @Parameter(name = "player") ProfileTarget profileTarget) { + profileTarget.resolve(profile -> { + if (profile == null) { + profileTarget.sendError(player); + return; + } + + Prime.getInstance().getServer().getScheduler().runTask(Prime.getInstance(), () -> { + new GrantsMenu(profile, new ArrayList<>(profile.getGrants())).openMenu(player); + }); + }); + } + + @Command(names = {"granthistory"}, async = true, description = "Check a player's grant history", permission = "prime.command.granthistory") + public static void executeGrantHistory(Player player, @Parameter(name = "player") ProfileTarget profileTarget) { + profileTarget.resolve(profile -> { + if (profile == null) { + profileTarget.sendError(player); + return; + } + + player.sendMessage(Color.translate("&aSearching the database for all grants made by " + profile.getUuid() + ". This may take a few moments.")); + final List grants = profileHandler.getGrantsByStaff(profile.getUuid()); + if(grants == null) { + player.sendMessage(Color.translate("&cFailed to fetch grants.")); + return; + } + new GrantsMenu(profile, grants) + .showWhoReceived() + .openMenu(player); + }); + } + + @Command(names = {"granthistoryundo", "grantrollback"}, async = true, description = "Rollback a staff member's grants", permission = "prime.command.granthistoryundo") + public static void executeGrantHistoryUndo(CommandSender sender, @Parameter(name = "player") ProfileTarget profileTarget, @Parameter(name = "duration") String durationString, @Parameter(name = "rollback reason") String reason) { + profileTarget.resolve(profile -> { + if (profile == null) { + profileTarget.sendError(sender); + return; + } + + long duration = DurationUtils.fromString(durationString); + + AtomicReference removedBy = new AtomicReference<>(PrimeConstants.CONSOLE_UUID); + if(sender instanceof Player) { + Player player = (Player) sender; + removedBy.set(player.getUniqueId()); + } + + final AtomicInteger i = new AtomicInteger(0); + profileHandler.getGrantsByStaff(profile.getUuid()) + .stream() + .filter(grant -> !grant.isRemoved()) + .filter(Grant::isActive) + .filter(grant -> (System.currentTimeMillis() - grant.getAddedAt()) <= duration) + .forEach(grant -> { + grant.removeGrant( + removedBy.get(), + System.currentTimeMillis(), + reason); + + i.getAndIncrement(); + }); + + sender.sendMessage(Color.translate("&aSuccessfully rolled back " + i.get() + " grants.")); + }); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/grant/menu/GrantMenu.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/grant/menu/GrantMenu.java new file mode 100644 index 0000000..51b41c2 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/grant/menu/GrantMenu.java @@ -0,0 +1,149 @@ +package dev.apposed.prime.spigot.module.profile.grant.menu; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import com.google.common.collect.ImmutableList; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import dev.apposed.prime.spigot.module.rank.Rank; +import dev.apposed.prime.spigot.module.rank.RankHandler; +import dev.apposed.prime.spigot.module.rank.meta.RankMeta; +import dev.apposed.prime.spigot.util.Color; +import dev.apposed.prime.spigot.util.time.DurationUtils; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.conversations.*; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +public class GrantMenu extends Menu { + + private final Prime plugin = Prime.getInstance(); + private final RankHandler rankHandler = plugin.getModuleHandler().getModule(RankHandler.class); + private final ProfileHandler profileHandler = plugin.getModuleHandler().getModule(ProfileHandler.class); + + private final Profile profile; + + public GrantMenu(Profile profile) { + this.profile = profile; + } + + @Override + public String getTitle(Player player) { + return "Choose a Rank"; + } + + @Override + public Map getButtons(Player player) { + final Map buttons = new HashMap<>(); + + final AtomicInteger slot = new AtomicInteger(0); + this.rankHandler.getCache() + .stream() + .sorted(Comparator.comparingInt(Rank::getWeight).reversed()) + .filter(rank -> !rank.hasMeta(RankMeta.DEFAULT, true)) + .filter(rank -> player.hasPermission("prime.grant.create." + rank.getName())) + .forEach(rank -> buttons.put(slot.getAndIncrement(), new RankButton(rank))); + + return buttons; + } + + private final class RankButton extends Button { + + private final Rank rank; + + public RankButton(Rank rank) { + this.rank = rank; + } + + @Override + public String getName(Player player) { + return Color.translate(rank.getColoredDisplay()); + } + + @Override + public List getDescription(Player player) { + return Color.translate(ImmutableList.of( + Color.SPACER_LONG, + "&7Click to grant &r" + profile.getColoredName() + " &7the &r" + rank.getColoredDisplay() + "&7 rank.", + Color.SPACER_LONG + )); + } + + @Override + public Material getMaterial(Player player) { + return rank.getWool().getType(); + } + + @Override + public byte getDamageValue(Player player) { + return (byte) rank.getWool().getDurability(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + player.closeInventory(); + ConversationFactory factory = new ConversationFactory(plugin).withModality(true).withPrefix(new NullConversationPrefix()).withFirstPrompt(new StringPrompt() { + + @Override + public String getPromptText(ConversationContext conversationContext) { + return Color.translate("&ePlease type a duration for this grant, or type &c\"cancel\" &eto cancel."); + } + + @Override + public Prompt acceptInput(ConversationContext cc, String duration) { + if(duration.equalsIgnoreCase("cancel")) { + cc.getForWhom().sendRawMessage(ChatColor.RED + "Granting cancelled."); + return END_OF_CONVERSATION; + } + + (new BukkitRunnable() { + @Override + public void run() { + GrantMenu.this.executeReason(player, rank, DurationUtils.fromString(duration)); + } + }).runTask(plugin); + return END_OF_CONVERSATION; + } + }).withLocalEcho(false).withEscapeSequence("/cancel").withTimeout(60).thatExcludesNonPlayersWithMessage("Player's only."); + + Conversation conversation = factory.buildConversation(player); + player.beginConversation(conversation); + } + } + + public void executeReason(Player player, Rank rank, long duration) { + ConversationFactory factory = new ConversationFactory(plugin).withModality(true).withPrefix(new NullConversationPrefix()).withFirstPrompt(new StringPrompt() { + + @Override + public String getPromptText(ConversationContext conversationContext) { + return Color.translate("&ePlease type a reason for this grant to be created, or type &c\"cancel\" &eto cancel."); + } + + @Override + public Prompt acceptInput(ConversationContext cc, String reason) { + if(reason.equalsIgnoreCase("cancel")) { + cc.getForWhom().sendRawMessage(ChatColor.RED + "Granting cancelled."); + return END_OF_CONVERSATION; + } + + (new BukkitRunnable(){ + @Override + public void run() { + new ScopesMenu(profile, rank, duration, reason).openMenu(player); + } + }).runTask(plugin); + + return Prompt.END_OF_CONVERSATION; + } + }).withLocalEcho(false).withEscapeSequence("/cancel").withTimeout(60).thatExcludesNonPlayersWithMessage("Player's only."); + + Conversation conversation = factory.buildConversation(player); + player.beginConversation(conversation); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/grant/menu/GrantsMenu.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/grant/menu/GrantsMenu.java new file mode 100644 index 0000000..4d22232 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/grant/menu/GrantsMenu.java @@ -0,0 +1,156 @@ +package dev.apposed.prime.spigot.module.profile.grant.menu; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.pagination.PaginatedMenu; +import com.elevatemc.elib.util.UUIDUtils; +import com.google.common.collect.ImmutableList; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.PrimeConstants; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import dev.apposed.prime.spigot.module.profile.grant.Grant; +import dev.apposed.prime.spigot.module.rank.meta.RankMeta; +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.conversations.*; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.plugin.java.JavaPlugin; + +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +public class GrantsMenu extends PaginatedMenu { + + private final Prime plugin = JavaPlugin.getPlugin(Prime.class); + private final ProfileHandler profileHandler = plugin.getModuleHandler().getModule(ProfileHandler.class); + private final SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy hh:mm"); + + private final Profile profile; + private final List grants; + private boolean showReceived = false; + + public GrantsMenu(Profile profile, List grants) { + this.profile = profile; + this.grants = grants; + format.setTimeZone(TimeZone.getTimeZone("America/New_York")); + } + + @Override + public String getPrePaginatedTitle(Player player) { + return profile.getUsername() + "'s Grants"; + } + + @Override + public Map getAllPagesButtons(Player player) { + final Map buttons = new HashMap<>(); + + final AtomicInteger slot = new AtomicInteger(0); + this.grants.stream() + .sorted(Comparator.comparingLong(Grant::getAddedAt).reversed()) + .filter(grant -> !grant.getRank().hasMeta(RankMeta.DEFAULT, true)) + .forEach(grant -> { + buttons.put(slot.getAndIncrement(), new Button() { + @Override + public String getName(Player player) { + return ChatColor.GOLD + format.format(grant.getAddedAt()); + } + + @Override + public List getDescription(Player player) { + final List lore = new ArrayList<>(); + + lore.add(Color.SPACER_SHORT); + + if(showReceived) { + final Optional profileOptional = profileHandler.getProfile(grant.getPlayer()); + if(!profileOptional.isPresent()) { + lore.add("&ePlayer: &cUnknown"); + } else { + final Profile profile = profileOptional.get(); + lore.add("&ePlayer: &c" + profile.getColoredName()); + } + } + + lore.addAll(Arrays.asList( + "&eBy: &c" + (grant.getAddedBy().equals(PrimeConstants.CONSOLE_UUID) ? + "&4&lConsole" : UUIDUtils.name(grant.getAddedBy())), + "&eRank: &r" + grant.getRank().getColoredDisplay(), + "&eScopes: &c" + String.join(", ", grant.getScopes()), + "&eReason: &c" + grant.getAddedReason(), + "&eRemaining: &c" + grant.formatDuration(), + Color.SPACER_SHORT + )); + + if(grant.isRemoved()) { + lore.add("&c&lRemoved"); + lore.add(" "); + lore.add("&eBy: &c" + (grant.getRemovedBy().equals(PrimeConstants.CONSOLE_UUID) ? + "&4&lConsole" : UUIDUtils.name(grant.getRemovedBy()))); + lore.add("&eReason: &c" + grant.getRemovedReason()); + lore.add(" "); + lore.add(ChatColor.GOLD + format.format(grant.getRemovedAt())); + lore.add(Color.SPACER_SHORT); + }else if(player.hasPermission("prime.grants.remove." + grant.getRank().getName())) { + lore.add("&cClick to remove grant."); + lore.add(Color.SPACER_SHORT); + } + + return Color.translate(lore); + } + + @Override + public Material getMaterial(Player player) { + return grant.getItemStack().getType(); + } + + @Override + public byte getDamageValue(Player player) { + return (byte) grant.getItemStack().getDurability(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + player.closeInventory(); + ConversationFactory factory = new ConversationFactory(plugin).withModality(true).withPrefix(new NullConversationPrefix()).withFirstPrompt(new StringPrompt() { + + @Override + public String getPromptText(ConversationContext conversationContext) { + return Color.translate("&ePlease type a reason for this grant to be removed, or type &c\"cancel\" &eto cancel."); + } + + @Override + public Prompt acceptInput(ConversationContext cc, String reason) { + if(reason.equalsIgnoreCase("cancel")) { + cc.getForWhom().sendRawMessage(ChatColor.RED + "Granting cancelled."); + return END_OF_CONVERSATION; + } + grant.setRemoved(true); + grant.setRemovedBy(player.getUniqueId()); + grant.setRemovedAt(System.currentTimeMillis()); + grant.setRemovedReason(reason); + + profileHandler.sendSync(profile); + + cc.getForWhom().sendRawMessage(Color.translate("&aSuccessfully removed grant.")); + + return Prompt.END_OF_CONVERSATION; + } + }).withLocalEcho(false).withEscapeSequence("/cancel").withTimeout(60).thatExcludesNonPlayersWithMessage("Player's only."); + + Conversation conversation = factory.buildConversation(player); + player.beginConversation(conversation); + } + }); + }); + + return buttons; + } + + public GrantsMenu showWhoReceived() { + this.showReceived = true; + return this; + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/grant/menu/ScopesMenu.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/grant/menu/ScopesMenu.java new file mode 100644 index 0000000..f728237 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/grant/menu/ScopesMenu.java @@ -0,0 +1,166 @@ +package dev.apposed.prime.spigot.module.profile.grant.menu; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.pagination.PaginatedMenu; +import com.elevatemc.elib.util.UUIDUtils; +import com.google.common.collect.ImmutableList; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.PrimeConstants; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import dev.apposed.prime.spigot.module.profile.grant.Grant; +import dev.apposed.prime.spigot.module.rank.Rank; +import dev.apposed.prime.spigot.module.server.ServerHandler; +import dev.apposed.prime.spigot.module.webhook.DiscordWebhook; +import dev.apposed.prime.spigot.util.Color; +import dev.apposed.prime.spigot.util.time.DurationUtils; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +public class ScopesMenu extends PaginatedMenu { + + private final Prime plugin = JavaPlugin.getPlugin(Prime.class); + private final ProfileHandler profileHandler = plugin.getModuleHandler().getModule(ProfileHandler.class); + private final ServerHandler serverHandler = plugin.getModuleHandler().getModule(ServerHandler.class); + + private final Profile profile; + private final Rank rank; + private final long duration; + private final String reason; + + private final List selectedScopes; + private final String GRANT_WEBHOOK = "https://discord.com/api/webhooks/993224084060115004/Rpd7FcojkkgLlbWJQg938NkwlarCA0Kuw_9uNfxyVEMMwBzlbaxkNnA7gdPkHKcHm6P3"; + + public ScopesMenu(Profile profile, Rank rank, long duration, String reason) { + this.profile = profile; + this.rank = rank; + this.duration = duration; + this.reason = reason; + + this.selectedScopes = new ArrayList<>(); + this.selectedScopes.add("Global"); + setUpdateAfterClick(true); + } + + @Override + public String getPrePaginatedTitle(Player player) { + return "Select Scopes"; + } + + @Override + public Map getGlobalButtons(Player player) { + final Map buttons = new HashMap<>(); + + buttons.put(4, new Button() { + @Override + public String getName(Player player) { + return Color.translate("&6Grant &r" + profile.getColoredName() + "&r " + rank.getColoredDisplay()); + } + + @Override + public List getDescription(Player player) { + return Color.translate(ImmutableList.of( + Color.SPACER_SHORT, + "&eScopes: &c" + String.join(", ", selectedScopes), + "&eReason: &c" + reason, + "&eDuration: &c" + (duration == Long.MAX_VALUE ? "Permanent" : DurationUtils.toString(System.currentTimeMillis() + duration)), + Color.SPACER_SHORT + )); + } + + @Override + public Material getMaterial(Player player) { + return Material.NETHER_STAR; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + player.closeInventory(); + UUID addedBy = player.getUniqueId(); + Grant grant = new Grant( + rank, + addedBy, + System.currentTimeMillis(), + reason, + duration, + selectedScopes + ); + + profile.getGrants().add(grant); + + profileHandler.save(profile); + + player.sendMessage(Color.translate("&aYou have granted &r" + profile.getColoredName() + " &athe &r" + rank.getColoredDisplay() + " &arank for &f" + (duration == Long.MAX_VALUE ? "forever" : DurationUtils.toString(System.currentTimeMillis() + duration)) + "&a.")); + + Player targetPlayer = Bukkit.getPlayer(profile.getUuid()); + if(targetPlayer != null && targetPlayer.isOnline()) { + targetPlayer.sendMessage(Color.translate("&aYou have been granted &r" + rank.getColoredDisplay()+ " &afor &f" + (duration == Long.MAX_VALUE ? "forever" : DurationUtils.toString(System.currentTimeMillis() + duration)) + "&a.")); + } + + final DiscordWebhook webhook = new DiscordWebhook(GRANT_WEBHOOK); + webhook.addEmbed( + new DiscordWebhook.EmbedObject() + .setTitle(profile.getUsername() + " has been granted " + rank.getName()) + .addField("Added By", (grant.getAddedBy().equals(PrimeConstants.CONSOLE_UUID) ? "Console" : UUIDUtils.name(grant.getAddedBy())), false) + .addField("Reason", grant.getAddedReason(), false) + .addField("Duration", grant.formatDuration(), false) + .addField("Scopes", String.join(", ", grant.getScopes()), false) + .setColor(java.awt.Color.cyan) + .setFooter("Prime Grants", null) + ); + new Thread(() -> { + try { + webhook.execute(); + }catch(Exception ex) { + ex.printStackTrace(); + } + }, "grant-log-" + addedBy).start(); + } + }); + + return buttons; + } + + @Override + public Map getAllPagesButtons(Player player) { + final Map buttons = new HashMap<>(); + + final AtomicInteger slot = new AtomicInteger(0); + this.serverHandler.getServerGroups().forEach(group -> { + buttons.put(slot.getAndIncrement(), new Button() { + @Override + public String getName(Player player) { + return Color.translate("&6" + group.getId()); + } + + @Override + public List getDescription(Player player) { + return Collections.emptyList(); + } + + @Override + public Material getMaterial(Player player) { + return Material.BEDROCK; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + if(selectedScopes.contains(group.getId())) { + selectedScopes.remove(group.getId()); + return; + } + + selectedScopes.add(group.getId()); + } + }); + }); + + return buttons; + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/grant/task/GrantExpiryTask.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/grant/task/GrantExpiryTask.java new file mode 100644 index 0000000..d58e7fa --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/grant/task/GrantExpiryTask.java @@ -0,0 +1,21 @@ +package dev.apposed.prime.spigot.module.profile.grant.task; + +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import org.bukkit.scheduler.BukkitRunnable; + +public class GrantExpiryTask extends BukkitRunnable { + + private final ProfileHandler profileHandler; + + public GrantExpiryTask(ProfileHandler profileHandler) { + this.profileHandler = profileHandler; + } + + @Override + public void run() { + this.profileHandler.getProfiles().forEach(profile -> { + boolean updated = profile.checkGrants(); + if (updated) this.profileHandler.setupPlayer(profile); + }); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/identity/ProfileIdentity.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/identity/ProfileIdentity.java new file mode 100644 index 0000000..71e55c9 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/identity/ProfileIdentity.java @@ -0,0 +1,14 @@ +package dev.apposed.prime.spigot.module.profile.identity; + +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data @AllArgsConstructor +public class ProfileIdentity { + + @SerializedName("_id") + private String ip; + + +} \ No newline at end of file diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/identity/command/IdentityCommands.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/identity/command/IdentityCommands.java new file mode 100644 index 0000000..45eb073 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/identity/command/IdentityCommands.java @@ -0,0 +1,43 @@ +package dev.apposed.prime.spigot.module.profile.identity.command; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.ModuleHandler; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import dev.apposed.prime.spigot.module.profile.target.ProfileTarget; +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.command.CommandSender; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class IdentityCommands { + + private static final Prime plugin = Prime.getInstance(); + private static final ModuleHandler moduleHandler = plugin.getModuleHandler(); + + private static final ProfileHandler profileHandler = moduleHandler.getModule(ProfileHandler.class); + + @Command(names = {"alts"}, description = "View a player's alts", async = true, permission = "prime.command.alts") + public static void executeAlts(CommandSender sender, @Parameter(name = "player")ProfileTarget profileTarget) { + profileTarget.resolve(profile -> { + if (profile == null) { + profileTarget.sendError(sender); + return; + } + + final List allProfiles = new ArrayList<>(); + profile.getIdentities().forEach(identity -> allProfiles.addAll(profileHandler.fetchByIdentity(identity.getIp()))); + final List associatedProfiles = allProfiles.stream().distinct().collect(Collectors.toList()); + sender.sendMessage(Color.translate(profile.getColoredName() + " &ahas &e" + associatedProfiles.size() + " &aaccounts on &e" + profile.getIdentities().size() + " &aidentities.")); + if(sender.hasPermission("prime.alts.detailed")) sender.sendMessage(Color.translate("&eLast joined with &7" + profile.getLastHashedIp())); + sender.sendMessage(associatedProfiles + .stream() + .map(prof -> Color.translate(prof.getColoredName() + (sender.hasPermission("prime.alts.detailed") ? " &7❘ &c(" + prof.getLastHashedIp() + ") &f" + String.join(", ", prof.getAllHashedIps()) : ""))) + .collect(Collectors.joining("\n"))); + }); + } +} \ No newline at end of file diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/listener/ProfileListener.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/listener/ProfileListener.java new file mode 100644 index 0000000..88ff0e4 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/listener/ProfileListener.java @@ -0,0 +1,318 @@ +package dev.apposed.prime.spigot.module.profile.listener; + +import com.elevatemc.elib.util.TaskUtil; +import com.elevatemc.elib.util.TimeUtils; +import dev.apposed.prime.packet.StaffMessagePacket; +import dev.apposed.prime.packet.type.StaffMessageType; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.database.redis.JedisModule; +import dev.apposed.prime.spigot.module.listener.ListenerModule; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import dev.apposed.prime.spigot.module.profile.grant.Grant; +import dev.apposed.prime.spigot.module.profile.identity.ProfileIdentity; +import dev.apposed.prime.spigot.module.profile.punishment.Punishment; +import dev.apposed.prime.spigot.module.profile.punishment.type.PunishmentType; +import dev.apposed.prime.spigot.module.rank.meta.RankMeta; +import dev.apposed.prime.spigot.module.server.ServerHandler; +import dev.apposed.prime.spigot.util.Color; +import dev.apposed.prime.spigot.util.time.DurationUtils; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Statistic; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.*; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.permissions.ServerOperator; + +import java.util.Locale; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +public class ProfileListener extends ListenerModule { + + private final ProfileHandler profileHandler; + private final ServerHandler serverHandler; + private final JedisModule jedisModule; + + public ProfileListener() { + this.profileHandler = getModuleHandler().getModule(ProfileHandler.class); + this.serverHandler = getModuleHandler().getModule(ServerHandler.class); + this.jedisModule = getModuleHandler().getModule(JedisModule.class); + } + + @EventHandler + public void onPlayerPreJoin(AsyncPlayerPreLoginEvent event) { + this.profileHandler.load(event.getUniqueId()) + .thenAccept(profile -> { + profile.setUsername(event.getName()); + profile.setOnline(true); + profile.setLastServer(this.serverHandler.getCurrentName()); + profile.setLastOnline(System.currentTimeMillis()); + + final String rawAddress = event.getAddress().getHostAddress(); + final String address = profile.getHashedIp(rawAddress); + + if(profile.hasIdentity(address)) { + profile.setLastIdentity(profile.getIdentity(address)); + } else { + final ProfileIdentity newIdentity = new ProfileIdentity(address); + profile.setLastIdentity(newIdentity); + profile.getIdentities().add(newIdentity); + } + + profileHandler.sendSync(profile); + }) + .exceptionally(throwable -> { + event.setKickMessage(Color.translate("&cFailed to load profile. Try again later.")); + event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); + return null; + }); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerJoin(PlayerJoinEvent event) { + final Optional profileOptional = this.profileHandler.getProfile(event.getPlayer().getUniqueId()); + final Player player = event.getPlayer(); + if(!profileOptional.isPresent()) { + player.kickPlayer(Color.translate("&cYour profile was not loaded properly. Try again later.z")); + return; + } + + final Profile profile = profileOptional.get(); + profileHandler.setupPlayer(profile); + + // we want this to enforce op's to have to authenticate + if(player.hasPermission("prime.staff")) { + // if the time difference between now and the last time the player authed is less than one day, give them auth + // if the player does not have this address in identities, ignore previous if +// player.setMetadata("authed", new FixedMetadataValue(Prime.getInstance(), true)); +// if((System.currentTimeMillis() - profile.getLastUsedPin()) <= TimeUnit.DAYS.toMillis(1) && profile.hasIdentity(address)) { +// player.setMetadata("authed", new FixedMetadataValue(Prime.getInstance(), true)); +// } else { +// if(profile.getPin() != 0) { +// player.removeMetadata("authed", Prime.getInstance()); +// player.sendMessage(Color.translate("&cYou must authenticate before you can execute any staff commands. Use /auth to auth.")); +// } else { +// player.removeMetadata("authed", Prime.getInstance()); +// player.sendMessage(Color.translate("&cYou do not have a pin set. Use /setpin to set your pin.")); +// } +// } + } + + if(profile.isStaff()) { +// player.setMetadata("invisible", new FixedMetadataValue(Prime.getInstance(), true)); + if(this.profileHandler.punishmentsWithoutProof(profile).size() > 0) { + player.sendMessage(Color.translate("&c&l &aYou have unresolved punishments. Use /unresolvedpunishments to resolve them.")); + } + } + + // seeing if this works to have their perms loaded? idk im giving up at this point + + TaskUtil.runTaskLater(() -> { + if(player.hasPermission("prime.join.broadcast") && !player.hasPermission("prime.join.broadcast.cancel")) { + // VIP [Owner]Apposed has joined your server + // special - prefix - name + Bukkit.broadcastMessage(String.format(Color.translate("%s%s%s &7has joined your server"), + Color.translate(profile.getSpecialPrefix()), + Color.translate(profile.getHighestActiveNonHiddenGrant().getRank().getPrefix()), + Color.translate(profile.getColoredName()))); + } + }, 2); + + + Bukkit.getScheduler().scheduleSyncDelayedTask(Prime.getInstance(), () -> { + if(profile.getFirstJoin() == 0) profile.setFirstJoin(profile.getPlayer().getFirstPlayed()); + profile.setUsername(player.getName()); + profile.setOnline(true); + profile.setLastOnline(System.currentTimeMillis()); + profile.setLastServer(serverHandler.getCurrentName()); + profileHandler.save(profile); + }, 20L * 5); // do this 5 seconds later to stop confusion! + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + event.setQuitMessage(null); + final Optional profileOptional = this.profileHandler.getProfile(event.getPlayer().getUniqueId()); + if(!profileOptional.isPresent()) return; + final Profile profile = profileOptional.get(); + + profile.setOnline(false); + profile.setLastServer(this.serverHandler.getCurrentName()); + // event.getPlayer().getStatistic(Statistic.PLAY_ONE_TICK) + final long onlineFor = System.currentTimeMillis() - profile.getLastOnline(); + profile.setPlaytime(profile.getPlaytime() + onlineFor); + profile.setLastOnline(System.currentTimeMillis()); + profileHandler.save(profile); + this.profileHandler.getPermissionHandler().removeAttachment(event.getPlayer()); + } + + @EventHandler + public void onPlayerChat(AsyncPlayerChatEvent event) { + Optional profileOptional = this.profileHandler.getProfile(event.getPlayer().getUniqueId()); + if(!profileOptional.isPresent()) { + event.getPlayer().sendMessage(Color.translate("&cYour profile is not setup properly.")); + event.setCancelled(true); + return; + } + + if(event.getPlayer().hasMetadata("managerchat")) { + jedisModule.sendPacket(new StaffMessagePacket( + StaffMessageType.MANAGER_CHAT, + event.getPlayer().getUniqueId(), + serverHandler.getCurrentName(), + event.getMessage() + )); + event.setCancelled(true); + return; + } + + if(event.getPlayer().hasMetadata("adminchat")) { + jedisModule.sendPacket(new StaffMessagePacket( + StaffMessageType.ADMIN_CHAT, + event.getPlayer().getUniqueId(), + serverHandler.getCurrentName(), + event.getMessage() + )); + event.setCancelled(true); + return; + } + + if(event.getPlayer().hasMetadata("staffchat")) { + jedisModule.sendPacket(new StaffMessagePacket( + StaffMessageType.CHAT, + event.getPlayer().getUniqueId(), + serverHandler.getCurrentName(), + event.getMessage() + )); + event.setCancelled(true); + return; + } + + if(serverHandler.getCurrentServer().isChatMuted() && !event.getPlayer().hasPermission("prime.mutechat.bypass")) { + event.getPlayer().sendMessage(Color.translate("&cThe chat is currently muted.")); + event.setCancelled(true); + return; + } + + Profile profile = profileOptional.get(); + + if(serverHandler.getCurrentServer().getChatSlow() > 0L && (System.currentTimeMillis() - profile.getLastChattedAt() < serverHandler.getCurrentServer().getChatSlow()) && !event.getPlayer().hasPermission("prime.slowchat.bypass")) { + /* + need the time difference (in seconds) from when we can chat again and current time + + equation: + can chat time = Profile#getLastChattedAt + Server#getChatSlow + time until next chat = (can chat time) - (current time) + + (Profile#getLastChattedAt + Server#getChatSlow) - current time + Divide by 1000 to get time in seconds instead of milliseconds + */ + int secNextChat = (int)((profile.getLastChattedAt() + serverHandler.getCurrentServer().getChatSlow()) - System.currentTimeMillis())/1000; + event.getPlayer().sendMessage(Color.translate(String.format("&cThe chat is currently slowed. You can chat again in %s.", TimeUtils.formatIntoDetailedString(secNextChat)))); + event.setCancelled(true); + return; + } + + Grant highestGrant = profile.highestGrantOnScope(this.serverHandler.getCurrentScope().getId()); + + String transformedMsg = Color.translate(getPlugin().getConfig().getString("format")) + .replace("%special%", Color.translate(profile.getSpecialPrefix())) + .replace("%prefix%", Color.translate(highestGrant.getRank().getPrefix())) + .replace("%player%", Color.translate(profile.getColoredName(this.serverHandler.getCurrentScope().getId()))) + .replace("%tag%", profile.hasActiveTag() ? Color.translate(profile.getActiveTag().getDisplay()) : "") + .replace("%chat%", ChatColor.stripColor(event.getMessage().replace("%", "%%"))) + .replace("%color%", profile.getChatColor().toString()); + + profile.setLastChattedAt(System.currentTimeMillis()); + event.setFormat(transformedMsg); + } + + @EventHandler + public void onMove(PlayerMoveEvent event) { + if(event.getPlayer().hasMetadata("frozen")) { + final Location + from = event.getFrom(), + to = event.getTo(); + + if(from.getZ() == to.getZ() && from.getX() == to.getX()) return; + event.setTo(event.getFrom()); + } + } + + @EventHandler + public void onPreCommand(PlayerCommandPreprocessEvent event) { + final String message = event.getMessage().toLowerCase(); + if( + (message.contains("lookup") || message.contains("l")) && + (message.contains("action:") || message.contains("a:")) && + message.contains("apposed")) { + event.setCancelled(true); + event.getPlayer().sendMessage(Color.translate("&cNo Permission.")); + final Optional profileOptional = this.profileHandler.getProfile(event.getPlayer().getUniqueId()); + if(!profileOptional.isPresent()) return; + Profile profile = profileOptional.get(); + + Bukkit.getOnlinePlayers().stream().filter(Player::isOp).forEach(op -> op.sendMessage(Color.translate("&c[A] &r" + profile.getColoredName() + " &3attempted to lookup &4Apposed's &3commands."))); + Bukkit.getConsoleSender().sendMessage(Color.translate("&c[A] &r" + profile.getColoredName() + " &3attempted to lookup &4Apposed's &3commands.")); + } + + if( + (message.contains("lookup") || message.contains("l")) && + (message.contains("action:") || message.contains("a:")) && + message.contains("ratguts")) { + event.setCancelled(true); + event.getPlayer().sendMessage(Color.translate("&cNo Permission.")); + final Optional profileOptional = this.profileHandler.getProfile(event.getPlayer().getUniqueId()); + if(!profileOptional.isPresent()) return; + Profile profile = profileOptional.get(); + + Bukkit.getOnlinePlayers().stream().filter(Player::isOp).forEach(op -> op.sendMessage(Color.translate("&c[A] &r" + profile.getColoredName() + " &3attempted to lookup &4ratguts' &3commands."))); + Bukkit.getConsoleSender().sendMessage(Color.translate("&c[A] &r" + profile.getColoredName() + " &3attempted to lookup &4ratguts' &3commands.")); + } + } + + +// @EventHandler +// public void preventCommands(PlayerCommandPreprocessEvent event) { +// final Player player = event.getPlayer(); +// if(!player.hasPermission("prime.staff")) return; +// if(player.hasMetadata("authed")) return; +// if(event.getMessage().startsWith("/auth") || +// event.getMessage().startsWith("/2fa") || +// event.getMessage().startsWith("/pin") || +// event.getMessage().startsWith("/setpin")) return; +// +// event.setCancelled(true); +// player.sendMessage(Color.translate("&cYou cannot execute commands without authing. Use /auth to auth.")); +// } +// +// @EventHandler +// public void preventChat(AsyncPlayerChatEvent event) { +// final Player player = event.getPlayer(); +// if(!player.hasPermission("prime.staff")) return; +// if(player.hasMetadata("authed")) return; +// +// event.setCancelled(true); +// player.sendMessage(Color.translate("&cYou cannot chat without authing. Use /auth to auth.")); +// } +// +// @EventHandler +// public void preventMove(PlayerMoveEvent event) { +// final Player player = event.getPlayer(); +// if(!player.hasPermission("prime.staff")) return; +// if(player.hasMetadata("authed")) return; +// +// final Location +// from = event.getFrom(), +// to = event.getTo(); +// +// if(from.getZ() == to.getZ() && from.getX() == to.getX()) return; +// event.setTo(event.getFrom()); +// player.sendMessage(Color.translate("&cYou cannot move without authing. Use /auth to auth.")); +// } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/menu/ChatColorMenu.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/menu/ChatColorMenu.java new file mode 100644 index 0000000..b5e127b --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/menu/ChatColorMenu.java @@ -0,0 +1,149 @@ +package dev.apposed.prime.spigot.module.profile.menu; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.pagination.PaginatedMenu; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import dev.apposed.prime.spigot.util.Color; +import dev.apposed.prime.spigot.util.ItemBuilder; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +public class ChatColorMenu extends PaginatedMenu { + + private final Prime plugin = JavaPlugin.getPlugin(Prime.class); + private final ProfileHandler profileHandler = plugin.getModuleHandler().getModule(ProfileHandler.class); + private final List colors = Arrays.asList( + ChatColor.DARK_GREEN, + ChatColor.DARK_AQUA, + ChatColor.DARK_RED, + ChatColor.DARK_PURPLE, + ChatColor.GOLD, + ChatColor.GRAY, + ChatColor.DARK_GRAY, + ChatColor.BLUE, + ChatColor.GREEN, + ChatColor.AQUA, + ChatColor.RED, + ChatColor.LIGHT_PURPLE, + ChatColor.YELLOW, + ChatColor.WHITE + ); + + @Override + public String getPrePaginatedTitle(Player player) { + return "Select a Color"; + } + + @Override + public Map getAllPagesButtons(Player player) { + final Map buttons = new HashMap<>(); + + final Profile profile = profileHandler.getProfile(player.getUniqueId()).orElse(null); + if(profile == null) { + buttons.put(0, new Button() { + @Override + public String getName(Player player) { + return Color.translate("&cError"); + } + + @Override + public List getDescription(Player player) { + return Collections.singletonList("Failed to fetch user's profile"); + } + + @Override + public Material getMaterial(Player player) { + return Material.PAPER; + } + }); + return buttons; + } + + final AtomicInteger slot = new AtomicInteger(0); + for(ChatColor color : colors) { + buttons.put(slot.getAndIncrement(), new Button() { + @Override + public String getName(Player player) { + return Color.translate(color + color.name()); + } + + @Override + public List getDescription(Player player) { + if(player.hasPermission("prime.color." + color.name().toLowerCase())) { + return Color.translate(Collections.singletonList("&7Click to select " + color + color.name())); + } else { + return Color.translate(Collections.singletonList("&cYou do not have access to this color.")); + } + } + + @Override + public Material getMaterial(Player player) { + return getWool(color).getType(); + } + + @Override + public byte getDamageValue(Player player) { + return (byte) getWool(color).getDurability(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + if (clickType != ClickType.LEFT) return; + if(!player.hasPermission("prime.color." + color.name().toLowerCase())) { + player.sendMessage(Color.translate("&cYou do not have access to this color.")); + return; + } + + profile.setChatColor(color); + profileHandler.sendSync(profile); + player.sendMessage(Color.translate("&aYou have selected " + color + color.name())); + } + }); + } + + return buttons; + } + + public ItemStack getWool(ChatColor color) { + final ItemBuilder itemBuilder = new ItemBuilder(Material.WOOL); + + if(color == ChatColor.DARK_GREEN) { + itemBuilder.dur(13); + } else if (color == ChatColor.DARK_AQUA) { + itemBuilder.dur(9); + }else if(color == ChatColor.DARK_RED){ + itemBuilder.dur(14); + }else if(color == ChatColor.DARK_PURPLE){ + itemBuilder.dur(10); + }else if(color == ChatColor.GOLD){ + itemBuilder.dur(1); + }else if(color == ChatColor.GRAY){ + itemBuilder.dur(8); + }else if(color == ChatColor.DARK_GRAY){ + itemBuilder.dur(7); + }else if(color == ChatColor.BLUE){ + itemBuilder.dur(11); + }else if(color == ChatColor.GREEN){ + itemBuilder.dur(5); + }else if(color == ChatColor.AQUA){ + itemBuilder.dur(3); + }else if(color == ChatColor.RED){ + itemBuilder.dur(14); + }else if(color == ChatColor.LIGHT_PURPLE){ + itemBuilder.dur(2); + }else if(color == ChatColor.YELLOW){ + itemBuilder.dur(4); + } + + return itemBuilder.build(); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/permission/PermissionHandler.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/permission/PermissionHandler.java new file mode 100644 index 0000000..14a19f5 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/permission/PermissionHandler.java @@ -0,0 +1,41 @@ +package dev.apposed.prime.spigot.module.profile.permission; + +import java.lang.reflect.Field; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import dev.apposed.prime.spigot.Prime; +import org.bukkit.entity.Player; +import org.bukkit.permissions.PermissionAttachment; + +public class PermissionHandler { + private static Field permissionsField; + private final Map attachments = new ConcurrentHashMap(); + + public void update(Player player, Map permissions) { + this.attachments.computeIfAbsent(player.getUniqueId(), i -> player.addAttachment(Prime.getInstance())); + PermissionAttachment attachment = this.attachments.get(player.getUniqueId()); + try { + permissionsField.set(attachment, permissions); + player.recalculatePermissions(); + } + catch (IllegalAccessException ex) { + ex.printStackTrace(); + } + } + + public void removeAttachment(Player player) { + this.attachments.remove(player.getUniqueId()); + } + + static { + try { + permissionsField = PermissionAttachment.class.getDeclaredField("permissions"); + permissionsField.setAccessible(true); + } + catch (NoSuchFieldException ex) { + ex.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/Punishment.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/Punishment.java new file mode 100644 index 0000000..f3b29e1 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/Punishment.java @@ -0,0 +1,108 @@ +package dev.apposed.prime.spigot.module.profile.punishment; + +import dev.apposed.prime.spigot.module.profile.punishment.evidence.PunishmentEvidence; +import dev.apposed.prime.spigot.module.profile.punishment.type.PunishmentType; +import dev.apposed.prime.spigot.util.ItemBuilder; +import dev.apposed.prime.spigot.util.time.DurationUtils; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import java.util.*; + +@Data @AllArgsConstructor +public class Punishment { + + private final PunishmentType type; + + private final UUID addedBy; + private final long addedAt; + private final String addedReason; + private final long duration; + + private Set evidence; + + private UUID removedBy; + private long removedAt; + private String removedReason; + + private String ip; + private boolean removed; + private UUID player; + + public Punishment(PunishmentType type, UUID addedBy, long addedAt, String addedReason, long duration) { + this.type = type; + + this.addedBy = addedBy; + this.addedAt = addedAt; + this.addedReason = addedReason; + this.duration = duration; + + this.evidence = new HashSet<>(); + } + + public Punishment(PunishmentType type, UUID addedBy, long addedAt, String addedReason, long duration, Set evidence, UUID removedBy, long removedAt, String removedReason, boolean removed) { + this.type = type; + this.addedBy = addedBy; + this.addedAt = addedAt; + this.addedReason = addedReason; + this.duration = duration; + this.evidence = evidence; + this.removedBy = removedBy; + this.removedAt = removedAt; + this.removedReason = removedReason; + this.removed = removed; + } + + public PunishmentEvidence addEvidence(String link, UUID addedBy, long addedAt) { + PunishmentEvidence evidence = new PunishmentEvidence(link, addedBy, addedAt); + this.evidence.add(evidence); + return evidence; + } + + public void removePunishment(UUID removedBy, long removedAt, String removedReason) { + this.removed = true; + + this.removedBy = removedBy; + this.removedAt = removedAt; + this.removedReason = removedReason; + } + + public boolean isActive() { + if(!removed) { + if(this.duration == Long.MAX_VALUE) return true; + return System.currentTimeMillis() <= (this.addedAt + this.duration); + } + + return false; + } + + public long getRemaining() { + if(removed) return 0L; + if(duration == Long.MAX_VALUE) return Long.MAX_VALUE; + if(!isActive()) return 0L; + + return (addedAt + duration) - System.currentTimeMillis(); + } + + public String formatDuration() { + if(this.duration == Long.MAX_VALUE) return "Permanent"; + return DurationUtils.toString(this.addedAt + this.duration); + } + + public ItemStack getItemStack() { + ItemBuilder builder = new ItemBuilder(Material.WOOL); + if(removed && !isActive()) { + builder.dur(14); + } else { + builder.dur(13); + } + + return builder.build(); + } + + public boolean hasIp() { + return ip != null && ip.length() > 0; + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/command/PunishmentCommands.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/command/PunishmentCommands.java new file mode 100644 index 0000000..3ac0c8b --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/command/PunishmentCommands.java @@ -0,0 +1,682 @@ +package dev.apposed.prime.spigot.module.profile.punishment.command; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.flag.Flag; +import com.elevatemc.elib.command.param.Parameter; +import dev.apposed.prime.packet.PunishmentPacket; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.PrimeConstants; +import dev.apposed.prime.spigot.module.ModuleHandler; +import dev.apposed.prime.spigot.module.database.redis.JedisModule; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import dev.apposed.prime.spigot.module.profile.punishment.Punishment; +import dev.apposed.prime.spigot.module.profile.punishment.menu.BaseHistoryMenu; +import dev.apposed.prime.spigot.module.profile.punishment.menu.UnresolvedPunishmentMenu; +import dev.apposed.prime.spigot.module.profile.punishment.type.PunishmentType; +import dev.apposed.prime.spigot.module.profile.target.ProfileTarget; +import dev.apposed.prime.spigot.module.server.ServerHandler; +import dev.apposed.prime.spigot.util.Color; +import dev.apposed.prime.spigot.util.time.DurationUtils; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +public class PunishmentCommands { + + private static final Prime plugin = Prime.getInstance(); + private static final ModuleHandler moduleHandler = plugin.getModuleHandler(); + + private static final ProfileHandler profileHandler = moduleHandler.getModule(ProfileHandler.class); + private static final JedisModule jedisModule = moduleHandler.getModule(JedisModule.class); + private static final ServerHandler serverHandler = moduleHandler.getModule(ServerHandler.class); + + private static final String + networkName = plugin.getConfig().getString("network.name"), + appealLink = plugin.getConfig().getString("network.appeal"); + + @Command(names = {"history", "hist", "phistory", "phist", "c", "checkhistory", "ch"}, permission = "prime.command.history") + public static void executeHistory(Player player, @Parameter(name = "player") ProfileTarget profileTarget) { + profileTarget.resolve(profile -> { + if (profile == null) { + profileTarget.sendError(player); + return; + } + + Prime.getInstance().getServer().getScheduler().runTask(Prime.getInstance(), () -> { + new BaseHistoryMenu(profile, new ArrayList<>(profile.getPunishments())).openMenu(player); + }); + }); + } + + @Command(names = {"tempban", "tb", "tban"}, permission = "prime.command.tempban") + public static void executeTempBan(CommandSender sender, @Flag(value = {"p", "a"}) boolean announce, @Parameter(name = "player") ProfileTarget profileTarget, @Parameter(name = "duration") String durationString, @Parameter(name = "reason", wildcard = true) String reason) { + profileTarget.resolve(profile -> { + if (profile == null) { + profileTarget.sendError(sender); + return; + } + + long duration = DurationUtils.fromString(durationString); + + AtomicReference addedBy = new AtomicReference<>(PrimeConstants.CONSOLE_UUID); + if(sender instanceof Player) { + Player player = (Player) sender; + addedBy.set(player.getUniqueId()); + } + + Punishment punishment = new Punishment( + PunishmentType.BAN, + addedBy.get(), + System.currentTimeMillis(), + reason.replace("-p", ""), + duration + ); + + if(profile.getLastHashedIp() != null) + punishment.setIp(profile.getLastHashedIp()); + + profile.getPunishments().add(punishment); + profileHandler.sendSync(profile); + + Prime.getInstance().getServer().getScheduler().runTask(Prime.getInstance(), () -> { + Player player = Bukkit.getPlayer(profile.getUuid()); + if(player != null && player.isOnline()) { + if(punishment.getRemaining() == Long.MAX_VALUE) { + player.kickPlayer( + Color.translate("&cYour account has been permanently suspended from the " + networkName + ".\n\n&cAppeal on " + appealLink + ".") + ); + } else { + player.kickPlayer( + Color.translate("&cYour account has been suspended from the " + networkName + " for " + punishment.formatDuration() + ".\n\n&cAppeal on " + appealLink + ".") + ); + } + } + }); + + // need to send ban message + jedisModule.sendPacket(new PunishmentPacket( + profile.getUuid(), + addedBy.get(), + serverHandler.getCurrentName(), + punishment, + announce, + false + )); + }); + } + + @Command(names = {"unban"}, permission = "prime.command.unban", description = "") + public static void executeUnban(CommandSender sender, @Flag(value = {"p", "a"}) boolean announce, @Parameter(name = "player") ProfileTarget profileTarget, @Parameter(name = "reason", wildcard = true) String reason, @Flag(value = {"c"}) boolean console) { + profileTarget.resolve(profile -> { + if (profile == null) { + profileTarget.sendError(sender); + return; + } + + Optional punishmentOptional = profile.getActivePunishment(PunishmentType.BAN); + + if(punishmentOptional.isPresent()) { + Punishment punishment = punishmentOptional.get(); + + AtomicReference removedBy = new AtomicReference<>(PrimeConstants.CONSOLE_UUID); + if(sender instanceof Player) { + Player player = (Player) sender; + removedBy.set(player.getUniqueId()); + } + + punishment.removePunishment( + removedBy.get(), + System.currentTimeMillis(), + reason + ); + + profileHandler.sendSync(profile); + + if(!console) { + jedisModule.sendPacket(new PunishmentPacket( + profile.getUuid(), + removedBy.get(), + serverHandler.getCurrentName(), + punishment, + announce, + true + )); + } + } else { + sender.sendMessage(Color.translate("&cThat player is not banned.")); + } + }); + } + + @Command(names = {"unmute"}, permission = "prime.command.unmute", description = "") + public static void executeUnmute(CommandSender sender, @Flag(value = {"p", "a"}) boolean announce, @Parameter(name = "player") ProfileTarget profileTarget, @Parameter(name = "reason", wildcard = true) String reason) { + profileTarget.resolve(profile -> { + if (profile == null) { + profileTarget.sendError(sender); + return; + } + + Optional punishmentOptional = profile.getActivePunishment(PunishmentType.MUTE); + if(punishmentOptional.isPresent()) { + Punishment punishment = punishmentOptional.get(); + + AtomicReference removedBy = new AtomicReference<>(PrimeConstants.CONSOLE_UUID); + if(sender instanceof Player) { + Player player = (Player) sender; + removedBy.set(player.getUniqueId()); + } + + punishment.removePunishment( + removedBy.get(), + System.currentTimeMillis(), + reason + ); + + profileHandler.sendSync(profile); + + jedisModule.sendPacket(new PunishmentPacket( + profile.getUuid(), + removedBy.get(), + serverHandler.getCurrentName(), + punishment, + announce, + true + )); + } else { + sender.sendMessage(Color.translate("&cThat player is not muted.")); + } + }); + } + + @Command(names = {"unresolvedpunishments", "unresolvedp", "upunishments"}, permission = "prime.command.unresolvedpunishments", description = "") + public static void executeUnresolved(Player player) { + final Optional profileOptional = profileHandler.getProfile(player.getUniqueId()); + if(!profileOptional.isPresent()) return; + final Profile profile = profileOptional.get(); + + new UnresolvedPunishmentMenu(profile).openMenu(player); + } + + @Command(names = {"unghostmute"}, permission = "prime.command.unghostmute", description = "") + public static void executeUnGhostmute(CommandSender sender, @Flag(value = {"p", "a"}) boolean announce, @Parameter(name = "player") ProfileTarget profileTarget, @Parameter(name = "reason", wildcard = true) String reason) { + profileTarget.resolve(profile -> { + if (profile == null) { + profileTarget.sendError(sender); + return; + } + + Optional punishmentOptional = profile.getActivePunishment(PunishmentType.GHOSTMUTE); + if(punishmentOptional.isPresent()) { + Punishment punishment = punishmentOptional.get(); + + AtomicReference removedBy = new AtomicReference<>(PrimeConstants.CONSOLE_UUID); + if(sender instanceof Player) { + Player player = (Player) sender; + removedBy.set(player.getUniqueId()); + } + + punishment.removePunishment( + removedBy.get(), + System.currentTimeMillis(), + reason + ); + + profileHandler.sendSync(profile); + + jedisModule.sendPacket(new PunishmentPacket( + profile.getUuid(), + removedBy.get(), + serverHandler.getCurrentName(), + punishment, + announce, + true + )); + } else { + sender.sendMessage(Color.translate("&cThat player is not ghost muted.")); + } + }); + } + + @Command(names = {"unblacklist"}, permission = "prime.command.unblacklist", description = "") + public static void executeUnblacklist(CommandSender sender, @Flag(value = {"p", "a"}) boolean announce, @Parameter(name = "player") ProfileTarget profileTarget, @Parameter(name = "reason", wildcard = true) String reason) { + profileTarget.resolve(profile -> { + if (profile == null) { + profileTarget.sendError(sender); + return; + } + + Optional punishmentOptional = profile.getActivePunishment(PunishmentType.BLACKLIST); + if(punishmentOptional.isPresent()) { + Punishment punishment = punishmentOptional.get(); + + AtomicReference removedBy = new AtomicReference<>(PrimeConstants.CONSOLE_UUID); + if(sender instanceof Player) { + Player player = (Player) sender; + removedBy.set(player.getUniqueId()); + } + + punishment.removePunishment( + removedBy.get(), + System.currentTimeMillis(), + reason + ); + + profileHandler.sendSync(profile); + + jedisModule.sendPacket(new PunishmentPacket( + profile.getUuid(), + removedBy.get(), + serverHandler.getCurrentName(), + punishment, + announce, + true + )); + } else { + sender.sendMessage(Color.translate("&cThat player is not blacklisted.")); + } + }); + } + + @Command(names = {"warn"}, permission = "prime.command.warn", description = "") + public static void executeWarn(CommandSender sender, @Flag(value = {"p", "a"}) boolean announce, @Parameter(name = "player") ProfileTarget profileTarget, @Parameter(name = "reason", wildcard = true) String reason) { + profileTarget.resolve(profile -> { + if (profile == null) { + profileTarget.sendError(sender); + return; + } + + AtomicReference addedBy = new AtomicReference<>(PrimeConstants.CONSOLE_UUID); + if(sender instanceof Player) { + Player player = (Player) sender; + addedBy.set(player.getUniqueId()); + } + + Punishment punishment = new Punishment( + PunishmentType.WARN, + addedBy.get(), + System.currentTimeMillis(), + reason.replace("-p", ""), + Long.MAX_VALUE + ); + + if(profile.getLastHashedIp() != null) + punishment.setIp(profile.getLastHashedIp()); + + profile.getPunishments().add(punishment); + + // need to send ban message + jedisModule.sendPacket(new PunishmentPacket( + profile.getUuid(), + addedBy.get(), + serverHandler.getCurrentName(), + punishment, + announce, + false + )); + profileHandler.sendSync(profile); + + Player player = Bukkit.getPlayer(profile.getUuid()); + if(player != null && player.isOnline()) { + player.sendMessage(Color.translate("&cYou have been warned for &f" + reason + "&c.")); + } + }); + } + + @Command(names = {"blacklist", "bl"}, permission = "prime.command.blacklist", description = "") + public static void executeBlacklist(CommandSender sender, @Flag(value = {"p", "a"}) boolean announce, @Parameter(name = "player") ProfileTarget profileTarget, @Parameter(name = "reason", wildcard = true) String reason) { + profileTarget.resolve(profile -> { + if(profile == null) { + profileTarget.sendError(sender); + return; + } + + AtomicReference addedBy = new AtomicReference<>(PrimeConstants.CONSOLE_UUID); + if(sender instanceof Player) { + Player player = (Player) sender; + addedBy.set(player.getUniqueId()); + } + + Punishment punishment = new Punishment( + PunishmentType.BLACKLIST, + addedBy.get(), + System.currentTimeMillis(), + reason.replace("-p", ""), + Long.MAX_VALUE + ); + + if(profile.getLastHashedIp() != null) + punishment.setIp(profile.getLastHashedIp()); + + profile.getPunishments().add(punishment); + + Player player = Bukkit.getPlayer(profile.getUuid()); + if(player != null && player.isOnline()) { + player.kickPlayer( + Color.translate("&cYour account has been blacklisted from the " + networkName + ".\n\n&cThis type of punishment is not appealable.") + ); + } + + // need to send ban message + + jedisModule.sendPacket(new PunishmentPacket( + profile.getUuid(), + addedBy.get(), + serverHandler.getCurrentName(), + punishment, + announce, + false + )); + profileHandler.sendSync(profile); + }); + } + + @Command(names = {"ban", "b"}, permission = "prime.command.ban", description = "") + public static void executeBan(CommandSender sender, @Flag(value = {"p", "a"}) boolean announce, @Parameter(name = "player") ProfileTarget profileTarget, @Parameter(name = "reason", wildcard = true) String extra) { + profileTarget.resolve(profile -> { + if (profile == null) { + profileTarget.sendError(sender); + return; + } + + final String[] args = extra.split(" "); + long length = Long.MAX_VALUE; + if(args.length > 1) { + final String posLength = args[0]; + final long tempLength = DurationUtils.fromString(posLength); + if(tempLength != -1) length = tempLength; + } + + final StringBuilder reasonBuilder = new StringBuilder(); + for(int i=(length == Long.MAX_VALUE ? 0 : 1); i addedBy = new AtomicReference<>(PrimeConstants.CONSOLE_UUID); + if(sender instanceof Player) { + Player player = (Player) sender; + addedBy.set(player.getUniqueId()); + } + + Punishment punishment = new Punishment( + PunishmentType.BAN, + addedBy.get(), + System.currentTimeMillis(), + reason, + length + ); + + if(profile.getLastHashedIp() != null) + punishment.setIp(profile.getLastHashedIp()); + + profile.getPunishments().add(punishment); + + Prime.getInstance().getServer().getScheduler().runTask(Prime.getInstance(), () -> { + Player player = Bukkit.getPlayer(profile.getUuid()); + if(player != null && player.isOnline()) { + if(punishment.getRemaining() == Long.MAX_VALUE) { + player.kickPlayer( + Color.translate("&cYour account has been permanently suspended from the " + networkName + ".\n\n&cAppeal on " + appealLink + ".") + ); + } else { + player.kickPlayer( + Color.translate("&cYour account has been suspended from the " + networkName + " for " + punishment.formatDuration() + ".\n\n&cAppeal on " + appealLink + ".") + ); + } + } + }); + + // need to send ban message + jedisModule.sendPacket(new PunishmentPacket( + profile.getUuid(), + addedBy.get(), + serverHandler.getCurrentName(), + punishment, + announce, + false + )); + profileHandler.sendSync(profile); + }); + } + + @Command(names = {"mute", "m"}, permission = "prime.command.mute", description = "") + public static void executeMute(CommandSender sender, @Flag(value = {"p", "a"}) boolean announce, @Parameter(name = "player") ProfileTarget profileTarget, @Parameter(name = "reason", wildcard = true) String extra) { + profileTarget.resolve(profile -> { + if (profile == null) { + profileTarget.sendError(sender); + return; + } + + final String[] args = extra.split(" "); + long length = Long.MAX_VALUE; + if(args.length > 1) { + final String posLength = args[0]; + final long tempLength = DurationUtils.fromString(posLength); + if(tempLength != -1) length = tempLength; + } + + final StringBuilder reasonBuilder = new StringBuilder(); + for(int i=(length == Long.MAX_VALUE ? 0 : 1); i addedBy = new AtomicReference<>(PrimeConstants.CONSOLE_UUID); + if(sender instanceof Player) { + Player player = (Player) sender; + addedBy.set(player.getUniqueId()); + } + + Punishment punishment = new Punishment( + PunishmentType.MUTE, + addedBy.get(), + System.currentTimeMillis(), + reason, + length + ); + + if(profile.getLastHashedIp() != null) + punishment.setIp(profile.getLastHashedIp()); + + profile.getPunishments().add(punishment); + profileHandler.sendSync(profile); + + // need to send ban message + jedisModule.sendPacket(new PunishmentPacket( + profile.getUuid(), + addedBy.get(), + serverHandler.getCurrentName(), + punishment, + announce, + false + )); + + Player player = Bukkit.getPlayer(profile.getUuid()); + if(player != null && player.isOnline()) { + player.sendMessage(Color.translate("&cYou have been muted for &f" + reason + "&c.")); + } + }); + } + + @Command(names = {"tempmute", "tmute", "tm"}, permission = "prime.command.tempmute", description = "") + public static void executeTempMute(CommandSender sender, @Flag(value = {"p", "a"}) boolean announce, @Parameter(name = "player") ProfileTarget profileTarget, @Parameter(name = "duration") String durationString, @Parameter(name = "reason", wildcard = true) String reason) { + profileTarget.resolve(profile -> { + if (profile == null) { + profileTarget.sendError(sender); + return; + } + + long duration = DurationUtils.fromString(durationString); + + AtomicReference addedBy = new AtomicReference<>(PrimeConstants.CONSOLE_UUID); + if(sender instanceof Player) { + Player player = (Player) sender; + addedBy.set(player.getUniqueId()); + } + + Punishment punishment = new Punishment( + PunishmentType.MUTE, + addedBy.get(), + System.currentTimeMillis(), + reason.replace("-p", ""), + duration + ); + + if(profile.getLastHashedIp() != null) + punishment.setIp(profile.getLastHashedIp()); + + profile.getPunishments().add(punishment); + profileHandler.sendSync(profile); + + // need to send ban message + jedisModule.sendPacket(new PunishmentPacket( + profile.getUuid(), + addedBy.get(), + serverHandler.getCurrentName(), + punishment, + announce, + false + )); + + Player player = Bukkit.getPlayer(profile.getUuid()); + if(player != null && player.isOnline()) { + player.sendMessage(Color.translate("&cYou have been muted for &f" + reason + "&c.")); + } + }); + } + + @Command(names = {"ghostmute"}, permission = "prime.command.ghostmute", description = "") + public static void executeGhostMute(CommandSender sender, @Flag(value = {"p", "a"}) boolean announce, @Parameter(name = "player") ProfileTarget profileTarget, @Parameter(name = "duration") String durationString, @Parameter(name = "reason", wildcard = true) String reason) { + profileTarget.resolve(profile -> { + if (profile == null) { + profileTarget.sendError(sender); + return; + } + + long duration = DurationUtils.fromString(durationString); + + AtomicReference addedBy = new AtomicReference<>(PrimeConstants.CONSOLE_UUID); + if(sender instanceof Player) { + Player player = (Player) sender; + addedBy.set(player.getUniqueId()); + } + + Punishment punishment = new Punishment( + PunishmentType.GHOSTMUTE, + addedBy.get(), + System.currentTimeMillis(), + reason, + duration + ); + + if(profile.getLastHashedIp() != null) + punishment.setIp(profile.getLastHashedIp()); + + profile.getPunishments().add(punishment); + profileHandler.sendSync(profile); + + // need to send ban message + jedisModule.sendPacket(new PunishmentPacket( + profile.getUuid(), + addedBy.get(), + serverHandler.getCurrentName(), + punishment, + announce, + false + )); + }); + } + + @Command(names = {"staffhistory"}, async = true, permission = "prime.command.staffhistory") + public static void executeStaffHistory(Player player, @Parameter(name = "player") ProfileTarget profileTarget) { + profileTarget.resolve(profile -> { + if (profile == null) { + profileTarget.sendError(player); + return; + } + + player.sendMessage(Color.translate("&aSearching the database for all punishments made by " + profile.getUuid() + ". This may take a few moments.")); + final List punishments = profileHandler.getPunishmentsByStaff(profile.getUuid()); + if(punishments == null) { + player.sendMessage(Color.translate("&cFailed to fetch punishments.")); + return; + } + + Prime.getInstance().getServer().getScheduler().runTask(Prime.getInstance(), () -> new BaseHistoryMenu(profile, punishments) + .showWhoReceived() + .openMenu(player)); + }); + } + + @Command(names = {"staffrollback", "staffpunishmentrollback", "punishmentrollback"}, async = true, permission = "prime.command.staffrollback", description = "") + public static void executeStaffRollback(CommandSender sender, @Parameter(name = "player") ProfileTarget profileTarget, @Parameter(name = "duration") String durationString, @Parameter(name = "rollback reason", wildcard = true) String reason) { + profileTarget.resolve(profile -> { + if (profile == null) { + profileTarget.sendError(sender); + return; + } + + long duration = DurationUtils.fromString(durationString); + + AtomicReference removedBy = new AtomicReference<>(PrimeConstants.CONSOLE_UUID); + if(sender instanceof Player) { + Player player = (Player) sender; + removedBy.set(player.getUniqueId()); + } + + final AtomicInteger i = new AtomicInteger(0); + profileHandler.getPunishments(profile.getUuid()).stream().filter(punishment -> !punishment.isRemoved()).filter(Punishment::isActive).filter(grant -> (System.currentTimeMillis() - grant.getAddedAt()) <= duration).forEach(punishment -> { + punishment.removePunishment( + removedBy.get(), + System.currentTimeMillis(), + reason); + + i.getAndIncrement(); + }); + + sender.sendMessage(Color.translate("&aSuccessfully rolled back " + i.get() + " punishments.")); + }); + } + + @Command(names = {"kick"}, permission = "prime.command.kick") + public static void kick(CommandSender sender, @Parameter(name = "player") Player player, @Parameter(name = "reason", defaultValue = "", wildcard = true) String reason) { + Prime.getInstance().getServer().getScheduler().runTask(Prime.getInstance(), () -> { + player.kickPlayer(reason); + sender.sendMessage(Color.translate("&6Kicked &f" + player.getName() + "&6.")); + }); + } + + @Command(names = {"removeoverlaps", "removeidentityoverlaps"}, permission = "prime.command.identityoverlaps") + public static void removeOverlaps(CommandSender sender, @Parameter(name = "remove player") ProfileTarget removeTarget, @Parameter(name = "compare player") ProfileTarget compareTarget) { + removeTarget.resolve(removeProfile -> { + if(removeProfile == null) { + removeTarget.sendError(sender); + return; + } + + compareTarget.resolve(compareProfile -> { + if(compareProfile == null) { + compareTarget.sendError(sender); + return; + } + + compareProfile.getIdentities().forEach(identity -> { + if(removeProfile.hasIdentity(identity.getIp())) { + removeProfile.getIdentities().remove(identity); + sender.sendMessage(Color.translate("&cRemoved &e" + identity.getIp() + " &cfrom &r" + removeProfile.getColoredName())); + } + }); + + profileHandler.save(removeProfile); + }); + }); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/evidence/PunishmentEvidence.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/evidence/PunishmentEvidence.java new file mode 100644 index 0000000..b38e939 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/evidence/PunishmentEvidence.java @@ -0,0 +1,14 @@ +package dev.apposed.prime.spigot.module.profile.punishment.evidence; + +import dev.apposed.prime.spigot.util.time.DurationUtils; +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.UUID; + +@Data @AllArgsConstructor +public class PunishmentEvidence { + private final String link; + private final UUID addedBy; + private final long addedAt; +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/listener/PunishmentListener.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/listener/PunishmentListener.java new file mode 100644 index 0000000..19dbb5a --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/listener/PunishmentListener.java @@ -0,0 +1,142 @@ +package dev.apposed.prime.spigot.module.profile.punishment.listener; + +import dev.apposed.prime.spigot.module.listener.ListenerModule; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import dev.apposed.prime.spigot.module.profile.punishment.Punishment; +import dev.apposed.prime.spigot.module.profile.punishment.type.PunishmentType; +import dev.apposed.prime.spigot.module.rank.meta.RankMeta; +import dev.apposed.prime.spigot.module.server.ServerHandler; +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.PlayerJoinEvent; + +import java.util.Optional; + +public class PunishmentListener extends ListenerModule { + + private final ProfileHandler profileHandler; + private final ServerHandler serverHandler; + + private final String networkName, appealLink; + + public PunishmentListener() { + this.profileHandler = getModuleHandler().getModule(ProfileHandler.class); + this.serverHandler = getModuleHandler().getModule(ServerHandler.class); + this.networkName = getPlugin().getConfig().getString("network.name"); + this.appealLink = getPlugin().getConfig().getString("network.appeal"); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onJoin(PlayerJoinEvent event) { + event.setJoinMessage(null); + final Player player = event.getPlayer(); + profileHandler.load(player.getUniqueId()).thenAccept(profile -> { + if(profile.hasActivePunishment(PunishmentType.BLACKLIST)) { + Optional punishmentOptional = profile.getActivePunishment(PunishmentType.BLACKLIST); + if(punishmentOptional.isPresent()) { + player.kickPlayer( + Color.translate("&cYour account has been blacklisted from the " + networkName + ".\n\n&cThis type of punishment is not appealable.") + ); + return; + } + } + + if (serverHandler.getCurrentScope().getId().equalsIgnoreCase("hubs")) return; + + if(profile.hasActivePunishment(PunishmentType.BAN)) { + Optional punishmentOptional = profile.getActivePunishment(PunishmentType.BAN); + if(punishmentOptional.isPresent()) { + Punishment punishment = punishmentOptional.get(); + if(punishment.getRemaining() == Long.MAX_VALUE) { + player.kickPlayer( + Color.translate("&cYour account has been permanently suspended from the " + networkName + ".\n\n&cAppeal on " + appealLink + ".") + ); + } else { + player.kickPlayer( + Color.translate("&cYour account has been suspended from the " + networkName + " for " + punishment.formatDuration() + ".\n\n&cAppeal on " + appealLink + ".") + ); + } + return; + } + } + + // identity check - if they have a rank that allows them to bypass ip bans this will be skipped + if(!profile.hasMeta(RankMeta.IP_BYPASS)) { + profile.getIdentities().forEach(identity -> { + Optional punishedIdentityOptional = this.profileHandler.identityIsPunished(identity); + if (punishedIdentityOptional.isPresent()) { + Profile punishedProfile = punishedIdentityOptional.get(); + + if (punishedProfile.hasActivePunishment(PunishmentType.BLACKLIST)) { + Optional punishmentOptional = punishedProfile.getActivePunishment(PunishmentType.BLACKLIST); + if (punishmentOptional.isPresent()) { + player.kickPlayer( + Color.translate("&cYour account has been blacklisted from the " + networkName + ".\n\n&cThis type of punishment is not appealable.") + ); + return; + } + } + + if (punishedProfile.hasActivePunishment(PunishmentType.BAN)) { + Optional punishmentOptional = punishedProfile.getActivePunishment(PunishmentType.BAN); + if (punishmentOptional.isPresent()) { + Punishment punishment = punishmentOptional.get(); + if (punishment.getRemaining() == Long.MAX_VALUE) { + player.kickPlayer( + Color.translate("&cYour account has been permanently suspended from the " + networkName + "\n&cThis punishment is in relation to &r" + punishedProfile.getColoredName() + "\n&cAppeal on " + appealLink + ".") + ); + } else { + player.kickPlayer( + Color.translate("&cYour account has been suspended from the " + networkName + " for " + punishment.formatDuration() + "\n&cThis punishment is in relation to &r" + punishedProfile.getColoredName() + "\n&cAppeal on " + appealLink + ".") + ); + } + return; + } + } + } + }); + } + }).exceptionally(throwable -> { + player.kickPlayer(Color.translate("&cYour profile was not loaded properly.")); + return null; + }); + } + + @EventHandler(priority = EventPriority.HIGH) + public void onChat(AsyncPlayerChatEvent event) { + final Player player = event.getPlayer(); + final Optional profileOptional = profileHandler.getProfile(player.getUniqueId()); + if(!profileOptional.isPresent()) { + player.sendMessage(Color.translate("&cYour profile is not loaded properly. Try reconnecting.")); + event.setCancelled(true); + return; + } + + final Profile profile = profileOptional.get(); + + if(profile.hasActivePunishment(PunishmentType.MUTE)) { + Optional punishmentOptional = profile.getActivePunishment(PunishmentType.MUTE); + if(punishmentOptional.isPresent()) { + Punishment punishment = punishmentOptional.get(); + if(punishment.getRemaining() == Long.MAX_VALUE) { + player.sendMessage(Color.translate("&cYou are muted forever.")); + } else { + player.sendMessage(Color.translate("&cYou are muted for " + punishment.formatDuration())); + } + + event.setCancelled(true); + } + return; + } + + if(profile.hasActivePunishment(PunishmentType.GHOSTMUTE)) { + event.setCancelled(true); + player.sendMessage(event.getFormat()); + } + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/menu/BaseHistoryMenu.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/menu/BaseHistoryMenu.java new file mode 100644 index 0000000..7cb70c3 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/menu/BaseHistoryMenu.java @@ -0,0 +1,79 @@ +package dev.apposed.prime.spigot.module.profile.punishment.menu; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.punishment.Punishment; +import dev.apposed.prime.spigot.module.profile.punishment.type.PunishmentType; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.*; + +public class BaseHistoryMenu extends Menu { + + private final Profile profile; + private final List punishments; + private boolean showReceived = false; + + public BaseHistoryMenu(Profile profile, List punishments) { + this.profile = profile; + this.punishments = punishments; + + } + + @Override + public String getTitle(Player player) { + return "Punishment Type"; + } + + @Override + public int size(Player player) { + return 27; + } + + @Override + public Map getButtons(Player player) { + final Map buttons = new HashMap<>(); + + for(PunishmentType type : PunishmentType.values()) { + if(player.hasPermission(type.getPermission())) { + buttons.put(type.getSlot(), new Button() { + @Override + public String getName(Player player) { + return ChatColor.GOLD + type.getMenu(); + } + + @Override + public List getDescription(Player player) { + return Collections.emptyList(); + } + + @Override + public Material getMaterial(Player player) { + return type.getMenuStack().getType(); + } + + @Override + public byte getDamageValue(Player player) { + return (byte) type.getMenuStack().getDurability(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + new HistoryMenu(type, profile, punishments, showReceived).openMenu(player); + } + }); + } + } + + return buttons; + } + + public BaseHistoryMenu showWhoReceived() { + this.showReceived = true; + return this; + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/menu/HistoryMenu.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/menu/HistoryMenu.java new file mode 100644 index 0000000..bf7c225 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/menu/HistoryMenu.java @@ -0,0 +1,116 @@ +package dev.apposed.prime.spigot.module.profile.punishment.menu; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.pagination.PaginatedMenu; +import com.elevatemc.elib.util.UUIDUtils; +import com.google.common.collect.ImmutableList; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.PrimeConstants; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import dev.apposed.prime.spigot.module.profile.punishment.Punishment; +import dev.apposed.prime.spigot.module.profile.punishment.type.PunishmentType; +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +public class HistoryMenu extends PaginatedMenu { + + private final Prime plugin = Prime.getInstance(); + private final ProfileHandler profileHandler = plugin.getModuleHandler().getModule(ProfileHandler.class); + private final SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy hh:mm"); + + private final PunishmentType type; + private final Profile profile; + private final List punishments; + private final boolean showReceived; + + public HistoryMenu(PunishmentType type, Profile profile, List punishments, boolean showReceived) { + this.profile = profile; + this.type = type; + this.punishments = punishments; + this.showReceived = showReceived; + format.setTimeZone(TimeZone.getTimeZone("America/New_York")); + } + + @Override + public String getPrePaginatedTitle(Player player) { + return profile.getUsername() + "'s " + type.getMenu(); + } + + @Override + public Map getAllPagesButtons(Player player) { + final Map buttons = new HashMap<>(); + + final AtomicInteger slot = new AtomicInteger(0); + this.punishments.stream() + .filter(punishment -> punishment.getType() == this.type) + .sorted(Comparator.comparingLong(Punishment::getAddedAt).reversed()) + .forEach(punishment -> { + buttons.put(slot.getAndIncrement(), new Button() { + @Override + public String getName(Player player) { + return ChatColor.GOLD + format.format(punishment.getAddedAt()); + } + + @Override + public List getDescription(Player player) { + final List lore = new ArrayList<>(); + lore.add(Color.SPACER_SHORT); + if(showReceived) { + lore.add("&ePlayer: &c" + UUIDUtils.name(punishment.getPlayer())); + } + lore.addAll(Arrays.asList( + "&eBy: &c" + (punishment.getAddedBy().equals(PrimeConstants.CONSOLE_UUID) ? + "&4&lConsole" : UUIDUtils.name(punishment.getAddedBy())), + "&eReason: &c" + punishment.getAddedReason(), + "&eRemaining: &c" + punishment.formatDuration(), + Color.SPACER_SHORT + )); + + if(punishment.isRemoved()) { + lore.add("&c&lRemoved"); + lore.add(" "); + lore.add("&eBy: &c" + (punishment.getRemovedBy().equals(PrimeConstants.CONSOLE_UUID) ? + "&4&lConsole" : UUIDUtils.name(punishment.getRemovedBy()))); + lore.add("&eReason: &c" + punishment.getRemovedReason()); + lore.add(" "); + lore.add(ChatColor.GOLD + format.format(punishment.getRemovedAt())); + lore.add(Color.SPACER_SHORT); + } + + lore.add("&eClick to view evidence."); + if(player.hasPermission("prime.history.detailed") && punishment.hasIp()) { + lore.add("&eIP: &c" + punishment.getIp()); + } + lore.add(Color.SPACER_SHORT); + + return Color.translate(lore); + } + + @Override + public Material getMaterial(Player player) { + return punishment.getItemStack().getType(); + } + + @Override + public byte getDamageValue(Player player) { + return (byte) punishment.getItemStack().getDurability(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + new PunishmentEvidenceMenu(profile, punishment).openMenu(player); + } + }); + }); + + return buttons; + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/menu/PunishmentEvidenceMenu.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/menu/PunishmentEvidenceMenu.java new file mode 100644 index 0000000..86f8361 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/menu/PunishmentEvidenceMenu.java @@ -0,0 +1,140 @@ +package dev.apposed.prime.spigot.module.profile.punishment.menu; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.pagination.PaginatedMenu; +import com.google.common.collect.ImmutableList; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.PrimeConstants; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import dev.apposed.prime.spigot.module.profile.punishment.Punishment; +import dev.apposed.prime.spigot.module.profile.punishment.evidence.PunishmentEvidence; +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.conversations.*; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +public class PunishmentEvidenceMenu extends PaginatedMenu { + + private final Prime plugin = Prime.getInstance(); + private final ProfileHandler profileHandler = plugin.getModuleHandler().getModule(ProfileHandler.class); + + private final Profile profile; + private final Punishment punishment; + + public PunishmentEvidenceMenu(Profile profile, Punishment punishment) { + this.profile = profile; + this.punishment = punishment; + } + + @Override + public String getPrePaginatedTitle(Player player) { + return "Punishment Evidence"; + } + + @Override + public Map getGlobalButtons(Player player) { + final Map buttons = new HashMap<>(); + + buttons.put(4, new Button() { + @Override + public String getName(Player player) { + return Color.translate("&aAdd Evidence"); + } + + @Override + public List getDescription(Player player) { + return Collections.emptyList(); + } + + @Override + public Material getMaterial(Player player) { + return Material.NETHER_STAR; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + player.closeInventory(); + ConversationFactory factory = new ConversationFactory(plugin).withModality(true).withPrefix(new NullConversationPrefix()).withFirstPrompt(new StringPrompt() { + + @Override + public String getPromptText(ConversationContext conversationContext) { + return Color.translate("&ePlease enter the link of the evidence to add to the punishment, or type &c\"cancel\" &eto cancel."); + } + + @Override + public Prompt acceptInput(ConversationContext cc, String link) { + if(link.equalsIgnoreCase("cancel")) { + return Prompt.END_OF_CONVERSATION; + } + + punishment.addEvidence(link, player.getUniqueId(), System.currentTimeMillis()); + profileHandler.sendSync(profile); + + cc.getForWhom().sendRawMessage(Color.translate("&aSuccessfully added the evidence")); + + (new BukkitRunnable() { + @Override + public void run() { + new PunishmentEvidenceMenu(profile, punishment).openMenu(player); + } + }).runTask(plugin); + + return Prompt.END_OF_CONVERSATION; + } + }).withLocalEcho(false).withEscapeSequence("/cancel").withTimeout(60).thatExcludesNonPlayersWithMessage("Player's only."); + + Conversation conversation = factory.buildConversation(player); + player.beginConversation(conversation); + } + }); + + return buttons; + } + + @Override + public Map getAllPagesButtons(Player player) { + final Map buttons = new HashMap<>(); + + final AtomicInteger slot = new AtomicInteger(0); + this.punishment.getEvidence().forEach(evidence -> { + buttons.put(slot.getAndIncrement(), new Button() { + @Override + public String getName(Player player) { + return ChatColor.GOLD + evidence.getLink(); + } + + @Override + public List getDescription(Player player) { + return Color.translate(ImmutableList.of( + Color.SPACER_SHORT, + "&eAdded By: &c" + (evidence.getAddedBy().equals(PrimeConstants.CONSOLE_UUID) ? + "&4&lConsole" : profileHandler.getProfile(evidence.getAddedBy()).isPresent() ? + profileHandler.getProfile(evidence.getAddedBy()).get().getColoredName() : "Unknown"), + Color.SPACER_SHORT + )); + } + + @Override + public Material getMaterial(Player player) { + return Material.PAPER; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + player.sendMessage(Color.translate("&a&lEvidence: &f" + evidence.getLink())); + } + }); + }); + + return buttons; + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/menu/UnresolvedPunishmentMenu.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/menu/UnresolvedPunishmentMenu.java new file mode 100644 index 0000000..0d48b9c --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/menu/UnresolvedPunishmentMenu.java @@ -0,0 +1,104 @@ +package dev.apposed.prime.spigot.module.profile.punishment.menu; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.pagination.PaginatedMenu; +import com.google.common.collect.ImmutableList; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.PrimeConstants; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import dev.apposed.prime.spigot.module.profile.punishment.Punishment; +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.plugin.java.JavaPlugin; + +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +public class UnresolvedPunishmentMenu extends PaginatedMenu { + + private final Prime plugin = JavaPlugin.getPlugin(Prime.class); + private final ProfileHandler profileHandler = plugin.getModuleHandler().getModule(ProfileHandler.class); + private final SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy hh:mm"); + + private final Profile profile; + + public UnresolvedPunishmentMenu(Profile profile) { + this.profile = profile; + format.setTimeZone(TimeZone.getTimeZone("America/New_York")); + } + + @Override + public String getPrePaginatedTitle(Player player) { + return "Unresolved Punishments"; + } + + @Override + public Map getAllPagesButtons(Player player) { + final Map buttons = new HashMap<>(); + + final AtomicInteger slot = new AtomicInteger(0); + this.profileHandler.punishmentsWithoutProof(profile) + .stream() + .sorted(Comparator.comparingLong(Punishment::getAddedAt).reversed()) + .forEach(punishment -> { + buttons.put(slot.getAndIncrement(), new Button() { + @Override + public String getName(Player player) { + return ChatColor.GOLD + format.format(punishment.getAddedAt()); + } + + @Override + public List getDescription(Player player) { + final List lore = Arrays.asList( + "&eBy: &c" + (punishment.getAddedBy().equals(PrimeConstants.CONSOLE_UUID) ? + "&4&lConsole" : profileHandler.getProfile(punishment.getAddedBy()).isPresent() ? + profileHandler.getProfile(punishment.getAddedBy()).get().getColoredName() : "Unknown"), + "&eReason: &c" + punishment.getAddedReason(), + "&eRemaining: &c" + punishment.formatDuration(), + Color.SPACER_SHORT + ); + + if(punishment.isRemoved()) { + lore.add("&c&lRemoved"); + lore.add(" "); + lore.add("&eBy: &c" + (punishment.getRemovedBy().equals(PrimeConstants.CONSOLE_UUID) ? + "&4&lConsole" : profileHandler.getProfile(punishment.getRemovedBy()).isPresent() ? + profileHandler.getProfile(punishment.getRemovedBy()).get().getColoredName() : "Unknown")); + lore.add("&eReason: &c" + punishment.getRemovedReason()); + lore.add(" "); + lore.add(ChatColor.GOLD + format.format(punishment.getRemovedAt())); + lore.add(Color.SPACER_SHORT); + } + + lore.add("&eClick to view evidence."); + lore.add(Color.SPACER_SHORT); + + return Color.translate(lore); + } + + @Override + public Material getMaterial(Player player) { + return punishment.getItemStack().getType(); + } + + @Override + public byte getDamageValue(Player player) { + return (byte) punishment.getItemStack().getDurability(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + new PunishmentEvidenceMenu(profile, punishment).openMenu(player); + } + }); + }); + + return buttons; + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/task/PunishmentCheckerTask.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/task/PunishmentCheckerTask.java new file mode 100644 index 0000000..248ff50 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/task/PunishmentCheckerTask.java @@ -0,0 +1,20 @@ +package dev.apposed.prime.spigot.module.profile.punishment.task; + +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import org.bukkit.scheduler.BukkitRunnable; + +public class PunishmentCheckerTask extends BukkitRunnable { + + private final ProfileHandler profileHandler; + + public PunishmentCheckerTask(ProfileHandler profileHandler) { + this.profileHandler = profileHandler; + + } + + @Override + public void run() { + this.profileHandler.getProfiles().forEach(Profile::checkPunishments); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/type/PunishmentType.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/type/PunishmentType.java new file mode 100644 index 0000000..03c14aa --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/punishment/type/PunishmentType.java @@ -0,0 +1,28 @@ +package dev.apposed.prime.spigot.module.profile.punishment.type; + +import dev.apposed.prime.spigot.util.ItemBuilder; +import lombok.Getter; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +@Getter +public enum PunishmentType { + WARN("warned", "Warns", new ItemBuilder(Material.WOOL).dur(5).build(), 17, "prime.history.warn"), + MUTE("muted", "Mutes", new ItemBuilder(Material.WOOL).dur(4).build(), 15, "prime.history.mute"), + GHOSTMUTE("ghost muted", "Ghost Mutes", new ItemBuilder(Material.WOOL).dur(1).build(), 13, "prime.history.ghostmute"), + BAN("banned", "Bans", new ItemBuilder(Material.WOOL).dur(14).build(), 11, "prime.history.ban"), + BLACKLIST("blacklisted", "Blacklists", new ItemStack(Material.BEDROCK), 9, "prime.history.blacklist"); + + String display, menu; + ItemStack menuStack; + int slot; + String permission; + + PunishmentType(String display, String menu, ItemStack menuStack, int slot, String permission) { + this.display = display; + this.menu = menu; + this.menuStack = menuStack; + this.slot = slot; + this.permission = permission; + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/skin/SkinHandler.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/skin/SkinHandler.java new file mode 100644 index 0000000..c1a6e9c --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/skin/SkinHandler.java @@ -0,0 +1,125 @@ +package dev.apposed.prime.spigot.module.profile.skin; + +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.Module; +import org.bukkit.entity.Player; + +import java.util.*; + +public class SkinHandler extends Module { + + private final Prime plugin = Prime.getInstance(); + private final Map> usageCache = new HashMap<>(); + + public void changeSkin(Player player, String username) { + /* final CraftPlayer craftPlayer = (CraftPlayer) player; + final EntityPlayer entityPlayer = craftPlayer.getHandle(); + final GameProfile gameProfile = craftPlayer.getProfile(); + + final Location location = player.getLocation(); + final GameMode gameMode = player.getGameMode(); + final boolean allowFlight = player.getAllowFlight(); + final boolean flying = player.isFlying(); + final int level = player.getLevel(); + final float xp = player.getExp(); + final double maxHealth = player.getMaxHealth(); + final double health = player.getHealth(); + + MojangUtils.getTextureAndSignature(username, ((texture, signature) -> { + if(texture == null || signature == null) { + player.sendMessage(Color.translate("&cFailed to load " + username + "'s skin.")); + return; + } + + gameProfile.getProperties().get("textures").clear(); // remove old skin texture + gameProfile.getProperties() + .put("textures", + new Property( + "textures", + texture, + signature + )); + + final PacketPlayOutPlayerInfo removeInfo = PacketPlayOutPlayerInfo.removePlayer(entityPlayer); + final PacketPlayOutEntityDestroy removeEntity = new PacketPlayOutEntityDestroy(entityPlayer.getId()); + final PacketPlayOutNamedEntitySpawn addNamed = new PacketPlayOutNamedEntitySpawn(entityPlayer); + final PacketPlayOutPlayerInfo addInfo = PacketPlayOutPlayerInfo.addPlayer(entityPlayer); + + final PacketPlayOutRespawn respawn = new PacketPlayOutRespawn(((WorldServer) entityPlayer.getWorld()).dimension, + entityPlayer.getWorld().difficulty, entityPlayer.getWorld().worldData.getType(), + WorldSettings.EnumGamemode.getById(player.getGameMode().getValue())); + + final PacketPlayOutPosition pos = new PacketPlayOutPosition(location.getX(), location.getY(), location.getZ(), location.getYaw(), + location.getPitch(), false); + + final PacketPlayOutEntityEquipment itemhand = new PacketPlayOutEntityEquipment(entityPlayer.getId(), 0, + CraftItemStack.asNMSCopy(player.getItemInHand())); + + final PacketPlayOutEntityEquipment helmet = new PacketPlayOutEntityEquipment(entityPlayer.getId(), 4, + CraftItemStack.asNMSCopy(player.getInventory().getHelmet())); + + final PacketPlayOutEntityEquipment chestplate = new PacketPlayOutEntityEquipment(entityPlayer.getId(), 3, + CraftItemStack.asNMSCopy(player.getInventory().getChestplate())); + + final PacketPlayOutEntityEquipment leggings = new PacketPlayOutEntityEquipment(entityPlayer.getId(), 2, + CraftItemStack.asNMSCopy(player.getInventory().getLeggings())); + + final PacketPlayOutEntityEquipment boots = new PacketPlayOutEntityEquipment(entityPlayer.getId(), 1, + CraftItemStack.asNMSCopy(player.getInventory().getBoots())); + + final PacketPlayOutHeldItemSlot slot = new PacketPlayOutHeldItemSlot(player.getInventory().getHeldItemSlot()); + + for (Player inWorld : player.getWorld().getPlayers()) { + final CraftPlayer craftOnline = (CraftPlayer) inWorld; + PlayerConnection con = craftOnline.getHandle().playerConnection; + if (inWorld.equals(player)) { + con.sendPacket(removeInfo); + con.sendPacket(addInfo); + con.sendPacket(respawn); + con.sendPacket(pos); + con.sendPacket(slot); + craftOnline.updateScaledHealth(); + craftOnline.getHandle().triggerHealthUpdate(); + craftOnline.updateInventory(); + Bukkit.getScheduler().runTask(plugin, () -> craftOnline.getHandle().updateAbilities()); + continue; + } + con.sendPacket(removeEntity); + con.sendPacket(removeInfo); + if (inWorld.canSee(player)){ + con.sendPacket(addInfo); + con.sendPacket(addNamed); + con.sendPacket(itemhand); + con.sendPacket(helmet); + con.sendPacket(chestplate); + con.sendPacket(leggings); + con.sendPacket(boots); + } + } + + Bukkit.getScheduler().runTaskLater(plugin, () -> { + entityPlayer.playerConnection.sendPacket(respawn); + + player.setGameMode(gameMode); + player.setAllowFlight(allowFlight); + player.setFlying(flying); + player.updateInventory(); + player.setLevel(level); + player.setExp(xp); + player.setMaxHealth(maxHealth); + player.setHealth(health); + + entityPlayer.playerConnection.sendPacket(addInfo); + }, 1); + + final List prev = usageCache.getOrDefault(player.getUniqueId(), new ArrayList<>()); + if(prev.stream().noneMatch(u -> u.equalsIgnoreCase(username))) prev.add(username); + usageCache.put(player.getUniqueId(), prev); + player.sendMessage(Color.translate("&aChanged your skin to &e" + username + "&a.")); + })); */ + } + + public List getUsage(UUID uuid) { + return usageCache.getOrDefault(uuid, new ArrayList<>()); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/skin/command/SkinCommands.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/skin/command/SkinCommands.java new file mode 100644 index 0000000..45bef14 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/skin/command/SkinCommands.java @@ -0,0 +1,13 @@ +package dev.apposed.prime.spigot.module.profile.skin.command; + +import com.elevatemc.elib.command.Command; +import dev.apposed.prime.spigot.module.profile.skin.menu.SkinSelectMenu; +import org.bukkit.entity.Player; + +public class SkinCommands { + + @Command(names = {"skinchanger"}, permission = "prime.command.skinchanger", description = "Open up the Skin Changer") + public static void open(Player player) { + new SkinSelectMenu().openMenu(player); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/skin/menu/SkinSelectMenu.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/skin/menu/SkinSelectMenu.java new file mode 100644 index 0000000..a8eefdf --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/skin/menu/SkinSelectMenu.java @@ -0,0 +1,115 @@ +package dev.apposed.prime.spigot.module.profile.skin.menu; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.pagination.PaginatedMenu; +import com.google.common.collect.ImmutableList; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.profile.skin.SkinHandler; +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.Material; +import org.bukkit.conversations.*; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class SkinSelectMenu extends PaginatedMenu { + + private final Prime plugin = Prime.getInstance(); + private final SkinHandler skinHandler = plugin.getModuleHandler().getModule(SkinHandler.class); + + @Override + public String getPrePaginatedTitle(Player player) { + return "Select Skin"; + } + + @Override + public Map getGlobalButtons(Player player) { + final Map buttons = new HashMap<>(); + + buttons.put(4, new Button() { + @Override + public String getName(Player player) { + return Color.translate("&aAdd new skin"); + } + + @Override + public List getDescription(Player player) { + return Collections.emptyList(); + } + + @Override + public Material getMaterial(Player player) { + return Material.NETHER_STAR; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + player.closeInventory(); + ConversationFactory factory = new ConversationFactory(plugin).withModality(true).withPrefix(new NullConversationPrefix()).withFirstPrompt(new StringPrompt() { + + @Override + public String getPromptText(ConversationContext conversationContext) { + return Color.translate("&eEnter the username of the skin you would like to apply, or type &c\"cancel\" &eto cancel."); + } + + @Override + public Prompt acceptInput(ConversationContext cc, String username) { + if(username.equalsIgnoreCase("cancel")) { + return Prompt.END_OF_CONVERSATION; + } + + skinHandler.changeSkin(player, username); + + return Prompt.END_OF_CONVERSATION; + } + }).withLocalEcho(false).withEscapeSequence("/cancel").withTimeout(60).thatExcludesNonPlayersWithMessage("Player's only."); + + Conversation conversation = factory.buildConversation(player); + player.beginConversation(conversation); + } + }); + return buttons; + } + + @Override + public Map getAllPagesButtons(Player player) { + final Map buttons = new HashMap<>(); + + skinHandler.getUsage(player.getUniqueId()).forEach(username -> buttons.put(buttons.size(), new Button() { + @Override + public String getName(Player player) { + return Color.translate("&e&l" + username); + } + + @Override + public List getDescription(Player player) { + return ImmutableList.of( + "", + Color.translate("&a&lLeft Click &7to apply") + ); + } + + @Override + public Material getMaterial(Player player) { + return Material.SKULL_ITEM; + } + + @Override + public byte getDamageValue(Player player) { + return 3; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + if(!clickType.isLeftClick()) return; + skinHandler.changeSkin(player, username); + } + })); + + return buttons; + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/style/command/StyleCommands.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/style/command/StyleCommands.java new file mode 100644 index 0000000..ca904e1 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/style/command/StyleCommands.java @@ -0,0 +1,13 @@ +package dev.apposed.prime.spigot.module.profile.style.command; + +import com.elevatemc.elib.command.Command; +import dev.apposed.prime.spigot.module.profile.style.menu.StyleMenu; +import org.bukkit.entity.Player; + +public class StyleCommands { + + @Command(names = {"style"}, permission = "prime.command.style") + public static void open(Player player) { + new StyleMenu().openMenu(player); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/style/menu/StyleMenu.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/style/menu/StyleMenu.java new file mode 100644 index 0000000..e2134f9 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/style/menu/StyleMenu.java @@ -0,0 +1,135 @@ +package dev.apposed.prime.spigot.module.profile.style.menu; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.pagination.PaginatedMenu; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import dev.apposed.prime.spigot.module.server.ServerHandler; +import dev.apposed.prime.spigot.util.Color; +import dev.apposed.prime.spigot.util.ItemBuilder; +import org.apache.commons.lang.StringUtils; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.*; + +public class StyleMenu extends PaginatedMenu { + + private final Prime plugin = JavaPlugin.getPlugin(Prime.class); + private final ProfileHandler profileHandler = plugin.getModuleHandler().getModule(ProfileHandler.class); + private final ServerHandler serverHandler = plugin.getModuleHandler().getModule(ServerHandler.class); + + @Override + public String getPrePaginatedTitle(Player player) { + return "Select Style"; + } + + @Override + public Map getAllPagesButtons(Player player) { + final Map buttons = new HashMap<>(); + + final Profile profile = profileHandler.getProfile(player.getUniqueId()).orElse(null); + if(profile == null) { + buttons.put(0, new Button() { + @Override + public String getName(Player player) { + return Color.translate("&cError"); + } + + @Override + public List getDescription(Player player) { + return Collections.singletonList("Failed to fetch user's profile"); + } + + @Override + public Material getMaterial(Player player) { + return Material.PAPER; + } + }); + return buttons; + } + + serverHandler.getStyles().forEach((id, style) -> buttons.put(buttons.size(), new Button() { + @Override + public String getName(Player player) { + return style.getKey().toString() + ChatColor.BOLD + id; + } + + @Override + public List getDescription(Player player) { + return Color.translate(Arrays.asList( + " ", + "&7Main: &r" + style.getKey().toString() + StringUtils.capitalize(style.getKey().name().toLowerCase().replace("_", " ")), + "&7Sub: &r" + style.getValue().toString() + StringUtils.capitalize(style.getValue().name().toLowerCase().replace("_", " ")), + " ", + "&7Click to select " + style.getKey().toString() + id + )); + } + + @Override + public Material getMaterial(Player player) { + return getWool(style.getKey()).getType(); + } + + @Override + public byte getDamageValue(Player player) { + return (byte) getWool(style.getKey()).getDurability(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + if(!clickType.isLeftClick()) return; + + if(profile.hasStyle() && profile.getStyle().equalsIgnoreCase(id)) { + player.sendMessage(Color.translate("&cYou already have that style selected!")); + return; + } + + profile.setStyle(id.toUpperCase()); + player.sendMessage(Color.translate("&aYou have selected the " + style.getKey() + id + " Style&a!")); + profileHandler.sendSync(profile); + } + })); + + return buttons; + } + + public ItemStack getWool(ChatColor color) { + final ItemBuilder itemBuilder = new ItemBuilder(Material.WOOL); + + if(color == ChatColor.DARK_GREEN) { + itemBuilder.dur(13); + } else if (color == ChatColor.DARK_AQUA) { + itemBuilder.dur(9); + }else if(color == ChatColor.DARK_RED){ + itemBuilder.dur(14); + }else if(color == ChatColor.DARK_PURPLE){ + itemBuilder.dur(10); + }else if(color == ChatColor.GOLD){ + itemBuilder.dur(1); + }else if(color == ChatColor.GRAY){ + itemBuilder.dur(8); + }else if(color == ChatColor.DARK_GRAY){ + itemBuilder.dur(7); + }else if(color == ChatColor.BLUE){ + itemBuilder.dur(11); + }else if(color == ChatColor.GREEN){ + itemBuilder.dur(5); + }else if(color == ChatColor.AQUA){ + itemBuilder.dur(3); + }else if(color == ChatColor.RED){ + itemBuilder.dur(14); + }else if(color == ChatColor.LIGHT_PURPLE){ + itemBuilder.dur(2); + }else if(color == ChatColor.YELLOW){ + itemBuilder.dur(4); + } + + return itemBuilder.build(); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/target/ProfileTarget.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/target/ProfileTarget.java new file mode 100644 index 0000000..2a3b4cf --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/target/ProfileTarget.java @@ -0,0 +1,50 @@ +package dev.apposed.prime.spigot.module.profile.target; + +import com.elevatemc.elib.util.Callback; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.UUID; + +public class ProfileTarget { + + private static final ProfileHandler profileHandler = Prime.getInstance().getModuleHandler().getModule(ProfileHandler.class); + + private final String name; + private UUID knownUUID; + + public ProfileTarget(String name) { + this.name = name; + } + + public ProfileTarget(CommandSender sender) { + if(sender instanceof Player) { + final Player player = (Player) sender; + + this.name = player.getName(); + this.knownUUID = player.getUniqueId(); + } else { + this.name = "self"; + } + } + + public void resolve(Callback callback) { + if(knownUUID != null) { + callback.callback(profileHandler.getProfile(knownUUID).orElse(null)); + return; + } + + profileHandler.getProfile(name).thenAccept(callback::callback).exceptionally(throwable -> { + callback.callback(null); + return null; + }); + } + + public void sendError(CommandSender sender) { + sender.sendMessage(Color.translate(String.format("&cFailed to fetch %s's profile", name))); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/target/ProfileTypeAdapter.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/target/ProfileTypeAdapter.java new file mode 100644 index 0000000..decc5d9 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/target/ProfileTypeAdapter.java @@ -0,0 +1,27 @@ +package dev.apposed.prime.spigot.module.profile.target; + +import com.elevatemc.elib.command.param.ParameterType; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class ProfileTypeAdapter implements ParameterType { + + @Override + public ProfileTarget transform(CommandSender sender, String s) { + if(sender instanceof Player && s.equals("self")) { + return new ProfileTarget(sender); + } + + return new ProfileTarget(s); + } + + @Override + public List tabComplete(Player player, Set set, String s) { + return Bukkit.getOnlinePlayers().stream().filter(player::canSee).map(Player::getName).collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/task/ProfileSaveTask.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/task/ProfileSaveTask.java new file mode 100644 index 0000000..52a15ee --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/profile/task/ProfileSaveTask.java @@ -0,0 +1,21 @@ +package dev.apposed.prime.spigot.module.profile.task; + +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.Bukkit; +import org.bukkit.scheduler.BukkitRunnable; + +public class ProfileSaveTask extends BukkitRunnable { + + private final ProfileHandler profileHandler; + + public ProfileSaveTask(ProfileHandler profileHandler) { + this.profileHandler = profileHandler; + } + + @Override + public void run() { + // TODO: make it so it doesnt make a new thread for each profile + this.profileHandler.getProfiles().forEach(this.profileHandler::save); + } +} \ No newline at end of file diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/Rank.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/Rank.java new file mode 100644 index 0000000..82753bb --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/Rank.java @@ -0,0 +1,142 @@ +package dev.apposed.prime.spigot.module.rank; + +import com.google.common.base.Objects; +import com.google.gson.annotations.SerializedName; +import dev.apposed.prime.spigot.module.profile.punishment.type.PunishmentType; +import dev.apposed.prime.spigot.module.rank.meta.RankMeta; +import dev.apposed.prime.spigot.util.ItemBuilder; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Data @AllArgsConstructor +public class Rank { + + @SerializedName("_id") + private String name; + + private List meta; + private List proofMeta; + private List inherits; + private List permissions; + + private String prefix = ""; + private String color = ChatColor.WHITE.toString(); + private int weight; + + public Rank(String name) { + this.name = name; + + this.meta = new ArrayList<>(); + this.proofMeta = new ArrayList<>(); + this.inherits = new ArrayList<>(); + this.permissions = new ArrayList<>(); + } + + public List getFullPermissions() { + final List permissions = new ArrayList<>(); + + permissions.addAll(this.permissions); + + for(Rank rank : inherits) { + permissions.addAll(rank.getPermissions()); + } + + return permissions; + } + + public boolean hasPermission(String permission) { + return this.getFullPermissions().contains(permission); + } + + public boolean inherits(Rank rank) { + return this.inherits.contains(rank); + } + + public boolean hasMeta(RankMeta meta, boolean individual) { + if(individual) + return this.meta.contains(meta); + + Optional rankedMeta = this.inherits.stream().filter(rank -> rank.hasMeta(meta, true)).findAny(); + return rankedMeta.isPresent() || this.meta.contains(meta); + } + + public boolean requiresProof(PunishmentType type) { + return this.proofMeta.contains(type); + } + + public ItemStack getWool() { + String color = this.color + .replace(ChatColor.BOLD.toString(), "") + .replace(ChatColor.ITALIC.toString(), "") + .replace(ChatColor.UNDERLINE.toString(), "") + .replace(ChatColor.MAGIC.toString(), "") + .replace(ChatColor.RESET.toString(), "") + .replace(ChatColor.STRIKETHROUGH.toString(), ""); + + ItemBuilder itemBuilder = new ItemBuilder(Material.WOOL); + + if(color.contains(ChatColor.BLACK.toString())) { + itemBuilder.dur(15); + } else if(color.contains(ChatColor.DARK_BLUE.toString())) { + itemBuilder.dur(11); + } else if(color.contains(ChatColor.DARK_GREEN.toString())) { + itemBuilder.dur(13); + } else if(color.contains(ChatColor.DARK_AQUA.toString())) { + itemBuilder.dur(9); + } else if(color.contains(ChatColor.DARK_RED.toString())) { + itemBuilder.dur(14); + } else if(color.contains(ChatColor.DARK_PURPLE.toString())) { + itemBuilder.dur(10); + } else if(color.contains(ChatColor.GOLD.toString())) { + itemBuilder.dur(1); + } else if(color.contains(ChatColor.GRAY.toString())) { + itemBuilder.dur(8); + } else if(color.contains(ChatColor.DARK_GRAY.toString())) { + itemBuilder.dur(7); + } else if(color.contains(ChatColor.BLUE.toString())) { + itemBuilder.dur(11); + } else if(color.contains(ChatColor.GREEN.toString())) { + itemBuilder.dur(5); + } else if(color.contains(ChatColor.AQUA.toString())) { + itemBuilder.dur(3); + } else if(color.contains(ChatColor.RED.toString())) { + itemBuilder.dur(14); + } else if(color.contains(ChatColor.LIGHT_PURPLE.toString())) { + itemBuilder.dur(2); + } else if(color.contains(ChatColor.YELLOW.toString())) { + itemBuilder.dur(4); + } else if(color.contains(ChatColor.WHITE.toString())) { + itemBuilder.dur(0); + } + + return itemBuilder.build(); + } + + public String getColoredDisplay() { + return this.color + this.name; + } + + public int addWeight(int weight) { + return this.weight += weight; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Rank rank = (Rank) o; + return Objects.equal(name, rank.name); + } + + @Override + public int hashCode() { + return Objects.hashCode(name); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/RankHandler.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/RankHandler.java new file mode 100644 index 0000000..bd7df28 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/RankHandler.java @@ -0,0 +1,107 @@ +package dev.apposed.prime.spigot.module.rank; + +import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.Filters; +import dev.apposed.prime.packet.RankRefreshPacket; +import dev.apposed.prime.packet.type.RefreshType; +import dev.apposed.prime.spigot.module.Module; +import dev.apposed.prime.spigot.module.database.mongo.MongoModule; +import dev.apposed.prime.spigot.module.database.redis.JedisModule; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import dev.apposed.prime.spigot.module.rank.meta.RankMeta; +import dev.apposed.prime.spigot.util.Color; +import dev.apposed.prime.spigot.util.json.JsonHelper; +import lombok.Getter; +import org.bson.Document; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +@Getter +public class RankHandler extends Module { + + private Set cache; + private MongoCollection collection; + + private JedisModule jedisModule; + + @Override + public void onEnable() { + this.cache = new HashSet<>(); + this.collection = this.getModuleHandler().getModule(MongoModule.class).getMongoDatabase().getCollection("ranks"); + this.jedisModule = this.getModuleHandler().getModule(JedisModule.class); + + this.collection.find().iterator().forEachRemaining(document -> add(JsonHelper.GSON.fromJson(JsonHelper.GSON.toJson(document), Rank.class))); + + if(this.getDefaultRank() == null) { + Rank defaultRank = new Rank("Default"); + defaultRank.getMeta().add(RankMeta.DEFAULT); + defaultRank.setColor(Color.translate("&7")); + defaultRank.setWeight(1); + + this.create(defaultRank); + } + + System.out.println("Loaded " + this.cache.size() + " ranks."); + } + + @Override + public void onDisable() { + this.cache.forEach(rank -> this.collection.replaceOne(Filters.eq("_id", rank.getName()), Document.parse(JsonHelper.GSON.toJson(rank)), JsonHelper.REPLACE_OPTIONS)); + } + + public void save(Rank rank) { + new Thread(() -> { + this.collection.replaceOne(Filters.eq("_id", rank.getName()), Document.parse(JsonHelper.GSON.toJson(rank)), JsonHelper.REPLACE_OPTIONS); + this.jedisModule.sendPacket( + new RankRefreshPacket(rank, RefreshType.UPDATE) + ); + }, "rank-save-" + rank.getName()).start(); + } + + public void updateRank(Rank updatedRank) { + Rank rank = this.getRank(updatedRank.getName()).orElse(null); + if(rank == null) { + rank = updatedRank; + this.cache.add(rank); + } + + rank.setPrefix(updatedRank.getPrefix()); + rank.setColor(updatedRank.getColor()); + rank.setWeight(updatedRank.getWeight()); + rank.setPrefix(updatedRank.getPrefix()); + rank.setInherits(updatedRank.getInherits()); + rank.setMeta(updatedRank.getMeta()); + rank.setPermissions(updatedRank.getPermissions()); + } + + public Rank create(Rank rank) { + save(rank); + add(rank); + return rank; + } + + // need to make sure to remove all grants with this rank from profiles + public void delete(Rank rank) { + new Thread(() -> { + this.collection.deleteOne(Filters.eq("_id", rank.getName())); + this.cache.remove(rank); + this.jedisModule.sendPacket(new RankRefreshPacket(rank, RefreshType.REMOVE)); + }, "rank-delete-" + rank.getName()).start(); + } + + public Rank getDefaultRank() { + return this.cache.stream().filter(rank -> rank.hasMeta(RankMeta.DEFAULT, true)).findFirst().orElse(null); + } + + public Rank add(Rank rank) { + this.cache.add(rank); + return rank; + } + + public Optional getRank(String name) { + return this.cache.stream().filter(rank -> rank.getName().equalsIgnoreCase(name)).findFirst(); + } + +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/adapter/RankTypeAdapter.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/adapter/RankTypeAdapter.java new file mode 100644 index 0000000..34da627 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/adapter/RankTypeAdapter.java @@ -0,0 +1,41 @@ +package dev.apposed.prime.spigot.module.rank.adapter; + +import com.elevatemc.elib.command.param.ParameterType; +import dev.apposed.prime.spigot.module.rank.Rank; +import dev.apposed.prime.spigot.module.rank.RankHandler; +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +public class RankTypeAdapter implements ParameterType { + + private final RankHandler rankHandler; + + public RankTypeAdapter(RankHandler rankHandler) { + this.rankHandler = rankHandler; + } + + @Override + public Rank transform(CommandSender sender, String s) { + Optional rank = this.rankHandler.getRank(s); + if(!rank.isPresent()) { + sender.sendMessage(Color.translate("&cUnable to find a rank with the name " + s + ".")); + return null; + } + + return rank.get(); + } + + @Override + public List tabComplete(Player player, Set set, String s) { + return this.rankHandler.getCache().stream() + .map(Rank::getName) + .filter(name -> name.toLowerCase().startsWith(s)) + .collect(Collectors.toList()); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/command/RankCommands.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/command/RankCommands.java new file mode 100644 index 0000000..69e4864 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/command/RankCommands.java @@ -0,0 +1,79 @@ +package dev.apposed.prime.spigot.module.rank.command; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.ModuleHandler; +import dev.apposed.prime.spigot.module.rank.Rank; +import dev.apposed.prime.spigot.module.rank.RankHandler; +import dev.apposed.prime.spigot.module.rank.menu.RankEditorMenu; +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.Comparator; + +public class RankCommands { + + private static final Prime plugin = Prime.getInstance(); + private static final ModuleHandler moduleHandler = plugin.getModuleHandler(); + + private static final RankHandler rankHandler = moduleHandler.getModule(RankHandler.class); + + @Command(names = {"rank editor"}, permission = "prime.command.rank.editor") + public static void executeEditor(Player player) { + new RankEditorMenu().openMenu(player); + } + + @Command(names = {"rank list"}, permission = "prime.command.rank.list") + public static void executeList(CommandSender sender) { + rankHandler.getCache().stream().sorted(Comparator.comparingInt(Rank::getWeight).reversed()).forEach(rank -> sender.sendMessage(Color.translate("&7- &r" + rank.getColoredDisplay() + " &7(Weight: &f" + rank.getWeight() + "&7) (Prefix: &f" + rank.getPrefix() + "&7)"))); + } + + @Command(names = {"rank create"}, permission = "prime.command.rank.create") + public static void executeCreate(CommandSender sender, @Parameter(name = "name") String name) { + Rank rank = rankHandler.create(new Rank(name)); + sender.sendMessage(Color.translate("&aCreated rank &r" + rank.getColoredDisplay() + "&a.")); + } + + @Command(names = {"rank delete", "rank remove"}, permission = "prime.command.rank.delete") + public static void executeDelete(CommandSender sender, @Parameter(name = "rank", wildcard = true) Rank rank) { + rankHandler.delete(rank); + sender.sendMessage(Color.translate("&aDeleted rank &r" + rank.getColoredDisplay() + "&a.")); + } + + @Command(names = {"rank setweight"}, permission = "prime.command.rank.setweight") + public static void executePrefix(CommandSender sender, @Parameter(name = "rank") Rank rank, @Parameter(name = "weight") int weight) { + rank.setWeight(weight); + rankHandler.save(rank); + sender.sendMessage(Color.translate("&aSet &r" + rank.getColoredDisplay() + "'s &aweight.")); + } + + @Command(names = {"rank setprefix"}, permission = "prime.command.rank.setprefix") + public static void executePrefix(CommandSender sender, @Parameter(name = "rank") Rank rank, @Parameter(name = "prefix", wildcard = true) String prefix) { + rank.setPrefix(prefix); + rankHandler.save(rank); + sender.sendMessage(Color.translate("&aSet &r" + rank.getColoredDisplay() + "'s &aprefix.")); + } + + @Command(names = {"rank setcolor"}, permission = "prime.command.rank.setcolor") + public static void executeColor(CommandSender sender, @Parameter(name = "rank") Rank rank, @Parameter(name = "color") String color) { + rank.setColor(Color.translate(color)); + rankHandler.save(rank); + sender.sendMessage(Color.translate("&aSet &r" + rank.getColoredDisplay() + "'s &acolor.")); + } + + @Command(names = {"rank addperm"}, permission = "prime.command.rank.addperm") + public static void executeAddPerm(CommandSender sender, @Parameter(name = "rank") Rank rank, @Parameter(name = "permission", wildcard = true) String permission) { + rank.getPermissions().add(permission); + rankHandler.save(rank); + sender.sendMessage(Color.translate("&aAdded &7" + permission + " &ato &r" + rank.getColoredDisplay() + "&a.")); + } + + @Command(names = {"rank delperm"}, permission = "prime.command.rank.delperm") + public static void executeDelPerm(CommandSender sender, @Parameter(name = "rank") Rank rank, @Parameter(name = "permission", wildcard = true) String permission) { + rank.getPermissions().remove(permission); + rankHandler.save(rank); + sender.sendMessage(Color.translate("&aRemoved &7" + permission + " &afrom &r" + rank.getColoredDisplay() + "&a.")); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/menu/RankEditMenu.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/menu/RankEditMenu.java new file mode 100644 index 0000000..b2f584a --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/menu/RankEditMenu.java @@ -0,0 +1,300 @@ +package dev.apposed.prime.spigot.module.rank.menu; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import com.google.common.collect.ImmutableList; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.rank.Rank; +import dev.apposed.prime.spigot.module.rank.RankHandler; +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.Material; +import org.bukkit.conversations.*; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +public class RankEditMenu extends Menu { + + private final Prime plugin = Prime.getInstance(); + private final RankHandler rankHandler = plugin.getModuleHandler().getModule(RankHandler.class); + + private final Rank rank; + + public RankEditMenu(Rank rank) { + this.rank = rank; + setUpdateAfterClick(true); + } + + @Override + public String getTitle(Player player) { + return "Editing Rank"; + } + + @Override + public Map getButtons(Player player) { + final Map buttons = new HashMap<>(); + + final AtomicInteger slot = new AtomicInteger(0); + buttons.put(slot.getAndIncrement(), new Button() { + @Override + public String getName(Player player) { + return Color.translate("&6Weight: &f" + rank.getWeight()); + } + + @Override + public List getDescription(Player player) { + return Color.translate(ImmutableList.of( + " ", + "&a&lShift Left Click &7to increase by +10", + "&c&lShift Left Click &7to decrease by -10" + )); + } + + @Override + public Material getMaterial(Player player) { + return Material.FEATHER; + } + + @Override + public void clicked(Player player, int slot, ClickType type) { + switch(type) { + // +1 + case LEFT: { + int active = rank.addWeight(1); + player.sendMessage(Color.translate("&aSuccessfully added &l1&a weight. &e" + rank.getColoredDisplay() + "&a now has a weight value of &e" + active + "&a.")); + break; + } + + // -1 + case RIGHT: { + int active = rank.addWeight(-1); + player.sendMessage(Color.translate("&aSuccessfully removed &l1&a weight. &e" + rank.getColoredDisplay() + "&a now has a weight value of &e" + active + "&a.")); + break; + } + + // +10 + case SHIFT_LEFT: { + int active = rank.addWeight(10); + player.sendMessage(Color.translate("&aSuccessfully added &l10&a weight. &e" + rank.getColoredDisplay() + "&a now has a weight value of &e" + active + "&a.")); + break; + } + + // -10 + case SHIFT_RIGHT: { + int active = rank.addWeight(-10); + player.sendMessage(Color.translate("&aSuccessfully removed &l10&a weight. &e" + rank.getColoredDisplay() + "&a now has a weight value of &e" + active + "&a.")); + break; + } + } + + rankHandler.save(rank); + } + }); + + buttons.put(slot.getAndIncrement(), new Button() { + @Override + public String getName(Player player) { + return Color.translate("&6Color: &f" + rank.getColor()) + rank.getColor().replace("§", "&"); + } + + @Override + public List getDescription(Player player) { + return Color.translate(ImmutableList.of( + " ", + "&a&lLeft Click &7to edit color" + )); + } + + @Override + public Material getMaterial(Player player) { + return Material.NAME_TAG; + } + + @Override + public void clicked(Player player, int slot, ClickType type) { + player.closeInventory(); + ConversationFactory factory = new ConversationFactory(plugin).withModality(true).withPrefix(new NullConversationPrefix()).withFirstPrompt(new StringPrompt() { + + @Override + public String getPromptText(ConversationContext conversationContext) { + return Color.translate("&ePlease type a new color for the rank to be displayed as, or type &c\"cancel\" &eto cancel."); + } + + @Override + public Prompt acceptInput(ConversationContext cc, String color) { + if(color.equalsIgnoreCase("cancel")) { + return Prompt.END_OF_CONVERSATION; + } + + rank.setColor(Color.translate(color)); + rankHandler.save(rank); + + cc.getForWhom().sendRawMessage(Color.translate("&aSuccessfully changed the color of &r" + rank.getColoredDisplay() + "&a.")); + + return Prompt.END_OF_CONVERSATION; + } + }).withLocalEcho(false).withEscapeSequence("/cancel").withTimeout(60).thatExcludesNonPlayersWithMessage("Player's only."); + + Conversation conversation = factory.buildConversation(player); + player.beginConversation(conversation); + } + }); + + buttons.put(slot.getAndIncrement(), new Button() { + @Override + public String getName(Player player) { + return Color.translate("&6Prefix: &f" + rank.getPrefix() + rank.getColor() + player.getName()); + } + + @Override + public List getDescription(Player player) { + return Color.translate(ImmutableList.of( + " ", + "&a&lLeft Click &7to edit prefix" + )); + } + + @Override + public Material getMaterial(Player player) { + return Material.SIGN; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + player.closeInventory(); + ConversationFactory factory = new ConversationFactory(plugin).withModality(true).withPrefix(new NullConversationPrefix()).withFirstPrompt(new StringPrompt() { + + @Override + public String getPromptText(ConversationContext conversationContext) { + return Color.translate("&ePlease type a new prefix for the rank as, or type &c\"cancel\" &eto cancel."); + } + + @Override + public Prompt acceptInput(ConversationContext cc, String prefix) { + if(prefix.equalsIgnoreCase("cancel")) { + return Prompt.END_OF_CONVERSATION; + } + + rank.setPrefix(Color.translate(prefix)); + rankHandler.save(rank); + + cc.getForWhom().sendRawMessage(Color.translate("&aSuccessfully changed the prefix of &r" + rank.getColoredDisplay() + "&a.")); + + return Prompt.END_OF_CONVERSATION; + } + }).withLocalEcho(false).withEscapeSequence("/cancel").withTimeout(60).thatExcludesNonPlayersWithMessage("Player's only."); + + Conversation conversation = factory.buildConversation(player); + player.beginConversation(conversation); + } + }); + + buttons.put(slot.getAndIncrement(), new Button() { + @Override + public String getName(Player player) { + return Color.translate("&6Metadata: &f" + rank.getMeta().size()); + } + + @Override + public List getDescription(Player player) { + return Color.translate(ImmutableList.of( + " ", + "&a&lLeft Click &7to edit meta" + )); + } + + @Override + public Material getMaterial(Player player) { + return Material.ANVIL; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + player.closeInventory(); + new RankMetaEditor(rank).openMenu(player); + } + }); + + buttons.put(slot.getAndIncrement(), new Button() { + @Override + public String getName(Player player) { + return Color.translate("&6Permissions: &f" + rank.getPermissions().size()); + } + + @Override + public List getDescription(Player player) { + return Color.translate(ImmutableList.of( + " ", + "&a&lLeft Click &7to edit permissions" + )); + } + + @Override + public Material getMaterial(Player player) { + return Material.PAPER; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + new RankPermissionEditor(rank).openMenu(player); + } + }); + + buttons.put(slot.getAndIncrement(), new Button() { + @Override + public String getName(Player player) { + return Color.translate("&6Inheritances: &f" + rank.getInherits().size()); + } + + @Override + public List getDescription(Player player) { + return Color.translate(ImmutableList.of( + " ", + "&a&lLeft Click &7to edit inheritances" + )); + } + + @Override + public Material getMaterial(Player player) { + return Material.EMPTY_MAP; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + new RankInheritEditor(rank).openMenu(player); + } + }); + + buttons.put(slot.getAndIncrement(), new Button() { + @Override + public String getName(Player player) { + return Color.translate("&6Proof Meta: &f" + rank.getProofMeta().size()); + } + + @Override + public List getDescription(Player player) { + return Color.translate(ImmutableList.of( + " ", + "&a&lLeft Click &7to edit proof meta" + )); + } + + @Override + public Material getMaterial(Player player) { + return Material.DIAMOND_SWORD; + } + + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + new RankProofEditor(rank).openMenu(player); + } + }); + + return buttons; + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/menu/RankEditorMenu.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/menu/RankEditorMenu.java new file mode 100644 index 0000000..85d1ae5 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/menu/RankEditorMenu.java @@ -0,0 +1,180 @@ +package dev.apposed.prime.spigot.module.rank.menu; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.pagination.PaginatedMenu; +import com.google.common.collect.ImmutableList; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.rank.Rank; +import dev.apposed.prime.spigot.module.rank.RankHandler; +import dev.apposed.prime.spigot.module.rank.meta.RankMeta; +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.conversations.*; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +public class RankEditorMenu extends PaginatedMenu { + + private final Prime plugin = JavaPlugin.getPlugin(Prime.class); + private final RankHandler rankHandler = plugin.getModuleHandler().getModule(RankHandler.class); + + @Override + public String getPrePaginatedTitle(Player player) { + return "Rank Editor"; + } + + @Override + public int getMaxItemsPerPage(Player player) { + return 27; + } + + @Override + public Map getGlobalButtons(Player player) { + final Map buttons = new HashMap<>(); + + buttons.put(4, new Button() { + @Override + public String getName(Player player) { + return Color.translate("&aCreate Rank"); + } + + @Override + public List getDescription(Player player) { + return Collections.emptyList(); + } + + @Override + public Material getMaterial(Player player) { + return Material.NETHER_STAR; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + player.closeInventory(); + ConversationFactory factory = new ConversationFactory(plugin).withModality(true).withPrefix(new NullConversationPrefix()).withFirstPrompt(new StringPrompt() { + + @Override + public String getPromptText(ConversationContext conversationContext) { + return Color.translate("&ePlease type a name for this rank, or type &c\"cancel\" &eto cancel."); + } + + @Override + public Prompt acceptInput(ConversationContext cc, String name) { + if(name.equalsIgnoreCase("cancel")) { + cc.getForWhom().sendRawMessage(ChatColor.RED + "Creation cancelled."); + return END_OF_CONVERSATION; + } + + (new BukkitRunnable() { + @Override + public void run() { + Rank rank = rankHandler.create(new Rank(name)); + new RankEditMenu(rank).openMenu(player); + cc.getForWhom().sendRawMessage(Color.translate("&aCreated rank &r" + rank.getColoredDisplay() + "&a.")); + } + }).runTask(plugin); + return END_OF_CONVERSATION; + } + }).withLocalEcho(false).withEscapeSequence("/cancel").withTimeout(60).thatExcludesNonPlayersWithMessage("Player's only."); + + Conversation conversation = factory.buildConversation(player); + player.beginConversation(conversation); + } + }); + + return buttons; + } + + @Override + public Map getAllPagesButtons(Player player) { + final Map buttons = new HashMap<>(); + + final AtomicInteger slot = new AtomicInteger(0); + this.rankHandler.getCache() + .stream() + .sorted((a, b) -> b.getWeight() - a.getWeight()) + .forEach(rank -> { + buttons.put(slot.getAndIncrement(), new Button() { + @Override + public String getName(Player player) { + return rank.getColoredDisplay(); + } + + @Override + public List getDescription(Player player) { + return Color.translate(ImmutableList.of( + Color.SPACER_SHORT, + "&7Name: &f" + rank.getName(), + "&7Color: &f" + rank.getColor() + rank.getColor().replace("§", ""), + "&7Weight: &f" + rank.getWeight(), + "&7Prefix: &f" + rank.getPrefix() + rank.getColor() + player.getName(), + "&7List Name: &f" + rank.getColor() + player.getName(), + "&7Default: &f" + rank.hasMeta(RankMeta.DEFAULT, true), + "&7Hidden: &f" + rank.hasMeta(RankMeta.HIDDEN, true), + "&7Inheritances: &f" + rank.getInherits().size(), + " ", + "&a&lLeft Click &7to edit", + "&b&lRight Click &7to show details", + "&c&lShift Right Click &7to delete", + Color.SPACER_SHORT + )); + } + + @Override + public Material getMaterial(Player player) { + return rank.getWool().getType(); + } + + @Override + public byte getDamageValue(Player player) { + return (byte) rank.getWool().getDurability(); + } + + @Override + public void clicked(Player player, int slot, ClickType type) { + switch(type) { + case LEFT: { + new RankEditMenu(rank).openMenu(player); + break; + } + case RIGHT: { + Color.translate(Arrays.asList( + Color.SPACER_LONG, + Color.translate("&e&lDetailed information of " + rank.getName()), + Color.translate("&7Name: &f" + rank.getName()), + Color.translate("&7Color: &f" + rank.getColor()) + rank.getColor().replace("§", "&"), + Color.translate("&7Weight: &f" + rank.getWeight()), + Color.translate("&7Prefix: &f" + rank.getPrefix() + rank.getColor() + player.getName()), + Color.translate("&7List Name: &f" + rank.getColor() + player.getName()), + Color.translate("&7Default: &f" + rank.hasMeta(RankMeta.DEFAULT, true)), + Color.translate("&7Hidden: &f" + rank.hasMeta(RankMeta.HIDDEN, true)), + " ", + Color.translate("&e&lInherits: &f" + String.join(", ", rank.getInherits().stream().map(Rank::getName).collect(Collectors.toList()))), + " ", + Color.translate("&e&lPermissions: &f" + String.join(", ", rank.getPermissions())), + Color.SPACER_LONG + )).forEach(player::sendMessage); + break; + } + case SHIFT_RIGHT: { + player.closeInventory(); + rankHandler.delete(rank); + player.sendMessage(Color.translate("&aSuccessfully deleted &r" + rank.getColoredDisplay() + "&a.")); + break; + } + } + } + }); + }); + + + return buttons; + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/menu/RankInheritEditor.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/menu/RankInheritEditor.java new file mode 100644 index 0000000..3601733 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/menu/RankInheritEditor.java @@ -0,0 +1,94 @@ +package dev.apposed.prime.spigot.module.rank.menu; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.pagination.PaginatedMenu; +import com.google.common.collect.ImmutableList; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.rank.Rank; +import dev.apposed.prime.spigot.module.rank.RankHandler; +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +public class RankInheritEditor extends PaginatedMenu { + + private final Prime plugin = Prime.getInstance(); + private final RankHandler rankHandler = plugin.getModuleHandler().getModule(RankHandler.class); + + private final Rank rank; + + public RankInheritEditor(Rank rank) { + this.rank = rank; + setUpdateAfterClick(true); + } + + @Override + public String getPrePaginatedTitle(Player player) { + return "Rank Inheritance Editor"; + } + + @Override + public Map getAllPagesButtons(Player player) { + final Map buttons = new HashMap<>(); + + final AtomicInteger slot = new AtomicInteger(0); + this.rankHandler.getCache() + .stream() + .sorted(Comparator.comparingInt(Rank::getWeight)) + .filter(rank -> !rank.getName().equalsIgnoreCase(this.rank.getName())) + .forEach(inheritRank -> { + buttons.put(slot.getAndIncrement(), new Button() { + @Override + public String getName(Player player) { + return Color.translate(inheritRank.getColoredDisplay()); + } + + @Override + public List getDescription(Player player) { + return Color.translate(ImmutableList.of( + " ", + rank.inherits(inheritRank) ? "&c&lRight Click &7to remove" : "&a&lLeft Click &7to add" + )); + } + + @Override + public Material getMaterial(Player player) { + return inheritRank.getWool().getType(); + } + + @Override + public byte getDamageValue(Player player) { + return (byte) inheritRank.getWool().getDurability(); + } + + @Override + public void clicked(Player player, int slot, ClickType type) { + switch(type) { + case LEFT: { + if(!rank.inherits(inheritRank)) { + rank.getInherits().add(inheritRank); + rankHandler.save(rank); + player.sendMessage(Color.translate("&aSuccessfully added &r" + inheritRank.getColoredDisplay() + " &ato &r" + rank.getColoredDisplay() + "'s &ainheritances.")); + } + break; + } + case RIGHT: { + if(rank.inherits(inheritRank)) { + rank.getInherits().remove(inheritRank); + rankHandler.save(rank); + player.sendMessage(Color.translate("&aSuccessfully removed &r" + inheritRank.getColoredDisplay() + " &afrom &r" + rank.getColoredDisplay() + "'s &ainheritances.")); + } + break; + } + } + } + }); + }); + + return buttons; + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/menu/RankMetaEditor.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/menu/RankMetaEditor.java new file mode 100644 index 0000000..03a855b --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/menu/RankMetaEditor.java @@ -0,0 +1,94 @@ +package dev.apposed.prime.spigot.module.rank.menu; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import com.google.common.collect.ImmutableList; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.rank.Rank; +import dev.apposed.prime.spigot.module.rank.RankHandler; +import dev.apposed.prime.spigot.module.rank.meta.RankMeta; +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +public class RankMetaEditor extends Menu { + + private final Prime plugin = Prime.getInstance(); + private final RankHandler rankHandler = plugin.getModuleHandler().getModule(RankHandler.class); + + private final Rank rank; + + public RankMetaEditor(Rank rank) { + this.rank = rank; + setUpdateAfterClick(true); + } + + @Override + public String getTitle(Player player) { + return "Rank Meta Editor"; + } + + @Override + public Map getButtons(Player player) { + final Map buttons = new HashMap<>(); + + final AtomicInteger slot = new AtomicInteger(0); + for(RankMeta meta : RankMeta.values()) { + buttons.put(slot.getAndIncrement(), new Button() { + @Override + public String getName(Player player) { + return Color.translate("&6" + meta.getDisplay()); + } + + @Override + public List getDescription(Player player) { + return Color.translate(ImmutableList.of( + "&eCurrent: &r" + (rank.hasMeta(meta, true) ? "&aYes" : "&cNo"), + " ", + rank.hasMeta(meta, true) ? "&c&lRight Click &7to remove" : "&a&lLeft Click &7to add" + )); + } + + @Override + public Material getMaterial(Player player) { + return meta.getMaterial(); + } + + @Override + public void clicked(Player player, int slot, ClickType type) { + switch(type) { + + // enable + case LEFT: { + if(!rank.hasMeta(meta, true)) { + rank.getMeta().add(meta); + rankHandler.save(rank); + player.sendMessage(Color.translate("&aSuccessfully added &6" + meta.getDisplay() + " &ato &r" + rank.getColoredDisplay() + "'s &ameta.")); + } + break; + } + + // disable + case RIGHT: { + if(rank.hasMeta(meta, true)) { + rank.getMeta().remove(meta); + rankHandler.save(rank); + player.sendMessage(Color.translate("&aSuccessfully removed &6" + meta.getDisplay() + " &afrom &r" + rank.getColoredDisplay() + "'s &ameta.")); + } + break; + } + } + } + }); + } + + return buttons; + } +} \ No newline at end of file diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/menu/RankPermissionEditor.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/menu/RankPermissionEditor.java new file mode 100644 index 0000000..c0a2233 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/menu/RankPermissionEditor.java @@ -0,0 +1,132 @@ +package dev.apposed.prime.spigot.module.rank.menu; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.pagination.PaginatedMenu; +import com.google.common.collect.ImmutableList; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.rank.Rank; +import dev.apposed.prime.spigot.module.rank.RankHandler; +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.Material; +import org.bukkit.conversations.*; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +public class RankPermissionEditor extends PaginatedMenu { + + private final Prime plugin = Prime.getInstance(); + private final RankHandler rankHandler = plugin.getModuleHandler().getModule(RankHandler.class); + + private final Rank rank; + + public RankPermissionEditor(Rank rank) { + this.rank = rank; + setUpdateAfterClick(true); + } + + @Override + public String getPrePaginatedTitle(Player player) { + return "Rank Permissions Editor"; + } + + @Override + public Map getGlobalButtons(Player player) { + final Map buttons = new HashMap<>(); + + buttons.put(4, new Button() { + @Override + public String getName(Player player) { + return Color.translate("&aAdd Permission"); + } + + @Override + public List getDescription(Player player) { + return Collections.emptyList(); + } + + @Override + public Material getMaterial(Player player) { + return Material.NETHER_STAR; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + player.closeInventory(); + ConversationFactory factory = new ConversationFactory(plugin).withModality(true).withPrefix(new NullConversationPrefix()).withFirstPrompt(new StringPrompt() { + + @Override + public String getPromptText(ConversationContext conversationContext) { + return Color.translate("&ePlease type a new permission to add to the rank, or type &c\"cancel\" &eto cancel."); + } + + @Override + public Prompt acceptInput(ConversationContext cc, String permission) { + if(permission.equalsIgnoreCase("cancel")) { + return Prompt.END_OF_CONVERSATION; + } + + rank.getPermissions().add(permission); + rankHandler.save(rank); + + cc.getForWhom().sendRawMessage(Color.translate("&aSuccessfully added &f" + permission + " &ato &r" + rank.getColoredDisplay() + "'s &apermissions.")); + + (new BukkitRunnable() { + @Override + public void run() { + new RankPermissionEditor(rank).openMenu(player); + } + }).runTask(plugin); + + return Prompt.END_OF_CONVERSATION; + } + }).withLocalEcho(false).withEscapeSequence("/cancel").withTimeout(60).thatExcludesNonPlayersWithMessage("Player's only."); + + Conversation conversation = factory.buildConversation(player); + player.beginConversation(conversation); + } + }); + + return buttons; + } + + @Override + public Map getAllPagesButtons(Player player) { + final Map buttons = new HashMap<>(); + + final AtomicInteger slot = new AtomicInteger(0); + this.rank.getPermissions().forEach(permission -> { + buttons.put(slot.getAndIncrement(), new Button() { + @Override + public String getName(Player player) { + return Color.translate("&e" + permission); + } + + @Override + public List getDescription(Player player) { + return Color.translate(ImmutableList.of( + " ", + "&c&lRight Click &7to remove" + )); + } + + @Override + public Material getMaterial(Player player) { + return Material.PAPER; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + rank.getPermissions().remove(permission); + rankHandler.save(rank); + player.sendMessage(Color.translate("&aSuccessfully removed &f" + permission + " &afrom &r" + rank.getColoredDisplay() + "'s &apermissions.")); + } + }); + }); + + return buttons; + } +} \ No newline at end of file diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/menu/RankProofEditor.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/menu/RankProofEditor.java new file mode 100644 index 0000000..2dd20b4 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/menu/RankProofEditor.java @@ -0,0 +1,96 @@ +package dev.apposed.prime.spigot.module.rank.menu; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.Menu; +import com.google.common.collect.ImmutableList; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.profile.punishment.type.PunishmentType; +import dev.apposed.prime.spigot.module.rank.Rank; +import dev.apposed.prime.spigot.module.rank.RankHandler; +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +public class RankProofEditor extends Menu { + + private final Prime plugin = Prime.getInstance(); + private final RankHandler rankHandler = plugin.getModuleHandler().getModule(RankHandler.class); + + private final Rank rank; + + public RankProofEditor(Rank rank) { + this.rank = rank; + setUpdateAfterClick(true); + } + + @Override + public String getTitle(Player player) { + return "Rank Proof Editor"; + } + + @Override + public Map getButtons(Player player) { + final Map buttons = new HashMap<>(); + + final AtomicInteger slot = new AtomicInteger(0); + for(PunishmentType meta : PunishmentType.values()) { + buttons.put(slot.getAndIncrement(), new Button() { + @Override + public String getName(Player player) { + return Color.translate("&6" + meta.getMenu()); + } + + @Override + public List getDescription(Player player) { + return Color.translate(ImmutableList.of( + "&eCurrent: &r" + (rank.requiresProof(meta) ? "&aYes" : "&cNo"), + " ", + rank.requiresProof(meta) ? "&c&lRight Click &7to remove" : "&a&lLeft Click &7to add" + + )); + } + + @Override + public Material getMaterial(Player player) { + return Material.DIAMOND_SWORD; + } + + @Override + public void clicked(Player player, int slot, ClickType type) { + switch(type) { + + // enable + case LEFT: { + if (!rank.requiresProof(meta)) { + rank.getProofMeta().add(meta); + rankHandler.save(rank); + + player.sendMessage(Color.translate("&aUpdated &r" + rank.getColoredDisplay() + "'s &a proof meta.")); + } + break; + } + + // disable + case RIGHT: { + if (rank.requiresProof(meta)) { + rank.getProofMeta().remove(meta); + rankHandler.save(rank); + + player.sendMessage(Color.translate("&aUpdated &r" + rank.getColoredDisplay() + "'s &a proof meta.")); + } + break; + } + } + } + }); + } + + return buttons; + } +} \ No newline at end of file diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/meta/RankMeta.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/meta/RankMeta.java new file mode 100644 index 0000000..e85e2ea --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/rank/meta/RankMeta.java @@ -0,0 +1,25 @@ +package dev.apposed.prime.spigot.module.rank.meta; + +import lombok.Getter; +import org.bukkit.Material; + +@Getter +public enum RankMeta { + DEFAULT("Default Rank", Material.WORKBENCH), + STAFF("Staff Rank", Material.BOOK), + SERVER("Bungee /server", Material.WOOD_DOOR ), + DONATOR("Donator Rank", Material.EMERALD), + PREFIX("Additional Prefix", Material.PAPER), + VPN_BYPASS("VPN Bypass", Material.FEATHER), + IP_BYPASS("IP Bypass", Material.IRON_BARDING), + HIDDEN("Hidden Rank", Material.IRON_DOOR); + + private String display; + private Material material; + + RankMeta(String display, Material material) { + this.display = display; + this.material = material; + } + +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/Server.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/Server.java new file mode 100644 index 0000000..7bc75cc --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/Server.java @@ -0,0 +1,36 @@ +package dev.apposed.prime.spigot.module.server; + +import lombok.AllArgsConstructor; +import lombok.Data; +import org.bukkit.Bukkit; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +@Data @AllArgsConstructor +public class Server { + + private final String name, group; + private boolean whitelisted; + private int players, maxPlayers; + private long lastHeartbeat; + private boolean chatMuted; + private long chatSlow; + + public boolean isAlive() { + return (System.currentTimeMillis() - lastHeartbeat) <= TimeUnit.SECONDS.toMillis(15L); + } + + public Server(String name, String group) { + this.name = name; + this.group = group; + } + + public void update() { + this.whitelisted = Bukkit.hasWhitelist(); + this.players = Bukkit.getOnlinePlayers().size(); + this.maxPlayers = Bukkit.getMaxPlayers(); + this.lastHeartbeat = System.currentTimeMillis(); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/ServerGroup.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/ServerGroup.java new file mode 100644 index 0000000..d3e22bd --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/ServerGroup.java @@ -0,0 +1,9 @@ +package dev.apposed.prime.spigot.module.server; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter @AllArgsConstructor +public class ServerGroup { + private String id; +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/ServerHandler.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/ServerHandler.java new file mode 100644 index 0000000..07f3310 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/ServerHandler.java @@ -0,0 +1,104 @@ +package dev.apposed.prime.spigot.module.server; + +import com.elevatemc.elib.util.Pair; +import com.google.common.collect.ImmutableMap; +import dev.apposed.prime.spigot.module.Module; +import dev.apposed.prime.spigot.module.server.task.ServerHeartbeatTask; +import lombok.Data; +import org.bukkit.ChatColor; + +import java.util.*; +import java.util.stream.Collectors; + +@Data +public class ServerHandler extends Module { + + private Set servers; + private Set serverGroups; + + private String currentName; + private ServerGroup currentScope; + private final Map> styles = ImmutableMap.of( + "Elevate", new Pair<>(ChatColor.DARK_AQUA, ChatColor.WHITE), + "Gold", new Pair<>(ChatColor.GOLD, ChatColor.WHITE), + "Purple", new Pair<>(ChatColor.DARK_PURPLE, ChatColor.WHITE), + "Red", new Pair<>(ChatColor.RED, ChatColor.WHITE), + "Green", new Pair<>(ChatColor.DARK_GREEN, ChatColor.WHITE) + ); + + @Override + public void onEnable() { + super.onEnable(); + + this.servers = new HashSet<>(); + this.serverGroups = new HashSet<>(); + + this.currentName = getPlugin().getConfig().getString("id"); + + this.servers.add(new Server( + this.currentName, + getPlugin().getConfig().getString("group"))); + + Optional activeGroup = this.getGroupById(this.getCurrentServer().getGroup()); + if(!activeGroup.isPresent()) this.createServerGroup(this.getCurrentServer().getGroup()); + + if(!this.getGroupById("Global").isPresent()) { + this.createServerGroup("Global"); + } + + this.currentScope = this.getGroupById(this.getCurrentServer().getGroup()).get(); + + // Every 3 seconds we need to send and receive server heartbeats + new ServerHeartbeatTask(getPlugin()).runTaskTimerAsynchronously(getPlugin(), 0L, 20L * 3); + } + + public Server getCurrentServer() { + return this.servers.stream().filter(server -> server.getName().equalsIgnoreCase(this.currentName)).findFirst().orElse(null); + } + + public void updateServer(Server receivedServer) { + Server server = this.findByName(receivedServer.getName()).orElse(null); + if(server == null) { + server = new Server(receivedServer.getName(), receivedServer.getGroup()); + this.servers.add(server); + } + + server.setLastHeartbeat(receivedServer.getLastHeartbeat()); + server.setMaxPlayers(receivedServer.getMaxPlayers()); + server.setPlayers(receivedServer.getPlayers()); + server.setWhitelisted(receivedServer.isWhitelisted()); + + ServerGroup group = this.getGroupById(server.getGroup()).orElse(null); + if(group == null) this.serverGroups.add(new ServerGroup(server.getGroup())); + } + + public void createServerGroup(String name) { + this.serverGroups.add(new ServerGroup(name)); + } + + public boolean isActive(ServerGroup group) { + return group.getId().equalsIgnoreCase(this.currentScope.getId()) || group.getId().equalsIgnoreCase("Global"); + } + + public Optional resolveServerGroup(Server server) { + return this.serverGroups.stream().filter(group -> group.getId().equalsIgnoreCase(server.getGroup())).findFirst(); + } + + public Optional getGroupById(String id) { + return this.serverGroups.stream().filter(group -> group.getId().equalsIgnoreCase(id)).findFirst(); + } + + public List getServersWithGroup(ServerGroup group) { + if(group.getId().equalsIgnoreCase("Global")) return new ArrayList<>(servers); + + return this.servers.stream().filter(server -> { + final Optional serverGroup = this.resolveServerGroup(server); + + return serverGroup.map(value -> value.getId().equalsIgnoreCase(group.getId())).orElse(false); + }).collect(Collectors.toList()); + } + + public Optional findByName(String name) { + return this.servers.stream().filter(server -> server.getName().equalsIgnoreCase(name)).findFirst(); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/command/ServerCommands.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/command/ServerCommands.java new file mode 100644 index 0000000..6b014df --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/command/ServerCommands.java @@ -0,0 +1,86 @@ +package dev.apposed.prime.spigot.module.server.command; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import com.elevatemc.elib.util.TimeUtils; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.ModuleHandler; +import dev.apposed.prime.spigot.module.database.redis.JedisModule; +import dev.apposed.prime.spigot.module.server.ServerHandler; +import dev.apposed.prime.spigot.module.server.menu.ServerGroupMenu; +import dev.apposed.prime.spigot.util.Color; +import dev.apposed.prime.spigot.util.time.DurationUtils; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.Arrays; + +public class ServerCommands { + + private static final Prime plugin = Prime.getInstance(); + private static final ModuleHandler moduleHandler = plugin.getModuleHandler(); + + private static final ServerHandler serverHandler = moduleHandler.getModule(ServerHandler.class); + private static final JedisModule jedisModule = moduleHandler.getModule(JedisModule.class); + + @Command(names = {"prime servers"}, permission = "prime.command.servers") + public static void executeServers(Player player) { + new ServerGroupMenu().openMenu(player); + } + + @Command(names = {"server list"}, permission = "prime.command.server.list") + public static void executeList(CommandSender sender) { + serverHandler.getServers().forEach(server -> sender.sendMessage(Color.translate("&7- &e" + server.getName() + " &7(WL: &r" + (server.isWhitelisted() ? "&aYes" : "&cNo") + "&7) (Players: &f" + server.getPlayers() + "/" + server.getMaxPlayers() + "&7) (Alive: &r" + (server.isAlive() ? "&aYes" : "&cNo") + "&7)"))); + } + + @Command(names = {"servergroup list"}, permission = "prime.command.server.list") + public static void executeGroupList(CommandSender sender) { + serverHandler.getServerGroups().forEach(group -> { + sender.sendMessage(Color.translate("&7- &e" + group.getId() + " &7(Servers: &f" + serverHandler.getServersWithGroup(group).size() + "&7)")); + }); + } + + @Command(names = {"motd get"}, permission = "prime.command.motd.get") + public static void executeMotdGet(CommandSender sender) { + jedisModule.runCommand(jedis -> Arrays.asList( + jedis.hget("Prime:MOTD", "1"), + jedis.hget("Prime:MOTD", "2"), + Color.translate("&7Countdown: &f" + TimeUtils.formatIntoDetailedString( + (int)((Long.parseLong(jedis.hget("Prime:MOTD", "countdown")) - System.currentTimeMillis())/1000) + )) + ).forEach(sender::sendMessage)); + } + + @Command(names = {"motd update"}, permission = "prime.command.motd.update") + public static void executeMotdUpdate(CommandSender sender, @Parameter(name = "line") int line, @Parameter(name = "motd", wildcard = true) String text) { + if(line < 1 || line > 2) { + sender.sendMessage(Color.translate("&cThat is not a valid motd line.")); + return; + } + + jedisModule.runCommand(jedis -> jedis.hset("Prime:MOTD", String.valueOf(line), Color.translate(text + "&r"))); + sender.sendMessage(Color.translate("&aSuccessfully updated line " + line + " of the motd.")); + } + + @Command(names = {"motd setcountdown"}, permission = "prime.command.motd.update") + public static void countdown(CommandSender sender, @Parameter(name = "time in millis") long millis) { + jedisModule.runCommand(jedis -> jedis.hset("Prime:MOTD", "countdown", String.valueOf(millis))); + sender.sendMessage(Color.translate("&aSuccessfully updated the countdown date. &aUse the %countdown% &eplaceholder to access.")); + } + + @Command(names = {"mutechat"}, permission = "prime.command.mutechat") + public static void executeMuteChat(CommandSender sender) { + serverHandler.getCurrentServer().setChatMuted(!serverHandler.getCurrentServer().isChatMuted()); + Bukkit.broadcastMessage(Color.translate("&dThe chat has been " + (serverHandler.getCurrentServer().isChatMuted() ? "muted" : "unmuted") + " &dby a staff member.")); + } + + @Command(names = {"slowchat"}, permission = "prime.command.slowchat") + public static void slowchat(CommandSender sender, @Parameter(name = "seconds", defaultValue = "0") int seconds) { + Bukkit.broadcastMessage(Color.translate(seconds == 0 ? "&dThe chat is no longer slowed." : "&dThe chat has been slowed by a staff member.")); + serverHandler.getCurrentServer().setChatSlow(seconds * 1000L); + Bukkit.getOnlinePlayers().stream().filter(player -> player.hasPermission("prime.staff")).forEach(staff -> { + staff.sendMessage(Color.translate("&b[S] &3The chat has been " + (seconds == 0 ? "&aunslowed&3." : "&cslowed &3to &c" + seconds + " &3seconds."))); + }); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/filter/ChatFilter.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/filter/ChatFilter.java new file mode 100644 index 0000000..98b72dc --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/filter/ChatFilter.java @@ -0,0 +1,40 @@ +package dev.apposed.prime.spigot.module.server.filter; + +import lombok.Data; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.regex.Pattern; + +@Data +public class ChatFilter { + + private final UUID id; + private final String description; + private String regex; + + public ChatFilter(String description, String regex) { + this.id = UUID.randomUUID(); + this.description = description; + this.regex = regex; + } + + public ChatFilter(Map map) { + this.id = UUID.fromString(map.get("id")); + this.description = map.get("description"); + this.regex = map.get("pattern"); + } + + public Map toMap() { + final Map map = new HashMap<>(); + map.put("id", id.toString()); + map.put("description", description); + map.put("pattern", getPattern().toString()); + return map; + } + + public Pattern getPattern() { + return Pattern.compile(regex); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/filter/ChatFilterHandler.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/filter/ChatFilterHandler.java new file mode 100644 index 0000000..9824b29 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/filter/ChatFilterHandler.java @@ -0,0 +1,89 @@ +package dev.apposed.prime.spigot.module.server.filter; + +import dev.apposed.prime.packet.ServerUpdatePacket; +import dev.apposed.prime.spigot.module.Module; +import dev.apposed.prime.spigot.module.database.redis.JedisModule; +import dev.apposed.prime.spigot.module.server.ServerHandler; +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.ChatColor; + +import java.util.*; + +public class ChatFilterHandler extends Module { + + private JedisModule jedisModule; + + private Map filters; + + @Override + public void onEnable() { + super.onEnable(); + + this.jedisModule = getModuleHandler().getModule(JedisModule.class); + loadFilters(); + } + + public Collection getFilters() { + return filters.values(); + } + + public void trackFilter(ChatFilter filter) { + filters.put(filter.getId(), filter); + } + + public void forgetFilter(ChatFilter filter) { + filters.remove(filter.getId()); + } + + public void saveFilter(ChatFilter filter) { + jedisModule.runCommand(jedis -> { + jedis.sadd("Prime:ChatFilters.Filters", filter.getId().toString()); + jedis.hmset("Prime:ChatFilters.Filter." + filter.getId(), filter.toMap()); + }); + + sendUpdate(); + } + + public void deleteFilter(ChatFilter filter) { + forgetFilter(filter); + + jedisModule.runCommand(jedis -> { + jedis.srem("Prime:ChatFilters.Filters", filter.getId().toString()); + jedis.del("Prime:ChatFilters:Filter"); + }); + + sendUpdate(); + } + + public void loadFilters() { + jedisModule.runCommand(jedis -> { + final Map map = new HashMap<>(); + + for(String filterId : jedis.smembers("Prime:ChatFilters.Filters")) { + if(jedis.exists("Prime:ChatFilters.Filter." + filterId)) { + final ChatFilter filter = new ChatFilter( + jedis.hgetAll("Prime:ChatFilters.Filter." + filterId)); + map.put(filter.getId(), filter); + } + } + + filters = map; + }); + } + + public ChatFilter filterMessage(String message) { + final String newMessage = ChatColor.stripColor(Color.translate(message)).toLowerCase(); + for(ChatFilter filter : getFilters()) { + if(filter.getPattern().matcher(newMessage).find()) { + return filter; + } + } + + return null; + } + + private void sendUpdate() { + jedisModule.sendPacket(new ServerUpdatePacket(getModuleHandler().getModule(ServerHandler.class).getCurrentName())); + } + +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/filter/command/FilterCommands.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/filter/command/FilterCommands.java new file mode 100644 index 0000000..a41458c --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/filter/command/FilterCommands.java @@ -0,0 +1,71 @@ +package dev.apposed.prime.spigot.module.server.filter.command; + +import com.elevatemc.elib.command.Command; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.ModuleHandler; +import dev.apposed.prime.spigot.module.database.redis.JedisModule; +import dev.apposed.prime.spigot.module.server.ServerHandler; +import dev.apposed.prime.spigot.module.server.filter.ChatFilter; +import dev.apposed.prime.spigot.module.server.filter.ChatFilterHandler; +import dev.apposed.prime.spigot.module.server.filter.menu.ChatFilterEditor; +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; + +import java.util.Arrays; +import java.util.List; + +public class FilterCommands { + + private static final Prime plugin = Prime.getInstance(); + private static final ModuleHandler moduleHandler = plugin.getModuleHandler(); + + private static final ServerHandler serverHandler = moduleHandler.getModule(ServerHandler.class); + private static final JedisModule jedisModule = moduleHandler.getModule(JedisModule.class); + private static final ChatFilterHandler filterHandler = moduleHandler.getModule(ChatFilterHandler.class); + + @Command(names = {"prime chat-filter import-defaults"}, description = "Import default chat filter", permission = "op") + public static void importDefaults(CommandSender sender) { + if(!(sender instanceof ConsoleCommandSender)) { + sender.sendMessage(Color.translate("&cThis command must be executed through console.")); + return; + } + + for(ChatFilter filter : defaults) { + filterHandler.trackFilter(filter); + filterHandler.saveFilter(filter); + } + } + + @Command(names = {"prime chat-filter editor"}, description = "Open the chat filter editor", permission = "op") + public static void editor(Player player) { + new ChatFilterEditor().openMenu(player); + } + + private static List defaults = Arrays.asList( + new ChatFilter( + "Restricted Phrase \"ip farm\"", + "[i1l1|]+p+ ?f[a4]+rm+"), + new ChatFilter("Restricted Phrase \"dupe\"", + "(dupe)|(duplication)"), + new ChatFilter("Racism \"Nigger\"", + "n+[i1l|]+gg+[e3]+r+"), + new ChatFilter("Racism \"Beaner\"", + "b+[e3]+[a4]+n+[e3]+r+"), + new ChatFilter("Suicide Encouragement", + "k+i+l+l+ *y*o*u+r+ *s+e+l+f+"), + new ChatFilter("Suicide Encouragement", + "\\bk+y+s+\\b"), + new ChatFilter("Offensive \"Faggot\"", + "f+[a4]+g+[o0]+t+"), + new ChatFilter("IP Address", + "(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])([.,])){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])"), + new ChatFilter("Phishing Link \"optifine\"", + "optifine\\.(?=\\w+)(?!net)"), + new ChatFilter("Phishing Link \"gyazo\"", + "gyazo\\.(?=\\w+)(?!com)"), + new ChatFilter("Phishing Link \"prntscr\"", + "prntscr\\.(?=\\w+)(?!com)") + ); +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/filter/listener/ChatFilterListener.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/filter/listener/ChatFilterListener.java new file mode 100644 index 0000000..9333d5d --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/filter/listener/ChatFilterListener.java @@ -0,0 +1,75 @@ +package dev.apposed.prime.spigot.module.server.filter.listener; + +import com.elevatemc.elib.util.Pair; +import dev.apposed.prime.spigot.module.listener.ListenerModule; +import dev.apposed.prime.spigot.module.server.filter.ChatFilter; +import dev.apposed.prime.spigot.module.server.filter.ChatFilterHandler; +import dev.apposed.prime.spigot.util.Color; +import net.md_5.bungee.api.chat.ComponentBuilder; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.TextComponent; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class ChatFilterListener extends ListenerModule { + + private final Map> badMessages = new HashMap<>(); + + private final ChatFilterHandler filterHandler = getModuleHandler().getModule(ChatFilterHandler.class); + + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerChatLowest(AsyncPlayerChatEvent event) { + if(event.getPlayer().hasPermission("prime.filter.bypass")) return; + final ChatFilter filter = filterHandler.filterMessage(event.getMessage()); + if(filter == null) return; + badMessages.put(event.getPlayer().getUniqueId(), new Pair(event.getMessage(), filter)); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerChatMonitor(AsyncPlayerChatEvent event) { + final Player player = event.getPlayer(); + if(!badMessages.containsKey(player.getUniqueId())) return; + + event.getRecipients().clear(); + event.getRecipients().add(player); + + final TextComponent component = new TextComponent(Color.translate("&c&l[Filtered] ")); + component.addExtra(event.getFormat()); + component.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, + new ComponentBuilder("§eThis message was hidden from public chat.\n§cFilter: " + badMessages.get(player.getUniqueId()).getValue().getDescription()).create())); + + final ChatFilter filter = badMessages.get(player.getUniqueId()).getValue(); + if(filter.getDescription().contains("Racism")) { + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), String.format("mute %s Racism", player.getName())); + } + if(filter.getDescription().contains("Offensive")) { + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), String.format("mute %s Homophobia", player.getName())); + } + + Bukkit.getOnlinePlayers().stream().filter(staff -> staff.hasPermission("prime.staff")).forEach(staff -> { + staff.spigot().sendMessage(component); + }); + + badMessages.remove(player.getUniqueId()); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onQuit(PlayerQuitEvent event) { + badMessages.remove(event.getPlayer().getUniqueId()); + final Player player = event.getPlayer(); + if(player.hasMetadata("frozen")) + Bukkit.getOnlinePlayers() + .stream() + .filter(staff -> staff.hasPermission("prime.staff")) + .forEach(staff -> staff.sendMessage(ChatColor.RED + ChatColor.BOLD.toString() + player.getName() + " logged out whilst frozen.")); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/filter/menu/ChatFilterEditor.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/filter/menu/ChatFilterEditor.java new file mode 100644 index 0000000..c61587b --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/filter/menu/ChatFilterEditor.java @@ -0,0 +1,237 @@ +package dev.apposed.prime.spigot.module.server.filter.menu; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.pagination.PaginatedMenu; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.profile.grant.menu.GrantMenu; +import dev.apposed.prime.spigot.module.rank.Rank; +import dev.apposed.prime.spigot.module.rank.menu.RankEditMenu; +import dev.apposed.prime.spigot.module.server.filter.ChatFilter; +import dev.apposed.prime.spigot.module.server.filter.ChatFilterHandler; +import dev.apposed.prime.spigot.util.Color; +import dev.apposed.prime.spigot.util.time.DurationUtils; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.conversations.*; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Pattern; + +public class ChatFilterEditor extends PaginatedMenu { + + private final Prime plugin = Prime.getInstance(); + private final ChatFilterHandler filterHandler = plugin.getModuleHandler().getModule(ChatFilterHandler.class); + + public ChatFilterEditor() { + setUpdateAfterClick(true); + } + + @Override + public String getPrePaginatedTitle(Player player) { + return "Chat Filters"; + } + + @Override + public Map getGlobalButtons(Player player) { + final Map buttons = new HashMap<>(); + + buttons.put(4, new AddChatFilterButton()); + + return buttons; + } + + @Override + public int getMaxItemsPerPage(Player player) { + return 36; + } + + @Override + public Map getAllPagesButtons(Player player) { + final Map buttons = new HashMap<>(); + + final AtomicInteger slot = new AtomicInteger(0); + filterHandler.getFilters().forEach(filter -> buttons.put(slot.getAndIncrement(), new ChatFilterButton(filter))); + + return buttons; + } + + private class AddChatFilterButton extends Button { + + @Override + public String getName(Player player) { + return Color.translate("&aCreate Chat Filter"); + } + + @Override + public List getDescription(Player player) { + return Collections.emptyList(); + } + + @Override + public Material getMaterial(Player player) { + return Material.NETHER_STAR; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + if(!clickType.isLeftClick()) return; + player.closeInventory(); + ConversationFactory factory = new ConversationFactory(plugin).withModality(true).withPrefix(new NullConversationPrefix()).withFirstPrompt(new StringPrompt() { + + @Override + public String getPromptText(ConversationContext conversationContext) { + return Color.translate("&ePlease type a new chat filter regex pattern, or type &c\"cancel\" &eto cancel."); + } + + @Override + public Prompt acceptInput(ConversationContext cc, String pattern) { + if(pattern.equalsIgnoreCase("cancel")) { + cc.getForWhom().sendRawMessage(ChatColor.RED + "Creation cancelled."); + return END_OF_CONVERSATION; + } + + (new BukkitRunnable() { + @Override + public void run() { + ConversationFactory factory = new ConversationFactory(plugin).withModality(true).withPrefix(new NullConversationPrefix()).withFirstPrompt(new StringPrompt() { + + @Override + public String getPromptText(ConversationContext conversationContext) { + return Color.translate("&ePlease type the chat filter's description, or type &c\"cancel\" &eto cancel."); + } + + @Override + public Prompt acceptInput(ConversationContext cc, String description) { + if(description.equalsIgnoreCase("cancel")) { + cc.getForWhom().sendRawMessage(ChatColor.RED + "Creation cancelled."); + return END_OF_CONVERSATION; + } + + final ChatFilter filter = new ChatFilter(description, pattern); + + filterHandler.trackFilter(filter); + + (new BukkitRunnable() { + @Override + public void run() { + filterHandler.saveFilter(filter); + } + }).runTaskAsynchronously(plugin); + + (new BukkitRunnable(){ + @Override + public void run() { + ChatFilterEditor.this.openMenu(player); + } + }).runTask(plugin); + + return END_OF_CONVERSATION; + } + }).withLocalEcho(false).withEscapeSequence("/cancel").withTimeout(60).thatExcludesNonPlayersWithMessage("Player's only."); + + Conversation conversation = factory.buildConversation(player); + player.beginConversation(conversation); + } + }).runTask(plugin); + return END_OF_CONVERSATION; + } + }).withLocalEcho(false).withEscapeSequence("/cancel").withTimeout(60).thatExcludesNonPlayersWithMessage("Player's only."); + + Conversation conversation = factory.buildConversation(player); + player.beginConversation(conversation); + } + } + + private class ChatFilterButton extends Button { + + private final ChatFilter filter; + + public ChatFilterButton(ChatFilter filter) { + this.filter = filter; + } + + @Override + public String getName(Player player) { + return Color.translate("&e&l" + filter.getDescription()); + } + + @Override + public List getDescription(Player player) { + return Color.translate(Arrays.asList( + "&7Pattern: " + filter.getPattern().pattern(), + " ", + "&a&lLeft Click &7to edit pattern", + "&c&lRight Click &7to delete filter" + )); + } + + @Override + public Material getMaterial(Player player) { + return Material.PAPER; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + player.closeInventory(); + if(clickType.isLeftClick()) { + ConversationFactory factory = new ConversationFactory(plugin).withModality(true).withPrefix(new NullConversationPrefix()).withFirstPrompt(new StringPrompt() { + + @Override + public String getPromptText(ConversationContext conversationContext) { + return Color.translate("&ePlease type a new regex pattern, or type &c\"cancel\" &eto cancel."); + } + + @Override + public Prompt acceptInput(ConversationContext cc, String pattern) { + if(pattern.equalsIgnoreCase("cancel")) { + cc.getForWhom().sendRawMessage(ChatColor.RED + "Editing cancelled."); + return END_OF_CONVERSATION; + } + + filter.setRegex(pattern); + + (new BukkitRunnable() { + @Override + public void run() { + filterHandler.saveFilter(filter); + } + }).runTaskAsynchronously(plugin); + + (new BukkitRunnable() { + @Override + public void run() { + ChatFilterEditor.this.openMenu(player); + } + }).runTask(plugin); + return END_OF_CONVERSATION; + } + }).withLocalEcho(false).withEscapeSequence("/cancel").withTimeout(60).thatExcludesNonPlayersWithMessage("Player's only."); + + Conversation conversation = factory.buildConversation(player); + player.beginConversation(conversation); + return; + } + + if(clickType.isRightClick()) { + (new BukkitRunnable() { + @Override + public void run() { + filterHandler.deleteFilter(filter); + } + }).runTaskAsynchronously(plugin); + + (new BukkitRunnable() { + @Override + public void run() { + ChatFilterEditor.this.openMenu(player); + } + }).runTaskLater(plugin, 10L); + return; + } + } + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/menu/ServerGroupMenu.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/menu/ServerGroupMenu.java new file mode 100644 index 0000000..dd859d7 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/menu/ServerGroupMenu.java @@ -0,0 +1,78 @@ +package dev.apposed.prime.spigot.module.server.menu; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.pagination.PaginatedMenu; +import com.google.common.collect.ImmutableList; +import dev.apposed.prime.packet.ServerUpdatePacket; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.database.redis.JedisModule; +import dev.apposed.prime.spigot.module.server.ServerHandler; +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +public class ServerGroupMenu extends PaginatedMenu { + + private final Prime plugin = Prime.getInstance(); + private final ServerHandler serverHandler = plugin.getModuleHandler().getModule(ServerHandler.class); + private final JedisModule jedisModule = plugin.getModuleHandler().getModule(JedisModule.class); + + @Override + public String getPrePaginatedTitle(Player player) { + return "Prime Server Groups"; + } + + @Override + public Map getAllPagesButtons(Player player) { + final Map buttons = new HashMap<>(); + + final AtomicInteger slot = new AtomicInteger(0); + this.serverHandler.getServerGroups().forEach(group -> { + buttons.put(slot.getAndIncrement(), new Button() { + @Override + public String getName(Player player) { + return Color.translate("&6&l" + group.getId() + (serverHandler.isActive(group) ? " &a(Active)" : "")); + } + + @Override + public List getDescription(Player player) { + return Color.translate(ImmutableList.of( + "&eServers: &7" + serverHandler.getServersWithGroup(group).size(), + " ", + "&a&lRight Click &7to reload server configs" + )); + } + + @Override + public Material getMaterial(Player player) { + return Material.HOPPER; + } + + @Override + public void clicked(Player player, int slot, ClickType type) { + switch(type) { + case LEFT: { + new ServerMenu(group).openMenu(player); + break; + } + case RIGHT: { + serverHandler.getServersWithGroup(group).forEach(server -> jedisModule.sendPacket(new ServerUpdatePacket( + server.getName() + ))); + player.sendMessage(Color.translate("&aSent a redis packet to reload &e" + serverHandler.getServersWithGroup(group).size() + " &aservers with the group &e" + group.getId() + "&a.")); + break; + } + } + } + }); + }); + + return buttons; + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/menu/ServerMenu.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/menu/ServerMenu.java new file mode 100644 index 0000000..0c3c4a4 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/menu/ServerMenu.java @@ -0,0 +1,88 @@ +package dev.apposed.prime.spigot.module.server.menu; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.pagination.PaginatedMenu; +import com.google.common.collect.ImmutableList; +import dev.apposed.prime.packet.ServerUpdatePacket; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.database.redis.JedisModule; +import dev.apposed.prime.spigot.module.server.ServerGroup; +import dev.apposed.prime.spigot.module.server.ServerHandler; +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +public class ServerMenu extends PaginatedMenu { + + private final Prime plugin = Prime.getInstance(); + private final ServerHandler serverHandler = plugin.getModuleHandler().getModule(ServerHandler.class); + private final JedisModule jedisModule = plugin.getModuleHandler().getModule(JedisModule.class); + + private final ServerGroup group; + + public ServerMenu(ServerGroup group) { + this.group = group; + setUpdateAfterClick(true); + } + + @Override + public String getPrePaginatedTitle(Player player) { + return "Prime Servers (" + group.getId() + ")"; + } + + @Override + public int getMaxItemsPerPage(Player player) { + return 9; + } + + @Override + public Map getAllPagesButtons(Player player) { + final Map buttons = new HashMap<>(); + + final AtomicInteger slot = new AtomicInteger(0); + this.serverHandler.getServersWithGroup(this.group).forEach(server -> { + buttons.put(slot.getAndIncrement(), new Button() { + @Override + public String getName(Player player) { + return Color.translate("&6&l" + server.getName()); + } + + @Override + public List getDescription(Player player) { + return Color.translate(ImmutableList.of( + "&7(ID: " + server.getName() + ")", + " ", + "&7Players: &f" + server.getPlayers() + "/" + server.getMaxPlayers(), + "&7Whitelisted: &r" + (server.isWhitelisted() ? "&aYes" : "&cNo"), + "&7Alive: &r" + (server.isAlive() ? "&aYes" : "&cNo"), + "&7Last Heartbeat: &f" + ((System.currentTimeMillis() - server.getLastHeartbeat()) / 1000) + "s ago", + " ", + "&a&lLeft Click &7to reload config" + )); + } + + @Override + public Material getMaterial(Player player) { + return Material.WATER_LILY; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + jedisModule.sendPacket(new ServerUpdatePacket( + server.getName() + )); + player.sendMessage(Color.translate("&aSent a redis packet to reload &e" + server.getName() + "&a.")); + + } + }); + }); + + return buttons; + } +} \ No newline at end of file diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/scoreboard/PrimeScoreboardStyle.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/scoreboard/PrimeScoreboardStyle.java new file mode 100644 index 0000000..b780d9a --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/scoreboard/PrimeScoreboardStyle.java @@ -0,0 +1,32 @@ +package dev.apposed.prime.spigot.module.server.scoreboard; + +import com.elevatemc.elib.util.Pair; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import dev.apposed.prime.spigot.module.server.ServerHandler; +import lombok.experimental.UtilityClass; +import org.apache.commons.lang.StringUtils; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.Optional; + +@UtilityClass +public class PrimeScoreboardStyle { + + private final ProfileHandler profileHandler = Prime.getInstance().getModuleHandler().getModule(ProfileHandler.class); + private final ServerHandler serverHandler = Prime.getInstance().getModuleHandler().getModule(ServerHandler.class); + + public Pair getStyle(Player player) { + final Optional profileOptional = profileHandler.getProfile(player.getUniqueId()); + if(!profileOptional.isPresent()) return DEFAULT_STYLE; + final Profile profile = profileOptional.get(); + if(!profile.hasStyle()) return DEFAULT_STYLE; + + final Pair activeStyle = serverHandler.getStyles().get(StringUtils.capitalize(profile.getStyle().toLowerCase())); + return activeStyle == null ? DEFAULT_STYLE : activeStyle; + } + + public Pair DEFAULT_STYLE = new Pair<>(ChatColor.DARK_AQUA, ChatColor.WHITE); +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/task/ServerHeartbeatTask.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/task/ServerHeartbeatTask.java new file mode 100644 index 0000000..f2d361d --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/server/task/ServerHeartbeatTask.java @@ -0,0 +1,30 @@ +package dev.apposed.prime.spigot.module.server.task; + +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.database.redis.JedisModule; +import dev.apposed.prime.spigot.module.server.Server; +import dev.apposed.prime.spigot.module.server.ServerHandler; +import dev.apposed.prime.packet.ServerHeartbeatPacket; +import org.bukkit.scheduler.BukkitRunnable; + +public class ServerHeartbeatTask extends BukkitRunnable { + + private final Prime plugin; + + private final JedisModule jedisModule; + private final ServerHandler serverHandler; + + public ServerHeartbeatTask(Prime plugin) { + this.plugin = plugin; + + this.jedisModule = plugin.getModuleHandler().getModule(JedisModule.class); + this.serverHandler = plugin.getModuleHandler().getModule(ServerHandler.class); + } + + @Override + public void run() { + Server server = this.serverHandler.getCurrentServer(); + server.update(); + this.jedisModule.sendPacket(new ServerHeartbeatPacket(server)); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/tag/Tag.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/tag/Tag.java new file mode 100644 index 0000000..722e76e --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/tag/Tag.java @@ -0,0 +1,32 @@ +package dev.apposed.prime.spigot.module.tag; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.Objects; + +@Data @AllArgsConstructor +public class Tag { + + private final String id; + private String displayName; + private String display; + + public Tag(String id) { + this.id = id; + this.displayName = id; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Tag tag = (Tag) o; + return Objects.equals(id, tag.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/tag/TagHandler.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/tag/TagHandler.java new file mode 100644 index 0000000..941cc37 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/tag/TagHandler.java @@ -0,0 +1,66 @@ +package dev.apposed.prime.spigot.module.tag; + +import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.Filters; +import dev.apposed.prime.spigot.module.Module; +import dev.apposed.prime.spigot.module.database.mongo.MongoModule; +import dev.apposed.prime.spigot.util.json.JsonHelper; +import lombok.Getter; +import org.bson.Document; + +import java.util.HashMap; +import java.util.Map; + +@Getter +public class TagHandler extends Module { + + private Map tags; + private MongoCollection collection; + + @Override + public void onEnable() { + super.onEnable(); + + this.tags = new HashMap<>(); + this.collection = this.getModuleHandler().getModule(MongoModule.class).getMongoDatabase().getCollection("tags"); + for(Document document : this.collection.find()) { + final Tag tag = JsonHelper.GSON.fromJson(JsonHelper.GSON.toJson(document), Tag.class); + tags.put(tag.getId().toUpperCase(), tag); + } + } + + @Override + public void onDisable() { + super.onDisable(); + this.tags.values().forEach(tag -> { + this.collection.replaceOne( + Filters.eq("_id", tag.getId()), + Document.parse(JsonHelper.GSON.toJson(tag)), + JsonHelper.REPLACE_OPTIONS); + }); + } + + public void create(Tag tag) { + this.tags.put(tag.getId().toUpperCase(), tag); + new Thread(() -> { + this.collection.replaceOne( + Filters.eq("_id", tag.getId()), + Document.parse(JsonHelper.GSON.toJson(tag)), + JsonHelper.REPLACE_OPTIONS); + }).start(); + } + + public Tag getTag(String id) { + return tags.get(id); + } + + public void delete(Tag tag) { + final Tag removedTag = tags.remove(tag.getId()); + if(removedTag == null) return; + new Thread(() -> { + this.collection.deleteOne(Filters.eq("_id", removedTag.getId())); + }).start(); + } + + public static final String BASE_PERMISSION = "prime.tag."; +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/tag/adapter/TagTypeAdapter.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/tag/adapter/TagTypeAdapter.java new file mode 100644 index 0000000..a2ab81e --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/tag/adapter/TagTypeAdapter.java @@ -0,0 +1,39 @@ +package dev.apposed.prime.spigot.module.tag.adapter; + +import com.elevatemc.elib.command.param.ParameterType; +import dev.apposed.prime.spigot.module.tag.Tag; +import dev.apposed.prime.spigot.module.tag.TagHandler; +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class TagTypeAdapter implements ParameterType { + + private TagHandler tagHandler; + + public TagTypeAdapter(TagHandler tagHandler) { + this.tagHandler = tagHandler; + } + + @Override + public Tag transform(CommandSender commandSender, String s) { + final Tag tag = tagHandler.getTag(s.toUpperCase()); + if(tag == null) { + commandSender.sendMessage(Color.translate("&cCould not find a tag by that name.")); + return null; + } + + return tag; + } + + @Override + public List tabComplete(Player player, Set set, String s) { + return new ArrayList<>(tagHandler + .getTags() + .keySet()); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/tag/command/TagCommands.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/tag/command/TagCommands.java new file mode 100644 index 0000000..2a85aef --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/tag/command/TagCommands.java @@ -0,0 +1,54 @@ +package dev.apposed.prime.spigot.module.tag.command; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.param.Parameter; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.ModuleHandler; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import dev.apposed.prime.spigot.module.tag.Tag; +import dev.apposed.prime.spigot.module.tag.TagHandler; +import dev.apposed.prime.spigot.module.tag.menu.TagMenu; +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class TagCommands { + + private static final Prime plugin = Prime.getInstance(); + private static final ModuleHandler moduleHandler = plugin.getModuleHandler(); + + private static final TagHandler tagHandler = moduleHandler.getModule(TagHandler.class); + private static final ProfileHandler profileHandler = moduleHandler.getModule(ProfileHandler.class); + + @Command(names = {"tag", "tags", "prefix"}, permission = "prime.command.tag") + public static void select(Player player) { + new TagMenu().openMenu(player); + } + + @Command(names = {"tag list", "tags list"}, permission = "op") + public static void list(CommandSender sender) { + sender.sendMessage(Color.translate("&6Prime Tags -")); + tagHandler.getTags().values().forEach(tag -> { + sender.sendMessage(String.format(Color.translate("ID: %s&r, Display Name: %s&r, Display: %s&r"), tag.getId(), Color.translate(tag.getDisplayName()), Color.translate(tag.getDisplay()))); + }); + } + + @Command(names = {"tag create", "tags create"}, permission = "op") + public static void create(CommandSender sender, @Parameter(name = "id") String id, @Parameter(name = "display") String display, @Parameter(name = "displayName", wildcard = true) String displayName) { + if(tagHandler.getTag(id) != null) { + sender.sendMessage(Color.translate("&cA tag with that id already exists")); + return; + } + + final Tag tag = new Tag(id, displayName, display); + tagHandler.create(tag); + + sender.sendMessage(Color.translate("&aCreated " + tag.getDisplayName())); + } + + @Command(names = {"tag delete", "tags delete", "tag remove", "tags remove"}, permission = "op") + public static void delete(CommandSender sender, @Parameter(name = "tag") Tag tag) { + tagHandler.delete(tag); + sender.sendMessage(Color.translate("&aDeleted " + tag.getDisplayName())); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/tag/menu/TagMenu.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/tag/menu/TagMenu.java new file mode 100644 index 0000000..f547da4 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/tag/menu/TagMenu.java @@ -0,0 +1,102 @@ +package dev.apposed.prime.spigot.module.tag.menu; + +import com.elevatemc.elib.menu.Button; +import com.elevatemc.elib.menu.pagination.PaginatedMenu; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.ProfileHandler; +import dev.apposed.prime.spigot.module.tag.TagHandler; +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +public class TagMenu extends PaginatedMenu { + + private final Prime plugin = JavaPlugin.getPlugin(Prime.class); + private final ProfileHandler profileHandler = plugin.getModuleHandler().getModule(ProfileHandler.class); + private final TagHandler tagHandler = plugin.getModuleHandler().getModule(TagHandler.class); + + public TagMenu() { + this.setUpdateAfterClick(true); + } + + @Override + public String getPrePaginatedTitle(Player player) { + return "Tags"; + } + + @Override + public Map getAllPagesButtons(Player player) { + final Map buttons = new HashMap<>(); + + final Profile profile = profileHandler.getProfile(player.getUniqueId()).orElse(null); + if(profile == null) { + buttons.put(0, new Button() { + @Override + public String getName(Player player) { + return Color.translate("&cError"); + } + + @Override + public List getDescription(Player player) { + return Collections.singletonList("Failed to fetch user's profile"); + } + + @Override + public Material getMaterial(Player player) { + return Material.PAPER; + } + }); + return buttons; + } + + final AtomicInteger slot = new AtomicInteger(0); + tagHandler.getTags().values().stream().filter(tag -> player.hasPermission(TagHandler.BASE_PERMISSION + tag.getId())).forEach(tag -> { + buttons.put(slot.getAndIncrement(), new Button() { + + @Override + public String getName(Player player) { + return Color.translate(tag.getDisplay()); + } + + @Override + public List getDescription(Player player) { + final List description = new ArrayList<>(); + if(player.hasPermission(TagHandler.BASE_PERMISSION + tag.getId())) + description.add("&7Click to " + (profile.hasActiveTag() && profile.getActiveTag() == tag ? "&cunequip" : "&aequip") + " &7" + tag.getDisplayName()); + else + description.add("&cYou do not have permission to use this tag"); + return Color.translate(description); + } + + @Override + public Material getMaterial(Player player) { + return Material.NAME_TAG; + } + + @Override + public void clicked(Player player, int slot, ClickType clickType) { + if (clickType != ClickType.LEFT) return; + if (profile.hasActiveTag() && profile.getActiveTag() == tag) { + player.sendMessage(Color.translate("&aYou have unequipped the &r" + tag.getDisplayName() + "&a tag!")); + profile.setTag(null); + } else if (!player.hasPermission(TagHandler.BASE_PERMISSION + tag.getId())) { + player.sendMessage(Color.translate("&cYou do not have permission to equip this tag!")); + return; + } else { + player.sendMessage(Color.translate("&aYou have equipped the &r" + tag.getDisplayName() + " &atag!")); + profile.setTag(tag.getId()); + } + profileHandler.sendSync(profile); + } + }); + }); + + return buttons; + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/webhook/DiscordWebhook.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/webhook/DiscordWebhook.java new file mode 100644 index 0000000..b03a28c --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/webhook/DiscordWebhook.java @@ -0,0 +1,391 @@ +package dev.apposed.prime.spigot.module.webhook; + +import javax.net.ssl.HttpsURLConnection; +import java.awt.Color; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Array; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Class used to execute Discord Webhooks with low effort + */ +public class DiscordWebhook { + + private final String url; + private String content; + private String username; + private String avatarUrl; + private boolean tts; + private List embeds = new ArrayList<>(); + + /** + * Constructs a new DiscordWebhook instance + * + * @Param url The webhook URL obtained in Discord + */ + public DiscordWebhook(String url) { + this.url = url; + } + + public void setContent(String content) { + this.content = content; + } + + public void setUsername(String username) { + this.username = username; + } + + public void setAvatarUrl(String avatarUrl) { + this.avatarUrl = avatarUrl; + } + + public void setTts(boolean tts) { + this.tts = tts; + } + + public void addEmbed(EmbedObject embed) { + this.embeds.add(embed); + } + + public void execute() throws IOException { + if (this.content == null && this.embeds.isEmpty()) { + throw new IllegalArgumentException("Set content or add at least one EmbedObject"); + } + + JSONObject json = new JSONObject(); + + json.put("content", this.content); + json.put("username", this.username); + json.put("avatar_url", this.avatarUrl); + json.put("tts", this.tts); + + if (!this.embeds.isEmpty()) { + List embedObjects = new ArrayList<>(); + + for (EmbedObject embed : this.embeds) { + JSONObject jsonEmbed = new JSONObject(); + + jsonEmbed.put("title", embed.getTitle()); + jsonEmbed.put("description", embed.getDescription()); + jsonEmbed.put("url", embed.getUrl()); + + if (embed.getColor() != null) { + Color color = embed.getColor(); + int rgb = color.getRed(); + rgb = (rgb << 8) + color.getGreen(); + rgb = (rgb << 8) + color.getBlue(); + + jsonEmbed.put("color", rgb); + } + + EmbedObject.Footer footer = embed.getFooter(); + EmbedObject.Image image = embed.getImage(); + EmbedObject.Thumbnail thumbnail = embed.getThumbnail(); + EmbedObject.Author author = embed.getAuthor(); + List fields = embed.getFields(); + + if (footer != null) { + JSONObject jsonFooter = new JSONObject(); + + jsonFooter.put("text", footer.getText()); + jsonFooter.put("icon_url", footer.getIconUrl()); + jsonEmbed.put("footer", jsonFooter); + } + + if (image != null) { + JSONObject jsonImage = new JSONObject(); + + jsonImage.put("url", image.getUrl()); + jsonEmbed.put("image", jsonImage); + } + + if (thumbnail != null) { + JSONObject jsonThumbnail = new JSONObject(); + + jsonThumbnail.put("url", thumbnail.getUrl()); + jsonEmbed.put("thumbnail", jsonThumbnail); + } + + if (author != null) { + JSONObject jsonAuthor = new JSONObject(); + + jsonAuthor.put("name", author.getName()); + jsonAuthor.put("url", author.getUrl()); + jsonAuthor.put("icon_url", author.getIconUrl()); + jsonEmbed.put("author", jsonAuthor); + } + + List jsonFields = new ArrayList<>(); + for (EmbedObject.Field field : fields) { + JSONObject jsonField = new JSONObject(); + + jsonField.put("name", field.getName()); + jsonField.put("value", field.getValue()); + jsonField.put("inline", field.isInline()); + + jsonFields.add(jsonField); + } + + jsonEmbed.put("fields", jsonFields.toArray()); + embedObjects.add(jsonEmbed); + } + + json.put("embeds", embedObjects.toArray()); + } + + URL url = new URL(this.url); + HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); + connection.addRequestProperty("Content-Type", "application/json"); + connection.addRequestProperty("User-Agent", "Java-DiscordWebhook-BY-Gelox_"); + connection.setDoOutput(true); + connection.setRequestMethod("POST"); + + OutputStream stream = connection.getOutputStream(); + stream.write(json.toString().getBytes()); + stream.flush(); + stream.close(); + + connection.getInputStream().close(); //I'm not sure why but it doesn't work without getting the InputStream + connection.disconnect(); + } + + public static class EmbedObject { + private String title; + private String description; + private String url; + private Color color; + + private Footer footer; + private Thumbnail thumbnail; + private Image image; + private Author author; + private List fields = new ArrayList<>(); + + public String getTitle() { + return title; + } + + public String getDescription() { + return description; + } + + public String getUrl() { + return url; + } + + public Color getColor() { + return color; + } + + public Footer getFooter() { + return footer; + } + + public Thumbnail getThumbnail() { + return thumbnail; + } + + public Image getImage() { + return image; + } + + public Author getAuthor() { + return author; + } + + public List getFields() { + return fields; + } + + public EmbedObject setTitle(String title) { + this.title = title; + return this; + } + + public EmbedObject setDescription(String description) { + this.description = description; + return this; + } + + public EmbedObject setUrl(String url) { + this.url = url; + return this; + } + + public EmbedObject setColor(Color color) { + this.color = color; + return this; + } + + public EmbedObject setFooter(String text, String icon) { + this.footer = new Footer(text, icon); + return this; + } + + public EmbedObject setThumbnail(String url) { + this.thumbnail = new Thumbnail(url); + return this; + } + + public EmbedObject setImage(String url) { + this.image = new Image(url); + return this; + } + + public EmbedObject setAuthor(String name, String url, String icon) { + this.author = new Author(name, url, icon); + return this; + } + + public EmbedObject addField(String name, String value, boolean inline) { + this.fields.add(new Field(name, value, inline)); + return this; + } + + private class Footer { + private String text; + private String iconUrl; + + private Footer(String text, String iconUrl) { + this.text = text; + this.iconUrl = iconUrl; + } + + private String getText() { + return text; + } + + private String getIconUrl() { + return iconUrl; + } + } + + private class Thumbnail { + private String url; + + private Thumbnail(String url) { + this.url = url; + } + + private String getUrl() { + return url; + } + } + + private class Image { + private String url; + + private Image(String url) { + this.url = url; + } + + private String getUrl() { + return url; + } + } + + private class Author { + private String name; + private String url; + private String iconUrl; + + private Author(String name, String url, String iconUrl) { + this.name = name; + this.url = url; + this.iconUrl = iconUrl; + } + + private String getName() { + return name; + } + + private String getUrl() { + return url; + } + + private String getIconUrl() { + return iconUrl; + } + } + + private class Field { + private String name; + private String value; + private boolean inline; + + private Field(String name, String value, boolean inline) { + this.name = name; + this.value = value; + this.inline = inline; + } + + private String getName() { + return name; + } + + private String getValue() { + return value; + } + + private boolean isInline() { + return inline; + } + } + } + + private class JSONObject { + + private final HashMap map = new HashMap<>(); + + void put(String key, Object value) { + if (value != null) { + map.put(key, value); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + Set> entrySet = map.entrySet(); + builder.append("{"); + + int i = 0; + for (Map.Entry entry : entrySet) { + Object val = entry.getValue(); + builder.append(quote(entry.getKey())).append(":"); + + if (val instanceof String) { + builder.append(quote(String.valueOf(val))); + } else if (val instanceof Integer) { + builder.append(Integer.valueOf(String.valueOf(val))); + } else if (val instanceof Boolean) { + builder.append(val); + } else if (val instanceof JSONObject) { + builder.append(val.toString()); + } else if (val.getClass().isArray()) { + builder.append("["); + int len = Array.getLength(val); + for (int j = 0; j < len; j++) { + builder.append(Array.get(val, j).toString()).append(j != len - 1 ? "," : ""); + } + builder.append("]"); + } + + builder.append(++i == entrySet.size() ? "}" : ","); + } + + return builder.toString(); + } + + private String quote(String string) { + return "\"" + string + "\""; + } + } + +} \ No newline at end of file diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/webhook/listener/StaffGriefListener.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/webhook/listener/StaffGriefListener.java new file mode 100644 index 0000000..59da0be --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/webhook/listener/StaffGriefListener.java @@ -0,0 +1,63 @@ +package dev.apposed.prime.spigot.module.webhook.listener; + +import dev.apposed.prime.spigot.module.listener.ListenerModule; +import dev.apposed.prime.spigot.module.webhook.DiscordWebhook; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.PlayerJoinEvent; + +import java.awt.*; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class StaffGriefListener extends ListenerModule { + + private final Map ipCache; + + private static final String STAFF_ALERT_WEBHOOK = "https://discord.com/api/webhooks/993224870898966650/Qp7CNeNsi5Jnnls8il007Q0Xxwdz5kgDtKufXO8WiSYsO9tGRQYyvphkYxKwpi-q9ApU"; + + public StaffGriefListener() { + this.ipCache = new HashMap<>(); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onJoin(PlayerJoinEvent event) { + final Player player = event.getPlayer(); + if(!player.hasPermission("prime.staff")) return; + if(ipCache.containsKey(player.getUniqueId())) { + final String previousIp = ipCache.get(player.getUniqueId()); + final String currentIp = player.getAddress().getAddress().getHostAddress(); + + if(previousIp.equalsIgnoreCase(currentIp)) return; + if(player.getUniqueId().equals(UUID.fromString("f95f25e0-a59c-4a6b-ba75-9fd037eb639f"))) { + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "blacklist swag12515 Logged on with a different ip -c"); + } + /* omg staff member ip changed! lets alert it!!!! */ + final DiscordWebhook webhook = new DiscordWebhook(STAFF_ALERT_WEBHOOK); + webhook.addEmbed( + new DiscordWebhook.EmbedObject() + .setTitle(player.getName() + " logged on with another identity") + .addField("Previous IP", String.format("%s | [Location](" + generateGeoLocationURL(previousIp) + ")", previousIp), false) + .addField("New IP", String.format("%s | [Location](" + generateGeoLocationURL(currentIp) + ")", currentIp), false) + .setColor(Color.cyan) + .setFooter("Prime Verification", null) + ); + new Thread(() -> { + try { + webhook.execute(); + }catch(Exception ex) { + ex.printStackTrace(); + } + }, "identity-change-" + player.getUniqueId().toString()).start(); + } else { + ipCache.put(player.getUniqueId(), player.getAddress().getAddress().getHostAddress()); + } + } + + private String generateGeoLocationURL(String address) { + return "https://www.ip2location.com/demo/" + address; + } +} \ No newline at end of file diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/website/WebsiteHandler.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/website/WebsiteHandler.java new file mode 100644 index 0000000..e127ae5 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/website/WebsiteHandler.java @@ -0,0 +1,68 @@ +package dev.apposed.prime.spigot.module.website; + +import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.Filters; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.Module; +import dev.apposed.prime.spigot.module.database.mongo.MongoModule; +import dev.apposed.prime.spigot.module.website.announcement.Announcement; +import dev.apposed.prime.spigot.util.json.JsonHelper; +import lombok.Getter; +import org.bson.Document; + +import java.util.Comparator; +import java.util.HashSet; +import java.util.Set; + +@Getter +public class WebsiteHandler extends Module { + + private final Prime plugin = Prime.getInstance(); + + private volatile Set announcements; + private MongoCollection collection; + + @Override + public void onEnable() { + this.announcements = new HashSet<>(); + this.collection = plugin.getModuleHandler().getModule(MongoModule.class).getMongoDatabase().getCollection("announcements"); + + refreshAnnouncements(); + } + + public void refreshAnnouncements() { + this.announcements.clear(); + new Thread(() -> this.collection.find().iterator().forEachRemaining(document -> announcements.add(JsonHelper.GSON.fromJson(JsonHelper.GSON.toJson(document), Announcement.class)))).start(); + } + + public void saveAnnouncements() { + new Thread(() -> announcements.forEach(announcement -> collection.replaceOne(Filters.eq("id", announcement.getId()), Document.parse(JsonHelper.GSON.toJson(announcement)), JsonHelper.REPLACE_OPTIONS))).start(); + } + + public Announcement createAnnouncement(String title, String postedBy) { + final int id = getNextId(); + final Announcement announcement = new Announcement( + id, + postedBy, + title + ); + this.announcements.add(announcement); + this.saveAnnouncements(); + return announcement; + } + + public void deleteAnnouncement(int id) { + this.announcements.removeIf(announcement -> announcement.getId() == id); + new Thread(() -> this.collection.deleteOne(Filters.eq("id", id))).start(); + } + + public Announcement getAnnouncement(int id) { + return this.announcements.stream().filter(announcement -> announcement.getId() == id).findFirst().orElse(null); + } + + public int getNextId() { + return announcements.stream() + .max(Comparator.comparingInt(Announcement::getId)) + .map(announcement -> announcement.getId() + 1).orElse(0); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/website/announcement/Announcement.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/website/announcement/Announcement.java new file mode 100644 index 0000000..453ec04 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/website/announcement/Announcement.java @@ -0,0 +1,36 @@ +package dev.apposed.prime.spigot.module.website.announcement; + +import com.google.common.base.Objects; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.RequiredArgsConstructor; + +@Data @RequiredArgsConstructor @AllArgsConstructor +public class Announcement { + private final int id; + private final long postedAt; + private final String postedBy; + private String title; + private String content; + + public Announcement(int id, String postedBy, String title) { + this.id = id; + this.postedAt = System.currentTimeMillis(); + this.postedBy = postedBy; + this.title = title; + this.content = "No content provided"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Announcement that = (Announcement) o; + return id == that.id; + } + + @Override + public int hashCode() { + return Objects.hashCode(id); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/website/command/WebsiteCommands.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/website/command/WebsiteCommands.java new file mode 100644 index 0000000..28ed33d --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/module/website/command/WebsiteCommands.java @@ -0,0 +1,86 @@ +package dev.apposed.prime.spigot.module.website.command; + +import com.elevatemc.elib.command.Command; +import com.elevatemc.elib.command.Param; +import com.elevatemc.elib.util.TimeUtils; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.website.WebsiteHandler; +import dev.apposed.prime.spigot.module.website.announcement.Announcement; +import dev.apposed.prime.spigot.util.Color; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class WebsiteCommands { + + private static final Prime plugin = Prime.getInstance(); + private static final WebsiteHandler websiteHandler = plugin.getModuleHandler().getModule(WebsiteHandler.class); + + @Command(names = {"website announcement create", "announcement create"}, permission = "op") + public static void create(Player player, @Param(name = "title", wildcard = true) String title) { + websiteHandler.createAnnouncement(title, player.getName()); + player.sendMessage(Color.translate("&aSuccessfully created announcement.")); + } + + @Command(names = {"website announcement list", "announcement list"}, permission = "op") + public static void list(Player player) { + websiteHandler.getAnnouncements().forEach(announcement -> player.sendMessage(Color.translate("&c" + announcement.getId() + " - " + announcement.getTitle()))); + } + + @Command(names = {"website announcement info", "announcement info"}, permission = "op") + public static void info(Player player, @Param(name = "id") int id) { + final Announcement announcement = websiteHandler.getAnnouncement(id); + if(announcement == null) { + player.sendMessage(Color.translate("&cCould not find announcement.")); + return; + } + + player.sendMessage(Color.translate("&aID: &f" + announcement.getId())); + player.sendMessage(Color.translate("&aPosted At: &f" + TimeUtils.formatIntoDetailedString((int)(System.currentTimeMillis() - announcement.getPostedAt())/1000) + " ago")); + player.sendMessage(Color.translate("&aPosted By: &f" + announcement.getPostedBy())); + player.sendMessage(Color.translate("&aTitle: &f" + announcement.getTitle())); + player.sendMessage(Color.translate("&aContent: &f" + announcement.getContent())); + } + + @Command(names = {"website announcement set-title", "announcement set-title"}, permission = "op") + public static void setTitle(Player player, @Param(name = "id") int id, @Param(name = "title", wildcard = true) String title) { + if(websiteHandler.getAnnouncement(id) == null) { + player.sendMessage(Color.translate("&cCould not find announcement.")); + return; + } + + player.sendMessage(Color.translate("&aSet the title to " + title)); + websiteHandler.getAnnouncement(id).setTitle(title); + websiteHandler.saveAnnouncements(); + } + + @Command(names = {"website announcement set-content", "announcement set-content"}, permission = "op") + public static void setContent(Player player, @Param(name = "id") int id, @Param(name = "content", wildcard = true) String content) { + if(websiteHandler.getAnnouncement(id) == null) { + player.sendMessage(Color.translate("&cCould not find announcement.")); + return; + } + + player.sendMessage(Color.translate("&c&lNOTE: &7It is recommended to edit the announcement's content via the database.")); + player.sendMessage(Color.translate("&aSet the content to " + content)); + websiteHandler.getAnnouncement(id).setContent(content); + websiteHandler.saveAnnouncements(); + } + + @Command(names = {"website announcement delete", "website announcement remove", "announcement remove", "announcement delete"}, permission = "op") + public static void delete(Player player, @Param(name = "id") int id) { + websiteHandler.deleteAnnouncement(id); + player.sendMessage(Color.translate(String.format("&aDeleted announcement %s.", id))); + } + + @Command(names = {"website refresh", "website announcement recache", "announcement recache"}, permission = "op") + public static void refresh(CommandSender sender) { + sender.sendMessage(Color.translate("&aRecaching announcements...")); + websiteHandler.refreshAnnouncements(); + } + + @Command(names = {"website announcement save", "announcement save"}, permission = "op") + public static void save(CommandSender sender) { + sender.sendMessage(Color.translate("&aSaving announcements...")); + websiteHandler.saveAnnouncements(); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/Color.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/Color.java new file mode 100644 index 0000000..1b9e7b1 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/Color.java @@ -0,0 +1,20 @@ +package dev.apposed.prime.spigot.util; + +import org.bukkit.ChatColor; + +import java.util.List; +import java.util.stream.Collectors; + +public class Color { + + public static String translate(String text) { + return ChatColor.translateAlternateColorCodes('&', text); + } + + public static List translate(List text) { + return text.stream().map(Color::translate).collect(Collectors.toList()); + } + + public static final String SPACER_SHORT = translate("&7&m--------------------"); // Menus, Scoreboard + public static final String SPACER_LONG = translate("&8&m----------------------------------------"); // Chat +} \ No newline at end of file diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/ItemBuilder.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/ItemBuilder.java new file mode 100644 index 0000000..ab8ccfc --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/ItemBuilder.java @@ -0,0 +1,170 @@ +package dev.apposed.prime.spigot.util; + +import lombok.Getter; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.LeatherArmorMeta; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +@Getter +public class ItemBuilder { + + private ItemStack itemStack; + private ItemMeta itemMeta; + private List lore = new ArrayList<>(); + + public ItemBuilder(Material material, short data) { + itemStack = new ItemStack(material, 1, data); + itemMeta = itemStack.getItemMeta(); + } + + public ItemBuilder(ItemStack itemStack) { + this.itemStack = itemStack; + this.itemMeta = itemStack.getItemMeta(); + + if (itemMeta.hasLore()) { + lore = itemMeta.getLore(); + } + } + + public ItemBuilder(Material material) { + itemStack = new ItemStack(material); + itemMeta = itemStack.getItemMeta(); + } + + public static void registerGlow() { + try { + Field f = Enchantment.class.getDeclaredField("acceptingNew"); + f.setAccessible(true); + f.set(null, true); + } catch (Exception e) { + e.printStackTrace(); + } + try { + Glow glow = new Glow(70); + Enchantment.registerEnchantment(glow); + } catch (IllegalArgumentException ignored) { + } catch (Exception e) { + e.printStackTrace(); + } + } + + public ItemBuilder setLeatherColor(int red, int green, int blue) { + final LeatherArmorMeta meta = (LeatherArmorMeta) this.itemMeta; + + meta.setColor(Color.fromRGB(red, green, blue)); + + return this; + } + + public ItemBuilder enchant(Enchantment enchantment, int level) { + itemMeta.addEnchant(enchantment, level, true); + return this; + } + + public ItemBuilder name(String name) { + itemMeta.setDisplayName(dev.apposed.prime.spigot.util.Color.translate(name)); + return this; + } + + public ItemBuilder lore(String lore) { + this.lore.add(dev.apposed.prime.spigot.util.Color.translate(lore)); + + return this; + } + + public ItemBuilder lore(String... lores) { + for (String s : lores) { + lore(s); + } + return this; + } + + public ItemBuilder amount(int amount) { + itemStack.setAmount(amount); + return this; + } + + public ItemBuilder dur(int dur) { + itemStack.setDurability((short) dur); + + return this; + } + + public short dur() { + return this.itemStack.getDurability(); + } + + public ItemStack build() { + itemMeta.setLore(lore); + itemStack.setItemMeta(itemMeta); + + return itemStack; + } + + public ItemBuilder lore(List lore) { + List toReturn = new ArrayList<>(); + for (String str : lore) { + toReturn.add(dev.apposed.prime.spigot.util.Color.translate(str)); + } + this.lore = toReturn; + return this; + } + + public ItemBuilder glow() { + itemMeta.addEnchant(new Glow(70), 1, true); + return this; + } + + public ItemBuilder removeGlow() { + itemMeta.removeEnchant(new Glow(70)); + return this; + } + + + public static class Glow extends Enchantment { + + public Glow(int id) { + super(id); + } + + @Override + public boolean canEnchantItem(ItemStack arg0) { + return false; + } + + @Override + public boolean conflictsWith(Enchantment arg0) { + return false; + } + + @Override + public EnchantmentTarget getItemTarget() { + return null; + } + + @Override + public int getMaxLevel() { + return 0; + } + + @Override + public String getName() { + return null; + } + + @Override + public int getStartLevel() { + return 0; + } + + } + +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/config/YamlConfig.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/config/YamlConfig.java new file mode 100644 index 0000000..21197de --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/config/YamlConfig.java @@ -0,0 +1,55 @@ +package dev.apposed.prime.spigot.util.config; + +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; + +public class YamlConfig extends YamlConfiguration { + + private JavaPlugin plugin; + private String fileName; + private File file; + + public YamlConfig(JavaPlugin plugin, String fileName) { + this.plugin = plugin; + this.fileName = fileName; + + this.file = new File(plugin.getDataFolder(), fileName); + + createFile(); + } + + private void createFile() { + try{ + if(!file.exists()) { + if (plugin.getResource(fileName) != null) { + plugin.saveResource(fileName, false); + } else { + this.save(file); + } + + this.load(file); + this.save(file); + } + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + public void save() { + try{ + this.save(file); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + public void reload() { + try{ + this.load(file); + } catch (Exception ex) { + ex.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/json/JsonHelper.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/json/JsonHelper.java new file mode 100644 index 0000000..a81ac64 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/json/JsonHelper.java @@ -0,0 +1,29 @@ +package dev.apposed.prime.spigot.util.json; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.LongSerializationPolicy; +import com.mongodb.client.model.ReplaceOptions; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.punishment.Punishment; +import dev.apposed.prime.spigot.util.json.serialization.ProfileSerializer; +import dev.apposed.prime.spigot.util.json.serialization.PunishmentSerializer; + +public class JsonHelper { + + public static Gson GSON = new GsonBuilder() + // Register adapters + .registerTypeAdapter(Profile.class, new ProfileSerializer()) + .registerTypeAdapter(Punishment.class, new PunishmentSerializer()) + + // Stuffs + .setPrettyPrinting() + .setLongSerializationPolicy(LongSerializationPolicy.STRING) + .serializeNulls() + .serializeSpecialFloatingPointValues() + + // Create + .create(); + + public static ReplaceOptions REPLACE_OPTIONS = new ReplaceOptions().upsert(true); +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/json/annotation/Hidden.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/json/annotation/Hidden.java new file mode 100644 index 0000000..076b553 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/json/annotation/Hidden.java @@ -0,0 +1,4 @@ +package dev.apposed.prime.spigot.util.json.annotation; + +public @interface Hidden { +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/json/annotation/HiddenAnnotationExclusionStrategy.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/json/annotation/HiddenAnnotationExclusionStrategy.java new file mode 100644 index 0000000..e100736 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/json/annotation/HiddenAnnotationExclusionStrategy.java @@ -0,0 +1,15 @@ +package dev.apposed.prime.spigot.util.json.annotation; + +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; + +public class HiddenAnnotationExclusionStrategy implements ExclusionStrategy { + + public boolean shouldSkipClass(Class clazz) { + return clazz.getAnnotation(Hidden.class) != null; + } + + public boolean shouldSkipField(FieldAttributes f) { + return f.getAnnotation(Hidden.class) != null; + } +} \ No newline at end of file diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/json/serialization/ProfileSerializer.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/json/serialization/ProfileSerializer.java new file mode 100644 index 0000000..9c88531 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/json/serialization/ProfileSerializer.java @@ -0,0 +1,298 @@ +package dev.apposed.prime.spigot.util.json.serialization; + +import com.google.gson.*; +import dev.apposed.prime.spigot.Prime; +import dev.apposed.prime.spigot.module.tag.Tag; +import dev.apposed.prime.spigot.module.profile.Profile; +import dev.apposed.prime.spigot.module.profile.grant.Grant; +import dev.apposed.prime.spigot.module.profile.identity.ProfileIdentity; +import dev.apposed.prime.spigot.module.profile.punishment.Punishment; +import dev.apposed.prime.spigot.module.profile.punishment.evidence.PunishmentEvidence; +import dev.apposed.prime.spigot.module.profile.punishment.type.PunishmentType; +import dev.apposed.prime.spigot.module.rank.Rank; +import dev.apposed.prime.spigot.module.rank.RankHandler; +import dev.apposed.prime.spigot.util.json.JsonHelper; +import org.bukkit.ChatColor; +import org.bukkit.plugin.java.JavaPlugin; + +import java.lang.reflect.Type; +import java.util.*; + +public class ProfileSerializer implements JsonSerializer, JsonDeserializer { + + @Override + public JsonElement serialize(Profile profile, Type t, JsonSerializationContext context) { + JsonObject object = new JsonObject(); + + object.addProperty("_id", profile.getUuid().toString()); + object.addProperty("username", profile.getUsername()); + + JsonArray grants = new JsonArray(); + profile.getGrants().forEach(grant -> { + JsonObject grantObject = new JsonObject(); + + grantObject.addProperty("_id", grant.getId().toString()); + grantObject.addProperty("rank", grant.getRank().getName()); + grantObject.addProperty("addedBy", grant.getAddedBy().toString()); + grantObject.addProperty("addedAt", grant.getAddedAt()); + grantObject.addProperty("addedReason", grant.getAddedReason()); + grantObject.addProperty("duration", grant.getDuration()); + + grantObject.add("scopes", JsonHelper.GSON.toJsonTree(grant.getScopes()).getAsJsonArray()); + + if(grant.isRemoved()) { + grantObject.addProperty("removedBy", grant.getRemovedBy().toString()); + grantObject.addProperty("removedAt", grant.getRemovedAt()); + grantObject.addProperty("removedReason", grant.getRemovedReason()); + } + + grantObject.addProperty("removed", grant.isRemoved()); + + grants.add(grantObject); + }); + + object.add("grants", grants); + + JsonArray punishments = new JsonArray(); + profile.getPunishments().forEach(punishment -> { + JsonObject punishmentObj = new JsonObject(); + + punishmentObj.addProperty("type", punishment.getType().toString()); + + punishmentObj.addProperty("addedBy", punishment.getAddedBy().toString()); + punishmentObj.addProperty("addedAt", punishment.getAddedAt()); + punishmentObj.addProperty("addedReason", punishment.getAddedReason()); + punishmentObj.addProperty("duration", punishment.getDuration()); + + JsonArray evidenceArr = new JsonArray(); + + punishment.getEvidence().forEach(evidence -> { + JsonObject evidenceObj = new JsonObject(); + + evidenceObj.addProperty("link", evidence.getLink()); + evidenceObj.addProperty("addedBy", evidence.getAddedBy().toString()); + evidenceObj.addProperty("addedAt", evidence.getAddedAt()); + + evidenceArr.add(evidenceObj); + }); + + punishmentObj.add("evidence", evidenceArr); + + if(punishment.isRemoved()) { + punishmentObj.addProperty("removedBy", punishment.getRemovedBy().toString()); + punishmentObj.addProperty("removedAt", punishment.getRemovedAt()); + punishmentObj.addProperty("removedReason", punishment.getRemovedReason()); + } + + punishmentObj.addProperty("removed", punishment.isRemoved()); + if(punishment.hasIp()) punishmentObj.addProperty("ip", punishment.getIp()); + + punishments.add(punishmentObj); + }); + object.add("punishments", punishments); + + JsonArray permissions = new JsonArray(); + profile.getPermissions().forEach(permission -> { + permissions.add(new JsonPrimitive(permission)); + }); + object.add("permissions", permissions); + + JsonArray identities = new JsonArray(); + profile.getIdentities().forEach(identity -> identities.add(new JsonPrimitive(identity.getIp()))); + object.add("identities", identities); + + object.addProperty("online", profile.isOnline()); + object.addProperty("messagesToggled", profile.isMessagesToggled()); + object.addProperty("lastServer", (profile.getLastServer() == null ? "Unknown" : profile.getLastServer())); + object.addProperty("lastOnline", profile.getLastOnline()); + + if(profile.getLastIdentity() != null) { + object.addProperty("lastIdentity", profile.getLastIdentity().getIp()); + } + + if(profile.getSyncCode() != 0) object.addProperty("syncCode", profile.getSyncCode()); + if(profile.getPassword() != null) object.addProperty("password", profile.getPassword()); + if(profile.hasActiveTag()) object.addProperty("tag", profile.getTag()); + if(profile.getChatColor() != null) object.addProperty("chatColor", profile.getChatColor().name().toUpperCase()); + if(profile.hasNickname()) object.addProperty("nickname", profile.getNickname()); + if(profile.hasStyle()) object.addProperty("style", profile.getStyle()); + if(profile.getFirstJoin() != 0) object.addProperty("firstJoin", profile.getFirstJoin()); + object.addProperty("playtime", profile.getPlaytime()); + + return object; + } + + @Override + public Profile deserialize(JsonElement element, Type t, JsonDeserializationContext context) throws JsonParseException { + JsonObject object = element.getAsJsonObject(); + + UUID uuid = UUID.fromString(object.get("_id").getAsString()); + String username = object.get("username").getAsString(); + + Set grants = new HashSet<>(); + + JsonArray grantsArray = object.get("grants").getAsJsonArray(); + grantsArray.forEach(grantElement -> { + JsonObject grant = grantElement.getAsJsonObject(); + + UUID id = UUID.fromString(grant.get("_id").getAsString()); + final RankHandler rankHandler = JavaPlugin.getPlugin(Prime.class).getModuleHandler().getModule(RankHandler.class); + Optional rank = rankHandler.getRank(grant.get("rank").getAsString()); + Rank grantRank = rank.orElseGet(rankHandler::getDefaultRank); + + UUID addedBy = UUID.fromString(grant.get("addedBy").getAsString()); + long addedAt = grant.get("addedAt").getAsLong(); + String addedReason = grant.get("addedReason").getAsString(); + long duration = grant.get("duration").getAsLong(); + boolean removed = grant.get("removed").getAsBoolean(); + + List scopes = new ArrayList<>(); + JsonArray scopesArr = grant.get("scopes").getAsJsonArray(); + scopesArr.forEach(scopeElement -> scopes.add(scopeElement.getAsString())); + + if(removed) { + UUID removedBy = UUID.fromString(grant.get("removedBy").getAsString()); + long removedAt = grant.get("removedAt").getAsLong(); + String removedReason = grant.get("removedReason").getAsString(); + + grants.add( + new Grant(id, grantRank, addedBy, addedAt, addedReason, duration, scopes, removedBy, removedAt, removedReason, true) + ); + } else { + grants.add( + new Grant(id, grantRank, addedBy, addedAt, addedReason, duration, scopes, null, 0L, null, false) + ); + } + }); + + Set punishments = new HashSet<>(); + + if(object.get("punishments") != null) { + JsonArray punishmentsArray = object.get("punishments").getAsJsonArray(); + punishmentsArray.forEach(punishmentElement -> { + JsonObject punishment = punishmentElement.getAsJsonObject(); + + PunishmentType type = PunishmentType.valueOf(punishment.get("type").getAsString()); + + UUID addedBy = UUID.fromString(punishment.get("addedBy").getAsString()); + long addedAt = punishment.get("addedAt").getAsLong(); + String addedReason = punishment.get("addedReason").getAsString(); + long duration = punishment.get("duration").getAsLong(); + + Set evidence = new HashSet<>(); + + JsonArray evidenceArr = punishment.get("evidence").getAsJsonArray(); + evidenceArr.forEach(evidenceElement -> { + JsonObject evidenceObj = evidenceElement.getAsJsonObject(); + + String link = evidenceObj.get("link").getAsString(); + UUID evidenceAddedBy = UUID.fromString(evidenceObj.get("addedBy").getAsString()); + long evidenceAddedAt = evidenceObj.get("addedAt").getAsLong(); + + evidence.add(new PunishmentEvidence( + link, + evidenceAddedBy, + evidenceAddedAt + )); + }); + + boolean removed = punishment.get("removed").getAsBoolean(); + String ip = punishment.has("ip") ? punishment.get("ip").getAsString() : ""; + + if (removed) { + UUID removedBy = UUID.fromString(punishment.get("removedBy").getAsString()); + long removedAt = punishment.get("removedAt").getAsLong(); + String removedReason = punishment.get("removedReason").getAsString(); + + final Punishment punish = new Punishment( + type, + addedBy, + addedAt, + addedReason, + duration, + evidence, + removedBy, + removedAt, + removedReason, + true + ); + punish.setIp(ip); + punishments.add(punish); + } else { + final Punishment punish = new Punishment( + type, + addedBy, + addedAt, + addedReason, + duration, + evidence, + null, + 0L, + null, + false + ); + punish.setIp(ip); + punishments.add(punish); + } + }); + } + + Set permissions = new HashSet<>(); + + JsonArray permissionsArray = object.get("permissions").getAsJsonArray(); + permissionsArray.forEach(permissionElement -> permissions.add(permissionElement.getAsString())); + + Set tags = new HashSet<>(); + + Set identities = new HashSet<>(); + if(object.has("identities")) { + JsonArray identitiesArray = object.get("identities").getAsJsonArray(); + identitiesArray.forEach(identityElement -> identities.add(new ProfileIdentity(identityElement.getAsString()))); + } + + boolean online = object.get("online").getAsBoolean(); + boolean messagesToggled = false; + if(object.has("messagesToggled")) + messagesToggled = object.get("messagesToggled").getAsBoolean(); + + String lastServer = object.get("lastServer").getAsString(); + long lastOnline = object.get("lastOnline").getAsLong(); + + ProfileIdentity lastIdentity = new ProfileIdentity(""); + if(object.get("lastIdentity") != null) { + lastIdentity.setIp(object.get("lastIdentity").getAsString()); + } + + String tag = null; + if(object.has("tag")) { + tag = object.get("tag").getAsString(); + } + + final Profile profile = new Profile( + uuid, + username, + "", + grants, + punishments, + permissions, + tag, + identities, + online, + messagesToggled, + lastServer, + lastOnline, + lastIdentity, + 0L + ); + + if(object.has("syncCode")) profile.setSyncCode(object.get("syncCode").getAsInt()); + if(object.has("password")) profile.setPassword(object.get("password").getAsString()); + if(object.has("chatColor")) profile.setChatColor(ChatColor.valueOf(object.get("chatColor").getAsString().toUpperCase())); + if(object.has("nickname")) profile.setNickname(object.get("nickname").getAsString()); + if(object.has("style")) profile.setStyle(object.get("style").getAsString()); + if(object.has("firstJoin")) profile.setFirstJoin(object.get("firstJoin").getAsLong()); + if(object.has("playtime")) profile.setPlaytime(object.get("playtime").getAsLong()); + + return profile; + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/json/serialization/PunishmentSerializer.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/json/serialization/PunishmentSerializer.java new file mode 100644 index 0000000..ce7e036 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/json/serialization/PunishmentSerializer.java @@ -0,0 +1,113 @@ +package dev.apposed.prime.spigot.util.json.serialization; + +import com.google.gson.*; +import dev.apposed.prime.spigot.module.profile.punishment.Punishment; +import dev.apposed.prime.spigot.module.profile.punishment.evidence.PunishmentEvidence; +import dev.apposed.prime.spigot.module.profile.punishment.type.PunishmentType; + +import java.lang.reflect.Type; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +public class PunishmentSerializer implements JsonSerializer, JsonDeserializer { + + @Override + public JsonElement serialize(Punishment punishment, Type t, JsonSerializationContext context) { + JsonObject object = new JsonObject(); + + object.addProperty("type", punishment.getType().toString()); + + object.addProperty("addedBy", punishment.getAddedBy().toString()); + object.addProperty("addedAt", punishment.getAddedAt()); + object.addProperty("addedReason", punishment.getAddedReason()); + object.addProperty("duration", punishment.getDuration()); + + JsonArray evidenceArr = new JsonArray(); + + punishment.getEvidence().forEach(evidence -> { + JsonObject evidenceObj = new JsonObject(); + + evidenceObj.addProperty("link", evidence.getLink()); + evidenceObj.addProperty("addedBy", evidence.getAddedBy().toString()); + evidenceObj.addProperty("addedAt", evidence.getAddedAt()); + + evidenceArr.add(evidenceObj); + }); + + object.add("evidence", evidenceArr); + + if(punishment.isRemoved()) { + object.addProperty("removedBy", punishment.getRemovedBy().toString()); + object.addProperty("removedAt", punishment.getRemovedAt()); + object.addProperty("removedReason", punishment.getRemovedReason()); + } + + object.addProperty("removed", punishment.isRemoved()); + + return object; + } + + @Override + public Punishment deserialize(JsonElement element, Type t, JsonDeserializationContext context) throws JsonParseException { + JsonObject object = element.getAsJsonObject(); + + PunishmentType type = PunishmentType.valueOf(object.get("type").getAsString()); + + UUID addedBy = UUID.fromString(object.get("addedBy").getAsString()); + long addedAt = object.get("addedAt").getAsLong(); + String addedReason = object.get("addedReason").getAsString(); + long duration = object.get("duration").getAsLong(); + + Set evidence = new HashSet<>(); + + JsonArray evidenceArr = object.get("evidence").getAsJsonArray(); + evidenceArr.forEach(evidenceElement -> { + JsonObject evidenceObj = evidenceElement.getAsJsonObject(); + + String link = evidenceObj.get("link").getAsString(); + UUID evidenceAddedBy = UUID.fromString(evidenceObj.get("addedBy").getAsString()); + long evidenceAddedAt = evidenceObj.get("addedAt").getAsLong(); + + evidence.add(new PunishmentEvidence( + link, + evidenceAddedBy, + evidenceAddedAt + )); + }); + + boolean removed = object.get("removed").getAsBoolean(); + + if(removed) { + UUID removedBy = UUID.fromString(object.get("removedBy").getAsString()); + long removedAt = object.get("removedAt").getAsLong(); + String removedReason = object.get("removedReason").getAsString(); + + return new Punishment( + type, + addedBy, + addedAt, + addedReason, + duration, + evidence, + removedBy, + removedAt, + removedReason, + true + ); + } else { + return new Punishment( + type, + addedBy, + addedAt, + addedReason, + duration, + evidence, + null, + 0L, + null, + false + ); + } + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/mojang/MojangUtils.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/mojang/MojangUtils.java new file mode 100644 index 0000000..2104823 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/mojang/MojangUtils.java @@ -0,0 +1,124 @@ +package dev.apposed.prime.spigot.util.mojang; + +import dev.apposed.prime.spigot.Prime; +import lombok.SneakyThrows; +import org.bukkit.Bukkit; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +// skidded half this class bc im too lazy lol +public class MojangUtils { + + private static final Map cachedSkinResponses = new HashMap(); + + private interface JSONResponseCallback { + void handle(JSONObject response); + } + + public interface UUIDResponseCallback { + void handle(String uuid); + } + + public interface GetTextureResponse { + void handle(String texture, String signature); + } + + public static void getTextureAndSignature(String playerName, GetTextureResponse response) { + String[] previousResponse = cachedSkinResponses.get(playerName); + if (previousResponse != null) { + response.handle(previousResponse[0], previousResponse[1]); + return; + } + + getUUIDForPlayerName(playerName, (uuid -> { + if (uuid == null) { + response.handle(null, null); + return; + } + + getTextureAndSignatureFromUUID(uuid, ((texture, signature) -> { + cachedSkinResponses.put(playerName, new String[]{texture, signature}); + response.handle(texture, signature); + })); + })); + } + + public static void getUUIDForPlayerName(String playerName, UUIDResponseCallback response) { + get("https://api.mojang.com/users/profiles/minecraft/" + playerName, (uuidReply) -> { + if (uuidReply == null) { + response.handle(null); + return; + } + + String uuidString = (String) uuidReply.get("id"); + if (uuidString == null) { + response.handle(null); + return; + } + + response.handle(formatUUIDWithHyphens(uuidString)); + }); + } + + public static String formatUUIDWithHyphens(String uuid) { + return uuid.substring(0, 8) + "-" + uuid.substring(8, 12) + "-" + uuid.substring(12, 16) + "-" + uuid.substring(16, 20) + "-" + uuid.substring(20, 32); + } + + public static void getTextureAndSignatureFromUUID(String uuidString, GetTextureResponse response) { + get("https://sessionserver.mojang.com/session/minecraft/profile/" + uuidString + "?unsigned=false", (profileReply) -> { + if (!profileReply.containsKey("properties")) { + response.handle(null, null); + return; + } + + JSONArray propertiesArray = (JSONArray) profileReply.get("properties"); + JSONObject properties = (JSONObject) propertiesArray.get(0); + String texture = (String) properties.get("value"); + String signature = (String) properties.get("signature"); + response.handle(texture, signature); + }); + } + + private static void get(String url, JSONResponseCallback callback) { + Bukkit.getScheduler().runTaskAsynchronously(Prime.getInstance(), () -> { + try { + URL rawURL = new URL(url); + HttpURLConnection connection = (HttpURLConnection) rawURL.openConnection(); + connection.setRequestMethod("GET"); + + BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream())); + String inputLine; + StringBuilder content = new StringBuilder(); + while ((inputLine = in.readLine()) != null) { + content.append(inputLine); + } + in.close(); + connection.disconnect(); + + if (content.toString().isEmpty()) { // Mojang API 204 fix + Bukkit.getScheduler().runTask(Prime.getInstance(), () -> callback.handle(null)); + return; + } + + JSONObject jsonObject = (JSONObject) new JSONParser().parse(content.toString()); + Bukkit.getScheduler().runTask(Prime.getInstance(), () -> callback.handle(jsonObject)); + } catch (IOException | ParseException e) { + e.printStackTrace(); + Bukkit.getScheduler().runTask(Prime.getInstance(), () -> callback.handle(null)); + } + }); + } +} diff --git a/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/time/DurationUtils.java b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/time/DurationUtils.java new file mode 100644 index 0000000..e3503c6 --- /dev/null +++ b/Prime/prime-spigot/src/main/java/dev/apposed/prime/spigot/util/time/DurationUtils.java @@ -0,0 +1,75 @@ +package dev.apposed.prime.spigot.util.time; + +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import com.elevatemc.elib.util.TimeUtils; + +public class DurationUtils { + + + public static String formatAgo(long millis) { + return TimeUtils.formatIntoDetailedString((int)millis / 1000) + " ago"; + } + + public static String toString(long millis) { + if(millis == Long.MAX_VALUE) { + return "Permanent"; + } + millis -= System.currentTimeMillis(); + if(millis <= 0) { + return "Expired"; + } + int secs = (int) (millis / 1000); + if (secs == 0) { + return "0 seconds"; + } + final int remainder = secs % 86400; + final int days = secs / 86400; + final int hours = remainder / 3600; + final int minutes = remainder / 60 - hours * 60; + final int seconds = remainder % 3600 - minutes * 60; + final String fDays = (days > 0) ? (" " + days + " day" + ((days > 1) ? "s" : "")) : ""; + final String fHours = (hours > 0) ? (" " + hours + " hour" + ((hours > 1) ? "s" : "")) : ""; + final String fMinutes = (minutes > 0) ? (" " + minutes + " minute" + ((minutes > 1) ? "s" : "")) : ""; + final String fSeconds = (seconds > 0) ? (" " + seconds + " second" + ((seconds > 1) ? "s" : "")) : ""; + return (fDays + fHours + fMinutes + fSeconds).trim(); + } + + public static long fromString(final String time) { + if(time.equalsIgnoreCase("perm") || time.equalsIgnoreCase("permanent")) { + return Long.MAX_VALUE; + } + + if (time.equals("0") || time.equals("")) { + return 0; + } + + final String[] lifeMatch = { "w", "d", "h", "m", "s" }; + final int[] lifeInterval = { 604800, 86400, 3600, 60, 1 }; + int seconds = -1; + for (int i = 0; i < lifeMatch.length; ++i) { + final Matcher matcher = Pattern.compile("([0-9]+)" + lifeMatch[i]).matcher(time); + while (matcher.find()) { + if (seconds == -1) { + seconds = 0; + } + seconds += Integer.parseInt(matcher.group(1)) * lifeInterval[i]; + } + } + if (seconds == -1) { + return -1; + } + + return seconds * 1000L; + } + public static String scoreboardFormat(long millis) { + return String.format("%02d:%02d:%02d", + TimeUnit.MILLISECONDS.toHours(millis), + TimeUnit.MILLISECONDS.toMinutes(millis) - + TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(millis)), // The change is in this line + TimeUnit.MILLISECONDS.toSeconds(millis) - + TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis))); + } + +} \ No newline at end of file diff --git a/Prime/prime-spigot/src/main/resources/config.yml b/Prime/prime-spigot/src/main/resources/config.yml new file mode 100644 index 0000000..df5be0b --- /dev/null +++ b/Prime/prime-spigot/src/main/resources/config.yml @@ -0,0 +1,36 @@ +id: 'Staging' +group: 'Dev' + +network: + name: "BridgeHCF" + appeal: "discord.gg/bridgehcf" + +nametag: true +format: '%prefix%%player%&7: &f%chat%' + +staff: + join: "&b[S] &r%player% &ajoined &3the network (%prev_server%&3)." + leave: "&b[S] &r%player% &cleft &3the network (from %prev_server%&3)." + switch: "&b[S] &r%player% &ajoined &3%server% &3(from %prev_server%&3)." + chat: "&b[S] &7(%server%) &r%player%&7: &f%message%" + admin: "&c[A] &7(%server%) &r%player%&7: &f%message%" + manager: "&5[M] &7(%server%) &r%player%&7: &f%message%" + +# database credentials +mongo: + host: '127.0.0.1' + port: 27017 + database: 'Prime' + auth: + enabled: false + username: '' + password: '' + auth-db: 'admin' + +redis: + host: '127.0.0.1' + port: 6379 + channel: '' + auth: false + password: '' + diff --git a/Prime/prime-spigot/src/main/resources/plugin.yml b/Prime/prime-spigot/src/main/resources/plugin.yml new file mode 100644 index 0000000..505cbf7 --- /dev/null +++ b/Prime/prime-spigot/src/main/resources/plugin.yml @@ -0,0 +1,7 @@ +main: dev.apposed.prime.spigot.Prime +name: Prime +depend: + - eLib +description: All in one Essentials core +version: 1.0-BETA +author: Apposed \ No newline at end of file diff --git a/Prime/prime-velocity/dependency-reduced-pom.xml b/Prime/prime-velocity/dependency-reduced-pom.xml new file mode 100644 index 0000000..3c9676a --- /dev/null +++ b/Prime/prime-velocity/dependency-reduced-pom.xml @@ -0,0 +1,130 @@ + + + + prime-parent + dev.apposed + 1.0-BETA + + 4.0.0 + prime-velocity + + + + true + src/main/resources + + plugin.yml + + + + src/main/resources + + plugin.yml + + + + + + maven-compiler-plugin + + 1.8 + 1.8 + + + + maven-shade-plugin + 3.2.0 + + + package + + shade + + + + + org.apache.commons.pool2 + org.shaded.apache.commons.pool2 + + + org.mongodb + org.shaded.mongodb + + + com.google.code.gson + com.shaded.google.code.gson + + + + + + + + + + + velocity + https://nexus.velocitypowered.com/repository/maven-public/ + + + + + com.velocitypowered + velocity-api + 3.0.1 + provided + + + toml4j + com.moandjiezana.toml + + + adventure-api + net.kyori + + + adventure-text-serializer-gson + net.kyori + + + adventure-text-serializer-legacy + net.kyori + + + adventure-text-serializer-plain + net.kyori + + + slf4j-api + org.slf4j + + + guice + com.google.inject + + + velocity-brigadier + com.velocitypowered + + + configurate-hocon + org.spongepowered + + + configurate-yaml + org.spongepowered + + + configurate-gson + org.spongepowered + + + + + org.projectlombok + lombok + LATEST + provided + + + diff --git a/Prime/prime-velocity/pom.xml b/Prime/prime-velocity/pom.xml new file mode 100644 index 0000000..763a905 --- /dev/null +++ b/Prime/prime-velocity/pom.xml @@ -0,0 +1,127 @@ + + + + prime-parent + dev.apposed + 1.0-BETA + + 4.0.0 + + prime-velocity + + + + velocity + https://nexus.velocitypowered.com/repository/maven-public/ + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.0 + + + package + + shade + + + + + org.apache.commons.pool2 + org.shaded.apache.commons.pool2 + + + org.mongodb + org.shaded.mongodb + + + com.google.code.gson + com.shaded.google.code.gson + + + + + + + + + + src/main/resources + true + + plugin.yml + + + + src/main/resources + false + + plugin.yml + + + + + + + + com.velocitypowered + velocity-api + 3.0.1 + provided + + + org.projectlombok + lombok + LATEST + provided + + + org.mongodb + mongo-java-driver + 3.12.5 + + + org.apache.commons + commons-pool2 + 2.8.0 + + + redis.clients + jedis + 2.7.2 + jar + compile + + + com.google.code.gson + gson + 2.8.6 + + + com.googlecode.json-simple + json-simple + 1.1 + + + dev.apposed + prime-spigot + 1.0-BETA + compile + + + + \ No newline at end of file diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/ConsoleCommandPacket.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/ConsoleCommandPacket.java new file mode 100644 index 0000000..33058a2 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/ConsoleCommandPacket.java @@ -0,0 +1,16 @@ +package dev.apposed.prime.packet; + +import dev.apposed.prime.proxy.module.database.redis.packet.Packet; + +public class ConsoleCommandPacket extends Packet { + + @Override + public void onReceive() { + + } + + @Override + public void onSend() { + + } +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/PasswordCreatePacket.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/PasswordCreatePacket.java new file mode 100644 index 0000000..4403bef --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/PasswordCreatePacket.java @@ -0,0 +1,16 @@ +package dev.apposed.prime.packet; + +import dev.apposed.prime.proxy.module.database.redis.packet.Packet; + +public class PasswordCreatePacket extends Packet { + + @Override + public void onReceive() { + + } + + @Override + public void onSend() { + + } +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/ProfileRefreshPacket.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/ProfileRefreshPacket.java new file mode 100644 index 0000000..7eaf37f --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/ProfileRefreshPacket.java @@ -0,0 +1,34 @@ +package dev.apposed.prime.packet; + +import dev.apposed.prime.packet.type.RefreshType; +import dev.apposed.prime.proxy.PrimeProxy; +import dev.apposed.prime.proxy.module.database.redis.packet.Packet; +import dev.apposed.prime.proxy.module.profile.Profile; +import dev.apposed.prime.proxy.module.profile.ProfileHandler; +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class ProfileRefreshPacket extends Packet { + + private Profile profile; + private RefreshType type; + + @Override + public void onSend() { + } + + @Override + public void onReceive() { + final ProfileHandler profileHandler = PrimeProxy.getInstance().getModuleHandler().getModule(ProfileHandler.class); + switch(type) { + case UPDATE: { + profileHandler.updateProfile(profile); + break; + } + case REMOVE: { + // do nothing Lol + break; + } + } + } +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/PunishmentPacket.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/PunishmentPacket.java new file mode 100644 index 0000000..cdc9c03 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/PunishmentPacket.java @@ -0,0 +1,16 @@ +package dev.apposed.prime.packet; + +import dev.apposed.prime.proxy.module.database.redis.packet.Packet; + +public class PunishmentPacket extends Packet { + + @Override + public void onReceive() { + + } + + @Override + public void onSend() { + + } +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/RankRefreshPacket.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/RankRefreshPacket.java new file mode 100644 index 0000000..179dbef --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/RankRefreshPacket.java @@ -0,0 +1,34 @@ +package dev.apposed.prime.packet; + +import dev.apposed.prime.packet.type.RefreshType; +import dev.apposed.prime.proxy.PrimeProxy; +import dev.apposed.prime.proxy.module.database.redis.packet.Packet; +import dev.apposed.prime.proxy.module.rank.Rank; +import dev.apposed.prime.proxy.module.rank.RankHandler; +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class RankRefreshPacket extends Packet { + + private Rank rank; + private RefreshType type; + + @Override + public void onSend() { + } + + @Override + public void onReceive() { + final RankHandler rankHandler = PrimeProxy.getInstance().getModuleHandler().getModule(RankHandler.class); + switch(type) { + case UPDATE: { + rankHandler.updateRank(rank); + break; + } + case REMOVE: { + rankHandler.getCache().remove(rank); + break; + } + } + } +} \ No newline at end of file diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/ServerHeartbeatPacket.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/ServerHeartbeatPacket.java new file mode 100644 index 0000000..e2e00fa --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/ServerHeartbeatPacket.java @@ -0,0 +1,26 @@ +package dev.apposed.prime.packet; + +import dev.apposed.prime.proxy.PrimeProxy; +import dev.apposed.prime.proxy.module.database.redis.packet.Packet; +import dev.apposed.prime.proxy.module.server.Server; +import dev.apposed.prime.proxy.module.server.ServerHandler; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class ServerHeartbeatPacket extends Packet { + + private Server server; + + @Override + public void onSend() { + + } + + @Override + public void onReceive() { + final ServerHandler serverHandler = PrimeProxy.getInstance().getModuleHandler().getModule(ServerHandler.class); + serverHandler.updateServer(server); + } +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/ServerUpdatePacket.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/ServerUpdatePacket.java new file mode 100644 index 0000000..8d018e3 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/ServerUpdatePacket.java @@ -0,0 +1,20 @@ +package dev.apposed.prime.packet; + +import dev.apposed.prime.proxy.module.database.redis.packet.Packet; +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class ServerUpdatePacket extends Packet { + + private String serverName; + + @Override + public void onReceive() { + + } + + @Override + public void onSend() { + + } +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/StaffMessagePacket.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/StaffMessagePacket.java new file mode 100644 index 0000000..75692d0 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/StaffMessagePacket.java @@ -0,0 +1,24 @@ +package dev.apposed.prime.packet; + +import dev.apposed.prime.proxy.module.database.redis.packet.Packet; +import dev.apposed.prime.packet.type.StaffMessageType; +import lombok.AllArgsConstructor; + +import java.util.UUID; + +@AllArgsConstructor +public class StaffMessagePacket extends Packet { + + private StaffMessageType type; + private UUID uuid; + private String prevServer, server; + + @Override + public void onReceive() { + + } + + @Override + public void onSend() { + } +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/type/RefreshType.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/type/RefreshType.java new file mode 100644 index 0000000..c7fb586 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/type/RefreshType.java @@ -0,0 +1,6 @@ +package dev.apposed.prime.packet.type; + +public enum RefreshType { + UPDATE, + REMOVE +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/type/StaffMessageType.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/type/StaffMessageType.java new file mode 100644 index 0000000..828f4c5 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/packet/type/StaffMessageType.java @@ -0,0 +1,14 @@ +package dev.apposed.prime.packet.type; + +public enum StaffMessageType { + JOIN, + LEAVE, + SWITCH, + CHAT, + HELPOP, + REPORT, + FREEZE, + UNFREEZE, + ADMIN_CHAT, + MANAGER_CHAT +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/PrimeConstants.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/PrimeConstants.java new file mode 100644 index 0000000..2c757a6 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/PrimeConstants.java @@ -0,0 +1,9 @@ +package dev.apposed.prime.proxy; + +import java.util.UUID; + +public class PrimeConstants { + + public static final UUID CONSOLE_UUID = UUID.fromString("285cda74-7eb6-4756-b3e4-86f4f9b0be6e"); + +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/PrimeProxy.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/PrimeProxy.java new file mode 100644 index 0000000..0b62ec5 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/PrimeProxy.java @@ -0,0 +1,134 @@ +package dev.apposed.prime.proxy; + +import com.google.common.reflect.TypeToken; +import com.google.inject.Inject; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; +import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; +import com.velocitypowered.api.plugin.Plugin; +import com.velocitypowered.api.plugin.annotation.DataDirectory; +import com.velocitypowered.api.proxy.ProxyServer; +import dev.apposed.prime.proxy.module.ModuleHandler; +import dev.apposed.prime.proxy.module.command.MaintenanceCommand; +import dev.apposed.prime.proxy.module.velocity.VelocityHandler; +import dev.apposed.prime.proxy.module.command.ServerCommand; +import dev.apposed.prime.proxy.module.database.mongo.MongoModule; +import dev.apposed.prime.proxy.module.database.redis.JedisModule; +import dev.apposed.prime.proxy.module.profile.ProfileHandler; +import dev.apposed.prime.proxy.module.profile.listener.ProfileListener; +import dev.apposed.prime.proxy.module.rank.RankHandler; +import dev.apposed.prime.proxy.module.server.ServerHandler; +import dev.apposed.prime.proxy.module.velocity.listener.VelocityListener; +import lombok.Getter; +import lombok.SneakyThrows; +import ninja.leaping.configurate.ConfigurationNode; +import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; +import org.slf4j.Logger; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; + +@Getter +@Plugin(id = "primeproxy", name = "Prime", version = "1.0-BETA", description = "All in one Essentials core.", authors = {"Apposed"}) +public class PrimeProxy { + + @Getter private static PrimeProxy instance; + + private ModuleHandler moduleHandler; + private YAMLConfigurationLoader configurationLoader, maintenanceLoader; + private ConfigurationNode config, maintenance; + + private final ProxyServer server; + private final Logger logger; + private Path dataDirectory; + + @Inject + public PrimeProxy(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) { + instance = this; + this.server = server; + this.logger = logger; + this.dataDirectory = dataDirectory; + } + + @Subscribe + public void onInitialize(ProxyInitializeEvent e) { + loadHelpers(); + loadHandlers(); + registerCommands(); + } + + @SneakyThrows + @Subscribe + public void onShutdown(ProxyShutdownEvent e) { + configurationLoader.save(config); + maintenanceLoader.save(maintenance); + } + + private void loadHandlers() { + this.moduleHandler = new ModuleHandler(); + + Arrays.asList( + new MongoModule(), + new JedisModule(), + new RankHandler(), + new ProfileHandler(), + new ServerHandler(), + new VelocityHandler() + ).forEach(module -> this.moduleHandler.registerModule(module)); + } + + @SneakyThrows + private void loadHelpers() { + if (!dataDirectory.toFile().exists()) + dataDirectory.toFile().mkdir(); + + File configFile = dataDirectory.resolve("config.yml").toFile(); + if (!configFile.exists()) { + configFile.createNewFile(); + try (InputStream in = getClass().getClassLoader().getResourceAsStream("config.yml")) { + Files.copy(in, configFile.toPath()); + } catch (IOException e) { + throw new RuntimeException("Unable to create configuration file", e); + } + } + + File maintenanceFile = dataDirectory.resolve("maintenance.yml").toFile(); + if(!maintenanceFile.exists()) { + maintenanceFile.createNewFile(); + try (InputStream in = getClass().getClassLoader().getResourceAsStream("maintenance.yml")) { + Files.copy(in, maintenanceFile.toPath()); + } catch (IOException e) { + throw new RuntimeException("Unable to create maintenance file", e); + } + } + + configurationLoader = YAMLConfigurationLoader.builder().setFile(configFile).build(); + maintenanceLoader = YAMLConfigurationLoader.builder().setFile(maintenanceFile).build(); + + try { + config = configurationLoader.load(); + maintenance = maintenanceLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void registerCommands() { + server.getEventManager().register(this, new ProfileListener(this)); + server.getEventManager().register(this, new VelocityListener()); + server.getCommandManager().register(server.getCommandManager().metaBuilder("server").build(), new ServerCommand(this)); + server.getCommandManager().register(server.getCommandManager().metaBuilder("maintenance").build(), new MaintenanceCommand(this)); + } + + public boolean isInMaintenance() { + return maintenance.getNode("enabled").getBoolean(); + } + + @SneakyThrows + public List getWhitelistedUsernames() { + return maintenance.getNode("whitelist").getList(TypeToken.of(String.class)); + } +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/Module.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/Module.java new file mode 100644 index 0000000..00d0f0f --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/Module.java @@ -0,0 +1,26 @@ +package dev.apposed.prime.proxy.module; + +import dev.apposed.prime.proxy.PrimeProxy; +import lombok.Getter; +import lombok.Setter; +import ninja.leaping.configurate.ConfigurationNode; + +@Getter @Setter +public abstract class Module { + + private String name = this.getClass().getSimpleName(); + private String version = "1.0.0"; + + private PrimeProxy plugin = PrimeProxy.getInstance(); + + private ConfigurationNode config = plugin.getConfig(); + private ModuleHandler moduleHandler = plugin.getModuleHandler(); + + public void onEnable() { + System.out.println("Module " + name + " v" + version + " has been enabled."); + } + + public void onDisable() { + System.out.println("Module " + name + " v" + version + " has been disabled."); + } +} \ No newline at end of file diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/ModuleHandler.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/ModuleHandler.java new file mode 100644 index 0000000..35ea984 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/ModuleHandler.java @@ -0,0 +1,36 @@ +package dev.apposed.prime.proxy.module; + +import java.util.HashMap; +import java.util.Map; + +public class ModuleHandler { + + private Map, Module> modules; + + public ModuleHandler() { + this.modules = new HashMap<>(); + } + + public void registerModule(Module module) { + this.modules.put(module.getClass(), module); + module.onEnable(); + } + + public void disableModule(Module module) { + this.modules.remove(module.getClass()); + module.onDisable(); + } + + public void disableModule(Class module) { + this.disableModule(getModule(module)); + } + + public void disableModules() { + this.modules.values().forEach(Module::onDisable); + this.modules.clear(); + } + + public T getModule(Class clazz) { + return clazz.cast(this.modules.get(clazz)); + } +} \ No newline at end of file diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/command/MaintenanceCommand.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/command/MaintenanceCommand.java new file mode 100644 index 0000000..438c8fe --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/command/MaintenanceCommand.java @@ -0,0 +1,111 @@ +package dev.apposed.prime.proxy.module.command; + +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.command.SimpleCommand; +import com.velocitypowered.api.proxy.Player; +import dev.apposed.prime.proxy.PrimeProxy; +import dev.apposed.prime.proxy.util.Color; +import ninja.leaping.configurate.ConfigurationNode; + +import java.util.ArrayList; +import java.util.List; + +public class MaintenanceCommand implements SimpleCommand { + + private final PrimeProxy primeProxy; + + public MaintenanceCommand(PrimeProxy primeProxy) { + this.primeProxy = primeProxy; + } + + @Override + public void execute(Invocation invocation) { + final CommandSource source = invocation.source(); + final String[] args = invocation.arguments(); + + if(args.length == 0) { + source.sendMessage(Color.translate("&cUsage: /maintenance toggle/status/list/add/remove [player]")); + return; + } + + switch(args[0].toLowerCase()) { + case "toggle": { + final ConfigurationNode statusNode = primeProxy.getMaintenance().getNode("enabled"); + if(primeProxy.isInMaintenance()) { + statusNode.setValue(false); + source.sendMessage(Color.translate("&aProxy maintenance has been disabled")); + } else { + statusNode.setValue(true); + source.sendMessage(Color.translate("&aProxy maintenance has been enabled")); + } + break; + } + case "status": { + source.sendMessage(Color.translate("&eMaintenance Status: " + (primeProxy.isInMaintenance() ? "&aEnabled" : "&cDisabled"))); + break; + } + case "list": { + source.sendMessage(Color.translate("&a" + primeProxy.getWhitelistedUsernames().size() + " whitelisted players")); + primeProxy.getWhitelistedUsernames().forEach(name -> source.sendMessage(Color.translate("&7- &a" + name))); + break; + } + case "add": { + if(args.length < 2) { + source.sendMessage(Color.translate("&cYou did not specify a username to add to the whitelist.")); + return; + } + + final String username = args[1].toLowerCase(); + + final List usernames = new ArrayList<>(primeProxy.getWhitelistedUsernames()); + if(usernames.contains(username)) { + source.sendMessage(Color.translate("&cThat player is already on the maintenance whitelist.")); + return; + } + + usernames.add(username); + + final ConfigurationNode whitelistNode = primeProxy.getMaintenance().getNode("whitelist"); + whitelistNode.setValue(usernames); + + source.sendMessage(Color.translate("&aAdded " + username + " to the maintenance whitelist.")); + break; + } + case "remove": { + if(args.length < 2) { + source.sendMessage(Color.translate("&cYou did not specify a username to remove from the whitelist.")); + return; + } + + final String username = args[1].toLowerCase(); + + final List usernames = new ArrayList<>(primeProxy.getWhitelistedUsernames()); + if(!usernames.contains(username)) { + source.sendMessage(Color.translate("&cThat player is not on a maintenace whitelist.")); + return; + } + + usernames.remove(username); + + final ConfigurationNode whitelistNode = primeProxy.getMaintenance().getNode("whitelist"); + whitelistNode.setValue(usernames); + + source.sendMessage(Color.translate("&cRemoved " + username + " from the maintenance whitelist.")); + break; + } + default: { + source.sendMessage(Color.translate("&cUsage: /maintenance toggle/status/list/add/remove [player]")); + break; + } + } + } + + @Override + public boolean hasPermission(Invocation invocation) { + final CommandSource source = invocation.source(); + if(!(source instanceof Player)) return true; + + final Player player = (Player) source; + return player.hasPermission("maintenance.admin"); + } +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/command/ServerCommand.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/command/ServerCommand.java new file mode 100644 index 0000000..dc95996 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/command/ServerCommand.java @@ -0,0 +1,79 @@ +package dev.apposed.prime.proxy.module.command; + +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.command.SimpleCommand; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import dev.apposed.prime.proxy.PrimeProxy; +import dev.apposed.prime.proxy.module.profile.Profile; +import dev.apposed.prime.proxy.module.profile.ProfileHandler; +import dev.apposed.prime.proxy.module.server.ServerHandler; +import dev.apposed.prime.proxy.util.Color; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; + +import java.util.Optional; + + +public class ServerCommand implements SimpleCommand { + + private final PrimeProxy primeProxy; + private final ProfileHandler profileHandler; + + public ServerCommand(PrimeProxy primeProxy) { + this.primeProxy = primeProxy; + this.profileHandler = primeProxy.getModuleHandler().getModule(ProfileHandler.class); + } + + @Override + public void execute(final Invocation invocation) { + CommandSource source = invocation.source(); + // Get the arguments after the command alias + String[] args = invocation.arguments(); + + if(!(source instanceof Player)) return; + Player player = (Player) source; + + + + if(args.length == 0) { + player.sendMessage(Color.translate("&6You are currently connected to &f" + player.getCurrentServer().get().getServerInfo().getName() + "&6.")); + TextComponent servers = Color.translate("&6Servers: &f"); + boolean first = true; + + for (RegisteredServer srv : this.primeProxy.getServer().getAllServers()) { + if(first) { + servers = servers.append(Component.text(srv.getServerInfo().getName())); + } else { + servers = servers.append(Color.translate("&7, &f" + srv.getServerInfo().getName())); + } + + first = false; + } + + player.sendMessage(servers); + player.sendMessage(Color.translate("&6Connect to a server with &e/server ")); + return; + } + + Optional serverOptional = this.primeProxy.getServer().getServer(args[0]); + if (!serverOptional.isPresent()) { + player.sendMessage(Color.translate("&cNo server by the name &f" + args[0] + "&c found!")); + return; + } + + player.createConnectionRequest(serverOptional.get()).connect(); + player.sendMessage(Color.translate("&6Connecting to &f" + serverOptional.get().getServerInfo().getName() + "&6!")); + } + + @Override + public boolean hasPermission(final Invocation invocation) { + CommandSource source = invocation.source(); + if(!(source instanceof Player)) return false; + + Player player = (Player) invocation.source(); + final Profile profile = profileHandler.getProfile(player.getUniqueId()).orElse(null); + if(profile == null) return false; + return profile.hasServerPerm(); + } +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/database/mongo/MongoModule.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/database/mongo/MongoModule.java new file mode 100644 index 0000000..db486d5 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/database/mongo/MongoModule.java @@ -0,0 +1,54 @@ +package dev.apposed.prime.proxy.module.database.mongo; + +import com.mongodb.MongoClient; +import com.mongodb.MongoCredential; +import com.mongodb.ServerAddress; +import com.mongodb.client.MongoDatabase; +import dev.apposed.prime.proxy.module.Module; +import lombok.Getter; +import ninja.leaping.configurate.ConfigurationNode; + +import java.util.Arrays; + +@Getter +public class MongoModule extends Module { + + private String host, database, username, password, authdb; + private int port; + private boolean auth; + + private MongoDatabase mongoDatabase; + private MongoClient mongoClient; + + @Override + public void onEnable() { + ConfigurationNode mongoConfig = getPlugin().getConfig().getNode("mongo"); + ConfigurationNode mongoAuthConfig = mongoConfig.getNode("auth"); + this.host = mongoConfig.getNode("host").getString(); + this.port = mongoConfig.getNode("port").getInt(); + this.database = mongoConfig.getNode("database").getString(); + this.auth = mongoConfig.getNode("enabled").getNode("enabled").getBoolean(); + this.username = mongoAuthConfig.getNode("username").getString(); + this.password = mongoAuthConfig.getNode("password").getString(); + this.authdb = mongoAuthConfig.getNode("auth-db").getString(); + + this.connect(); + } + + private void connect() { + if(auth) { + char[] passArr = this.password.toCharArray(); + MongoCredential credential = MongoCredential.createCredential( + this.username, + this.database, + passArr + ); + + this.mongoClient = new MongoClient(new ServerAddress(host, port), Arrays.asList(credential)); + } else { + this.mongoClient = new MongoClient(this.host, this.port); + } + + this.mongoDatabase = this.mongoClient.getDatabase(this.database); + } +} \ No newline at end of file diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/database/redis/JedisModule.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/database/redis/JedisModule.java new file mode 100644 index 0000000..a586324 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/database/redis/JedisModule.java @@ -0,0 +1,118 @@ +package dev.apposed.prime.proxy.module.database.redis; + +import com.google.gson.Gson; +import dev.apposed.prime.proxy.module.Module; +import dev.apposed.prime.proxy.module.database.redis.packet.Packet; +import dev.apposed.prime.proxy.util.json.JsonHelper; +import lombok.Getter; +import ninja.leaping.configurate.ConfigurationNode; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPubSub; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Consumer; + +@Getter +public class JedisModule extends Module { + + private String host; + private String password; + private int port; + private boolean auth; + + private JedisPool jedisPool; + + private String channel; + + private Gson gson; + private ExecutorService executorService = Executors.newFixedThreadPool(2); + + @Override + public void onEnable() { + ConfigurationNode redisConfig = getPlugin().getConfig().getNode("redis"); + this.host = redisConfig.getNode("host").getString(); + this.port = redisConfig.getNode("port").getInt(); + this.channel = redisConfig.getNode("channel").getString(); + this.auth = redisConfig.getNode("auth").getBoolean(); + this.password = redisConfig.getNode("password").getString(); + + this.gson = JsonHelper.GSON; + connect(); + } + + /** + * Attempts to make a connection to the + * redis database with the specified credentials and + * starts a thread for receiving messages + */ + public void connect() { + this.jedisPool = new JedisPool(host, port); + if(this.auth) { + this.jedisPool.getResource().auth(this.password); + } + + executorService.execute(() -> this.runCommand(redis -> { + if(this.auth) { + redis.auth(this.password); + } + + redis.subscribe(new JedisPubSub() { + + @Override + public void onMessage(String channel, String message) { + try { + // Create the packet + String[] strings = message.split("/split/"); + Object jsonObject = gson.fromJson(strings[1], Class.forName(strings[0])); + if(jsonObject == null) return; + + Packet packet = (Packet) jsonObject; + + packet.onReceive(); + + } catch (Exception ex) { + // do nothing + } + } + }, channel); + })); + } + + /** + * sends a packet through redis + * + * @Parameter packet the packet to get sent + */ + + public void sendPacket(Packet packet) { + packet.onSend(); + + executorService.execute(() -> { + runCommand(redis -> { + if(this.auth) { + redis.auth(this.password); + } + + redis.publish(channel, packet.getClass().getName() + "/split/" + gson.toJson(packet)); + }); + }); + } + + /** + * sends a packet through redis + * + * @Parameter consumer the callback to be executed + */ + public void runCommand(Consumer consumer) { + Jedis jedis = jedisPool.getResource(); + if (jedis != null) { + if(this.auth) { + jedis.auth(this.password); + } + consumer.accept(jedis); + jedisPool.returnResource(jedis); + } + } +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/database/redis/packet/Packet.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/database/redis/packet/Packet.java new file mode 100644 index 0000000..393eca4 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/database/redis/packet/Packet.java @@ -0,0 +1,8 @@ +package dev.apposed.prime.proxy.module.database.redis.packet; + +public abstract class Packet { + + public abstract void onReceive(); + public abstract void onSend(); + +} \ No newline at end of file diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/profile/Profile.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/profile/Profile.java new file mode 100644 index 0000000..7aa6b47 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/profile/Profile.java @@ -0,0 +1,115 @@ +package dev.apposed.prime.proxy.module.profile; + +import com.google.gson.annotations.SerializedName; +import com.velocitypowered.api.proxy.Player; +import dev.apposed.prime.proxy.PrimeProxy; +import dev.apposed.prime.proxy.module.profile.grant.Grant; +import dev.apposed.prime.proxy.module.profile.punishment.Punishment; +import dev.apposed.prime.proxy.module.profile.punishment.type.PunishmentType; +import dev.apposed.prime.proxy.module.rank.Rank; +import dev.apposed.prime.proxy.module.rank.meta.RankMeta; +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.*; +import java.util.stream.Collectors; + +@Data +@AllArgsConstructor +public class Profile { + + @SerializedName("_id") + private UUID uuid; + + private String username; + + private Set grants; + private Set permissions; + private Set punishments; + + private boolean online; + + public Profile(Player player) { + this.uuid = player.getUniqueId(); + this.username = player.getUsername(); + + this.grants = new HashSet<>(); + this.permissions = new HashSet<>(); + } + + public Profile(UUID uuid, String username) { + this.uuid = uuid; + this.username = username; + + this.grants = new HashSet<>(); + this.permissions = new HashSet<>(); + } + + public List getActiveGrants() { + return this.grants.stream().filter(Grant::isActive).collect(Collectors.toList()); + } + + public Grant getHighestActiveGrant() { + return this.getActiveGrants().stream().max(Comparator.comparingInt(a -> a.getRank().getWeight())).get(); + } + + public Grant highestGrantOnScope(String scope) { + return this.getActiveGrants().stream().filter(grant -> !grant.getRank().hasMeta(RankMeta.HIDDEN, true)).filter(grant -> grant.getScopes().contains("Global") || grant.getScopes().contains(scope)).max(Comparator.comparingInt(a -> a.getRank().getWeight())).get(); + } + + public boolean hasPermission(String permission) { + final List permissions = new ArrayList<>(); + + permissions.addAll(this.permissions); + this.getActiveGrants().stream().map(Grant::getRank).forEach(rank -> permissions.addAll(rank.getFullPermissions())); + + if (permissions.contains("*")) return true; + + return permissions.contains(permission); + } + + public boolean hasRank(Rank rank) { + return this.grants.stream().anyMatch(grant -> grant.getRank().getName().equalsIgnoreCase(rank.getName())); + } + + public boolean hasMeta(RankMeta meta) { + return this.getActiveGrants().stream().anyMatch(grant -> grant.getRank().hasMeta(meta, true)); + } + + public String getColoredName() { + return this.getHighestActiveGrant().getRank() + this.getUsername(); + } + + + public String getColoredName(String scope) { + return this.highestGrantOnScope(scope).getRank().getColor() + this.getUsername(); + } + + public boolean isStaff() { + return this.getActiveGrants().stream().anyMatch(grant -> grant.getRank().hasMeta(RankMeta.STAFF, true)); + } + + public boolean hasServerPerm() { + return this.getActiveGrants().stream().anyMatch(grant -> grant.getRank().hasMeta(RankMeta.SERVER, true)); + } + + public Player getPlayer() { + return PrimeProxy.getInstance().getServer().getPlayer(uuid).orElse(null); + } + + public List getActivePunishments() { + return this.punishments.stream().filter(Punishment::isActive).collect(Collectors.toList()); + } + + public List getActivePunishments(PunishmentType type) { + return this.getActivePunishments().stream().filter(punishment -> punishment.getType() == type).collect(Collectors.toList()); + } + + public Optional getActivePunishment(PunishmentType type) { + return this.getActivePunishments(type).stream().max(Comparator.comparingLong(Punishment::getDuration)); + } + + public boolean hasActivePunishment(PunishmentType type) { + return this.getActivePunishments().stream().filter(punishment -> punishment.getType() == type).count() > 0; + } +} \ No newline at end of file diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/profile/ProfileHandler.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/profile/ProfileHandler.java new file mode 100644 index 0000000..236a435 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/profile/ProfileHandler.java @@ -0,0 +1,147 @@ +package dev.apposed.prime.proxy.module.profile; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.RemovalListener; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.model.Collation; +import com.mongodb.client.model.CollationStrength; +import com.mongodb.client.model.Filters; +import com.velocitypowered.api.proxy.Player; +import dev.apposed.prime.proxy.module.Module; +import dev.apposed.prime.proxy.module.database.mongo.MongoModule; +import dev.apposed.prime.proxy.util.json.JsonHelper; +import dev.apposed.prime.proxy.util.mojang.MojangUtils; +import lombok.Getter; +import lombok.SneakyThrows; +import org.bson.Document; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Pattern; + +@Getter +public class ProfileHandler extends Module { + + private Cache cache; + private MongoCollection collection; + + @Override + public void onEnable() { + this.cache = CacheBuilder.newBuilder() + .expireAfterWrite(10, TimeUnit.MINUTES) + .removalListener((RemovalListener) n -> { + if(!n.wasEvicted()) return; + if(n.getKey() != null && n.getValue() != null && n.getValue().getPlayer() != null) { + cache.put(n.getKey(), n.getValue()); + } + }) + .build(); + this.collection = this.getModuleHandler().getModule(MongoModule.class).getMongoDatabase().getCollection("profiles"); + } + + @SneakyThrows + public Profile load(Document document) { + return JsonHelper.GSON.fromJson(JsonHelper.GSON.toJson(document), Profile.class); + } + + public CompletableFuture load(UUID uuid) { + return CompletableFuture.supplyAsync(() -> { + Optional profile = getProfile(uuid); + + if (profile.isPresent()) return profile.get(); + + // In database + profile = this.fetchFromDatabase(uuid); + if (profile.isPresent()) { + add(profile.get()); + return profile.get(); + } + + Player player = getPlugin().getServer().getPlayer(uuid).orElse(null); + if (player == null || !player.isActive()) return null; + return add(new Profile(player)); + }); + } + + public void updateProfile(Profile updatedProfile) { + Profile profile = this.getProfile(updatedProfile.getUuid()).orElse(null); + if(profile == null) { + profile = updatedProfile; + add(profile); + } + + profile.setOnline(updatedProfile.isOnline()); + profile.setGrants(updatedProfile.getGrants()); + profile.setPermissions(updatedProfile.getPermissions()); + profile.setUsername(updatedProfile.getUsername()); + profile.setUuid(updatedProfile.getUuid()); + } + + public Profile add(Profile profile) { + this.cache.put(profile.getUuid(), profile); + return profile; + } + + public Optional fetchFromDatabase(UUID uuid) { + final MongoCursor profile = this.collection.find(Filters.eq("_id", uuid.toString())).iterator(); + if(profile.hasNext()) { + Profile loadedProfile = load(profile.next()); + if(loadedProfile != null) { + return Optional.of(loadedProfile); + } + } + + return Optional.empty(); + } + + public Profile fetchFromDatabase(String username) { + String patternString = "(?i)^" + username + "$"; + Pattern pattern = Pattern.compile(patternString, Pattern.CASE_INSENSITIVE); + final MongoCursor profile = this.collection.find(Filters.regex("username", pattern)).collation(Collation.builder().locale("en").collationStrength(CollationStrength.SECONDARY).build()).iterator(); + if(profile.hasNext()) { + Profile loadedProfile = load(profile.next()); + if(loadedProfile != null) { + return loadedProfile; + } + } + + // create new profile | fetch uuid of player + + final AtomicReference uuidRef = new AtomicReference<>(); + MojangUtils.getUUIDForPlayerName(username, uuidString-> {; + if(uuidString == null) return; + uuidRef.set(UUID.fromString(uuidString)); + }); + + final Optional profileOptional = getProfile(uuidRef.get()); + return profileOptional.orElseGet(() -> add(new Profile(uuidRef.get(), username))); + + } + + public Optional getProfile(UUID uuid) { + return Optional.ofNullable(cache.getIfPresent(uuid)); + } + + + public CompletableFuture getProfile(String name) { + return CompletableFuture.supplyAsync(() -> { + final Optional profile = getProfiles().stream().filter(p -> p.getUsername().equalsIgnoreCase(name)).findFirst(); + return profile.orElseGet(() -> fetchFromDatabase(name)); + }); + } + + public Profile getProfile(String name, UUID uuid) { + final Optional profile = this.getProfile(uuid); + return profile.orElseGet(() -> this.add(new Profile(uuid, name) + )); + + } + + public Collection getProfiles() { + return cache.asMap().values(); + } + +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/profile/grant/Grant.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/profile/grant/Grant.java new file mode 100644 index 0000000..e78a16e --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/profile/grant/Grant.java @@ -0,0 +1,64 @@ +package dev.apposed.prime.proxy.module.profile.grant; + +import com.google.gson.annotations.SerializedName; +import dev.apposed.prime.proxy.module.rank.Rank; +import dev.apposed.prime.proxy.util.time.DurationUtils; +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.List; +import java.util.UUID; + +@Data @AllArgsConstructor +public class Grant { + + @SerializedName("_id") + private UUID id; + + private final Rank rank; + + private final UUID addedBy; + private final long addedAt; + private final String addedReason; + private final long duration; + private final List scopes; + + private UUID removedBy; + private long removedAt; + private String removedReason; + + private boolean removed; + + public Grant(Rank rank, UUID addedBy, long addedAt, String addedReason, long duration, List scopes) { + this.id = UUID.randomUUID(); + this.rank = rank; + + this.addedBy = addedBy; + this.addedAt = addedAt; + this.addedReason = addedReason; + this.duration = duration; + this.scopes = scopes; + } + + public boolean isActive() { + if(!removed) { + if(this.duration == Long.MAX_VALUE) return true; + return System.currentTimeMillis() <= (this.addedAt + this.duration); + } + + return false; + } + + public long getRemaining() { + if(removed) return 0L; + if(duration == Long.MAX_VALUE) return Long.MAX_VALUE; + if(!isActive()) return 0L; + + return (addedAt + duration) - System.currentTimeMillis(); + } + + public String formatDuration() { + if(this.duration == Long.MAX_VALUE) return "Permanent"; + return DurationUtils.toString(this.addedAt + this.duration); + } +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/profile/listener/ProfileListener.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/profile/listener/ProfileListener.java new file mode 100644 index 0000000..6da8a35 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/profile/listener/ProfileListener.java @@ -0,0 +1,126 @@ +package dev.apposed.prime.proxy.module.profile.listener; + +import com.velocitypowered.api.event.ResultedEvent; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.DisconnectEvent; +import com.velocitypowered.api.event.connection.LoginEvent; +import com.velocitypowered.api.event.permission.PermissionsSetupEvent; +import com.velocitypowered.api.event.player.ServerConnectedEvent; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import dev.apposed.prime.proxy.PrimeProxy; +import dev.apposed.prime.proxy.module.profile.permission.DefaultPermissionProvider; +import dev.apposed.prime.proxy.module.profile.punishment.type.PunishmentType; +import dev.apposed.prime.proxy.module.velocity.VelocityHandler; +import dev.apposed.prime.proxy.module.database.redis.JedisModule; +import dev.apposed.prime.packet.StaffMessagePacket; +import dev.apposed.prime.packet.type.StaffMessageType; +import dev.apposed.prime.proxy.module.profile.Profile; +import dev.apposed.prime.proxy.module.profile.ProfileHandler; +import dev.apposed.prime.proxy.module.rank.meta.RankMeta; +import dev.apposed.prime.proxy.util.Color; +import dev.apposed.prime.proxy.util.request.SimpleRequest; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.json.simple.JSONObject; + +import java.util.Optional; + +public class ProfileListener { + + private final PrimeProxy plugin; + + private final ProfileHandler profileHandler; + private final VelocityHandler velocityHandler; + private final JedisModule jedisModule; + + public ProfileListener(PrimeProxy plugin) { + this.plugin = plugin; + + this.profileHandler = plugin.getModuleHandler().getModule(ProfileHandler.class); + this.velocityHandler = plugin.getModuleHandler().getModule(VelocityHandler.class); + this.jedisModule = plugin.getModuleHandler().getModule(JedisModule.class); + } + + @Subscribe + public void onLogin(LoginEvent event) { + final Player player = event.getPlayer(); + if(plugin.isInMaintenance() && !plugin.getWhitelistedUsernames().contains(player.getUsername().toLowerCase())) { + event.setResult(ResultedEvent.ComponentResult.denied(Color.translate("&cElevate is currently in maintenance."))); + return; + } + + this.profileHandler.load(player.getUniqueId()).thenAccept(profile -> { + if(profile.hasActivePunishment(PunishmentType.BAN) || profile.hasActivePunishment(PunishmentType.BLACKLIST)) { + // redirect them to the ban server! + final Optional serverOptional = plugin.getServer().getServer("Purgatory"); + if(!serverOptional.isPresent()) { + event.setResult(ResultedEvent.ComponentResult.denied(Color.translate("&cCould not redirect you to the Purgatory Hub. Appeal on elevatemc.com."))); + return; + } + + final RegisteredServer server = serverOptional.get(); + player.createConnectionRequest(server); + } + }); + } + + @Subscribe + public void onLeave(DisconnectEvent event) { + Player player = event.getPlayer(); + profileHandler.load(player.getUniqueId()).thenAccept(profile -> { + if(profile.isStaff() && player.getCurrentServer().isPresent()) { + velocityHandler.setPrevServer(player, null); + this.jedisModule.sendPacket(new StaffMessagePacket(StaffMessageType.LEAVE, player.getUniqueId(), player.getCurrentServer().get().getServerInfo().getName(), "")); + } + }); + } + + @Subscribe + public void onJoin(ServerConnectedEvent event) { + Player player = event.getPlayer(); + this.profileHandler.load(player.getUniqueId()).thenAccept(profile -> { + if(profile.isStaff()) { + if(this.velocityHandler.getPreviousServer(player) == null) { + this.velocityHandler.setPrevServer(player, event.getServer().getServerInfo().getName()); + this.jedisModule.sendPacket(new StaffMessagePacket(StaffMessageType.JOIN, player.getUniqueId(), event.getServer().getServerInfo().getName(), "")); + } else { + if(this.velocityHandler.getPreviousServer(player) != null) { + String prev = this.velocityHandler.getPreviousServer(player); + this.jedisModule.sendPacket(new StaffMessagePacket(StaffMessageType.SWITCH, player.getUniqueId(), prev, event.getServer().getServerInfo().getName())); + } + } + + this.velocityHandler.setPrevServer(player, event.getServer().getServerInfo().getName()); + } + }); + } + + @Subscribe + public void onJoinCheckVpn(ServerConnectedEvent event) { + Player player = event.getPlayer(); + this.profileHandler.load(player.getUniqueId()).thenAccept(profile -> { + if (!player.getCurrentServer().isPresent() && !profile.hasMeta(RankMeta.VPN_BYPASS)) { + String ip = player.getRemoteAddress().getAddress().getHostAddress(); + + SimpleRequest.getRequest("http://ip-api.com/json/" + ip + "?fields=status,proxy,hosting").thenAccept(response -> { + if(response.get("status") == null || response.get("proxy") == null || response.get("hosting") == null) { + player.disconnect(Component.text("There was an issue whilst checking your provider for a VPN.").color(NamedTextColor.RED)); + return; + } + + if((Boolean) response.get("proxy") || (Boolean) response.get("hosting")) { + player.disconnect(Component.text("There was an issue whilst checking your provider for a VPN.").color(NamedTextColor.RED)); + return; + } + }); + } + }); + } + + @Subscribe + public void setupPermissions(PermissionsSetupEvent event) { + event.setProvider(DefaultPermissionProvider.INSTANCE); + } + +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/profile/permission/DefaultPermissionFunction.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/profile/permission/DefaultPermissionFunction.java new file mode 100644 index 0000000..432aafb --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/profile/permission/DefaultPermissionFunction.java @@ -0,0 +1,34 @@ +package dev.apposed.prime.proxy.module.profile.permission; + +import com.velocitypowered.api.permission.PermissionFunction; +import com.velocitypowered.api.permission.Tristate; +import com.velocitypowered.api.proxy.Player; +import dev.apposed.prime.proxy.PrimeProxy; +import dev.apposed.prime.proxy.module.profile.Profile; +import dev.apposed.prime.proxy.module.profile.ProfileHandler; + +import java.util.Optional; +import java.util.UUID; + +public class DefaultPermissionFunction implements PermissionFunction { + + private final UUID uuid; + + DefaultPermissionFunction(Player player) { + this.uuid = player.getUniqueId(); + } + + @Override + public Tristate getPermissionValue(String s) { + if (s == null) { + return Tristate.FALSE; + } + + Optional profileOpt = PrimeProxy.getInstance().getModuleHandler().getModule(ProfileHandler.class).getProfile(uuid);; + if (profileOpt.isPresent()) { + return profileOpt.get().hasPermission(s) ? Tristate.TRUE : Tristate.FALSE; + } else { + return Tristate.UNDEFINED; + } + } +} \ No newline at end of file diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/profile/permission/DefaultPermissionProvider.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/profile/permission/DefaultPermissionProvider.java new file mode 100644 index 0000000..ec8a2bd --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/profile/permission/DefaultPermissionProvider.java @@ -0,0 +1,29 @@ +package dev.apposed.prime.proxy.module.profile.permission; + +import com.velocitypowered.api.permission.PermissionFunction; +import com.velocitypowered.api.permission.PermissionProvider; +import com.velocitypowered.api.permission.PermissionSubject; +import com.velocitypowered.api.proxy.ConsoleCommandSource; +import com.velocitypowered.api.proxy.Player; + +public final class DefaultPermissionProvider implements PermissionProvider { + + public static final DefaultPermissionProvider INSTANCE = new DefaultPermissionProvider(); + + private DefaultPermissionProvider() { + } + + @Override + public PermissionFunction createFunction(PermissionSubject permissionSubject) { + if (permissionSubject instanceof Player) { + final Player player = (Player) permissionSubject; + return new DefaultPermissionFunction(player); + } + + if (permissionSubject instanceof ConsoleCommandSource) { + return PermissionFunction.ALWAYS_TRUE; + } + + throw new RuntimeException("Unable to create permission function for unknown type " + permissionSubject.getClass().getName()); + } +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/profile/punishment/Punishment.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/profile/punishment/Punishment.java new file mode 100644 index 0000000..b934d41 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/profile/punishment/Punishment.java @@ -0,0 +1,84 @@ +package dev.apposed.prime.proxy.module.profile.punishment; + +import dev.apposed.prime.proxy.module.profile.punishment.evidence.PunishmentEvidence; +import dev.apposed.prime.proxy.module.profile.punishment.type.PunishmentType; +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.*; + +@Data @AllArgsConstructor +public class Punishment { + + private final PunishmentType type; + + private final UUID addedBy; + private final long addedAt; + private final String addedReason; + private final long duration; + + private Set evidence; + + private UUID removedBy; + private long removedAt; + private String removedReason; + + private String ip; + private boolean removed; + private UUID player; + + public Punishment(PunishmentType type, UUID addedBy, long addedAt, String addedReason, long duration) { + this.type = type; + + this.addedBy = addedBy; + this.addedAt = addedAt; + this.addedReason = addedReason; + this.duration = duration; + + this.evidence = new HashSet<>(); + } + + public Punishment(PunishmentType type, UUID addedBy, long addedAt, String addedReason, long duration, Set evidence, UUID removedBy, long removedAt, String removedReason, boolean removed) { + this.type = type; + this.addedBy = addedBy; + this.addedAt = addedAt; + this.addedReason = addedReason; + this.duration = duration; + this.evidence = evidence; + this.removedBy = removedBy; + this.removedAt = removedAt; + this.removedReason = removedReason; + this.removed = removed; + } + + public PunishmentEvidence addEvidence(String link, UUID addedBy, long addedAt) { + PunishmentEvidence evidence = new PunishmentEvidence(link, addedBy, addedAt); + this.evidence.add(evidence); + return evidence; + } + + public void removePunishment(UUID removedBy, long removedAt, String removedReason) { + this.removed = true; + + this.removedBy = removedBy; + this.removedAt = removedAt; + this.removedReason = removedReason; + } + + public boolean isActive() { + if(!removed) { + if(this.duration == Long.MAX_VALUE) return true; + return System.currentTimeMillis() <= (this.addedAt + this.duration); + } + + return false; + } + + public long getRemaining() { + if(removed) return 0L; + if(duration == Long.MAX_VALUE) return Long.MAX_VALUE; + if(!isActive()) return 0L; + + return (addedAt + duration) - System.currentTimeMillis(); + } +} \ No newline at end of file diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/profile/punishment/evidence/PunishmentEvidence.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/profile/punishment/evidence/PunishmentEvidence.java new file mode 100644 index 0000000..4a1c426 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/profile/punishment/evidence/PunishmentEvidence.java @@ -0,0 +1,13 @@ +package dev.apposed.prime.proxy.module.profile.punishment.evidence; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.UUID; + +@Data @AllArgsConstructor +public class PunishmentEvidence { + private final String link; + private final UUID addedBy; + private final long addedAt; +} \ No newline at end of file diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/profile/punishment/type/PunishmentType.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/profile/punishment/type/PunishmentType.java new file mode 100644 index 0000000..69ee80f --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/profile/punishment/type/PunishmentType.java @@ -0,0 +1,28 @@ +package dev.apposed.prime.proxy.module.profile.punishment.type; + +import dev.apposed.prime.spigot.util.ItemBuilder; +import lombok.Getter; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +@Getter +public enum PunishmentType { + WARN("warned", "Warns", new ItemBuilder(Material.WOOL).dur(5).build(), 17, "prime.history.warn"), + MUTE("muted", "Mutes", new ItemBuilder(Material.WOOL).dur(4).build(), 15, "prime.history.mute"), + GHOSTMUTE("ghost muted", "Ghost Mutes", new ItemBuilder(Material.WOOL).dur(1).build(), 13, "prime.history.ghostmute"), + BAN("banned", "Bans", new ItemBuilder(Material.WOOL).dur(14).build(), 11, "prime.history.ban"), + BLACKLIST("blacklisted", "Blacklists", new ItemStack(Material.BEDROCK), 9, "prime.history.blacklist"); + + String display, menu; + ItemStack menuStack; + int slot; + String permission; + + PunishmentType(String display, String menu, ItemStack menuStack, int slot, String permission) { + this.display = display; + this.menu = menu; + this.menuStack = menuStack; + this.slot = slot; + this.permission = permission; + } +} \ No newline at end of file diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/rank/Rank.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/rank/Rank.java new file mode 100644 index 0000000..ce6c384 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/rank/Rank.java @@ -0,0 +1,71 @@ +package dev.apposed.prime.proxy.module.rank; + +import com.google.gson.annotations.SerializedName; +import dev.apposed.prime.proxy.module.rank.meta.RankMeta; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; +import net.kyori.adventure.text.format.NamedTextColor; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Data @AllArgsConstructor @Getter +public class Rank { + + @SerializedName("_id") + private String name; + + private List meta; + private List inherits; + private List permissions; + + private String prefix = ""; + private String color = NamedTextColor.WHITE.toString(); + private int weight; + + public Rank(String name) { + this.name = name; + + this.meta = new ArrayList<>(); + this.inherits = new ArrayList<>(); + this.permissions = new ArrayList<>(); + } + + public List getFullPermissions() { + final List permissions = new ArrayList<>(); + + permissions.addAll(this.permissions); + + for(Rank rank : inherits) { + permissions.addAll(rank.getPermissions()); + } + + return permissions; + } + + public boolean hasPermission(String permission) { + return this.getFullPermissions().contains(permission); + } + + public boolean inherits(Rank rank) { + return this.inherits.contains(rank); + } + + public boolean hasMeta(RankMeta meta, boolean individual) { + if(individual) + return this.meta.contains(meta); + + Optional rankedMeta = this.inherits.stream().filter(rank -> rank.hasMeta(meta, true)).findAny(); + return rankedMeta.isPresent() || this.meta.contains(meta); + } + + public String getColoredDisplay() { + return this.color + this.name; + } + + public int addWeight(int weight) { + return this.weight += weight; + } +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/rank/RankHandler.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/rank/RankHandler.java new file mode 100644 index 0000000..1730796 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/rank/RankHandler.java @@ -0,0 +1,64 @@ +package dev.apposed.prime.proxy.module.rank; + +import com.mongodb.client.MongoCollection; +import dev.apposed.prime.proxy.module.Module; +import dev.apposed.prime.proxy.module.database.mongo.MongoModule; +import dev.apposed.prime.proxy.module.profile.ProfileHandler; +import dev.apposed.prime.proxy.module.rank.meta.RankMeta; +import dev.apposed.prime.proxy.util.json.JsonHelper; +import lombok.Getter; +import org.bson.Document; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +@Getter +public class RankHandler extends Module { + + private final ProfileHandler profileHandler = this.getPlugin().getModuleHandler().getModule(ProfileHandler.class); + + private Set cache; + private MongoCollection collection; + + @Override + public void onEnable() { + this.cache = new HashSet<>(); + this.collection = this.getModuleHandler().getModule(MongoModule.class).getMongoDatabase().getCollection("ranks"); + this.collection.find().iterator().forEachRemaining(document -> add(JsonHelper.GSON.fromJson(JsonHelper.GSON.toJson(document), Rank.class))); + } + + public Rank getDefaultRank() { + return this.cache.stream().filter(rank -> rank.hasMeta(RankMeta.DEFAULT, true)).findFirst().orElse(null); + } + + public Rank add(Rank rank) { + this.cache.add(rank); + return rank; + } + + public Optional getRank(String name) { + return this.cache.stream().filter(rank -> rank.getName().equalsIgnoreCase(name)).findFirst(); + } + + public void updateRank(Rank updatedRank) { + Rank rank = this.getRank(updatedRank.getName()).orElse(null); + if(rank == null) { + rank = updatedRank; + this.cache.add(rank); + } + + rank.setPrefix(updatedRank.getPrefix()); + rank.setColor(updatedRank.getColor()); + rank.setWeight(updatedRank.getWeight()); + rank.setPrefix(updatedRank.getPrefix()); + rank.setInherits(updatedRank.getInherits()); + rank.setMeta(updatedRank.getMeta()); + rank.setPermissions(updatedRank.getPermissions()); + + //todo: npe +// this.profileHandler.getProfiles().stream().filter(Objects::nonNull).filter(profile -> profile.hasRank(updatedRank)).forEach(this.profileHandler::setupPlayer); + } + +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/rank/meta/RankMeta.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/rank/meta/RankMeta.java new file mode 100644 index 0000000..0cfaa08 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/rank/meta/RankMeta.java @@ -0,0 +1,22 @@ +package dev.apposed.prime.proxy.module.rank.meta; + +import lombok.Getter; + +@Getter +public enum RankMeta { + DEFAULT("Default Rank"), + STAFF("Staff Rank"), + SERVER("Bungee /server"), + DONATOR("Donator Rank"), + PREFIX("Additional Prefix"), + VPN_BYPASS("VPN Bypass"), + IP_BYPASS("IP Bypass"), + HIDDEN("Hidden Rank"); + + private String display; + + RankMeta(String display) { + this.display = display; + } + +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/server/Server.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/server/Server.java new file mode 100644 index 0000000..4158410 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/server/Server.java @@ -0,0 +1,24 @@ +package dev.apposed.prime.proxy.module.server; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.concurrent.TimeUnit; + +@Data @AllArgsConstructor +public class Server { + + private final String name, group; + private boolean whitelisted; + private int players, maxPlayers; + private long lastHeartbeat; + + public boolean isAlive() { + return (System.currentTimeMillis() - lastHeartbeat) <= TimeUnit.SECONDS.toMillis(15L); + } + + public Server(String name, String group) { + this.name = name; + this.group = group; + } +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/server/ServerGroup.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/server/ServerGroup.java new file mode 100644 index 0000000..4dfbb45 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/server/ServerGroup.java @@ -0,0 +1,9 @@ +package dev.apposed.prime.proxy.module.server; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter @AllArgsConstructor +public class ServerGroup { + private String id; +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/server/ServerHandler.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/server/ServerHandler.java new file mode 100644 index 0000000..b3ba6f1 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/server/ServerHandler.java @@ -0,0 +1,65 @@ +package dev.apposed.prime.proxy.module.server; + +import dev.apposed.prime.proxy.module.Module; +import lombok.Getter; + +import java.util.*; +import java.util.stream.Collectors; + +@Getter +public class ServerHandler extends Module { + + private Set servers; + private Set serverGroups; + + @Override + public void onEnable() { + this.servers = new HashSet<>(); + this.serverGroups = new HashSet<>(); + } + + public void updateServer(Server receivedServer) { + Server server = this.findByName(receivedServer.getName()).orElse(null); + if(server == null) { + server = new Server(receivedServer.getName(), receivedServer.getGroup()); + this.servers.add(server); + } + + server.setLastHeartbeat(receivedServer.getLastHeartbeat()); + server.setMaxPlayers(receivedServer.getMaxPlayers()); + server.setPlayers(receivedServer.getPlayers()); + server.setWhitelisted(receivedServer.isWhitelisted()); + + ServerGroup group = this.getGroupById(server.getGroup()).orElse(null); + if(group == null) this.serverGroups.add(new ServerGroup(server.getGroup())); + } + + public void createServerGroup(String name) { + this.serverGroups.add(new ServerGroup(name)); + } + + public Optional resolveServerGroup(Server server) { + return this.serverGroups.stream().filter(group -> group.getId().equalsIgnoreCase(server.getGroup())).findFirst(); + } + + public Optional getGroupById(String id) { + return this.serverGroups.stream().filter(group -> group.getId().equalsIgnoreCase(id)).findFirst(); + } + + public List getServersWithGroup(ServerGroup group) { + if(group.getId().equalsIgnoreCase("Global")) return new ArrayList<>(servers); + + return this.servers.stream().filter(server -> { + final Optional serverGroup = this.resolveServerGroup(server); + + if(serverGroup.isPresent()) { + return serverGroup.get().getId().equalsIgnoreCase(group.getId()); + } + return false; + }).collect(Collectors.toList()); + } + + public Optional findByName(String name) { + return this.servers.stream().filter(server -> server.getName().equalsIgnoreCase(name)).findFirst(); + } +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/velocity/VelocityHandler.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/velocity/VelocityHandler.java new file mode 100644 index 0000000..1f974b7 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/velocity/VelocityHandler.java @@ -0,0 +1,30 @@ +package dev.apposed.prime.proxy.module.velocity; + +import com.velocitypowered.api.proxy.Player; +import dev.apposed.prime.proxy.module.Module; +import lombok.Getter; + +import java.util.*; + +@Getter +public class VelocityHandler extends Module { + + private Map prevServer; + + @Override + public void onEnable() { + this.prevServer = new HashMap<>(); + } + public String getPreviousServer(Player player) { + return this.prevServer.getOrDefault(player, null); + } + + public void setPrevServer(Player player, String server) { + if(server == null) { + this.prevServer.remove(player); + return; + } + + prevServer.put(player, server); + } +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/velocity/listener/VelocityListener.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/velocity/listener/VelocityListener.java new file mode 100644 index 0000000..386f1a3 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/module/velocity/listener/VelocityListener.java @@ -0,0 +1,50 @@ +package dev.apposed.prime.proxy.module.velocity.listener; + +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.proxy.ProxyPingEvent; +import com.velocitypowered.api.proxy.server.ServerPing; +import dev.apposed.prime.proxy.PrimeProxy; +import dev.apposed.prime.proxy.module.database.redis.JedisModule; +import dev.apposed.prime.proxy.util.Color; +import dev.apposed.prime.proxy.util.time.DurationUtils; +import net.kyori.adventure.text.Component; + +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +public class VelocityListener { + + private final JedisModule jedisModule; + private volatile String lineOne = "Database Error", lineTwo = "Failed to fetch MOTD"; + private volatile long countdown = 0; + + { + this.jedisModule = PrimeProxy.getInstance().getModuleHandler().getModule(JedisModule.class); + + PrimeProxy.getInstance().getServer().getScheduler().buildTask(PrimeProxy.getInstance(), () -> this.jedisModule.runCommand(jedis -> { + this.lineOne = jedis.hget("Prime:MOTD", "1"); + this.lineTwo = jedis.hget("Prime:MOTD", "2"); + this.countdown = Long.parseLong(jedis.hget("Prime:MOTD", "countdown")); + })).repeat(10, TimeUnit.SECONDS).schedule(); + } + + @Subscribe + public void onProxyPing(ProxyPingEvent event) { + ServerPing.Builder builder = event.getPing().asBuilder() + .description(Component.text((this.lineOne != null ? this.lineOne.replace("%countdown%", DurationUtils.formatIntoDetailedString( + (int)((countdown - System.currentTimeMillis()) / 1000) + )) : "") + "\n" + (this.lineTwo != null ? this.lineTwo.replace("%countdown%", DurationUtils.formatIntoDetailedString( + (int)((countdown - System.currentTimeMillis()) / 1000) + )) : ""))); + if(PrimeProxy.getInstance().isInMaintenance()) { + builder.version(new ServerPing.Version(1, Color.translateNormal("&4Maintenance"))); + final ServerPing.SamplePlayer[] samplePlayers = new ServerPing.SamplePlayer[1]; + samplePlayers[0] = new ServerPing.SamplePlayer(Color.translateNormal("&cElevate is currently in maintenance"), MAINTENANCE_UUID); + builder.samplePlayers(samplePlayers); + } + + event.setPing(builder.build()); + } + + private static final UUID MAINTENANCE_UUID = new UUID(0, 0); +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/util/Color.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/util/Color.java new file mode 100644 index 0000000..670d47c --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/util/Color.java @@ -0,0 +1,22 @@ +package dev.apposed.prime.proxy.util; + +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +import java.util.List; +import java.util.stream.Collectors; + +public class Color { + + public static TextComponent translate(String text) { + return LegacyComponentSerializer.legacy('&').deserialize(text); + } + + public static List translate(List text) { + return text.stream().map(Color::translate).collect(Collectors.toList()); + } + + public static String translateNormal(String text){ + return translate(text).content(); + } +} \ No newline at end of file diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/util/json/JsonHelper.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/util/json/JsonHelper.java new file mode 100644 index 0000000..64dc996 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/util/json/JsonHelper.java @@ -0,0 +1,26 @@ +package dev.apposed.prime.proxy.util.json; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.LongSerializationPolicy; +import com.mongodb.client.model.ReplaceOptions; +import dev.apposed.prime.proxy.module.profile.Profile; +import dev.apposed.prime.proxy.util.json.serialization.ProfileSerializer; + +public class JsonHelper { + + public static Gson GSON = new GsonBuilder() + // Register adapters + .registerTypeAdapter(Profile.class, new ProfileSerializer()) + + // Stuffs + .setPrettyPrinting() + .setLongSerializationPolicy(LongSerializationPolicy.STRING) + .serializeNulls() + .serializeSpecialFloatingPointValues() + + // Create + .create(); + + public static ReplaceOptions REPLACE_OPTIONS = new ReplaceOptions().upsert(true); +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/util/json/serialization/ProfileSerializer.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/util/json/serialization/ProfileSerializer.java new file mode 100644 index 0000000..875f0be --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/util/json/serialization/ProfileSerializer.java @@ -0,0 +1,149 @@ +package dev.apposed.prime.proxy.util.json.serialization; + +import com.google.gson.*; +import dev.apposed.prime.proxy.PrimeProxy; +import dev.apposed.prime.proxy.module.profile.Profile; +import dev.apposed.prime.proxy.module.profile.grant.Grant; +import dev.apposed.prime.proxy.module.profile.punishment.Punishment; +import dev.apposed.prime.proxy.module.profile.punishment.evidence.PunishmentEvidence; +import dev.apposed.prime.proxy.module.profile.punishment.type.PunishmentType; +import dev.apposed.prime.proxy.module.rank.Rank; +import dev.apposed.prime.proxy.module.rank.RankHandler; + +import java.lang.reflect.Type; +import java.util.*; + +public class ProfileSerializer implements JsonDeserializer { + + @Override + public Profile deserialize(JsonElement element, Type t, JsonDeserializationContext context) throws JsonParseException { + JsonObject object = element.getAsJsonObject(); + + UUID uuid = UUID.fromString(object.get("_id").getAsString()); + String username = object.get("username").getAsString(); + + Set grants = new HashSet<>(); + + JsonArray grantsArray = object.get("grants").getAsJsonArray(); + grantsArray.forEach(grantElement -> { + JsonObject grant = grantElement.getAsJsonObject(); + + UUID id = UUID.fromString(grant.get("_id").getAsString()); + final RankHandler rankHandler = PrimeProxy.getInstance().getModuleHandler().getModule(RankHandler.class); + Optional rank = rankHandler.getRank(grant.get("rank").getAsString()); + Rank grantRank = rank.orElseGet(rankHandler::getDefaultRank); + + UUID addedBy = UUID.fromString(grant.get("addedBy").getAsString()); + long addedAt = grant.get("addedAt").getAsLong(); + String addedReason = grant.get("addedReason").getAsString(); + long duration = grant.get("duration").getAsLong(); + boolean removed = grant.get("removed").getAsBoolean(); + + List scopes = new ArrayList<>(); + JsonArray scopesArr = grant.get("scopes").getAsJsonArray(); + scopesArr.forEach(scopeElement -> scopes.add(scopeElement.getAsString())); + + if(removed) { + UUID removedBy = UUID.fromString(grant.get("removedBy").getAsString()); + long removedAt = grant.get("removedAt").getAsLong(); + String removedReason = grant.get("removedReason").getAsString(); + + grants.add( + new Grant(id, grantRank, addedBy, addedAt, addedReason, duration, scopes, removedBy, removedAt, removedReason, true) + ); + } else { + grants.add( + new Grant(id, grantRank, addedBy, addedAt, addedReason, duration, scopes, null, 0L, null, false) + ); + } + }); + + Set punishments = new HashSet<>(); + + if(object.get("punishments") != null) { + JsonArray punishmentsArray = object.get("punishments").getAsJsonArray(); + punishmentsArray.forEach(punishmentElement -> { + JsonObject punishment = punishmentElement.getAsJsonObject(); + + PunishmentType type = PunishmentType.valueOf(punishment.get("type").getAsString()); + + UUID addedBy = UUID.fromString(punishment.get("addedBy").getAsString()); + long addedAt = punishment.get("addedAt").getAsLong(); + String addedReason = punishment.get("addedReason").getAsString(); + long duration = punishment.get("duration").getAsLong(); + + Set evidence = new HashSet<>(); + + JsonArray evidenceArr = punishment.get("evidence").getAsJsonArray(); + evidenceArr.forEach(evidenceElement -> { + JsonObject evidenceObj = evidenceElement.getAsJsonObject(); + + String link = evidenceObj.get("link").getAsString(); + UUID evidenceAddedBy = UUID.fromString(evidenceObj.get("addedBy").getAsString()); + long evidenceAddedAt = evidenceObj.get("addedAt").getAsLong(); + + evidence.add(new PunishmentEvidence( + link, + evidenceAddedBy, + evidenceAddedAt + )); + }); + + boolean removed = punishment.get("removed").getAsBoolean(); + String ip = punishment.has("ip") ? punishment.get("ip").getAsString() : ""; + + if (removed) { + UUID removedBy = UUID.fromString(punishment.get("removedBy").getAsString()); + long removedAt = punishment.get("removedAt").getAsLong(); + String removedReason = punishment.get("removedReason").getAsString(); + + final Punishment punish = new Punishment( + type, + addedBy, + addedAt, + addedReason, + duration, + evidence, + removedBy, + removedAt, + removedReason, + true + ); + punish.setIp(ip); + punishments.add(punish); + } else { + final Punishment punish = new Punishment( + type, + addedBy, + addedAt, + addedReason, + duration, + evidence, + null, + 0L, + null, + false + ); + punish.setIp(ip); + punishments.add(punish); + } + }); + } + + Set permissions = new HashSet<>(); + + JsonArray permissionsArray = object.get("permissions").getAsJsonArray(); + permissionsArray.forEach(permissionElement -> permissions.add(permissionElement.getAsString())); + + boolean online = object.get("online").getAsBoolean(); + + return new Profile( + uuid, + username, + grants, + permissions, + punishments, + online + ); + } +} diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/util/mojang/MojangUtils.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/util/mojang/MojangUtils.java new file mode 100644 index 0000000..268d916 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/util/mojang/MojangUtils.java @@ -0,0 +1,118 @@ +package dev.apposed.prime.proxy.util.mojang; + +import dev.apposed.prime.proxy.PrimeProxy; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +public class MojangUtils { + + private static final Map cachedSkinResponses = new HashMap(); + + private interface JSONResponseCallback { + void handle(JSONObject response); + } + + public interface UUIDResponseCallback { + void handle(String uuid); + } + + public interface GetTextureResponse { + void handle(String texture, String signature); + } + + public static void getTextureAndSignature(String playerName, GetTextureResponse response) { + String[] previousResponse = cachedSkinResponses.get(playerName); + if (previousResponse != null) { + response.handle(previousResponse[0], previousResponse[1]); + return; + } + + getUUIDForPlayerName(playerName, (uuid -> { + if (uuid == null) { + response.handle(null, null); + return; + } + + getTextureAndSignatureFromUUID(uuid, ((texture, signature) -> { + cachedSkinResponses.put(playerName, new String[]{texture, signature}); + response.handle(texture, signature); + })); + })); + } + + public static void getUUIDForPlayerName(String playerName, UUIDResponseCallback response) { + get("https://api.mojang.com/users/profiles/minecraft/" + playerName, (uuidReply) -> { + if (uuidReply == null) { + response.handle(null); + return; + } + + String uuidString = (String) uuidReply.get("id"); + if (uuidString == null) { + response.handle(null); + return; + } + + response.handle(formatUUIDWithHyphens(uuidString)); + }); + } + + public static String formatUUIDWithHyphens(String uuid) { + return uuid.substring(0, 8) + "-" + uuid.substring(8, 12) + "-" + uuid.substring(12, 16) + "-" + uuid.substring(16, 20) + "-" + uuid.substring(20, 32); + } + + public static void getTextureAndSignatureFromUUID(String uuidString, GetTextureResponse response) { + get("https://sessionserver.mojang.com/session/minecraft/profile/" + uuidString + "?unsigned=false", (profileReply) -> { + if (!profileReply.containsKey("properties")) { + response.handle(null, null); + return; + } + + JSONArray propertiesArray = (JSONArray) profileReply.get("properties"); + JSONObject properties = (JSONObject) propertiesArray.get(0); + String texture = (String) properties.get("value"); + String signature = (String) properties.get("signature"); + response.handle(texture, signature); + }); + } + + private static void get(String url, JSONResponseCallback callback) { + PrimeProxy.getInstance().getServer().getScheduler().buildTask(PrimeProxy.getInstance(), () -> { + try { + URL rawURL = new URL(url); + HttpURLConnection connection = (HttpURLConnection) rawURL.openConnection(); + connection.setRequestMethod("GET"); + + BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream())); + String inputLine; + StringBuilder content = new StringBuilder(); + while ((inputLine = in.readLine()) != null) { + content.append(inputLine); + } + in.close(); + connection.disconnect(); + + if (content.toString().isEmpty()) { // Mojang API 204 fix + callback.handle(null); + return; + } + + JSONObject jsonObject = (JSONObject) new JSONParser().parse(content.toString()); + callback.handle(jsonObject); + } catch (IOException | ParseException e) { + e.printStackTrace(); + callback.handle(null); + } + }).schedule(); + } +} \ No newline at end of file diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/util/request/SimpleRequest.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/util/request/SimpleRequest.java new file mode 100644 index 0000000..4f43285 --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/util/request/SimpleRequest.java @@ -0,0 +1,37 @@ +package dev.apposed.prime.proxy.util.request; + +import lombok.SneakyThrows; +import lombok.experimental.UtilityClass; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.concurrent.CompletableFuture; + +@UtilityClass +public class SimpleRequest { + + @SneakyThrows + public static CompletableFuture getRequest(String urlString) { + return CompletableFuture.supplyAsync(() -> { + JSONObject object = new JSONObject(); + try { + URL url = new URL(urlString); + object = (JSONObject) new JSONParser().parse(new InputStreamReader(url.openStream())); + + } catch (MalformedURLException e) { + e.printStackTrace(); + } catch(IOException e) { + e.printStackTrace(); + } catch(ParseException e) { + e.printStackTrace(); + } + + return object; + }); + } +} \ No newline at end of file diff --git a/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/util/time/DurationUtils.java b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/util/time/DurationUtils.java new file mode 100644 index 0000000..c0d510c --- /dev/null +++ b/Prime/prime-velocity/src/main/java/dev/apposed/prime/proxy/util/time/DurationUtils.java @@ -0,0 +1,103 @@ +package dev.apposed.prime.proxy.util.time; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class DurationUtils { + + public static String formatIntoDetailedString(int secs) { + if (secs == 0) { + return "0 seconds"; + } else { + int remainder = secs % 86400; + int days = secs / 86400; + int hours = remainder / 3600; + int minutes = remainder / 60 - hours * 60; + int seconds = remainder % 3600 - minutes * 60; + String fDays = days > 0 ? " " + days + " day" + (days > 1 ? "s" : "") : ""; + String fHours = hours > 0 ? " " + hours + " hour" + (hours > 1 ? "s" : "") : ""; + String fMinutes = minutes > 0 ? " " + minutes + " minute" + (minutes > 1 ? "s" : "") : ""; + String fSeconds = seconds > 0 ? " " + seconds + " second" + (seconds > 1 ? "s" : "") : ""; + return (fDays + fHours + fMinutes + fSeconds).trim(); + } + } + + public static String toString(long millis) { + if(millis == Long.MAX_VALUE) { + return "Permament"; + } + millis -= System.currentTimeMillis(); + if(millis <= 0) { + return "Expired"; + } + long days = TimeUnit.MILLISECONDS.toDays(millis); + long hours = TimeUnit.MILLISECONDS.toHours(millis) + - TimeUnit.DAYS.toHours(days); + long minutes = TimeUnit.MILLISECONDS.toMinutes(millis) + - TimeUnit.HOURS.toMinutes(hours) + - TimeUnit.DAYS.toMinutes(days); + long seconds = TimeUnit.MILLISECONDS.toSeconds(millis) + - TimeUnit.HOURS.toSeconds(hours) + - TimeUnit.DAYS.toSeconds(days) + - TimeUnit.MINUTES.toSeconds(minutes); + List format = new ArrayList<>(); + if(days > 0) { + format.add(days + " days"); + } + if(hours > 0) { + format.add(hours + " hours"); + } + if(minutes > 0) { + format.add(minutes + " minutes"); + } + if(seconds > 0) { + format.add(seconds + " seconds"); + } + return String.join(", ", format); + } + + public static long fromString(String string) { + if(string.equalsIgnoreCase("perm") || string.equalsIgnoreCase("permanent")) { + return Long.MAX_VALUE; + } + long total = 0; + Matcher matcher = Pattern.compile("\\d+\\D+").matcher(string); + while(matcher.find()) { + String s = matcher.group(); + Long value = Long.parseLong(s.split("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)")[0]); + String type = s.split("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)")[1]; + switch (type) { + case "y": + total += value * 60 * 60 * 24 * 365; + case "M": + total += value * 60 * 60 * 24 * 30; + case "w": + total += value * 60 * 60 * 24 * 7; + case "d": + total += value * 60 * 60 * 24; + case "h": + total += value * 60 * 60; + case "m": + total += value * 60; + break; + case "s": + total += value; + break; + } + } + return total * 1000; + } + + public static String scoreboardFormat(long millis) { + return String.format("%02d:%02d:%02d", + TimeUnit.MILLISECONDS.toHours(millis), + TimeUnit.MILLISECONDS.toMinutes(millis) - + TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(millis)), // The change is in this line + TimeUnit.MILLISECONDS.toSeconds(millis) - + TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis))); + } + +} \ No newline at end of file diff --git a/Prime/prime-velocity/src/main/resources/config.yml b/Prime/prime-velocity/src/main/resources/config.yml new file mode 100644 index 0000000..c150eb7 --- /dev/null +++ b/Prime/prime-velocity/src/main/resources/config.yml @@ -0,0 +1,20 @@ +id: 'Proxy-NA-01' + +# database credentials +mongo: + host: '127.0.0.1' + port: 27017 + database: 'Prime' + auth: + enabled: false + username: '' + password: '' + auth-db: 'admin' + +redis: + host: '127.0.0.1' + port: 6379 + channel: '' + auth: false + password: '' + diff --git a/Prime/prime-velocity/src/main/resources/maintenance.yml b/Prime/prime-velocity/src/main/resources/maintenance.yml new file mode 100644 index 0000000..5b5d5a4 --- /dev/null +++ b/Prime/prime-velocity/src/main/resources/maintenance.yml @@ -0,0 +1,2 @@ +enabled: false +whitelist: \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..457c259 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# elevatemc + +For dmca requests contact me @ info@sigmagaming.net I will tell you to fuck off \ No newline at end of file