From b97890fe7a4b8aa124abc285e9775699e1a37e8f Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sun, 4 Aug 2024 21:37:50 +0200 Subject: [PATCH] Add SpigotCore module --- SpigotCore/SpigotCore_10/build.gradle.kts | 56 + SpigotCore/SpigotCore_12/build.gradle.kts | 57 + .../steamwar/core/LocaleChangeWrapper12.java | 34 + SpigotCore/SpigotCore_14/build.gradle.kts | 60 + .../de/steamwar/core/FlatteningWrapper14.java | 361 ++++ .../core/RecipeDiscoverWrapper14.java | 30 + .../de/steamwar/core/WorldEditWrapper14.java | 610 +++++++ .../src/de/steamwar/techhider/BlockIds14.java | 73 + .../de/steamwar/techhider/ChunkHider14.java | 42 + .../steamwar/techhider/ProtocolWrapper14.java | 52 + SpigotCore/SpigotCore_15/build.gradle.kts | 56 + SpigotCore/SpigotCore_18/build.gradle.kts | 64 + .../steamwar/core/CraftbukkitWrapper18.java | 40 + .../de/steamwar/core/ProtocolWrapper18.java | 61 + .../de/steamwar/core/WorldEditWrapper18.java | 44 + .../de/steamwar/techhider/ChunkHider18.java | 165 ++ .../steamwar/techhider/ProtocolWrapper18.java | 107 ++ SpigotCore/SpigotCore_19/build.gradle.kts | 64 + .../src/de/steamwar/core/ChatWrapper19.java | 45 + .../de/steamwar/core/ProtocolWrapper19.java | 64 + .../steamwar/techhider/ProtocolWrapper19.java | 34 + SpigotCore/SpigotCore_20/build.gradle.kts | 58 + SpigotCore/SpigotCore_8/build.gradle.kts | 58 + .../de/steamwar/core/BountifulWrapper8.java | 98 + .../src/de/steamwar/core/ChatWrapper8.java | 51 + .../de/steamwar/core/CraftbukkitWrapper8.java | 33 + .../de/steamwar/core/FlatteningWrapper8.java | 121 ++ .../src/de/steamwar/core/IDConverter8.java | 113 ++ .../steamwar/core/LocaleChangeWrapper8.java | 24 + .../de/steamwar/core/ProtocolWrapper8.java | 71 + .../steamwar/core/RecipeDiscoverWrapper8.java | 24 + .../de/steamwar/core/WorldEditWrapper8.java | 265 +++ .../src/de/steamwar/techhider/BlockIds8.java | 38 + .../de/steamwar/techhider/ChunkHider8.java | 40 + .../steamwar/techhider/ProtocolWrapper8.java | 86 + SpigotCore/SpigotCore_8/src/legacy.yml | 1623 +++++++++++++++++ SpigotCore/SpigotCore_9/build.gradle.kts | 57 + .../de/steamwar/core/BountifulWrapper9.java | 95 + .../de/steamwar/core/CraftbukkitWrapper9.java | 37 + .../de/steamwar/techhider/ChunkHider9.java | 169 ++ SpigotCore/SpigotCore_Main/build.gradle.kts | 65 + .../src/src/SpigotCore.properties | 107 ++ .../src/src/SpigotCore_de.properties | 102 ++ .../comphenix/tinyprotocol/Reflection.java | 397 ++++ .../comphenix/tinyprotocol/TinyProtocol.java | 207 +++ .../CaseInsensitiveCommandsListener.java | 35 + .../steamwar/command/CommandRegistering.java | 65 + .../src/de/steamwar/command/SWCommand.java | 160 ++ .../src/de/steamwar/command/TypeMapper.java | 40 + .../src/de/steamwar/command/TypeUtils.java | 88 + .../de/steamwar/command/TypeValidator.java | 25 + .../de/steamwar/core/BountifulWrapper.java | 53 + .../src/src/de/steamwar/core/ChatWrapper.java | 29 + .../src/de/steamwar/core/CheckpointUtils.java | 147 ++ .../src/de/steamwar/core/CommandRemover.java | 40 + .../src/src/de/steamwar/core/Core.java | 127 ++ .../de/steamwar/core/CraftbukkitWrapper.java | 32 + .../src/de/steamwar/core/CrashDetector.java | 91 + .../src/de/steamwar/core/ErrorHandler.java | 163 ++ .../de/steamwar/core/FlatteningWrapper.java | 61 + .../de/steamwar/core/LocaleChangeWrapper.java | 26 + .../src/de/steamwar/core/ProtocolWrapper.java | 57 + .../steamwar/core/RecipeDiscoverWrapper.java | 26 + .../src/de/steamwar/core/TPSWarpUtils.java | 56 + .../src/src/de/steamwar/core/TPSWatcher.java | 100 + .../de/steamwar/core/VersionDependent.java | 46 + .../de/steamwar/core/WorldEditWrapper.java | 25 + .../core/authlib/AuthlibInjector.java | 35 + .../SteamwarGameProfileRepository.java | 60 + .../de/steamwar/core/events/AntiNocom.java | 96 + .../steamwar/core/events/ChattingEvent.java | 35 + .../core/events/PartialChunkFixer.java | 84 + .../core/events/PlayerJoinedEvent.java | 55 + .../steamwar/core/events/WorldLoadEvent.java | 33 + .../src/de/steamwar/entity/RArmorStand.java | 81 + .../src/src/de/steamwar/entity/REntity.java | 491 +++++ .../src/de/steamwar/entity/REntityServer.java | 329 ++++ .../steamwar/entity/RFallingBlockEntity.java | 39 + .../src/src/de/steamwar/entity/RPlayer.java | 103 ++ .../de/steamwar/inventory/InvCallback.java | 26 + .../src/de/steamwar/inventory/SWAnvilInv.java | 99 + .../de/steamwar/inventory/SWInventory.java | 173 ++ .../src/src/de/steamwar/inventory/SWItem.java | 201 ++ .../src/de/steamwar/inventory/SWListInv.java | 183 ++ .../steamwar/inventory/SchematicSelector.java | 656 +++++++ .../SchematicSelectorInjectable.java | 59 + .../src/de/steamwar/inventory/UtilGui.java | 54 + .../src/src/de/steamwar/message/Message.java | 158 ++ .../steamwar/network/CoreNetworkHandler.java | 71 + .../de/steamwar/network/NetworkReceiver.java | 36 + .../de/steamwar/network/NetworkSender.java | 38 + .../network/handlers/InventoryHandler.java | 56 + .../network/handlers/ServerDataHandler.java | 12 + .../de/steamwar/providers/BauServerInfo.java | 47 + .../de/steamwar/scoreboard/SWScoreboard.java | 117 ++ .../scoreboard/ScoreboardCallback.java | 29 + .../src/src/de/steamwar/sql/PersonalKit.java | 143 ++ .../src/de/steamwar/sql/SQLConfigImpl.java | 37 + .../src/de/steamwar/sql/SQLWrapperImpl.java | 81 + .../src/de/steamwar/sql/SchematicData.java | 75 + .../src/de/steamwar/techhider/BlockIds.java | 33 + .../src/de/steamwar/techhider/ChunkHider.java | 59 + .../de/steamwar/techhider/ProtocolUtils.java | 198 ++ .../steamwar/techhider/ProtocolWrapper.java | 39 + .../src/de/steamwar/techhider/TechHider.java | 168 ++ SpigotCore/SpigotCore_Main/src/src/plugin.yml | 12 + SpigotCore/build.gradle.kts | 72 + build.gradle.kts | 20 + gradle.properties | 2 +- settings.gradle.kts | 13 + 110 files changed, 11957 insertions(+), 1 deletion(-) create mode 100644 SpigotCore/SpigotCore_10/build.gradle.kts create mode 100644 SpigotCore/SpigotCore_12/build.gradle.kts create mode 100644 SpigotCore/SpigotCore_12/src/src/de/steamwar/core/LocaleChangeWrapper12.java create mode 100644 SpigotCore/SpigotCore_14/build.gradle.kts create mode 100644 SpigotCore/SpigotCore_14/src/src/de/steamwar/core/FlatteningWrapper14.java create mode 100644 SpigotCore/SpigotCore_14/src/src/de/steamwar/core/RecipeDiscoverWrapper14.java create mode 100644 SpigotCore/SpigotCore_14/src/src/de/steamwar/core/WorldEditWrapper14.java create mode 100644 SpigotCore/SpigotCore_14/src/src/de/steamwar/techhider/BlockIds14.java create mode 100644 SpigotCore/SpigotCore_14/src/src/de/steamwar/techhider/ChunkHider14.java create mode 100644 SpigotCore/SpigotCore_14/src/src/de/steamwar/techhider/ProtocolWrapper14.java create mode 100644 SpigotCore/SpigotCore_15/build.gradle.kts create mode 100644 SpigotCore/SpigotCore_18/build.gradle.kts create mode 100644 SpigotCore/SpigotCore_18/src/src/de/steamwar/core/CraftbukkitWrapper18.java create mode 100644 SpigotCore/SpigotCore_18/src/src/de/steamwar/core/ProtocolWrapper18.java create mode 100644 SpigotCore/SpigotCore_18/src/src/de/steamwar/core/WorldEditWrapper18.java create mode 100644 SpigotCore/SpigotCore_18/src/src/de/steamwar/techhider/ChunkHider18.java create mode 100644 SpigotCore/SpigotCore_18/src/src/de/steamwar/techhider/ProtocolWrapper18.java create mode 100644 SpigotCore/SpigotCore_19/build.gradle.kts create mode 100644 SpigotCore/SpigotCore_19/src/src/de/steamwar/core/ChatWrapper19.java create mode 100644 SpigotCore/SpigotCore_19/src/src/de/steamwar/core/ProtocolWrapper19.java create mode 100644 SpigotCore/SpigotCore_19/src/src/de/steamwar/techhider/ProtocolWrapper19.java create mode 100644 SpigotCore/SpigotCore_20/build.gradle.kts create mode 100644 SpigotCore/SpigotCore_8/build.gradle.kts create mode 100644 SpigotCore/SpigotCore_8/src/de/steamwar/core/BountifulWrapper8.java create mode 100644 SpigotCore/SpigotCore_8/src/de/steamwar/core/ChatWrapper8.java create mode 100644 SpigotCore/SpigotCore_8/src/de/steamwar/core/CraftbukkitWrapper8.java create mode 100644 SpigotCore/SpigotCore_8/src/de/steamwar/core/FlatteningWrapper8.java create mode 100644 SpigotCore/SpigotCore_8/src/de/steamwar/core/IDConverter8.java create mode 100644 SpigotCore/SpigotCore_8/src/de/steamwar/core/LocaleChangeWrapper8.java create mode 100644 SpigotCore/SpigotCore_8/src/de/steamwar/core/ProtocolWrapper8.java create mode 100644 SpigotCore/SpigotCore_8/src/de/steamwar/core/RecipeDiscoverWrapper8.java create mode 100644 SpigotCore/SpigotCore_8/src/de/steamwar/core/WorldEditWrapper8.java create mode 100644 SpigotCore/SpigotCore_8/src/de/steamwar/techhider/BlockIds8.java create mode 100644 SpigotCore/SpigotCore_8/src/de/steamwar/techhider/ChunkHider8.java create mode 100644 SpigotCore/SpigotCore_8/src/de/steamwar/techhider/ProtocolWrapper8.java create mode 100644 SpigotCore/SpigotCore_8/src/legacy.yml create mode 100644 SpigotCore/SpigotCore_9/build.gradle.kts create mode 100644 SpigotCore/SpigotCore_9/src/de/steamwar/core/BountifulWrapper9.java create mode 100644 SpigotCore/SpigotCore_9/src/de/steamwar/core/CraftbukkitWrapper9.java create mode 100644 SpigotCore/SpigotCore_9/src/de/steamwar/techhider/ChunkHider9.java create mode 100644 SpigotCore/SpigotCore_Main/build.gradle.kts create mode 100644 SpigotCore/SpigotCore_Main/src/src/SpigotCore.properties create mode 100644 SpigotCore/SpigotCore_Main/src/src/SpigotCore_de.properties create mode 100644 SpigotCore/SpigotCore_Main/src/src/com/comphenix/tinyprotocol/Reflection.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/com/comphenix/tinyprotocol/TinyProtocol.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/command/CaseInsensitiveCommandsListener.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/command/CommandRegistering.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/command/SWCommand.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/command/TypeMapper.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/command/TypeUtils.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/command/TypeValidator.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/BountifulWrapper.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/ChatWrapper.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/CheckpointUtils.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/CommandRemover.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/Core.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/CraftbukkitWrapper.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/CrashDetector.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/ErrorHandler.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/FlatteningWrapper.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/LocaleChangeWrapper.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/ProtocolWrapper.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/RecipeDiscoverWrapper.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/TPSWarpUtils.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/TPSWatcher.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/VersionDependent.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/WorldEditWrapper.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/authlib/AuthlibInjector.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/authlib/SteamwarGameProfileRepository.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/events/AntiNocom.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/events/ChattingEvent.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/events/PartialChunkFixer.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/events/PlayerJoinedEvent.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/events/WorldLoadEvent.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/entity/RArmorStand.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/entity/REntity.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/entity/REntityServer.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/entity/RFallingBlockEntity.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/entity/RPlayer.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/InvCallback.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/SWAnvilInv.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/SWInventory.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/SWItem.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/SWListInv.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/SchematicSelector.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/SchematicSelectorInjectable.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/UtilGui.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/message/Message.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/network/CoreNetworkHandler.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/network/NetworkReceiver.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/network/NetworkSender.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/network/handlers/InventoryHandler.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/network/handlers/ServerDataHandler.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/providers/BauServerInfo.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/scoreboard/SWScoreboard.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/scoreboard/ScoreboardCallback.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/sql/PersonalKit.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/sql/SQLConfigImpl.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/sql/SQLWrapperImpl.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/sql/SchematicData.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/techhider/BlockIds.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/techhider/ChunkHider.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/techhider/ProtocolUtils.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/techhider/ProtocolWrapper.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/de/steamwar/techhider/TechHider.java create mode 100644 SpigotCore/SpigotCore_Main/src/src/plugin.yml create mode 100644 SpigotCore/build.gradle.kts diff --git a/SpigotCore/SpigotCore_10/build.gradle.kts b/SpigotCore/SpigotCore_10/build.gradle.kts new file mode 100644 index 00000000..145b04d3 --- /dev/null +++ b/SpigotCore/SpigotCore_10/build.gradle.kts @@ -0,0 +1,56 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2024 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +plugins { + id("java") + id("base") +} + +group = "de.steamwar" +version = "" + +tasks.compileJava { + options.encoding = "UTF-8" +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +sourceSets { + main { + java { + srcDirs("src/") + } + resources { + srcDirs("src/") + exclude("**/*.java", "**/*.kt") + } + } +} + +dependencies { + compileOnly("org.projectlombok:lombok:1.18.32") + annotationProcessor("org.projectlombok:lombok:1.18.32") + + compileOnly(project(":SpigotCore:SpigotCore_Main")) + + compileOnly("de.steamwar:spigot:1.10") +} diff --git a/SpigotCore/SpigotCore_12/build.gradle.kts b/SpigotCore/SpigotCore_12/build.gradle.kts new file mode 100644 index 00000000..976aa202 --- /dev/null +++ b/SpigotCore/SpigotCore_12/build.gradle.kts @@ -0,0 +1,57 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2024 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +plugins { + id("java") + id("base") +} + +group = "de.steamwar" +version = "" + +tasks.compileJava { + options.encoding = "UTF-8" +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +sourceSets { + main { + java { + srcDirs("src/") + } + resources { + srcDirs("src/") + exclude("**/*.java", "**/*.kt") + } + } +} + +dependencies { + compileOnly("org.projectlombok:lombok:1.18.32") + annotationProcessor("org.projectlombok:lombok:1.18.32") + + compileOnly(project(":SpigotCore:SpigotCore_Main")) + compileOnly(project(":CommonCore")) + + compileOnly("de.steamwar:spigot:1.12") +} diff --git a/SpigotCore/SpigotCore_12/src/src/de/steamwar/core/LocaleChangeWrapper12.java b/SpigotCore/SpigotCore_12/src/src/de/steamwar/core/LocaleChangeWrapper12.java new file mode 100644 index 00000000..152751ce --- /dev/null +++ b/SpigotCore/SpigotCore_12/src/src/de/steamwar/core/LocaleChangeWrapper12.java @@ -0,0 +1,34 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.core; + +import de.steamwar.sql.SteamwarUser; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerLocaleChangeEvent; + +import java.util.Locale; + +public class LocaleChangeWrapper12 implements LocaleChangeWrapper { + + @EventHandler + private void onLocale(PlayerLocaleChangeEvent event) { + SteamwarUser.get(event.getPlayer().getUniqueId()).setLocale(Locale.forLanguageTag(event.getLocale()), false); + } +} diff --git a/SpigotCore/SpigotCore_14/build.gradle.kts b/SpigotCore/SpigotCore_14/build.gradle.kts new file mode 100644 index 00000000..49b7ef24 --- /dev/null +++ b/SpigotCore/SpigotCore_14/build.gradle.kts @@ -0,0 +1,60 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2024 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +plugins { + id("java") + id("base") +} + +group = "de.steamwar" +version = "" + +tasks.compileJava { + options.encoding = "UTF-8" +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +sourceSets { + main { + java { + srcDirs("src/") + } + resources { + srcDirs("src/") + exclude("**/*.java", "**/*.kt") + } + } +} + +dependencies { + compileOnly("org.projectlombok:lombok:1.18.32") + annotationProcessor("org.projectlombok:lombok:1.18.32") + + compileOnly(project(":SpigotCore:SpigotCore_Main")) + compileOnly(project(":SpigotCore:SpigotCore_8")) + compileOnly(project(":SpigotCore:SpigotCore_9")) + compileOnly(project(":CommonCore")) + + compileOnly("de.steamwar:spigot:1.14") + compileOnly("de.steamwar:worldedit:1.15") +} diff --git a/SpigotCore/SpigotCore_14/src/src/de/steamwar/core/FlatteningWrapper14.java b/SpigotCore/SpigotCore_14/src/src/de/steamwar/core/FlatteningWrapper14.java new file mode 100644 index 00000000..f5626d03 --- /dev/null +++ b/SpigotCore/SpigotCore_14/src/src/de/steamwar/core/FlatteningWrapper14.java @@ -0,0 +1,361 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.core; + +import com.comphenix.tinyprotocol.Reflection; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.World; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.SkullMeta; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class FlatteningWrapper14 implements FlatteningWrapper.IFlatteningWrapper { + + private static final Map renamedLegacy = new HashMap<>(); + + static{ + renamedLegacy.put("WOOD", Material.OAK_WOOD); + renamedLegacy.put("SAPLING", Material.OAK_SAPLING); + renamedLegacy.put("STATIONARY_WATER", Material.WATER); + renamedLegacy.put("STATIONARY_LAVA", Material.LAVA); + renamedLegacy.put("LOG", Material.OAK_LOG); + renamedLegacy.put("LEAVES", Material.OAK_LEAVES); + renamedLegacy.put("BED_BLOCK", Material.RED_BED); + renamedLegacy.put("BED", Material.RED_BED); + renamedLegacy.put("PISTON_STICKY_BASE", Material.STICKY_PISTON); + renamedLegacy.put("WEB", Material.COBWEB); + renamedLegacy.put("LONG_GRASS", Material.TALL_GRASS); + renamedLegacy.put("PISTON_BASE", Material.PISTON); + renamedLegacy.put("PISTON_EXTENSION", Material.PISTON_HEAD); + renamedLegacy.put("WOOL", Material.WHITE_WOOL); + renamedLegacy.put("PISTON_MOVING_PIECE", Material.MOVING_PISTON); + renamedLegacy.put("YELLOW_FLOWER", Material.DANDELION); + renamedLegacy.put("RED_ROSE", Material.POPPY); + renamedLegacy.put("DOUBLE_STEP", Material.SMOOTH_STONE); + renamedLegacy.put("STEP", Material.SMOOTH_STONE_SLAB); + renamedLegacy.put("MOB_SPAWNER", Material.SPAWNER); + renamedLegacy.put("WOOD_STAIRS", Material.OAK_STAIRS); + renamedLegacy.put("WORKBENCH", Material.CRAFTING_TABLE); + renamedLegacy.put("CROPS", Material.WHEAT_SEEDS); + renamedLegacy.put("SEEDS", Material.WHEAT_SEEDS); + renamedLegacy.put("SOIL", Material.FARMLAND); + renamedLegacy.put("BURNING_FURNACE", Material.FURNACE); + renamedLegacy.put("SIGN_POST", Material.OAK_SIGN); + renamedLegacy.put("SIGN", Material.OAK_SIGN); + renamedLegacy.put("WOODEN_DOOR", Material.OAK_DOOR); + renamedLegacy.put("WOOD_DOOR", Material.OAK_DOOR); + renamedLegacy.put("RAILS", Material.RAIL); + renamedLegacy.put("WALL_SIGN", Material.OAK_WALL_SIGN); + renamedLegacy.put("STONE_PLATE", Material.STONE_PRESSURE_PLATE); + renamedLegacy.put("WOOD_PLATE", Material.OAK_PRESSURE_PLATE); + renamedLegacy.put("GLOWING_REDSTONE_ORE", Material.REDSTONE_ORE); + renamedLegacy.put("REDSTONE_TORCH_OFF", Material.REDSTONE_TORCH); + renamedLegacy.put("REDSTONE_TORCH_ON", Material.REDSTONE_TORCH); + renamedLegacy.put("IRON_DOOR_BLOCK", Material.IRON_DOOR); + renamedLegacy.put("SUGAR_CANE_BLOCK", Material.SUGAR_CANE); + renamedLegacy.put("CAKE_BLOCK", Material.CAKE); + renamedLegacy.put("MELON_BLOCK", Material.MELON); + renamedLegacy.put("BEETROOT_BLOCK", Material.BEETROOT); + renamedLegacy.put("FENCE", Material.OAK_FENCE); + renamedLegacy.put("PORTAL", Material.NETHER_PORTAL); + renamedLegacy.put("DIODE_BLOCK_OFF", Material.REPEATER); + renamedLegacy.put("DIODE_BLOCK_ON", Material.REPEATER); + renamedLegacy.put("DIODE", Material.REPEATER); + renamedLegacy.put("STAINED_GLASS", Material.WHITE_STAINED_GLASS); + renamedLegacy.put("TRAP_DOOR", Material.OAK_TRAPDOOR); + renamedLegacy.put("MONSTER_EGGS", Material.SKELETON_SPAWN_EGG); + renamedLegacy.put("MONSTER_EGG", Material.SKELETON_SPAWN_EGG); + renamedLegacy.put("SMOOTH_BRICK", Material.STONE_BRICKS); + renamedLegacy.put("HUGE_MUSHROOM_1", Material.MUSHROOM_STEM); + renamedLegacy.put("HUGE_MUSHROOM_2", Material.RED_MUSHROOM); + renamedLegacy.put("IRON_FENCE", Material.IRON_BARS); + renamedLegacy.put("THIN_GLASS", Material.GLASS_PANE); + renamedLegacy.put("FENCE_GATE", Material.OAK_FENCE_GATE); + renamedLegacy.put("SMOOTH_STAIRS", Material.STONE_BRICK_STAIRS); + renamedLegacy.put("MYCEL", Material.MYCELIUM); + renamedLegacy.put("WATER_LILY", Material.LILY_PAD); + renamedLegacy.put("NETHER_FENCE", Material.NETHER_BRICK_FENCE); + renamedLegacy.put("NETHER_WARTS", Material.NETHER_WART); + renamedLegacy.put("NETHER_STALK", Material.NETHER_WART); + renamedLegacy.put("ENCHANTMENT_TABLE", Material.ENCHANTING_TABLE); + renamedLegacy.put("ENDER_PORTAL", Material.END_PORTAL); + renamedLegacy.put("ENDER_PORTAL_FRAME", Material.END_PORTAL_FRAME); + renamedLegacy.put("ENDER_STONE", Material.END_STONE); + renamedLegacy.put("REDSTONE_LAMP_OFF", Material.REDSTONE_LAMP); + renamedLegacy.put("REDSTONE_LAMP_ON", Material.REDSTONE_LAMP); + renamedLegacy.put("WOOD_DOUBLE_STEP", Material.OAK_SLAB); + renamedLegacy.put("WOOD_STEP", Material.OAK_SLAB); + renamedLegacy.put("SPRUCE_WOOD_STAIRS", Material.SPRUCE_STAIRS); + renamedLegacy.put("BIRCH_WOOD_STAIRS", Material.BIRCH_STAIRS); + renamedLegacy.put("JUNGLE_WOOD_STAIRS", Material.JUNGLE_STAIRS); + renamedLegacy.put("COMMAND", Material.COMMAND_BLOCK); + renamedLegacy.put("COBBLE_WALL", Material.COBBLESTONE_WALL); + renamedLegacy.put("WOOD_BUTTON", Material.OAK_BUTTON); + renamedLegacy.put("SKULL", Material.SKELETON_SKULL); + renamedLegacy.put("SKULL_ITEM", Material.SKELETON_SKULL); + renamedLegacy.put("GOLD_PLATE", Material.LIGHT_WEIGHTED_PRESSURE_PLATE); + renamedLegacy.put("IRON_PLATE", Material.HEAVY_WEIGHTED_PRESSURE_PLATE); + renamedLegacy.put("REDSTONE_COMPARATOR_OFF", Material.COMPARATOR); + renamedLegacy.put("REDSTONE_COMPARATOR_ON", Material.COMPARATOR); + renamedLegacy.put("REDSTONE_COMPARATOR", Material.COMPARATOR); + renamedLegacy.put("QUARTZ_ORE", Material.QUARTZ); + renamedLegacy.put("STAINED_CLAY", Material.WHITE_TERRACOTTA); + renamedLegacy.put("STAINED_GLASS_PANE", Material.WHITE_STAINED_GLASS_PANE); + renamedLegacy.put("LEAVES_2", Material.ACACIA_LEAVES); + renamedLegacy.put("LOG_2", Material.ACACIA_LOG); + renamedLegacy.put("CARPET", Material.WHITE_CARPET); + renamedLegacy.put("HARD_CLAY", Material.TERRACOTTA); + renamedLegacy.put("DOUBLE_PLANT", Material.SUNFLOWER); + renamedLegacy.put("STANDING_BANNER", Material.WHITE_BANNER); + renamedLegacy.put("BANNER", Material.WHITE_BANNER); + renamedLegacy.put("WALL_BANNER", Material.WHITE_WALL_BANNER); + renamedLegacy.put("DAYLIGHT_DETECTOR_INVERTED", Material.DAYLIGHT_DETECTOR); + renamedLegacy.put("DOUBLE_STONE_SLAB2", Material.RED_SANDSTONE_SLAB); + renamedLegacy.put("STONE_SLAB2", Material.RED_SANDSTONE_SLAB); + renamedLegacy.put("PURPUR_DOUBLE_SLAB", Material.PURPUR_SLAB); + renamedLegacy.put("END_BRICKS", Material.END_STONE_BRICKS); + renamedLegacy.put("COMMAND_REPEATING", Material.REPEATING_COMMAND_BLOCK); + renamedLegacy.put("COMMAND_CHAIN", Material.CHAIN_COMMAND_BLOCK); + renamedLegacy.put("MAGMA", Material.MAGMA_BLOCK); + renamedLegacy.put("RED_NETHER_BRICK", Material.RED_NETHER_BRICKS); + renamedLegacy.put("SILVER_SHULKER_BOX", Material.LIGHT_GRAY_SHULKER_BOX); + renamedLegacy.put("SILVER_GLAZED_TERRACOTTA", Material.LIGHT_GRAY_TERRACOTTA); + renamedLegacy.put("CONCRETE", Material.WHITE_CONCRETE); + renamedLegacy.put("CONCRETE_POWDER", Material.WHITE_CONCRETE_POWDER); + renamedLegacy.put("IRON_SPADE", Material.IRON_SHOVEL); + renamedLegacy.put("WOOD_SWORD", Material.WOODEN_SWORD); + renamedLegacy.put("WOOD_SPADE", Material.WOODEN_SHOVEL); + renamedLegacy.put("WOOD_PICKAXE", Material.WOODEN_PICKAXE); + renamedLegacy.put("WOOD_AXE", Material.WOODEN_AXE); + renamedLegacy.put("STONE_SPADE", Material.STONE_SHOVEL); + renamedLegacy.put("DIAMOND_SPADE", Material.DIAMOND_SHOVEL); + renamedLegacy.put("MUSHROOM_SOUP", Material.MUSHROOM_STEW); + renamedLegacy.put("GOLD_SWORD", Material.GOLDEN_SWORD); + renamedLegacy.put("GOLD_SPADE", Material.GOLDEN_SHOVEL); + renamedLegacy.put("GOLD_PICKAXE", Material.GOLDEN_PICKAXE); + renamedLegacy.put("GOLD_AXE", Material.GOLDEN_AXE); + renamedLegacy.put("SULPHUR", Material.GUNPOWDER); + renamedLegacy.put("WOOD_HOE", Material.WOODEN_HOE); + renamedLegacy.put("GOLD_HOE", Material.GOLDEN_HOE); + renamedLegacy.put("GOLD_HELMET", Material.GOLDEN_HELMET); + renamedLegacy.put("GOLD_CHESTPLATE", Material.GOLDEN_CHESTPLATE); + renamedLegacy.put("GOLD_LEGGINGS", Material.GOLDEN_LEGGINGS); + renamedLegacy.put("GOLD_BOOTS", Material.GOLDEN_BOOTS); + renamedLegacy.put("PORK", Material.PORKCHOP); + renamedLegacy.put("GRILLED_PORK", Material.COOKED_PORKCHOP); + renamedLegacy.put("SNOW_BALL", Material.SNOWBALL); + renamedLegacy.put("BOAT", Material.OAK_BOAT); + renamedLegacy.put("CLAY_BRICK", Material.BRICKS); + renamedLegacy.put("STORAGE_MINECART", Material.CHEST_MINECART); + renamedLegacy.put("POWERED_MINECART", Material.FURNACE_MINECART); + renamedLegacy.put("WATCH", Material.CLOCK); + renamedLegacy.put("RAW_FISH", Material.SALMON); + renamedLegacy.put("COOKED_FISH", Material.COOKED_SALMON); + renamedLegacy.put("INK_SACK", Material.INK_SAC); + renamedLegacy.put("RAW_BEEF", Material.BEEF); + renamedLegacy.put("RAW_CHICKEN", Material.CHICKEN); + renamedLegacy.put("EYE_OF_ENDER", Material.ENDER_EYE); + renamedLegacy.put("SPECKLED_MELON", Material.GLISTERING_MELON_SLICE); + renamedLegacy.put("EXP_BOTTLE", Material.EXPERIENCE_BOTTLE); + renamedLegacy.put("FIREBALL", Material.FIRE_CHARGE); + renamedLegacy.put("BOOK_AND_QUILL", Material.WRITABLE_BOOK); + renamedLegacy.put("FLOWER_POT_ITEM", Material.FLOWER_POT); + renamedLegacy.put("EMPTY_MAP", Material.MAP); + renamedLegacy.put("BREWING_STAND_ITEM", Material.BREWING_STAND); + renamedLegacy.put("CAULDRON_ITEM", Material.CAULDRON); + renamedLegacy.put("CARROT_ITEM", Material.CARROT); + renamedLegacy.put("POTATO_ITEM", Material.POTATO); + renamedLegacy.put("SPRUCE_DOOR_ITEM", Material.SPRUCE_DOOR); + renamedLegacy.put("BIRCH_DOOR_ITEM", Material.BIRCH_DOOR); + renamedLegacy.put("JUNGLE_DOOR_ITEM", Material.JUNGLE_DOOR); + renamedLegacy.put("ACACIA_DOOR_ITEM", Material.ACACIA_DOOR); + renamedLegacy.put("DARK_OAK_DOOR_ITEM", Material.DARK_OAK_DOOR); + renamedLegacy.put("CARROT_STICK", Material.CARROT_ON_A_STICK); + renamedLegacy.put("FIREWORK", Material.FIREWORK_ROCKET); + renamedLegacy.put("FIREWORK_CHARGE", Material.FIREWORK_STAR); + renamedLegacy.put("NETHER_BRICK_ITEM", Material.NETHER_BRICKS); + renamedLegacy.put("EXPLOSIVE_MINECART", Material.TNT_MINECART); + renamedLegacy.put("IRON_BARDING", Material.IRON_HORSE_ARMOR); + renamedLegacy.put("GOLD_BARDING", Material.GOLDEN_HORSE_ARMOR); + renamedLegacy.put("DIAMOND_BARDING", Material.DIAMOND_HORSE_ARMOR); + renamedLegacy.put("LEASH", Material.LEAD); + renamedLegacy.put("COMMAND_MINECART", Material.COMMAND_BLOCK_MINECART); + renamedLegacy.put("CHORUS_FRUIT_POPPED", Material.POPPED_CHORUS_FRUIT); + renamedLegacy.put("DRAGONS_BREATH", Material.DRAGON_BREATH); + renamedLegacy.put("BOAT_SPRUCE", Material.SPRUCE_BOAT); + renamedLegacy.put("BOAT_BIRCH", Material.BIRCH_BOAT); + renamedLegacy.put("BOAT_JUNGLE", Material.JUNGLE_BOAT); + renamedLegacy.put("BOAT_ACACIA", Material.ACACIA_BOAT); + renamedLegacy.put("BOAT_DARK_OAK", Material.DARK_OAK_BOAT); + renamedLegacy.put("TOTEM", Material.TOTEM_OF_UNDYING); + renamedLegacy.put("GOLD_RECORD", Material.MUSIC_DISC_13); + renamedLegacy.put("GREEN_RECORD", Material.MUSIC_DISC_CAT); + renamedLegacy.put("RECORD_3", Material.MUSIC_DISC_BLOCKS); + renamedLegacy.put("RECORD_4", Material.MUSIC_DISC_CHIRP); + renamedLegacy.put("RECORD_5", Material.MUSIC_DISC_FAR); + renamedLegacy.put("RECORD_6", Material.MUSIC_DISC_MALL); + renamedLegacy.put("RECORD_7", Material.MUSIC_DISC_MELLOHI); + renamedLegacy.put("RECORD_8", Material.MUSIC_DISC_STAL); + renamedLegacy.put("RECORD_9", Material.MUSIC_DISC_STRAD); + renamedLegacy.put("RECORD_10", Material.MUSIC_DISC_WARD); + renamedLegacy.put("RECORD_11", Material.MUSIC_DISC_11); + renamedLegacy.put("RECORD_12", Material.MUSIC_DISC_WAIT); + } + + private static final Reflection.FieldAccessor scoreboardName = Reflection.getField(FlatteningWrapper.scoreboardObjective, Reflection.getClass("{nms.network.chat}.IChatBaseComponent"), 0); + @Override + public void setScoreboardTitle(Object packet, String title) { + scoreboardName.set(packet, ChatWrapper.impl.stringToChatComponent(title)); + } + + private static final Class scoreActionEnum = Reflection.getClass("{nms.server}.ScoreboardServer$Action"); + private static final Reflection.FieldAccessor scoreAction = Reflection.getField(FlatteningWrapper.scoreboardScore, scoreActionEnum, 0); + private static final Object scoreActionChange = scoreActionEnum.getEnumConstants()[0]; + + @Override + public void setScoreAction(Object packet) { + scoreAction.set(packet, scoreActionChange); + } + + @Override + public Material getMaterial(String material) { + try{ + return Material.valueOf(material); + }catch(IllegalArgumentException e){ + return renamedLegacy.get(material); + } + } + + @Override + public Material getDye(int colorCode) { + switch(colorCode){ + case 1: + return Material.RED_DYE; + case 2: + return Material.GREEN_DYE; + case 3: + return Material.BROWN_DYE; + case 4: + return Material.LAPIS_LAZULI; + case 5: + return Material.PURPLE_DYE; + case 6: + return Material.CYAN_DYE; + case 7: + return Material.LIGHT_GRAY_DYE; + case 8: + return Material.GRAY_DYE; + case 9: + return Material.PINK_DYE; + case 10: + return Material.LIME_DYE; + case 11: + return Material.YELLOW_DYE; + case 12: + return Material.LIGHT_BLUE_DYE; + case 13: + return Material.MAGENTA_DYE; + case 14: + return Material.ORANGE_DYE; + case 15: + return Material.WHITE_DYE; + default: + return Material.BLACK_DYE; + } + } + + @SuppressWarnings("deprecation") + @Override + public ItemStack setSkullOwner(String player) { + ItemStack head = new ItemStack(Material.PLAYER_HEAD, 1); + SkullMeta headmeta = (SkullMeta) head.getItemMeta(); + assert headmeta != null; + headmeta.setOwningPlayer(Bukkit.getOfflinePlayer(player.startsWith(".") ? player.substring(1) : player)); + headmeta.setDisplayName(player); + head.setItemMeta(headmeta); + return head; + } + + private static final Class entityPose = Reflection.getClass("{nms.world.entity}.EntityPose"); + private static final Object standing = entityPose.getEnumConstants()[0]; + private static final Object swimming = entityPose.getEnumConstants()[3]; + private static final Object sneaking = entityPose.getEnumConstants()[5]; + @Override + public Object getPose(FlatteningWrapper.EntityPose pose) { + switch (pose) { + case SNEAKING: + return sneaking; + case SWIMMING: + return swimming; + case NORMAL: + default: + return standing; + } + } + + @Override + public void setNamedSpawnPacketDataWatcher(Object packet) { + // field not present + } + + @Override + public Object formatDisplayName(String displayName) { + return displayName != null ? Optional.of(ChatWrapper.impl.stringToChatComponent(displayName)) : Optional.empty(); + } + + private static final Class registryBlocks = Reflection.getClass("{nms.core}.RegistryBlocks"); + private static final Class entityTypes = Reflection.getClass("{nms.world.entity}.EntityTypes"); + private static final Object entityTypesRegistry = Reflection.getField(Reflection.getClass(Core.getVersion() > 18 ? "net.minecraft.core.registries.BuiltInRegistries" : "{nms.core}.IRegistry"), registryBlocks, 0, entityTypes).get(null); + private static final Reflection.MethodInvoker get = Reflection.getMethod(registryBlocks, null, Reflection.getClass("{nms.resources}.MinecraftKey")); + private static final Reflection.FieldAccessor spawnType = Reflection.getField(ProtocolWrapper.spawnPacket, entityTypes, 0); + private static final Reflection.FieldAccessor spawnLivingType = Core.getVersion() > 18 ? spawnType : Reflection.getField(ProtocolWrapper.spawnLivingPacket, int.class, 1); + private static final Reflection.MethodInvoker toMinecraft = Reflection.getMethod("{obc}.util.CraftNamespacedKey", "toMinecraft", NamespacedKey.class); + private static final Map types = new HashMap<>(); + static { + types.put(EntityType.ARMOR_STAND, 1); + } + @Override + public void setSpawnPacketType(Object packet, EntityType type) { + if(type.isAlive()) { + spawnLivingType.set(packet, Core.getVersion() > 18 ? get.invoke(entityTypesRegistry, toMinecraft.invoke(null, type.getKey())) : types.get(type)); + } else { + spawnType.set(packet, get.invoke(entityTypesRegistry, toMinecraft.invoke(null, type.getKey()))); + } + } + + @Override + public int getViewDistance(Player player) { + return player.getClientViewDistance(); + } + + private static final Reflection.MethodInvoker getHandle = Reflection.getMethod("{obc}.CraftWorld", "getHandle"); + private static final Reflection.MethodInvoker save = Reflection.getMethod("{nms.server.level}.WorldServer", null, Reflection.getClass("{nms.util}.IProgressUpdate"), boolean.class, boolean.class); + @Override + public void syncSave(World world) { + save.invoke(getHandle.invoke(world), null, true, false); + } +} diff --git a/SpigotCore/SpigotCore_14/src/src/de/steamwar/core/RecipeDiscoverWrapper14.java b/SpigotCore/SpigotCore_14/src/src/de/steamwar/core/RecipeDiscoverWrapper14.java new file mode 100644 index 00000000..93e050d8 --- /dev/null +++ b/SpigotCore/SpigotCore_14/src/src/de/steamwar/core/RecipeDiscoverWrapper14.java @@ -0,0 +1,30 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.core; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerRecipeDiscoverEvent; + +public class RecipeDiscoverWrapper14 implements RecipeDiscoverWrapper { + @EventHandler + public void onRecipeDiscover(PlayerRecipeDiscoverEvent e) { + e.setCancelled(true); + } +} diff --git a/SpigotCore/SpigotCore_14/src/src/de/steamwar/core/WorldEditWrapper14.java b/SpigotCore/SpigotCore_14/src/src/de/steamwar/core/WorldEditWrapper14.java new file mode 100644 index 00000000..746da57a --- /dev/null +++ b/SpigotCore/SpigotCore_14/src/src/de/steamwar/core/WorldEditWrapper14.java @@ -0,0 +1,610 @@ +package de.steamwar.core; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; +import com.sk89q.jnbt.*; +import com.sk89q.worldedit.EmptyClipboardException; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.extension.input.InputParseException; +import com.sk89q.worldedit.extension.input.ParserContext; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.extension.platform.Capability; +import com.sk89q.worldedit.extension.platform.Platform; +import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; +import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.extent.clipboard.io.*; +import com.sk89q.worldedit.extent.clipboard.io.legacycompat.*; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.session.ClipboardHolder; +import com.sk89q.worldedit.world.DataFixer; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldedit.world.registry.LegacyMapper; +import de.steamwar.sql.NoClipboardException; +import org.bukkit.entity.Player; + +import java.io.*; +import java.util.*; +import java.util.logging.Level; +import java.util.stream.Collectors; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class WorldEditWrapper14 implements WorldEditWrapper.IWorldEditWrapper { + + private static final ClipboardFormat SCHEMATIC = ClipboardFormats.findByAlias("schematic"); + private static final ClipboardFormat SCHEM = ClipboardFormats.findByAlias("schem"); + + @Override + public InputStream getPlayerClipboard(Player player, boolean schemFormat) { + ClipboardHolder clipboardHolder; + try { + clipboardHolder = WorldEditWrapper.getWorldEditPlugin().getSession(player).getClipboard(); + } catch (EmptyClipboardException e) { + throw new NoClipboardException(); + } + + Clipboard clipboard = clipboardHolder.getClipboard(); + if(clipboard == null) + throw new NoClipboardException(); + + PipedOutputStream outputStream = new PipedOutputStream(); + PipedInputStream inputStream; + try { + inputStream = new PipedInputStream(outputStream, 4096); + }catch(NullPointerException e){ + throw new RuntimeException(e.getMessage(), new IOException(e)); + } catch (IOException e) { + throw new SecurityException("Could not init piped input stream", e); + } + + new Thread(() -> { + try{ + if(schemFormat){ + ClipboardWriter writer = SCHEM.getWriter(outputStream); + writer.write(clipboard); + writer.close(); + }else{ + SCHEMATIC.getWriter(outputStream).write(clipboard); + } + }catch(NullPointerException | IOException e) { + Core.getInstance().getLogger().log(Level.SEVERE, "Could not write schematic", e); + } + try { + outputStream.close(); + } catch (IOException e) { + Core.getInstance().getLogger().log(Level.SEVERE, "Could not close schem writer", e); + } + }, "SchemWriter").start(); + + return inputStream; + } + + @Override + public void setPlayerClipboard(Player player, InputStream is, boolean schemFormat) { + Clipboard clipboard = null; + try { + clipboard = getClipboard(is, schemFormat); + } catch (IOException e) { + throw new RuntimeException(e.getMessage(), e); + } + + if (clipboard == null) + throw new NoClipboardException(); + + Actor actor = WorldEditWrapper.getWorldEditPlugin().wrapCommandSender(player); + WorldEditWrapper.getWorldEditPlugin().getWorldEdit().getSessionManager().get(actor).setClipboard(new ClipboardHolder(clipboard)); + } + + @Override + public Clipboard getClipboard(InputStream is, boolean schemFormat) throws IOException { + try { + if(schemFormat){ + return new SpongeSchematicReader(new NBTInputStream(is)).read(); + }else{ + return new MCEditSchematicReader(new NBTInputStream(is)).read(); + } + } catch (NullPointerException e) { + throw new NoClipboardException(); + } + } + + private static class MCEditSchematicReader extends NBTSchematicReader { + + private final NBTInputStream inputStream; + private final DataFixer fixer; + private boolean faweSchem = false; + private static final ImmutableList COMPATIBILITY_HANDLERS + = ImmutableList.of( + new SignCompatibilityHandler(), + new FlowerPotCompatibilityHandler(), + new NoteBlockCompatibilityHandler(), + new SkullBlockCompatibilityHandler() + ); + + /** + * Create a new instance. + * + * @param inputStream the input stream to read from + */ + MCEditSchematicReader(NBTInputStream inputStream) { + checkNotNull(inputStream); + this.inputStream = inputStream; + this.fixer = null; + } + + @Override + public Clipboard read() throws IOException { + // Schematic tag + NamedTag rootTag = inputStream.readNamedTag(); + if (!rootTag.getName().equals("Schematic")) { + throw new IOException("Tag 'Schematic' does not exist or is not first"); + } + CompoundTag schematicTag = (CompoundTag) rootTag.getTag(); + + // Check + Map schematic = schematicTag.getValue(); + if (!schematic.containsKey("Blocks")) { + throw new IOException("Schematic file is missing a 'Blocks' tag"); + } + + // Check type of Schematic + String materials = requireTag(schematic, "Materials", StringTag.class).getValue(); + if (!materials.equals("Alpha")) { + throw new IOException("Schematic file is not an Alpha schematic"); + } + + // ==================================================================== + // Metadata + // ==================================================================== + + BlockVector3 origin; + Region region; + + // Get information + short width = requireTag(schematic, "Width", ShortTag.class).getValue(); + short height = requireTag(schematic, "Height", ShortTag.class).getValue(); + short length = requireTag(schematic, "Length", ShortTag.class).getValue(); + + int originX = 0; + int originY = 0; + int originZ = 0; + try { + originX = requireTag(schematic, "WEOriginX", IntTag.class).getValue(); + originY = requireTag(schematic, "WEOriginY", IntTag.class).getValue(); + originZ = requireTag(schematic, "WEOriginZ", IntTag.class).getValue(); + BlockVector3 min = BlockVector3.at(originX, originY, originZ); + + int offsetX = requireTag(schematic, "WEOffsetX", IntTag.class).getValue(); + int offsetY = requireTag(schematic, "WEOffsetY", IntTag.class).getValue(); + int offsetZ = requireTag(schematic, "WEOffsetZ", IntTag.class).getValue(); + BlockVector3 offset = BlockVector3.at(offsetX, offsetY, offsetZ); + + origin = min.subtract(offset); + region = new CuboidRegion(min, min.add(width, height, length).subtract(BlockVector3.ONE)); + } catch (IOException ignored) { + origin = BlockVector3.ZERO; + region = new CuboidRegion(origin, origin.add(width, height, length).subtract(BlockVector3.ONE)); + } + + // ==================================================================== + // Blocks + // ==================================================================== + + // Get blocks + byte[] blockId = requireTag(schematic, "Blocks", ByteArrayTag.class).getValue(); + byte[] blockData = requireTag(schematic, "Data", ByteArrayTag.class).getValue(); + byte[] addId = new byte[0]; + short[] blocks = new short[blockId.length]; // Have to later combine IDs + + // We support 4096 block IDs using the same method as vanilla Minecraft, where + // the highest 4 bits are stored in a separate byte array. + if (schematic.containsKey("AddBlocks")) { + addId = requireTag(schematic, "AddBlocks", ByteArrayTag.class).getValue(); + } + + // Combine the AddBlocks data with the first 8-bit block ID + for (int index = 0; index < blockId.length; index++) { + if ((index >> 1) >= addId.length) { // No corresponding AddBlocks index + blocks[index] = (short) (blockId[index] & 0xFF); + } else { + if ((index & 1) == 0) { + blocks[index] = (short) (((addId[index >> 1] & 0x0F) << 8) + (blockId[index] & 0xFF)); + } else { + blocks[index] = (short) (((addId[index >> 1] & 0xF0) << 4) + (blockId[index] & 0xFF)); + } + } + } + + // Need to pull out tile entities + final ListTag tileEntityTag = getTag(schematic, "TileEntities", ListTag.class); + List tileEntities = tileEntityTag == null ? new ArrayList<>() : tileEntityTag.getValue(); + Map> tileEntitiesMap = new HashMap<>(); + Map blockStates = new HashMap<>(); + + for (Tag tag : tileEntities) { + if (!(tag instanceof CompoundTag)) continue; + CompoundTag t = (CompoundTag) tag; + int x = t.getInt("x"); + int y = t.getInt("y"); + int z = t.getInt("z"); + int index = y * width * length + z * width + x; + if(index < 0 || index >= blocks.length) + faweSchem = true; + } + + for (Tag tag : tileEntities) { + if (!(tag instanceof CompoundTag)) continue; + CompoundTag t = (CompoundTag) tag; + Map values = new HashMap<>(t.getValue()); + String id = t.getString("id"); + values.put("id", new StringTag(convertBlockEntityId(id))); + int x = t.getInt("x"); + int y = t.getInt("y"); + int z = t.getInt("z"); + if(faweSchem){ + x -= originX; + y -= originY; + z -= originZ; + } + + int index = y * width * length + z * width + x; + + try{ + BlockState block = getBlockState(blocks[index], blockData[index]); + BlockState newBlock = block; + if (newBlock != null) { + for (NBTCompatibilityHandler handler : COMPATIBILITY_HANDLERS) { + if (handler.isAffectedBlock(newBlock)) { + newBlock = handler.updateNBT(block, values); + if (newBlock == null || values.isEmpty()) { + break; + } + } + } + } + if (values.isEmpty()) { + t = null; + } else { + t = new CompoundTag(values); + } + + if (fixer != null && t != null) { + t = fixer.fixUp(DataFixer.FixTypes.BLOCK_ENTITY, t, -1); + } + + BlockVector3 vec = BlockVector3.at(x, y, z); + if (t != null) { + tileEntitiesMap.put(vec, t.getValue()); + } + blockStates.put(vec, newBlock); + }catch(ArrayIndexOutOfBoundsException e){ + //ignored + } + } + + BlockArrayClipboard clipboard = new BlockArrayClipboard(region); + clipboard.setOrigin(origin); + + + for (int x = 0; x < width; ++x) { + for (int y = 0; y < height; ++y) { + for (int z = 0; z < length; ++z) { + int index = y * width * length + z * width + x; + BlockVector3 pt = BlockVector3.at(x, y, z); + BlockState state = blockStates.computeIfAbsent(pt, p -> getBlockState(blocks[index], blockData[index])); + + try { + if (state != null) { + if (tileEntitiesMap.containsKey(pt)) { + clipboard.setBlock(region.getMinimumPoint().add(pt), state.toBaseBlock(new CompoundTag(tileEntitiesMap.get(pt)))); + } else { + clipboard.setBlock(region.getMinimumPoint().add(pt), state); + } + } + } catch (WorldEditException ignored) { // BlockArrayClipboard won't throw this + } + } + } + } + + return clipboard; + } + + private String convertBlockEntityId(String id) { + switch (id) { + case "Cauldron": + return "brewing_stand"; + case "Control": + return "command_block"; + case "DLDetector": + return "daylight_detector"; + case "Trap": + return "dispenser"; + case "EnchantTable": + return "enchanting_table"; + case "EndGateway": + return "end_gateway"; + case "AirPortal": + return "end_portal"; + case "EnderChest": + return "ender_chest"; + case "FlowerPot": + return "flower_pot"; + case "RecordPlayer": + return "jukebox"; + case "MobSpawner": + return "mob_spawner"; + case "Music": + case "noteblock": + return "note_block"; + case "Structure": + return "structure_block"; + case "Chest": + return "chest"; + case "Sign": + return "sign"; + case "Banner": + return "banner"; + case "Beacon": + return "beacon"; + case "Comparator": + return "comparator"; + case "Dropper": + return "dropper"; + case "Furnace": + return "furnace"; + case "Hopper": + return "hopper"; + case "Skull": + return "skull"; + default: + return id; + } + } + + private BlockState getBlockState(int id, int data) { + return LegacyMapper.getInstance().getBlockFromLegacy(id, data); + } + + @Override + public void close() throws IOException { + inputStream.close(); + } + } + private static class SpongeSchematicReader extends NBTSchematicReader { + + private final NBTInputStream inputStream; + private DataFixer fixer = null; + private int schematicVersion = -1; + private int dataVersion = -1; + private boolean faweSchem = false; + + /** + * Create a new instance. + * + * @param inputStream the input stream to read from + */ + public SpongeSchematicReader(NBTInputStream inputStream) { + checkNotNull(inputStream); + this.inputStream = inputStream; + } + + @Override + public Clipboard read() throws IOException { + CompoundTag schematicTag = getBaseTag(); + Map schematic = schematicTag.getValue(); + + final Platform platform = WorldEdit.getInstance().getPlatformManager() + .queryCapability(Capability.WORLD_EDITING); + int liveDataVersion = platform.getDataVersion(); + + if (schematicVersion == 1) { + dataVersion = 1631; // this is a relatively safe assumption unless someone imports a schematic from 1.12, e.g. sponge 7.1- + fixer = platform.getDataFixer(); + return readVersion1(schematicTag); + } else if (schematicVersion == 2) { + dataVersion = requireTag(schematic, "DataVersion", IntTag.class).getValue(); + if (dataVersion < liveDataVersion) { + fixer = platform.getDataFixer(); + } + + return readVersion1(schematicTag); + } + throw new IOException("This schematic version is currently not supported"); + } + + @Override + public OptionalInt getDataVersion() { + try { + CompoundTag schematicTag = getBaseTag(); + Map schematic = schematicTag.getValue(); + if (schematicVersion == 1) { + return OptionalInt.of(1631); + } else if (schematicVersion == 2) { + return OptionalInt.of(requireTag(schematic, "DataVersion", IntTag.class).getValue()); + } + return OptionalInt.empty(); + } catch (IOException e) { + return OptionalInt.empty(); + } + } + + private CompoundTag getBaseTag() throws IOException { + NamedTag rootTag = inputStream.readNamedTag(); + if (!rootTag.getName().equals("Schematic")) { + throw new IOException("Tag 'Schematic' does not exist or is not first"); + } + CompoundTag schematicTag = (CompoundTag) rootTag.getTag(); + + // Check + Map schematic = schematicTag.getValue(); + + schematicVersion = requireTag(schematic, "Version", IntTag.class).getValue(); + return schematicTag; + } + + private BlockArrayClipboard readVersion1(CompoundTag schematicTag) throws IOException { + BlockVector3 origin; + Region region; + Map schematic = schematicTag.getValue(); + + int width = requireTag(schematic, "Width", ShortTag.class).getValue(); + int height = requireTag(schematic, "Height", ShortTag.class).getValue(); + int length = requireTag(schematic, "Length", ShortTag.class).getValue(); + + IntArrayTag offsetTag = getTag(schematic, "Offset", IntArrayTag.class); + int[] offsetParts; + if (offsetTag != null) { + offsetParts = offsetTag.getValue(); + if (offsetParts.length != 3) { + throw new IOException("Invalid offset specified in schematic."); + } + } else { + offsetParts = new int[] {0, 0, 0}; + } + + BlockVector3 min = BlockVector3.at(offsetParts[0], offsetParts[1], offsetParts[2]); + + CompoundTag metadataTag = getTag(schematic, "Metadata", CompoundTag.class); + int offsetX = 0; + int offsetY = 0; + int offsetZ = 0; + if (metadataTag != null && metadataTag.containsKey("WEOffsetX")) { + // We appear to have WorldEdit Metadata + Map metadata = metadataTag.getValue(); + offsetX = requireTag(metadata, "WEOffsetX", IntTag.class).getValue(); + offsetY = requireTag(metadata, "WEOffsetY", IntTag.class).getValue(); + offsetZ = requireTag(metadata, "WEOffsetZ", IntTag.class).getValue(); + BlockVector3 offset = BlockVector3.at(offsetX, offsetY, offsetZ); + origin = min.subtract(offset); + region = new CuboidRegion(min, min.add(width, height, length).subtract(BlockVector3.ONE)); + } else { + origin = min; + region = new CuboidRegion(origin, origin.add(width, height, length).subtract(BlockVector3.ONE)); + } + + IntTag paletteMaxTag = getTag(schematic, "PaletteMax", IntTag.class); + Map paletteObject = requireTag(schematic, "Palette", CompoundTag.class).getValue(); + if (paletteMaxTag != null && paletteObject.size() != paletteMaxTag.getValue()) { + throw new IOException("Block palette size does not match expected size."); + } + + Map palette = new HashMap<>(); + + ParserContext parserContext = new ParserContext(); + parserContext.setRestricted(false); + parserContext.setTryLegacy(false); + parserContext.setPreferringWildcard(false); + + for (String palettePart : paletteObject.keySet()) { + int id = requireTag(paletteObject, palettePart, IntTag.class).getValue(); + if (fixer != null) { + palettePart = fixer.fixUp(DataFixer.FixTypes.BLOCK_STATE, palettePart, dataVersion); + } + BlockState state; + try { + state = WorldEdit.getInstance().getBlockFactory().parseFromInput(palettePart, parserContext).toImmutableState(); + } catch (InputParseException e) { + state = BlockTypes.AIR.getDefaultState(); + } + palette.put(id, state); + } + + byte[] blocks = requireTag(schematic, "BlockData", ByteArrayTag.class).getValue(); + + Map> tileEntitiesMap = new HashMap<>(); + ListTag tileEntities = getTag(schematic, "BlockEntities", ListTag.class); + if (tileEntities == null) { + tileEntities = getTag(schematic, "TileEntities", ListTag.class); + } + if (tileEntities != null) { + List> tileEntityTags = tileEntities.getValue().stream() + .map(tag -> (CompoundTag) tag) + .map(CompoundTag::getValue) + .collect(Collectors.toList()); + + for (Map tileEntity : tileEntityTags) { + int[] pos = requireTag(tileEntity, "Pos", IntArrayTag.class).getValue(); + if(pos[0] < 0 || pos[0] >= width || pos[1] < 0 || pos[1] >= height || pos[2] < 0 || pos[2] >= length) + faweSchem = true; + } + + for (Map tileEntity : tileEntityTags) { + int[] pos = requireTag(tileEntity, "Pos", IntArrayTag.class).getValue(); + final BlockVector3 pt = BlockVector3.at(pos[0], pos[1], pos[2]); + Map values = Maps.newHashMap(tileEntity); + if(faweSchem){ + values.put("x", new IntTag(pt.getBlockX() - offsetX)); + values.put("y", new IntTag(pt.getBlockY() - offsetY)); + values.put("z", new IntTag(pt.getBlockZ() - offsetZ)); + }else{ + values.put("x", new IntTag(pt.getBlockX())); + values.put("y", new IntTag(pt.getBlockY())); + values.put("z", new IntTag(pt.getBlockZ())); + } + values.put("id", values.get("Id")); + values.remove("Id"); + values.remove("Pos"); + if (fixer != null) { + tileEntity = fixer.fixUp(DataFixer.FixTypes.BLOCK_ENTITY, new CompoundTag(values), dataVersion).getValue(); + } else { + tileEntity = values; + } + tileEntitiesMap.put(pt, tileEntity); + } + } + + BlockArrayClipboard clipboard = new BlockArrayClipboard(region); + clipboard.setOrigin(origin); + + int index = 0; + int i = 0; + int value; + int varintLength; + while (i < blocks.length) { + value = 0; + varintLength = 0; + + while (true) { + value |= (blocks[i] & 127) << (varintLength++ * 7); + if (varintLength > 5) { + throw new IOException("VarInt too big (probably corrupted data)"); + } + if ((blocks[i] & 128) != 128) { + i++; + break; + } + i++; + } + // index = (y * length * width) + (z * width) + x + int y = index / (width * length); + int z = (index % (width * length)) / width; + int x = (index % (width * length)) % width; + BlockState state = palette.get(value); + BlockVector3 pt = BlockVector3.at(x, y, z); + try { + if (tileEntitiesMap.containsKey(pt)) { + clipboard.setBlock(clipboard.getMinimumPoint().add(pt), state.toBaseBlock(new CompoundTag(tileEntitiesMap.get(pt)))); + } else { + clipboard.setBlock(clipboard.getMinimumPoint().add(pt), state); + } + } catch (WorldEditException e) { + throw new IOException("Failed to load a block in the schematic"); + } + + index++; + } + + return clipboard; + } + + @Override + public void close() throws IOException { + inputStream.close(); + } + } +} diff --git a/SpigotCore/SpigotCore_14/src/src/de/steamwar/techhider/BlockIds14.java b/SpigotCore/SpigotCore_14/src/src/de/steamwar/techhider/BlockIds14.java new file mode 100644 index 00000000..322ce206 --- /dev/null +++ b/SpigotCore/SpigotCore_14/src/src/de/steamwar/techhider/BlockIds14.java @@ -0,0 +1,73 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2021 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + */ + +package de.steamwar.techhider; + +import com.comphenix.tinyprotocol.Reflection; +import com.google.common.collect.ImmutableList; +import org.bukkit.Material; + +import java.util.HashSet; +import java.util.Set; + +public class BlockIds14 implements BlockIds { + + private static final Class blockStateList = Reflection.getClass("{nms.world.level.block.state}.BlockStateList"); + private static final Class fluidTypeFlowing = Reflection.getClass("{nms.world.level.material}.FluidTypeFlowing"); + private static final Class fluid = Reflection.getClass("{nms.world.level.material}.Fluid"); + + private static final Reflection.MethodInvoker getBlockData = Reflection.getTypedMethod(TechHider.block, null, TechHider.iBlockData); + @Override + public int materialToId(Material material) { + return getCombinedId(getBlockData.invoke(getBlock(material))); + } + + private static final Reflection.MethodInvoker getStates = Reflection.getTypedMethod(TechHider.block, null, blockStateList); + private static final Reflection.MethodInvoker getStateList = Reflection.getTypedMethod(blockStateList, null, ImmutableList.class); + private static final Object water = Reflection.getTypedMethod(fluidTypeFlowing, null, fluid, boolean.class).invoke(Reflection.getField(Reflection.getClass("{nms.world.level.material}.FluidTypes"), fluidTypeFlowing, 1).get(null), false); + private static final Iterable registryBlockId = (Iterable) Reflection.getField(TechHider.block, Reflection.getClass("{nms.core}.RegistryBlockID"), 0).get(null); + private static final Reflection.MethodInvoker getFluid = Reflection.getTypedMethod(TechHider.iBlockData, null, fluid); + @Override + public Set materialToAllIds(Material material) { + Set ids = new HashSet<>(); + for(Object data : (ImmutableList) getStateList.invoke(getStates.invoke(getBlock(material)))) { + ids.add(getCombinedId(data)); + } + + if(material == Material.WATER) { + for(Object data : registryBlockId) { + if(getFluid.invoke(data) == water) { + ids.add(getCombinedId(data)); + } + } + } + + return ids; + } + + private static final Reflection.MethodInvoker getBlock = Reflection.getTypedMethod(TechHider.craftMagicNumbers, "getBlock", TechHider.block, Material.class); + private Object getBlock(Material material) { + return getBlock.invoke(null, material); + } + + private static final Reflection.MethodInvoker getCombinedId = Reflection.getTypedMethod(TechHider.block, null, int.class, TechHider.iBlockData); + private int getCombinedId(Object blockData) { + return (int) getCombinedId.invoke(null, blockData); + } +} diff --git a/SpigotCore/SpigotCore_14/src/src/de/steamwar/techhider/ChunkHider14.java b/SpigotCore/SpigotCore_14/src/src/de/steamwar/techhider/ChunkHider14.java new file mode 100644 index 00000000..9a2d50e8 --- /dev/null +++ b/SpigotCore/SpigotCore_14/src/src/de/steamwar/techhider/ChunkHider14.java @@ -0,0 +1,42 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2021 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + */ + +package de.steamwar.techhider; + +import io.netty.buffer.ByteBuf; + +import java.util.Collections; +import java.util.Set; + +public class ChunkHider14 extends ChunkHider9 { + + @Override + protected void dataHider(PosEvaluator locationEvaluator, int obfuscationTarget, Set obfuscate, ByteBuf in, ByteBuf out, boolean skip, boolean blockPrecise) { + out.writeShort(in.readShort()); // Block count + byte bitsPerBlock = in.readByte(); + out.writeByte(bitsPerBlock); + + if(bitsPerBlock < 9) { + obfuscationTarget = ChunkHider.processPalette(obfuscationTarget, obfuscate, in, out); + obfuscate = Collections.emptySet(); + } + + processDataArray(locationEvaluator, obfuscationTarget, obfuscate, in, out, bitsPerBlock, skip || (!blockPrecise && bitsPerBlock < 9)); + } +} diff --git a/SpigotCore/SpigotCore_14/src/src/de/steamwar/techhider/ProtocolWrapper14.java b/SpigotCore/SpigotCore_14/src/src/de/steamwar/techhider/ProtocolWrapper14.java new file mode 100644 index 00000000..76b4d9f8 --- /dev/null +++ b/SpigotCore/SpigotCore_14/src/src/de/steamwar/techhider/ProtocolWrapper14.java @@ -0,0 +1,52 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2021 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + */ + +package de.steamwar.techhider; + +import com.comphenix.tinyprotocol.Reflection; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.UnaryOperator; + +public class ProtocolWrapper14 extends ProtocolWrapper8 { + + @Override + public BiFunction blockBreakHiderGenerator(Class blockBreakPacket, Object obfuscationTarget, Set obfuscate, TechHider.LocationEvaluator locationEvaluator) { + UnaryOperator blockBreakCloner = ProtocolUtils.shallowCloneGenerator(blockBreakPacket); + Reflection.FieldAccessor blockBreakPosition = Reflection.getField(blockBreakPacket, TechHider.blockPosition, 0); + Reflection.FieldAccessor blockBreakBlockData = Reflection.getField(blockBreakPacket, TechHider.iBlockData, 0); + + return (p, packet) -> { + switch (locationEvaluator.checkBlockPos(p, blockBreakPosition.get(packet))) { + case SKIP: + return packet; + case CHECK: + if(!TechHider.iBlockDataHidden(obfuscate, blockBreakBlockData.get(packet))) + return packet; + default: + packet = blockBreakCloner.apply(packet); + blockBreakBlockData.set(packet, obfuscationTarget); + return packet; + } + }; + } +} diff --git a/SpigotCore/SpigotCore_15/build.gradle.kts b/SpigotCore/SpigotCore_15/build.gradle.kts new file mode 100644 index 00000000..9ff92f83 --- /dev/null +++ b/SpigotCore/SpigotCore_15/build.gradle.kts @@ -0,0 +1,56 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2024 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +plugins { + id("java") + id("base") +} + +group = "de.steamwar" +version = "" + +tasks.compileJava { + options.encoding = "UTF-8" +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +sourceSets { + main { + java { + srcDirs("src/") + } + resources { + srcDirs("src/") + exclude("**/*.java", "**/*.kt") + } + } +} + +dependencies { + compileOnly("org.projectlombok:lombok:1.18.32") + annotationProcessor("org.projectlombok:lombok:1.18.32") + + compileOnly(project(":SpigotCore:SpigotCore_Main")) + + compileOnly("de.steamwar:spigot:1.15") +} diff --git a/SpigotCore/SpigotCore_18/build.gradle.kts b/SpigotCore/SpigotCore_18/build.gradle.kts new file mode 100644 index 00000000..e31910f7 --- /dev/null +++ b/SpigotCore/SpigotCore_18/build.gradle.kts @@ -0,0 +1,64 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2024 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +plugins { + id("java") + id("base") +} + +group = "de.steamwar" +version = "" + +tasks.compileJava { + options.encoding = "UTF-8" +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +sourceSets { + main { + java { + srcDirs("src/") + } + resources { + srcDirs("src/") + exclude("**/*.java", "**/*.kt") + } + } +} + +dependencies { + compileOnly("org.projectlombok:lombok:1.18.32") + annotationProcessor("org.projectlombok:lombok:1.18.32") + + compileOnly(project(":SpigotCore:SpigotCore_Main")) + compileOnly(project(":SpigotCore:SpigotCore_14")) + compileOnly(project(":CommonCore")) + + compileOnly("de.steamwar:fastasyncworldedit:1.18") + compileOnly("de.steamwar:spigot:1.18") + + compileOnly("org.spigotmc:spigot-api:1.18-R0.1-SNAPSHOT") + compileOnly("com.mojang:datafixerupper:4.0.26") + compileOnly("io.netty:netty-all:4.1.68.Final") + compileOnly("com.mojang:authlib:1.5.25") +} diff --git a/SpigotCore/SpigotCore_18/src/src/de/steamwar/core/CraftbukkitWrapper18.java b/SpigotCore/SpigotCore_18/src/src/de/steamwar/core/CraftbukkitWrapper18.java new file mode 100644 index 00000000..1627791c --- /dev/null +++ b/SpigotCore/SpigotCore_18/src/src/de/steamwar/core/CraftbukkitWrapper18.java @@ -0,0 +1,40 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2021 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.core; + +import com.comphenix.tinyprotocol.Reflection; +import com.comphenix.tinyprotocol.TinyProtocol; +import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; +import net.minecraft.world.level.World; +import net.minecraft.world.level.chunk.Chunk; +import net.minecraft.world.level.lighting.LightEngine; +import org.bukkit.entity.Player; + +public class CraftbukkitWrapper18 implements CraftbukkitWrapper.ICraftbukkitWrapper { + + private static final Reflection.MethodInvoker getHandle = Reflection.getMethod("{obc}.CraftChunk", "getHandle"); + private static final Reflection.MethodInvoker getLightEngine = Reflection.getTypedMethod(World.class, null, LightEngine.class); + + @Override + public void sendChunk(Player p, int chunkX, int chunkZ) { + Chunk chunk = (Chunk) getHandle.invoke(p.getWorld().getChunkAt(chunkX, chunkZ)); + TinyProtocol.instance.sendPacket(p, new ClientboundLevelChunkWithLightPacket(chunk, (LightEngine) getLightEngine.invoke(chunk.q), null, null, false, true)); + } +} diff --git a/SpigotCore/SpigotCore_18/src/src/de/steamwar/core/ProtocolWrapper18.java b/SpigotCore/SpigotCore_18/src/src/de/steamwar/core/ProtocolWrapper18.java new file mode 100644 index 00000000..54cc3030 --- /dev/null +++ b/SpigotCore/SpigotCore_18/src/src/de/steamwar/core/ProtocolWrapper18.java @@ -0,0 +1,61 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.core; + +import com.comphenix.tinyprotocol.Reflection; +import com.mojang.authlib.GameProfile; +import com.mojang.datafixers.util.Pair; +import org.bukkit.GameMode; + +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; + +public class ProtocolWrapper18 implements ProtocolWrapper { + + private static final Reflection.FieldAccessor equipmentStack = Reflection.getField(equipmentPacket, List.class, 0); + @Override + public void setEquipmentPacketStack(Object packet, Object slot, Object stack) { + equipmentStack.set(packet, Collections.singletonList(new Pair<>(slot, stack))); + } + + private static final Class playerInfoPacket = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutPlayerInfo"); + private static final Class playerInfoActionClass = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutPlayerInfo$EnumPlayerInfoAction"); + private static final Reflection.FieldAccessor playerInfoAction = Reflection.getField(playerInfoPacket, playerInfoActionClass, 0); + private static final Reflection.FieldAccessor playerInfoData = Reflection.getField(playerInfoPacket, List.class, 0); + private static final EnumMap actions = new EnumMap<>(PlayerInfoAction.class); + static { + Object[] nativeActions = playerInfoActionClass.getEnumConstants(); + actions.put(PlayerInfoAction.ADD, nativeActions[0]); + actions.put(PlayerInfoAction.GAMEMODE, nativeActions[1]); + actions.put(PlayerInfoAction.REMOVE, nativeActions[4]); + } + private static final Class iChatBaseComponent = Reflection.getClass("{nms.network.chat}.IChatBaseComponent"); + private static final Reflection.ConstructorInvoker playerInfoDataConstructor = Reflection.getConstructor("{nms.network.protocol.game}.PacketPlayOutPlayerInfo$PlayerInfoData", GameProfile.class, int.class, enumGamemode, iChatBaseComponent); + + @Override + @SuppressWarnings("deprecation") + public Object playerInfoPacketConstructor(PlayerInfoAction action, GameProfile profile, GameMode mode) { + Object packet = Reflection.newInstance(playerInfoPacket); + playerInfoAction.set(packet, actions.get(action)); + playerInfoData.set(packet, Collections.singletonList(playerInfoDataConstructor.invoke(profile, 0, ProtocolWrapper.getGameModeById.invoke(null, mode.getValue()), null))); + return packet; + } +} diff --git a/SpigotCore/SpigotCore_18/src/src/de/steamwar/core/WorldEditWrapper18.java b/SpigotCore/SpigotCore_18/src/src/de/steamwar/core/WorldEditWrapper18.java new file mode 100644 index 00000000..4942b2a1 --- /dev/null +++ b/SpigotCore/SpigotCore_18/src/src/de/steamwar/core/WorldEditWrapper18.java @@ -0,0 +1,44 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.core; + +import com.sk89q.jnbt.NBTInputStream; +import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.extent.clipboard.io.MCEditSchematicReader; +import com.sk89q.worldedit.extent.clipboard.io.SpongeSchematicReader; +import de.steamwar.sql.NoClipboardException; + +import java.io.IOException; +import java.io.InputStream; + +public class WorldEditWrapper18 extends WorldEditWrapper14 { + + @Override + @SuppressWarnings("removal") + public Clipboard getClipboard(InputStream is, boolean schemFormat) throws IOException { + //Use FAWE reader due to FAWE capability of reading corrupt FAWE schems + NBTInputStream nbtStream = new NBTInputStream(is); + try { + return (schemFormat ? new SpongeSchematicReader(nbtStream) : new MCEditSchematicReader(nbtStream)).read(); + } catch (NullPointerException e) { + throw new NoClipboardException(); + } + } +} diff --git a/SpigotCore/SpigotCore_18/src/src/de/steamwar/techhider/ChunkHider18.java b/SpigotCore/SpigotCore_18/src/src/de/steamwar/techhider/ChunkHider18.java new file mode 100644 index 00000000..774cf2d7 --- /dev/null +++ b/SpigotCore/SpigotCore_18/src/src/de/steamwar/techhider/ChunkHider18.java @@ -0,0 +1,165 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2021 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + */ + +package de.steamwar.techhider; + +import com.comphenix.tinyprotocol.Reflection; +import de.steamwar.core.Core; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import net.minecraft.core.IRegistry; +import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData; +import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; +import net.minecraft.resources.MinecraftKey; +import net.minecraft.util.SimpleBitStorage; +import net.minecraft.world.level.block.entity.TileEntityTypes; +import org.bukkit.entity.Player; + +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; + +public class ChunkHider18 implements ChunkHider { + @Override + public Class mapChunkPacket() { + return ClientboundLevelChunkWithLightPacket.class; + } + + private static final UnaryOperator chunkPacketCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class); + private static final UnaryOperator chunkDataCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class); + + private static final Reflection.FieldAccessor chunkXField = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, int.class, 0); + private static final Reflection.FieldAccessor chunkZField = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, int.class, 1); + private static final Reflection.FieldAccessor chunkData = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkPacketData.class, 0); + + private static final Reflection.FieldAccessor dataField = Reflection.getField(ClientboundLevelChunkPacketData.class, byte[].class, 0); + private static final Reflection.FieldAccessor tileEntities = Reflection.getField(ClientboundLevelChunkPacketData.class, List.class, 0); + + @Override + public BiFunction chunkHiderGenerator(TechHider.LocationEvaluator locationEvaluator, int obfuscationTarget, Set obfuscate, Set hiddenBlockEntities) { + return (p, packet) -> { + int chunkX = chunkXField.get(packet); + int chunkZ = chunkZField.get(packet); + if (locationEvaluator.skipChunk(p, chunkX, chunkZ)) + return packet; + + packet = chunkPacketCloner.apply(packet); + Object dataWrapper = chunkDataCloner.apply(chunkData.get(packet)); + + tileEntities.set(dataWrapper, ((List)tileEntities.get(dataWrapper)).stream().filter(te -> tileEntityVisible(hiddenBlockEntities, te)).collect(Collectors.toList())); + + ByteBuf in = Unpooled.wrappedBuffer(dataField.get(dataWrapper)); + ByteBuf out = Unpooled.buffer(in.readableBytes() + 64); + int xOffset = 16*chunkX; + int zOffset = 16*chunkZ; + for(int yOffset = p.getWorld().getMinHeight(); yOffset < p.getWorld().getMaxHeight(); yOffset += 16) { + int finalYOffset = yOffset; + int chunkY = yOffset/16; + dataHider((x, y, z) -> locationEvaluator.check(p, x+xOffset, y+finalYOffset, z+zOffset), obfuscationTarget, obfuscate, in, out, locationEvaluator.skipChunkSection(p, chunkX, chunkY, chunkZ), locationEvaluator.blockPrecise(p, chunkX, chunkY, chunkZ)); + } + out.writeBytes(in); // MC appends a 0 byte at the end if there is a full chunk, idk why + + byte[] data = new byte[out.readableBytes()]; + out.readBytes(data); + dataField.set(dataWrapper, data); + + chunkData.set(packet, dataWrapper); + return packet; + }; + } + + public static final Class tileEntity = Reflection.getClass("net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData$a"); + protected static final Reflection.FieldAccessor entityType = Reflection.getField(tileEntity, TileEntityTypes.class, 0); + private static final IRegistry tileEntityTypes = Reflection.getField(Core.getVersion() > 18 ? Reflection.getClass("net.minecraft.core.registries.BuiltInRegistries") : IRegistry.class, IRegistry.class, 0, TileEntityTypes.class).get(null); + private static final Reflection.MethodInvoker getKey = Reflection.getTypedMethod(IRegistry.class, null, MinecraftKey.class, Object.class); + private static final Reflection.MethodInvoker getName = Reflection.getTypedMethod(MinecraftKey.class, null, String.class); + protected boolean tileEntityVisible(Set hiddenBlockEntities, Object tile) { + return !hiddenBlockEntities.contains((String) getName.invoke(getKey.invoke(tileEntityTypes, entityType.get(tile)))); + } + + private void dataHider(PosEvaluator locationEvaluator, int obfuscationTarget, Set obfuscate, ByteBuf in, ByteBuf out, boolean skip, boolean blockPrecise) { + out.writeShort(in.readShort()); // Block count + + containerWalker(in, out, 15, obfuscationTarget, skip ? Collections.emptySet() : obfuscate, (paletteTarget, dataArrayLength, bitsPerBlock) -> { + Set palettedObfuscate; + if(bitsPerBlock < 15) { + palettedObfuscate = Collections.emptySet(); + } else { + paletteTarget = obfuscationTarget; + palettedObfuscate = obfuscate; + } + + if(skip || dataArrayLength == 0 || (!blockPrecise && bitsPerBlock < 15)) { + out.writeBytes(in, dataArrayLength*8); + return; + } + + long[] array = new long[dataArrayLength]; + for(int i = 0; i < dataArrayLength; i++) + array[i] = in.readLong(); + SimpleBitStorage values = new SimpleBitStorage(bitsPerBlock, 4096, array); + + for (int y = 0; y < 16; y++) { + for (int z = 0; z < 16; z++) { + for (int x = 0; x < 16; x++) { + int pos = (((y * 16) + z) * 16) + x; + + switch (locationEvaluator.test(x, y, z)) { + case SKIP: + break; + case CHECK: + if(!palettedObfuscate.contains(values.a(pos))) + break; + default: + values.b(pos, paletteTarget); + } + } + } + } + + for (long l : values.a()) + out.writeLong(l); + }); + containerWalker(in, out, 6, 0, Collections.emptySet(), (paletteTarget, dataArrayLength, bitsPerBlock) -> out.writeBytes(in, dataArrayLength * 8)); // Biomes + } + + private void containerWalker(ByteBuf in, ByteBuf out, int globalPalette, int obfuscationTarget, Set obfuscate, TriConsumer dataArray) { + byte bitsPerBlock = in.readByte(); + out.writeByte(bitsPerBlock); + + //blockId -> obfuscate.contains(blockId) ? obfuscationTarget : blockId + if(bitsPerBlock == 0) { + int value = ProtocolUtils.readVarInt(in); + ProtocolUtils.writeVarInt(out, obfuscate.contains(value) ? obfuscationTarget : value); + }else if(bitsPerBlock < globalPalette) { + obfuscationTarget = ChunkHider.processPalette(obfuscationTarget, obfuscate, in, out); + } + + int dataArrayLength = ProtocolUtils.readVarInt(in); + ProtocolUtils.writeVarInt(out, dataArrayLength); + dataArray.accept(obfuscationTarget, dataArrayLength, bitsPerBlock); + } + + private interface TriConsumer { + void accept(X x, Y y, Z z); + } +} diff --git a/SpigotCore/SpigotCore_18/src/src/de/steamwar/techhider/ProtocolWrapper18.java b/SpigotCore/SpigotCore_18/src/src/de/steamwar/techhider/ProtocolWrapper18.java new file mode 100644 index 00000000..d314c2d3 --- /dev/null +++ b/SpigotCore/SpigotCore_18/src/src/de/steamwar/techhider/ProtocolWrapper18.java @@ -0,0 +1,107 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2021 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + */ + +package de.steamwar.techhider; + +import com.comphenix.tinyprotocol.Reflection; +import net.minecraft.core.SectionPosition; +import net.minecraft.network.protocol.game.PacketPlayOutBlockBreak; +import net.minecraft.world.level.block.entity.TileEntitySign; +import net.minecraft.world.level.block.entity.TileEntityTypes; +import net.minecraft.world.level.block.state.IBlockData; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.Set; +import java.util.function.BiFunction; + +public class ProtocolWrapper18 implements ProtocolWrapper { + + private static final Reflection.FieldAccessor multiBlockChangeChunk = Reflection.getField(TechHider.multiBlockChangePacket, SectionPosition.class, 0); + private static final Reflection.FieldAccessor multiBlockChangePos = Reflection.getField(TechHider.multiBlockChangePacket, short[].class, 0); + private static final Reflection.FieldAccessor multiBlockChangeBlocks = Reflection.getField(TechHider.multiBlockChangePacket, IBlockData[].class, 0); + @Override + public BiFunction multiBlockChangeGenerator(Object obfuscationTarget, Set obfuscate, TechHider.LocationEvaluator locationEvaluator) { + return (p, packet) -> { + Object chunkCoords = multiBlockChangeChunk.get(packet); + int chunkX = TechHider.blockPositionX.get(chunkCoords); + int chunkY = TechHider.blockPositionY.get(chunkCoords); + int chunkZ = TechHider.blockPositionZ.get(chunkCoords); + if(locationEvaluator.skipChunkSection(p, chunkX, chunkY, chunkZ)) + return packet; + + packet = TechHider.multiBlockChangeCloner.apply(packet); + final short[] oldPos = multiBlockChangePos.get(packet); + final IBlockData[] oldBlocks = multiBlockChangeBlocks.get(packet); + ArrayList poss = new ArrayList<>(oldPos.length); + ArrayList blocks = new ArrayList<>(oldPos.length); + for(int i = 0; i < oldPos.length; i++) { + short pos = oldPos[i]; + IBlockData block = oldBlocks[i]; + switch(locationEvaluator.check(p, 16*chunkX + (pos >> 8 & 0xF), 16*chunkY + (pos & 0xF), 16*chunkZ + (pos >> 4 & 0xF))) { + case SKIP: + poss.add(pos); + blocks.add(block); + break; + case CHECK: + poss.add(pos); + blocks.add(TechHider.iBlockDataHidden(obfuscate, block) ? (IBlockData) obfuscationTarget : block); + break; + default: + break; + } + } + + if(blocks.isEmpty()) + return null; + + short[] newPos = new short[poss.size()]; + for(int i = 0; i < newPos.length; i++) + newPos[i] = poss.get(i); + + multiBlockChangePos.set(packet, newPos); + multiBlockChangeBlocks.set(packet, blocks.toArray(new IBlockData[0])); + return packet; + }; + } + + private static final Reflection.FieldAccessor tileEntityType = Reflection.getField(TechHider.tileEntityDataPacket, TileEntityTypes.class, 0); + private static final TileEntityTypes signType = Reflection.getField(TileEntityTypes.class, TileEntityTypes.class, 0, TileEntitySign.class).get(null); + @Override + public boolean unfilteredTileEntityDataAction(Object packet) { + return tileEntityType.get(packet) != signType; + } + + @Override + public BiFunction blockBreakHiderGenerator(Class blockBreakPacket, Object obfuscationTarget, Set obfuscate, TechHider.LocationEvaluator locationEvaluator) { + return (p, packet) -> { + PacketPlayOutBlockBreak breakPacket = (PacketPlayOutBlockBreak) packet; + switch (locationEvaluator.checkBlockPos(p, breakPacket.b())) { + case SKIP: + return packet; + case CHECK: + if(!TechHider.iBlockDataHidden(obfuscate, breakPacket.c())) + return packet; + default: + return new PacketPlayOutBlockBreak(breakPacket.b(), (IBlockData) obfuscationTarget, breakPacket.d(), breakPacket.a()); + } + }; + } +} diff --git a/SpigotCore/SpigotCore_19/build.gradle.kts b/SpigotCore/SpigotCore_19/build.gradle.kts new file mode 100644 index 00000000..2363a872 --- /dev/null +++ b/SpigotCore/SpigotCore_19/build.gradle.kts @@ -0,0 +1,64 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2024 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +plugins { + id("java") + id("base") +} + +group = "de.steamwar" +version = "" + +tasks.compileJava { + options.encoding = "UTF-8" +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +sourceSets { + main { + java { + srcDirs("src/") + } + resources { + srcDirs("src/") + exclude("**/*.java", "**/*.kt") + } + } +} + +dependencies { + compileOnly("org.projectlombok:lombok:1.18.32") + annotationProcessor("org.projectlombok:lombok:1.18.32") + + compileOnly(project(":SpigotCore:SpigotCore_Main")) + compileOnly(project(":SpigotCore:SpigotCore_14")) + compileOnly(project(":SpigotCore:SpigotCore_18")) + + compileOnly("de.steamwar:worldedit:1.15") + compileOnly("de.steamwar:spigot:1.19") + + compileOnly("org.spigotmc:spigot-api:1.19-R0.1-SNAPSHOT") + compileOnly("com.mojang:brigadier:1.0.18") + compileOnly("com.mojang:datafixerupper:4.0.26") + compileOnly("com.mojang:authlib:1.5.25") +} diff --git a/SpigotCore/SpigotCore_19/src/src/de/steamwar/core/ChatWrapper19.java b/SpigotCore/SpigotCore_19/src/src/de/steamwar/core/ChatWrapper19.java new file mode 100644 index 00000000..d8225ae6 --- /dev/null +++ b/SpigotCore/SpigotCore_19/src/src/de/steamwar/core/ChatWrapper19.java @@ -0,0 +1,45 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.core; + +import net.minecraft.network.chat.IChatMutableComponent; +import net.minecraft.network.chat.contents.LiteralContents; +import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata; +import net.minecraft.network.syncher.DataWatcher; + +import java.util.ArrayList; + +public class ChatWrapper19 implements ChatWrapper { + + @Override + public Object stringToChatComponent(String text) { + return IChatMutableComponent.a(new LiteralContents(text)); + } + + @Override + public Object getDataWatcherPacket(int entityId, Object... dataWatcherKeyValues) { + ArrayList> nativeWatchers = new ArrayList<>(1); + for(int i = 0; i < dataWatcherKeyValues.length; i+=2) { + nativeWatchers.add(((DataWatcher.Item) BountifulWrapper.impl.getDataWatcherItem(dataWatcherKeyValues[i], dataWatcherKeyValues[i+1])).e()); + } + + return new PacketPlayOutEntityMetadata(entityId, nativeWatchers); + } +} diff --git a/SpigotCore/SpigotCore_19/src/src/de/steamwar/core/ProtocolWrapper19.java b/SpigotCore/SpigotCore_19/src/src/de/steamwar/core/ProtocolWrapper19.java new file mode 100644 index 00000000..f4f399f3 --- /dev/null +++ b/SpigotCore/SpigotCore_19/src/src/de/steamwar/core/ProtocolWrapper19.java @@ -0,0 +1,64 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.core; + +import com.comphenix.tinyprotocol.Reflection; +import com.mojang.authlib.GameProfile; +import com.mojang.datafixers.util.Pair; +import net.minecraft.SystemUtils; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; +import net.minecraft.world.level.EnumGamemode; +import org.bukkit.GameMode; + +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.function.LongSupplier; + +public class ProtocolWrapper19 implements ProtocolWrapper { + + private static final Reflection.FieldAccessor equipmentStack = Reflection.getField(equipmentPacket, List.class, 0); + @Override + public void setEquipmentPacketStack(Object packet, Object slot, Object stack) { + equipmentStack.set(packet, Collections.singletonList(new Pair<>(slot, stack))); + } + + private static final Reflection.ConstructorInvoker removePacketConstructor = Reflection.getConstructor(ClientboundPlayerInfoRemovePacket.class, List.class); + private static final Reflection.FieldAccessor updateActions = Reflection.getField(ClientboundPlayerInfoUpdatePacket.class, EnumSet.class, 0); + private static final Reflection.FieldAccessor updatePlayers = Reflection.getField(ClientboundPlayerInfoUpdatePacket.class, List.class, 0); + + @Override + @SuppressWarnings("deprecation") + public Object playerInfoPacketConstructor(PlayerInfoAction action, GameProfile profile, GameMode mode) { + if(action == PlayerInfoAction.REMOVE) + return removePacketConstructor.invoke(Collections.singletonList(profile.getId())); + + Object packet = Reflection.newInstance(ClientboundPlayerInfoUpdatePacket.class); + updateActions.set(packet, action == PlayerInfoAction.ADD ? EnumSet.of(ClientboundPlayerInfoUpdatePacket.a.a, ClientboundPlayerInfoUpdatePacket.a.c) : EnumSet.of(ClientboundPlayerInfoUpdatePacket.a.c)); + updatePlayers.set(packet, Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.b(profile.getId(), profile, false, 0, EnumGamemode.a(mode.getValue()), null, null))); + return packet; + } + + @Override + public void initTPSWarp(LongSupplier longSupplier) { + SystemUtils.a = () -> System.nanoTime() + longSupplier.getAsLong(); + } +} diff --git a/SpigotCore/SpigotCore_19/src/src/de/steamwar/techhider/ProtocolWrapper19.java b/SpigotCore/SpigotCore_19/src/src/de/steamwar/techhider/ProtocolWrapper19.java new file mode 100644 index 00000000..38ac90cd --- /dev/null +++ b/SpigotCore/SpigotCore_19/src/src/de/steamwar/techhider/ProtocolWrapper19.java @@ -0,0 +1,34 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2021 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + */ + +package de.steamwar.techhider; + +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.util.Set; +import java.util.function.BiFunction; + +public class ProtocolWrapper19 extends ProtocolWrapper18 { + + @Override + public BiFunction blockBreakHiderGenerator(Class blockBreakPacket, Object obfuscationTarget, Set obfuscate, TechHider.LocationEvaluator locationEvaluator) { + return null; + } +} diff --git a/SpigotCore/SpigotCore_20/build.gradle.kts b/SpigotCore/SpigotCore_20/build.gradle.kts new file mode 100644 index 00000000..671ab444 --- /dev/null +++ b/SpigotCore/SpigotCore_20/build.gradle.kts @@ -0,0 +1,58 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2024 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +plugins { + id("java") + id("base") +} + +group = "de.steamwar" +version = "" + +tasks.compileJava { + options.encoding = "UTF-8" +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +sourceSets { + main { + java { + srcDirs("src/") + } + resources { + srcDirs("src/") + exclude("**/*.java", "**/*.kt") + } + } +} + +dependencies { + compileOnly("org.projectlombok:lombok:1.18.32") + annotationProcessor("org.projectlombok:lombok:1.18.32") + + compileOnly(project(":SpigotCore:SpigotCore_Main")) + + compileOnly("de.steamwar:spigot:1.20") + + compileOnly("org.spigotmc:spigot-api:1.20-R0.1-SNAPSHOT") +} diff --git a/SpigotCore/SpigotCore_8/build.gradle.kts b/SpigotCore/SpigotCore_8/build.gradle.kts new file mode 100644 index 00000000..aeb7077e --- /dev/null +++ b/SpigotCore/SpigotCore_8/build.gradle.kts @@ -0,0 +1,58 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2024 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +plugins { + id("java") + id("base") +} + +group = "de.steamwar" +version = "" + +tasks.compileJava { + options.encoding = "UTF-8" +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +sourceSets { + main { + java { + srcDirs("src/") + } + resources { + srcDirs("src/") + exclude("**/*.java", "**/*.kt") + } + } +} + +dependencies { + compileOnly("org.projectlombok:lombok:1.18.32") + annotationProcessor("org.projectlombok:lombok:1.18.32") + + compileOnly(project(":SpigotCore:SpigotCore_Main")) + compileOnly(project(":CommonCore")) + + compileOnly("de.steamwar:spigot:1.8") + compileOnly("de.steamwar:worldedit:1.12") +} diff --git a/SpigotCore/SpigotCore_8/src/de/steamwar/core/BountifulWrapper8.java b/SpigotCore/SpigotCore_8/src/de/steamwar/core/BountifulWrapper8.java new file mode 100644 index 00000000..d901401c --- /dev/null +++ b/SpigotCore/SpigotCore_8/src/de/steamwar/core/BountifulWrapper8.java @@ -0,0 +1,98 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + */ + +package de.steamwar.core; + +import com.comphenix.tinyprotocol.Reflection; +import net.md_5.bungee.api.ChatMessageType; +import net.md_5.bungee.api.chat.BaseComponent; +import net.minecraft.server.v1_8_R3.ChatComponentText; +import net.minecraft.server.v1_8_R3.MathHelper; +import net.minecraft.server.v1_8_R3.PacketPlayOutChat; +import org.bukkit.Sound; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; + +public class BountifulWrapper8 implements BountifulWrapper.IBountifulWrapper { + + @Override + public void playPling(Player player) { + player.playSound(player.getLocation(), Sound.ORB_PICKUP, 1, 1); + } + + @Override + public void sendMessage(Player player, ChatMessageType type, BaseComponent... msg) { + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutChat(new ChatComponentText(BaseComponent.toLegacyText(msg)), (byte)type.ordinal())); + } + + @Override + public Object getDataWatcherObject(int index, Class type) { + return index; + } + + private static final Class watchableObject = Reflection.getClass("{nms}.DataWatcher$WatchableObject"); + private static final Reflection.ConstructorInvoker watchableObjectConstructor = Reflection.getConstructor(watchableObject, int.class, int.class, Object.class); + private static final Map, Integer> watchableDatatypes = new HashMap<>(); + static { + watchableDatatypes.put(byte.class, 0); + watchableDatatypes.put(short.class, 1); + watchableDatatypes.put(int.class, 2); + watchableDatatypes.put(float.class, 3); + watchableDatatypes.put(String.class, 4); + } + + @Override + public Object getDataWatcherItem(Object dwo, Object value) { + return watchableObjectConstructor.invoke(watchableDatatypes.get(value.getClass()), dwo, value); + } + + @Override + public BountifulWrapper.PositionSetter getPositionSetter(Class packetClass, int fieldOffset) { + Reflection.FieldAccessor posX = Reflection.getField(packetClass, int.class, fieldOffset); + Reflection.FieldAccessor posY = Reflection.getField(packetClass, int.class, fieldOffset +1); + Reflection.FieldAccessor posZ = Reflection.getField(packetClass, int.class, fieldOffset +2); + + return (packet, x, y, z) -> { + posX.set(packet, MathHelper.floor(x * 32)); + posY.set(packet, MathHelper.floor(y * 32)); + posZ.set(packet, MathHelper.floor(z * 32)); + }; + } + + @Override + public BountifulWrapper.PositionSetter getRelMoveSetter(Class packetClass) { + Reflection.FieldAccessor moveX = Reflection.getField(packetClass, "b", byte.class); + Reflection.FieldAccessor moveY = Reflection.getField(packetClass, "c", byte.class); + Reflection.FieldAccessor moveZ = Reflection.getField(packetClass, "d", byte.class); + + return (packet, x, y, z) -> { + moveX.set(packet, (byte)(x*32)); + moveY.set(packet, (byte)(y*32)); + moveZ.set(packet, (byte)(z*32)); + }; + } + + @Override + public BountifulWrapper.UUIDSetter getUUIDSetter(Class packetClass) { + return (packet, uuid) -> {}; + } +} diff --git a/SpigotCore/SpigotCore_8/src/de/steamwar/core/ChatWrapper8.java b/SpigotCore/SpigotCore_8/src/de/steamwar/core/ChatWrapper8.java new file mode 100644 index 00000000..951cdc8a --- /dev/null +++ b/SpigotCore/SpigotCore_8/src/de/steamwar/core/ChatWrapper8.java @@ -0,0 +1,51 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.core; + +import com.comphenix.tinyprotocol.Reflection; + +import java.util.ArrayList; +import java.util.List; + +public class ChatWrapper8 implements ChatWrapper { + + private static final Reflection.ConstructorInvoker chatComponentConstructor = Reflection.getConstructor(Reflection.getClass("{nms.network.chat}.ChatComponentText"), String.class); + @Override + public Object stringToChatComponent(String text) { + return chatComponentConstructor.invoke(text); + } + + private static final Class metadataPacket = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutEntityMetadata"); + private static final Reflection.FieldAccessor metadataEntity = Reflection.getField(metadataPacket, int.class, 0); + private static final Reflection.FieldAccessor metadataMetadata = Reflection.getField(metadataPacket, List.class, 0); + @Override + public Object getDataWatcherPacket(int entityId, Object... dataWatcherKeyValues) { + Object packet = Reflection.newInstance(metadataPacket); + metadataEntity.set(packet, entityId); + + ArrayList nativeWatchers = new ArrayList<>(1); + for(int i = 0; i < dataWatcherKeyValues.length; i+=2) { + nativeWatchers.add(BountifulWrapper.impl.getDataWatcherItem(dataWatcherKeyValues[i], dataWatcherKeyValues[i+1])); + } + metadataMetadata.set(packet, nativeWatchers); + + return packet; + } +} diff --git a/SpigotCore/SpigotCore_8/src/de/steamwar/core/CraftbukkitWrapper8.java b/SpigotCore/SpigotCore_8/src/de/steamwar/core/CraftbukkitWrapper8.java new file mode 100644 index 00000000..23be7911 --- /dev/null +++ b/SpigotCore/SpigotCore_8/src/de/steamwar/core/CraftbukkitWrapper8.java @@ -0,0 +1,33 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.core; + +import net.minecraft.server.v1_8_R3.PacketPlayOutMapChunk; +import org.bukkit.craftbukkit.v1_8_R3.CraftChunk; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import org.bukkit.entity.Player; + +public class CraftbukkitWrapper8 implements CraftbukkitWrapper.ICraftbukkitWrapper { + + @Override + public void sendChunk(Player p, int chunkX, int chunkZ) { + ((CraftPlayer)p).getHandle().playerConnection.sendPacket(new PacketPlayOutMapChunk(((CraftChunk)p.getWorld().getChunkAt(chunkX, chunkZ)).getHandle(), true, 65535)); + } +} diff --git a/SpigotCore/SpigotCore_8/src/de/steamwar/core/FlatteningWrapper8.java b/SpigotCore/SpigotCore_8/src/de/steamwar/core/FlatteningWrapper8.java new file mode 100644 index 00000000..535c5261 --- /dev/null +++ b/SpigotCore/SpigotCore_8/src/de/steamwar/core/FlatteningWrapper8.java @@ -0,0 +1,121 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.core; + +import com.comphenix.tinyprotocol.Reflection; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.SkullMeta; + +import java.util.HashMap; +import java.util.Map; + +public class FlatteningWrapper8 implements FlatteningWrapper.IFlatteningWrapper { + + private static final Reflection.FieldAccessor scoreboardName = Reflection.getField(FlatteningWrapper.scoreboardObjective, String.class, 1); + private static final Class scoreActionEnum = Reflection.getClass("{nms}.PacketPlayOutScoreboardScore$EnumScoreboardAction"); + private static final Reflection.FieldAccessor scoreAction = Reflection.getField(FlatteningWrapper.scoreboardScore, scoreActionEnum, 0); + private static final Object scoreActionChange = scoreActionEnum.getEnumConstants()[0]; + + @Override + public void setScoreboardTitle(Object packet, String title) { + scoreboardName.set(packet, title); + } + + @Override + public void setScoreAction(Object packet) { + scoreAction.set(packet, scoreActionChange); + } + + @Override + public Material getMaterial(String material) { + try{ + return Material.valueOf(material); + }catch(IllegalArgumentException e){ + return Material.STONE; + } + } + + @Override + public Material getDye(int colorCode) { + return Material.INK_SACK; + } + + @Override + public ItemStack setSkullOwner(String player) { + ItemStack head = new ItemStack(Material.SKULL_ITEM, 1, (short) 3); + SkullMeta headmeta = (SkullMeta) head.getItemMeta(); + headmeta.setOwner(player.startsWith(".") ? player.substring(1) : player); + headmeta.setDisplayName(player); + head.setItemMeta(headmeta); + return head; + } + + @Override + public Object getPose(FlatteningWrapper.EntityPose pose) { + return Byte.valueOf((byte)(pose == FlatteningWrapper.EntityPose.SNEAKING ? 2 : 0)); + } + + private static final Class dataWatcher = Reflection.getClass("{nms}.DataWatcher"); + private static final Class namedSpawnPacket = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutNamedEntitySpawn"); + private static final Reflection.FieldAccessor namedSpawnDataWatcher = Reflection.getField(namedSpawnPacket, dataWatcher, 0); + private static final Class entity = Reflection.getClass("{nms}.Entity"); + private static final Reflection.ConstructorInvoker dataWatcherConstructor = Reflection.getConstructor(dataWatcher, entity); + @Override + public void setNamedSpawnPacketDataWatcher(Object packet) { + namedSpawnDataWatcher.set(packet, dataWatcherConstructor.invoke((Object) null)); + } + + @Override + public Object formatDisplayName(String displayName) { + return displayName != null ? displayName : ""; + } + + + private static final Reflection.FieldAccessor spawnType = Reflection.getField(ProtocolWrapper.spawnPacket, int.class, Core.getVersion() > 8 ? 6 : 9); + private static final Reflection.FieldAccessor spawnLivingType = Reflection.getField(ProtocolWrapper.spawnLivingPacket, int.class, 1); + private static final Map types = new HashMap<>(); + static { + types.put(EntityType.PRIMED_TNT, 50); + types.put(EntityType.ARMOR_STAND, 30); + types.put(EntityType.ARROW, 60); + types.put(EntityType.FIREBALL, 63); + types.put(EntityType.ITEM_FRAME, 18); + types.put(EntityType.FALLING_BLOCK, 21); + } + @Override + public void setSpawnPacketType(Object packet, EntityType type) { + (type.isAlive() ? spawnLivingType : spawnType).set(packet, types.get(type)); + } + + @Override + public int getViewDistance(Player player) { + return 10; + } + + private static final Reflection.MethodInvoker save = Reflection.getMethod("{obc}.CraftWorld", "save", boolean.class); + @Override + public void syncSave(World world) { + save.invoke(world, true); + } +} diff --git a/SpigotCore/SpigotCore_8/src/de/steamwar/core/IDConverter8.java b/SpigotCore/SpigotCore_8/src/de/steamwar/core/IDConverter8.java new file mode 100644 index 00000000..7026365b --- /dev/null +++ b/SpigotCore/SpigotCore_8/src/de/steamwar/core/IDConverter8.java @@ -0,0 +1,113 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.core; + +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.InputStreamReader; +import java.util.*; +import java.util.stream.Collectors; + +public class IDConverter8 { + + private final Map> availibleAttributes; + private final Map, BlockTypeID>> map; + + public IDConverter8() { + Map, BlockTypeID>> map = new HashMap<>(); + + YamlConfiguration legacy = YamlConfiguration.loadConfiguration(new InputStreamReader(Objects.requireNonNull(IDConverter8.class.getClassLoader().getResourceAsStream("legacy.yml")))); + for(String blockString : legacy.getKeys(false)){ + String[] legacyBlockId = legacy.getString(blockString).split(":"); + + map.computeIfAbsent(getBlockId(blockString), bId -> { + Map, BlockTypeID> attributeMap = new HashMap<>(); + attributeMap.put(new HashSet<>(), new BlockTypeID(legacyBlockId[0], "0")); + return attributeMap; + }).put(getAttributes(blockString), new BlockTypeID(legacyBlockId[0], legacyBlockId[1])); + } + this.map = map; + + Map> availableAttributes = new HashMap<>(); + for (Map.Entry, BlockTypeID>> entry : map.entrySet()) { + availableAttributes.put(entry.getKey(), entry.getValue().keySet().stream().flatMap(Collection::stream).collect(Collectors.toSet())); + } + this.availibleAttributes = availableAttributes; + } + + public BlockTypeID getId(String blockString) { + String blockId = getBlockId(blockString); + Map, IDConverter8.BlockTypeID> attributeMap = map.get(blockId); + if(attributeMap == null) { // Block nonexistent pre-flattening + return new BlockTypeID("0", "0"); + } + + Set attributes = getAttributes(blockString); + Set knownAttributes = this.availibleAttributes.get(blockId); + attributes.removeIf(attribute -> !knownAttributes.contains(attribute)); + + long bestMatch = -1; + BlockTypeID blockID = null; + for(Map.Entry, BlockTypeID> entry : attributeMap.entrySet()) { + Set attrs = entry.getKey(); + if(attrs.size() <= bestMatch) + continue; + + long matching = attributes.stream().filter(attrs::contains).count(); + if(matching > bestMatch) { + blockID = entry.getValue(); + bestMatch = matching; + + if(bestMatch == attributes.size()) + break; + } + } + return blockID; + } + + private String getBlockId(String blockString) { + return blockString.split("\\[", 2)[0]; + } + + private Set getAttributes(String blockString) { + Set attributes = new HashSet<>(); + if(blockString.contains("[")) + Collections.addAll(attributes, blockString.split("\\[")[1].replace("]", "").split(",")); + return attributes; + } + + static class BlockTypeID{ + private final int blockId; + private final byte dataId; + + private BlockTypeID(String blockId, String dataId) { + this.blockId = Integer.parseInt(blockId); + this.dataId = Byte.parseByte(dataId); + } + + int getBlockId() { + return blockId; + } + + byte getDataId() { + return dataId; + } + } +} diff --git a/SpigotCore/SpigotCore_8/src/de/steamwar/core/LocaleChangeWrapper8.java b/SpigotCore/SpigotCore_8/src/de/steamwar/core/LocaleChangeWrapper8.java new file mode 100644 index 00000000..ddee9c6c --- /dev/null +++ b/SpigotCore/SpigotCore_8/src/de/steamwar/core/LocaleChangeWrapper8.java @@ -0,0 +1,24 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.core; + +public class LocaleChangeWrapper8 implements LocaleChangeWrapper { + // Event not available in 1.8-1.10 +} diff --git a/SpigotCore/SpigotCore_8/src/de/steamwar/core/ProtocolWrapper8.java b/SpigotCore/SpigotCore_8/src/de/steamwar/core/ProtocolWrapper8.java new file mode 100644 index 00000000..9f0b234b --- /dev/null +++ b/SpigotCore/SpigotCore_8/src/de/steamwar/core/ProtocolWrapper8.java @@ -0,0 +1,71 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.core; + +import com.comphenix.tinyprotocol.Reflection; +import com.mojang.authlib.GameProfile; +import org.bukkit.GameMode; + +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; + +public class ProtocolWrapper8 implements ProtocolWrapper { + + private static final Reflection.FieldAccessor equipmentSlot; + static { + if(Core.getVersion() == 8) { + equipmentSlot = Reflection.getField(equipmentPacket, int.class, 1); + } else { + Class enumItemSlot = Reflection.getClass("{nms.world.entity}.EnumItemSlot"); + equipmentSlot = Reflection.getField(equipmentPacket, enumItemSlot, 0); + } + } + + private static final Reflection.FieldAccessor equipmentStack = Reflection.getField(equipmentPacket, itemStack, 0); + @Override + public void setEquipmentPacketStack(Object packet, Object slot, Object stack) { + equipmentSlot.set(packet, slot); + equipmentStack.set(packet, stack); + } + + private static final Class playerInfoPacket = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutPlayerInfo"); + private static final Class playerInfoActionClass = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutPlayerInfo$EnumPlayerInfoAction"); + private static final Reflection.FieldAccessor playerInfoAction = Reflection.getField(playerInfoPacket, playerInfoActionClass, 0); + private static final Reflection.FieldAccessor playerInfoData = Reflection.getField(playerInfoPacket, List.class, 0); + private static final EnumMap actions = new EnumMap<>(PlayerInfoAction.class); + static { + Object[] nativeActions = playerInfoActionClass.getEnumConstants(); + actions.put(PlayerInfoAction.ADD, nativeActions[0]); + actions.put(PlayerInfoAction.GAMEMODE, nativeActions[1]); + actions.put(PlayerInfoAction.REMOVE, nativeActions[4]); + } + private static final Class iChatBaseComponent = Reflection.getClass("{nms.network.chat}.IChatBaseComponent"); + private static final Reflection.ConstructorInvoker playerInfoDataConstructor = Reflection.getConstructor("{nms.network.protocol.game}.PacketPlayOutPlayerInfo$PlayerInfoData", playerInfoPacket, GameProfile.class, int.class, enumGamemode, iChatBaseComponent); + + @Override + @SuppressWarnings("deprecation") + public Object playerInfoPacketConstructor(PlayerInfoAction action, GameProfile profile, GameMode mode) { + Object packet = Reflection.newInstance(playerInfoPacket); + playerInfoAction.set(packet, actions.get(action)); + playerInfoData.set(packet, Collections.singletonList(playerInfoDataConstructor.invoke(packet, profile, 0, ProtocolWrapper.getGameModeById.invoke(null, mode.getValue()), null))); + return packet; + } +} diff --git a/SpigotCore/SpigotCore_8/src/de/steamwar/core/RecipeDiscoverWrapper8.java b/SpigotCore/SpigotCore_8/src/de/steamwar/core/RecipeDiscoverWrapper8.java new file mode 100644 index 00000000..59faa4cb --- /dev/null +++ b/SpigotCore/SpigotCore_8/src/de/steamwar/core/RecipeDiscoverWrapper8.java @@ -0,0 +1,24 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.core; + +public class RecipeDiscoverWrapper8 implements RecipeDiscoverWrapper { + // Event not available pre flattening +} diff --git a/SpigotCore/SpigotCore_8/src/de/steamwar/core/WorldEditWrapper8.java b/SpigotCore/SpigotCore_8/src/de/steamwar/core/WorldEditWrapper8.java new file mode 100644 index 00000000..e5f6ac82 --- /dev/null +++ b/SpigotCore/SpigotCore_8/src/de/steamwar/core/WorldEditWrapper8.java @@ -0,0 +1,265 @@ +package de.steamwar.core; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import com.sk89q.jnbt.*; +import com.sk89q.worldedit.*; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.bukkit.BukkitWorld; +import com.sk89q.worldedit.extension.input.ParserContext; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; +import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat; +import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; +import com.sk89q.worldedit.extent.clipboard.io.SchematicReader; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.session.ClipboardHolder; +import com.sk89q.worldedit.world.registry.WorldData; +import de.steamwar.sql.NoClipboardException; +import org.bukkit.entity.Player; + +import java.io.*; +import java.util.*; +import java.util.logging.Level; +import java.util.stream.Collectors; + +public class WorldEditWrapper8 implements WorldEditWrapper.IWorldEditWrapper { + + @Override + public InputStream getPlayerClipboard(Player player, boolean schemFormat) { + ClipboardHolder clipboardHolder; + try { + clipboardHolder = WorldEditWrapper.getWorldEditPlugin().getSession(player).getClipboard(); + } catch (EmptyClipboardException e) { + throw new NoClipboardException(); + } + + Clipboard clipboard = clipboardHolder.getClipboard(); + if(clipboard == null) + throw new NoClipboardException(); + + PipedOutputStream outputStream = new PipedOutputStream(); + PipedInputStream inputStream; + try { + inputStream = new PipedInputStream(outputStream, 4096); + } catch (IOException e) { + throw new SecurityException("Could not init piped input stream", e); + } + + new Thread(() -> { + try { + ClipboardFormat.SCHEMATIC.getWriter(outputStream).write(clipboard, clipboardHolder.getWorldData()); + } catch (IOException e) { + Core.getInstance().getLogger().log(Level.SEVERE, "Could not write schematic", e); + } + try { + outputStream.close(); + } catch (IOException e) { + Core.getInstance().getLogger().log(Level.SEVERE, "Could not close schem writer", e); + } + }, "SchemWriter").start(); + + return inputStream; + } + + @Override + public void setPlayerClipboard(Player player, InputStream is, boolean schemFormat) { + WorldData world = new BukkitWorld(player.getWorld()).getWorldData(); + Clipboard clipboard; + try { + clipboard = getClipboard(is, schemFormat); + } catch (IOException e) { + throw new RuntimeException(e); + } + + Actor actor = WorldEditWrapper.getWorldEditPlugin().wrapCommandSender(player); + WorldEditWrapper.getWorldEditPlugin().getWorldEdit().getSessionManager().get(actor).setClipboard(new ClipboardHolder(clipboard, world)); + } + + @Override + public Clipboard getClipboard(InputStream is, boolean schemFormat) throws IOException { + if(schemFormat) + return new SpongeSchematicReader(new NBTInputStream(is)).read(WorldEdit.getInstance().getServer().getWorlds().get(0).getWorldData()); + else + return new SchematicReader(new NBTInputStream(is)).read(WorldEdit.getInstance().getServer().getWorlds().get(0).getWorldData()); + } + + private static class SpongeSchematicReader implements ClipboardReader { + + private final NBTInputStream inputStream; + private int schematicVersion = -1; + + SpongeSchematicReader(NBTInputStream inputStream) { + Preconditions.checkNotNull(inputStream); + this.inputStream = inputStream; + } + + @Override + public Clipboard read(WorldData worldData) throws IOException { + CompoundTag schematicTag = this.getBaseTag(); + if (this.schematicVersion == 1) { + return this.readSchematic(schematicTag); + } else if (this.schematicVersion == 2) { + return this.readSchematic(schematicTag); + } else { + throw new IOException("This schematic version is currently not supported"); + } + } + + private CompoundTag getBaseTag() throws IOException { + NamedTag rootTag = this.inputStream.readNamedTag(); + if (!rootTag.getName().equals("Schematic")) { + throw new IOException("Tag 'Schematic' does not exist or is not first"); + } else { + CompoundTag schematicTag = (CompoundTag)rootTag.getTag(); + Map schematic = schematicTag.getValue(); + this.schematicVersion = (requireTag(schematic, "Version", IntTag.class)).getValue(); + return schematicTag; + } + } + + private BlockArrayClipboard readSchematic(CompoundTag schematicTag) throws IOException { + IDConverter8 ids = new IDConverter8(); + + Map schematic = schematicTag.getValue(); + int width = (requireTag(schematic, "Width", ShortTag.class)).getValue(); + int height = (requireTag(schematic, "Height", ShortTag.class)).getValue(); + int length = (requireTag(schematic, "Length", ShortTag.class)).getValue(); + IntArrayTag offsetTag = getTag(schematic, "Offset", IntArrayTag.class); + int[] offsetParts; + if (offsetTag != null) { + offsetParts = offsetTag.getValue(); + if (offsetParts.length != 3) + throw new IOException("Invalid offset specified in schematic."); + } else { + offsetParts = new int[]{0, 0, 0}; + } + + BlockVector min = new BlockVector(offsetParts[0], offsetParts[1], offsetParts[2]); + CompoundTag metadataTag = getTag(schematic, "Metadata", CompoundTag.class); + Vector origin; + CuboidRegion region; + if (metadataTag != null && metadataTag.containsKey("WEOffsetX")) { + Map metadata = metadataTag.getValue(); + int offsetX = (requireTag(metadata, "WEOffsetX", IntTag.class)).getValue(); + int offsetY = (requireTag(metadata, "WEOffsetY", IntTag.class)).getValue(); + int offsetZ = (requireTag(metadata, "WEOffsetZ", IntTag.class)).getValue(); + BlockVector offset = new BlockVector(offsetX, offsetY, offsetZ); + origin = min.subtract(offset); + region = new CuboidRegion(min, min.add(width, height, length).subtract(BlockVector.ONE)); + } else { + origin = min; + region = new CuboidRegion(min, min.add(width, height, length).subtract(BlockVector.ONE)); + } + + IntTag paletteMaxTag = getTag(schematic, "PaletteMax", IntTag.class); + Map paletteObject = requireTag(schematic, "Palette", CompoundTag.class).getValue(); + if (paletteMaxTag != null && paletteObject.size() != paletteMaxTag.getValue()) + throw new IOException("Block palette size does not match expected size."); + + Map palette = new HashMap<>(); + ParserContext parserContext = new ParserContext(); + parserContext.setRestricted(false); + parserContext.setPreferringWildcard(false); + + for(String palettePart : paletteObject.keySet()) { + IDConverter8.BlockTypeID blockID = ids.getId(palettePart); + palette.put(requireTag(paletteObject, palettePart, IntTag.class).getValue(), new BaseBlock(blockID.getBlockId(), blockID.getDataId())); + } + + byte[] blocks = requireTag(schematic, "BlockData", ByteArrayTag.class).getValue(); + Map> tileEntitiesMap = new HashMap<>(); + ListTag tileEntities = getTag(schematic, "BlockEntities", ListTag.class); + if (tileEntities == null) { + tileEntities = getTag(schematic, "TileEntities", ListTag.class); + } + + if (tileEntities != null) { + List> tileEntityTags = tileEntities.getValue().stream().map((tag) -> + (CompoundTag)tag + ).map(CompoundTag::getValue).collect(Collectors.toList()); + + BlockVector pt; + Map tileEntity; + for(Iterator> var20 = tileEntityTags.iterator(); var20.hasNext(); tileEntitiesMap.put(pt, tileEntity)) { + tileEntity = var20.next(); + int[] pos = requireTag(tileEntity, "Pos", IntArrayTag.class).getValue(); + pt = new BlockVector(pos[0], pos[1], pos[2]); + Map values = Maps.newHashMap(tileEntity); + values.put("x", new IntTag(pt.getBlockX())); + values.put("y", new IntTag(pt.getBlockY())); + values.put("z", new IntTag(pt.getBlockZ())); + values.put("id", values.get("Id")); + values.remove("Id"); + values.remove("Pos"); + tileEntity = values; + } + } + + BlockArrayClipboard clipboard = new BlockArrayClipboard(region); + clipboard.setOrigin(origin); + int index = 0; + + for(int i = 0; i < blocks.length; ++index) { + int value = 0; + int varintLength = 0; + + while(true) { + value |= (blocks[i] & 127) << varintLength++ * 7; + if (varintLength > 5) { + throw new IOException("VarInt too big (probably corrupted data)"); + } + + if ((blocks[i] & 128) != 128) { + ++i; + int y = index / (width * length); + int z = index % (width * length) / width; + int x = index % (width * length) % width; + BaseBlock block = palette.get(value); + BlockVector pt = new BlockVector(x, y, z); + + try { + if (tileEntitiesMap.containsKey(pt)) { + block.setNbtData(new CompoundTag(tileEntitiesMap.get(pt))); + clipboard.setBlock(clipboard.getMinimumPoint().add(pt), block); + } else { + clipboard.setBlock(clipboard.getMinimumPoint().add(pt), block); + } + break; + } catch (WorldEditException var30) { + throw new IOException("Failed to load a block in the schematic"); + } + } + + ++i; + } + } + + return clipboard; + } + + private static T requireTag(Map items, String key, Class expected) throws IOException { + if (!items.containsKey(key)) { + throw new IOException("Schematic file is missing a \"" + key + "\" tag"); + } else { + Tag tag = items.get(key); + if (!expected.isInstance(tag)) { + throw new IOException(key + " tag is not of tag type " + expected.getName()); + } else { + return expected.cast(tag); + } + } + } + + private static T getTag(Map items, String key, Class expected) { + if (!items.containsKey(key)) { + return null; + } else { + Tag test = items.get(key); + return !expected.isInstance(test) ? null : expected.cast(test); + } + } + } +} diff --git a/SpigotCore/SpigotCore_8/src/de/steamwar/techhider/BlockIds8.java b/SpigotCore/SpigotCore_8/src/de/steamwar/techhider/BlockIds8.java new file mode 100644 index 00000000..538e8dbd --- /dev/null +++ b/SpigotCore/SpigotCore_8/src/de/steamwar/techhider/BlockIds8.java @@ -0,0 +1,38 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2021 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + */ + +package de.steamwar.techhider; + +import org.bukkit.Material; + +import java.util.Collections; +import java.util.Set; + +public class BlockIds8 implements BlockIds { + @Override + @SuppressWarnings("deprecation") + public int materialToId(Material material) { + return material.getId() << 4; + } + + @Override + public Set materialToAllIds(Material material) { + return Collections.singleton(materialToId(material)); + } +} diff --git a/SpigotCore/SpigotCore_8/src/de/steamwar/techhider/ChunkHider8.java b/SpigotCore/SpigotCore_8/src/de/steamwar/techhider/ChunkHider8.java new file mode 100644 index 00000000..b7da81e1 --- /dev/null +++ b/SpigotCore/SpigotCore_8/src/de/steamwar/techhider/ChunkHider8.java @@ -0,0 +1,40 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2021 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + */ + +package de.steamwar.techhider; + +import com.comphenix.tinyprotocol.Reflection; +import org.bukkit.entity.Player; + +import java.util.Set; +import java.util.function.BiFunction; + +public class ChunkHider8 implements ChunkHider { + + protected static final Class mapChunkPacket = Reflection.getClass("{nms}.PacketPlayOutMapChunk"); + @Override + public Class mapChunkPacket() { + return mapChunkPacket; + } + + @Override + public BiFunction chunkHiderGenerator(TechHider.LocationEvaluator locationEvaluator, int obfuscationTarget, Set obfuscate, Set hiddenBlockEntities) { + return (player, packet) -> packet; + } +} diff --git a/SpigotCore/SpigotCore_8/src/de/steamwar/techhider/ProtocolWrapper8.java b/SpigotCore/SpigotCore_8/src/de/steamwar/techhider/ProtocolWrapper8.java new file mode 100644 index 00000000..ba307d0d --- /dev/null +++ b/SpigotCore/SpigotCore_8/src/de/steamwar/techhider/ProtocolWrapper8.java @@ -0,0 +1,86 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2021 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + */ + +package de.steamwar.techhider; + +import com.comphenix.tinyprotocol.Reflection; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Set; +import java.util.function.BiFunction; + +public class ProtocolWrapper8 implements ProtocolWrapper { + private static final Class chunkCoordinateIntPair = Reflection.getClass("{nms}.ChunkCoordIntPair"); + private static final Reflection.FieldAccessor multiBlockChangeChunk = Reflection.getField(TechHider.multiBlockChangePacket, chunkCoordinateIntPair, 0); + private static final Reflection.FieldAccessor chunkCoordinateX = Reflection.getField(chunkCoordinateIntPair, int.class, 0); + private static final Reflection.FieldAccessor chunkCoordinateZ = Reflection.getField(chunkCoordinateIntPair, int.class, 1); + private static final Class multiBlockChangeInfo = Reflection.getClass("{nms}.PacketPlayOutMultiBlockChange$MultiBlockChangeInfo"); + private static final Reflection.ConstructorInvoker multiBlockChangeInfoConstructor = Reflection.getConstructor(multiBlockChangeInfo, TechHider.multiBlockChangePacket, short.class, TechHider.iBlockData); + private static final Reflection.FieldAccessor multiBlockChangeInfoBlock = Reflection.getField(multiBlockChangeInfo, TechHider.iBlockData, 0); + private static final Reflection.FieldAccessor multiBlockChangeInfoPos = Reflection.getField(multiBlockChangeInfo, short.class, 0); + private static final Class multiBlockChangeInfoArray = Reflection.getClass("[L{nms}.PacketPlayOutMultiBlockChange$MultiBlockChangeInfo;"); + private static final Reflection.FieldAccessor multiBlockChangeInfos = Reflection.getField(TechHider.multiBlockChangePacket, multiBlockChangeInfoArray, 0); + @Override + public BiFunction multiBlockChangeGenerator(Object obfuscationTarget, Set obfuscate, TechHider.LocationEvaluator locationEvaluator) { + return (p, packet) -> { + Object chunkCoords = multiBlockChangeChunk.get(packet); + int chunkX = chunkCoordinateX.get(chunkCoords); + int chunkZ = chunkCoordinateZ.get(chunkCoords); + if(locationEvaluator.skipChunk(p, chunkX, chunkZ)) + return packet; + + packet = TechHider.multiBlockChangeCloner.apply(packet); + Object[] mbcis = (Object[]) multiBlockChangeInfos.get(packet); + ArrayList blockChangeInfos = new ArrayList<>(mbcis.length); + for(Object mbci : mbcis) { + short pos = (short) multiBlockChangeInfoPos.get(mbci); + switch(locationEvaluator.check(p, 16*chunkX + (pos >> 12 & 0xF), pos & 0xFF, 16*chunkZ + (pos >> 8 & 0xF))) { + case SKIP: + blockChangeInfos.add(mbci); + break; + case CHECK: + blockChangeInfos.add(TechHider.iBlockDataHidden(obfuscate, multiBlockChangeInfoBlock.get(mbci)) ? multiBlockChangeInfoConstructor.invoke(packet, pos, obfuscationTarget) : mbci); + break; + default: + break; + } + } + + if(blockChangeInfos.isEmpty()) + return null; + + multiBlockChangeInfos.set(packet, blockChangeInfos.toArray((Object[])Array.newInstance(multiBlockChangeInfo, 0))); + return packet; + }; + } + + private static final Reflection.FieldAccessor tileEntityDataAction = Reflection.getField(TechHider.tileEntityDataPacket, int.class, 0); + @Override + public boolean unfilteredTileEntityDataAction(Object packet) { + return tileEntityDataAction.get(packet) != 9; + } + + @Override + public BiFunction blockBreakHiderGenerator(Class blockBreakPacket, Object obfuscationTarget, Set obfuscate, TechHider.LocationEvaluator bypass) { + return null; + } +} diff --git a/SpigotCore/SpigotCore_8/src/legacy.yml b/SpigotCore/SpigotCore_8/src/legacy.yml new file mode 100644 index 00000000..51097cd6 --- /dev/null +++ b/SpigotCore/SpigotCore_8/src/legacy.yml @@ -0,0 +1,1623 @@ +"minecraft:air": "0:0" +"minecraft:stone": "1:0" +"minecraft:granite": "1:1" +"minecraft:polished_granite": "1:2" +"minecraft:diorite": "1:3" +"minecraft:polished_diorite": "1:4" +"minecraft:andesite": "1:5" +"minecraft:polished_andesite": "1:6" +"minecraft:grass_block[snowy=false]": "2:0" +"minecraft:dirt": "3:0" +"minecraft:coarse_dirt": "3:1" +"minecraft:podzol[snowy=false]": "3:2" +"minecraft:cobblestone": "4:0" +"minecraft:oak_planks": "5:0" +"minecraft:spruce_planks": "5:1" +"minecraft:birch_planks": "5:2" +"minecraft:jungle_planks": "5:3" +"minecraft:acacia_planks": "5:4" +"minecraft:dark_oak_planks": "5:5" +"minecraft:oak_sapling[stage=0]": "6:0" +"minecraft:spruce_sapling[stage=0]": "6:1" +"minecraft:birch_sapling[stage=0]": "6:2" +"minecraft:jungle_sapling[stage=0]": "6:3" +"minecraft:acacia_sapling[stage=0]": "6:4" +"minecraft:dark_oak_sapling[stage=0]": "6:5" +"minecraft:oak_sapling[stage=1]": "6:8" +"minecraft:spruce_sapling[stage=1]": "6:9" +"minecraft:birch_sapling[stage=1]": "6:10" +"minecraft:jungle_sapling[stage=1]": "6:11" +"minecraft:acacia_sapling[stage=1]": "6:12" +"minecraft:dark_oak_sapling[stage=1]": "6:13" +"minecraft:bedrock": "7:0" +"minecraft:water[level=0]": "8:0" +"minecraft:water[level=1]": "8:1" +"minecraft:water[level=2]": "8:2" +"minecraft:water[level=3]": "8:3" +"minecraft:water[level=4]": "8:4" +"minecraft:water[level=5]": "8:5" +"minecraft:water[level=6]": "8:6" +"minecraft:water[level=7]": "8:7" +"minecraft:water[level=8]": "8:8" +"minecraft:water[level=9]": "8:9" +"minecraft:water[level=10]": "8:10" +"minecraft:water[level=11]": "8:11" +"minecraft:water[level=12]": "8:12" +"minecraft:water[level=13]": "8:13" +"minecraft:water[level=14]": "8:14" +"minecraft:water[level=15]": "8:15" +"minecraft:lava[level=0]": "10:0" +"minecraft:lava[level=1]": "10:1" +"minecraft:lava[level=2]": "10:2" +"minecraft:lava[level=3]": "10:3" +"minecraft:lava[level=4]": "10:4" +"minecraft:lava[level=5]": "10:5" +"minecraft:lava[level=6]": "10:6" +"minecraft:lava[level=7]": "10:7" +"minecraft:lava[level=8]": "10:8" +"minecraft:lava[level=9]": "10:9" +"minecraft:lava[level=10]": "10:10" +"minecraft:lava[level=11]": "10:11" +"minecraft:lava[level=12]": "10:12" +"minecraft:lava[level=13]": "10:13" +"minecraft:lava[level=14]": "10:14" +"minecraft:lava[level=15]": "10:15" +"minecraft:sand": "12:0" +"minecraft:red_sand": "12:1" +"minecraft:gravel": "13:0" +"minecraft:gold_ore": "14:0" +"minecraft:iron_ore": "15:0" +"minecraft:coal_ore": "16:0" +"minecraft:oak_log[axis=y]": "17:0" +"minecraft:spruce_log[axis=y]": "17:1" +"minecraft:birch_log[axis=y]": "17:2" +"minecraft:jungle_log[axis=y]": "17:3" +"minecraft:oak_log[axis=x]": "17:4" +"minecraft:spruce_log[axis=x]": "17:5" +"minecraft:birch_log[axis=x]": "17:6" +"minecraft:jungle_log[axis=x]": "17:7" +"minecraft:oak_log[axis=z]": "17:8" +"minecraft:spruce_log[axis=z]": "17:9" +"minecraft:birch_log[axis=z]": "17:10" +"minecraft:jungle_log[axis=z]": "17:11" +"minecraft:oak_wood": "17:12" +"minecraft:spruce_wood": "17:13" +"minecraft:birch_wood": "17:14" +"minecraft:jungle_wood": "17:15" +"minecraft:oak_leaves[persistent=false,distance=1]": "18:0" +"minecraft:spruce_leaves[persistent=false,distance=1]": "18:1" +"minecraft:birch_leaves[persistent=false,distance=1]": "18:2" +"minecraft:jungle_leaves[persistent=false,distance=1]": "18:3" +"minecraft:oak_leaves[persistent=true,distance=1]": "18:4" +"minecraft:spruce_leaves[persistent=true,distance=1]": "18:5" +"minecraft:birch_leaves[persistent=true,distance=1]": "18:6" +"minecraft:jungle_leaves[persistent=true,distance=1]": "18:7" +"minecraft:sponge": "19:0" +"minecraft:wet_sponge": "19:1" +"minecraft:glass": "20:0" +"minecraft:lapis_ore": "21:0" +"minecraft:lapis_block": "22:0" +"minecraft:dispenser[triggered=false,facing=down]": "23:0" +"minecraft:dispenser[triggered=false,facing=up]": "23:1" +"minecraft:dispenser[triggered=false,facing=north]": "23:2" +"minecraft:dispenser[triggered=false,facing=south]": "23:3" +"minecraft:dispenser[triggered=false,facing=west]": "23:4" +"minecraft:dispenser[triggered=false,facing=east]": "23:5" +"minecraft:dispenser[triggered=true,facing=down]": "23:8" +"minecraft:dispenser[triggered=true,facing=up]": "23:9" +"minecraft:dispenser[triggered=true,facing=north]": "23:10" +"minecraft:dispenser[triggered=true,facing=south]": "23:11" +"minecraft:dispenser[triggered=true,facing=west]": "23:12" +"minecraft:dispenser[triggered=true,facing=east]": "23:13" +"minecraft:sandstone": "24:0" +"minecraft:chiseled_sandstone": "24:1" +"minecraft:cut_sandstone": "24:2" +"minecraft:note_block": "25:0" +"minecraft:red_bed[part=foot,facing=south,occupied=false]": "26:0" +"minecraft:red_bed[part=foot,facing=west,occupied=false]": "26:1" +"minecraft:red_bed[part=foot,facing=north,occupied=false]": "26:2" +"minecraft:red_bed[part=foot,facing=east,occupied=false]": "26:3" +"minecraft:red_bed[part=foot,facing=south,occupied=true]": "26:4" +"minecraft:red_bed[part=foot,facing=west,occupied=true]": "26:5" +"minecraft:red_bed[part=foot,facing=north,occupied=true]": "26:6" +"minecraft:red_bed[part=foot,facing=east,occupied=true]": "26:7" +"minecraft:red_bed[part=head,facing=south,occupied=false]": "26:8" +"minecraft:red_bed[part=head,facing=west,occupied=false]": "26:9" +"minecraft:red_bed[part=head,facing=north,occupied=false]": "26:10" +"minecraft:red_bed[part=head,facing=east,occupied=false]": "26:11" +"minecraft:red_bed[part=head,facing=south,occupied=true]": "26:12" +"minecraft:red_bed[part=head,facing=west,occupied=true]": "26:13" +"minecraft:red_bed[part=head,facing=north,occupied=true]": "26:14" +"minecraft:red_bed[part=head,facing=east,occupied=true]": "26:15" +"minecraft:powered_rail[shape=north_south,powered=false]": "27:0" +"minecraft:powered_rail[shape=east_west,powered=false]": "27:1" +"minecraft:powered_rail[shape=ascending_east,powered=false]": "27:2" +"minecraft:powered_rail[shape=ascending_west,powered=false]": "27:3" +"minecraft:powered_rail[shape=ascending_north,powered=false]": "27:4" +"minecraft:powered_rail[shape=ascending_south,powered=false]": "27:5" +"minecraft:powered_rail[shape=north_south,powered=true]": "27:8" +"minecraft:powered_rail[shape=east_west,powered=true]": "27:9" +"minecraft:powered_rail[shape=ascending_east,powered=true]": "27:10" +"minecraft:powered_rail[shape=ascending_west,powered=true]": "27:11" +"minecraft:powered_rail[shape=ascending_north,powered=true]": "27:12" +"minecraft:powered_rail[shape=ascending_south,powered=true]": "27:13" +"minecraft:detector_rail[shape=north_south,powered=false]": "28:0" +"minecraft:detector_rail[shape=east_west,powered=false]": "28:1" +"minecraft:detector_rail[shape=ascending_east,powered=false]": "28:2" +"minecraft:detector_rail[shape=ascending_west,powered=false]": "28:3" +"minecraft:detector_rail[shape=ascending_north,powered=false]": "28:4" +"minecraft:detector_rail[shape=ascending_south,powered=false]": "28:5" +"minecraft:detector_rail[shape=north_south,powered=true]": "28:8" +"minecraft:detector_rail[shape=east_west,powered=true]": "28:9" +"minecraft:detector_rail[shape=ascending_east,powered=true]": "28:10" +"minecraft:detector_rail[shape=ascending_west,powered=true]": "28:11" +"minecraft:detector_rail[shape=ascending_north,powered=true]": "28:12" +"minecraft:detector_rail[shape=ascending_south,powered=true]": "28:13" +"minecraft:sticky_piston[facing=down,extended=false]": "29:0" +"minecraft:sticky_piston[facing=up,extended=false]": "29:1" +"minecraft:sticky_piston[facing=north,extended=false]": "29:2" +"minecraft:sticky_piston[facing=south,extended=false]": "29:3" +"minecraft:sticky_piston[facing=west,extended=false]": "29:4" +"minecraft:sticky_piston[facing=east,extended=false]": "29:5" +"minecraft:sticky_piston[facing=down,extended=true]": "29:8" +"minecraft:sticky_piston[facing=up,extended=true]": "29:9" +"minecraft:sticky_piston[facing=north,extended=true]": "29:10" +"minecraft:sticky_piston[facing=south,extended=true]": "29:11" +"minecraft:sticky_piston[facing=west,extended=true]": "29:12" +"minecraft:sticky_piston[facing=east,extended=true]": "29:13" +"minecraft:cobweb": "30:0" +"minecraft:dead_bush": "31:0" +"minecraft:grass": "31:1" +"minecraft:fern": "31:2" +"minecraft:piston[facing=down,extended=false]": "33:0" +"minecraft:piston[facing=up,extended=false]": "33:1" +"minecraft:piston[facing=north,extended=false]": "33:2" +"minecraft:piston[facing=south,extended=false]": "33:3" +"minecraft:piston[facing=west,extended=false]": "33:4" +"minecraft:piston[facing=east,extended=false]": "33:5" +"minecraft:piston[facing=down,extended=true]": "33:8" +"minecraft:piston[facing=up,extended=true]": "33:9" +"minecraft:piston[facing=north,extended=true]": "33:10" +"minecraft:piston[facing=south,extended=true]": "33:11" +"minecraft:piston[facing=west,extended=true]": "33:12" +"minecraft:piston[facing=east,extended=true]": "33:13" +"minecraft:piston_head[short=false,facing=down,type=normal]": "34:0" +"minecraft:piston_head[short=false,facing=up,type=normal]": "34:1" +"minecraft:piston_head[short=false,facing=north,type=normal]": "34:2" +"minecraft:piston_head[short=false,facing=south,type=normal]": "34:3" +"minecraft:piston_head[short=false,facing=west,type=normal]": "34:4" +"minecraft:piston_head[short=false,facing=east,type=normal]": "34:5" +"minecraft:piston_head[short=false,facing=down,type=sticky]": "34:8" +"minecraft:piston_head[short=false,facing=up,type=sticky]": "34:9" +"minecraft:piston_head[short=false,facing=north,type=sticky]": "34:10" +"minecraft:piston_head[short=false,facing=south,type=sticky]": "34:11" +"minecraft:piston_head[short=false,facing=west,type=sticky]": "34:12" +"minecraft:piston_head[short=false,facing=east,type=sticky]": "34:13" +"minecraft:white_wool": "35:0" +"minecraft:orange_wool": "35:1" +"minecraft:magenta_wool": "35:2" +"minecraft:light_blue_wool": "35:3" +"minecraft:yellow_wool": "35:4" +"minecraft:lime_wool": "35:5" +"minecraft:pink_wool": "35:6" +"minecraft:gray_wool": "35:7" +"minecraft:light_gray_wool": "35:8" +"minecraft:cyan_wool": "35:9" +"minecraft:purple_wool": "35:10" +"minecraft:blue_wool": "35:11" +"minecraft:brown_wool": "35:12" +"minecraft:green_wool": "35:13" +"minecraft:red_wool": "35:14" +"minecraft:black_wool": "35:15" +"minecraft:moving_piston[facing=down,type=normal]": "36:0" +"minecraft:moving_piston[facing=up,type=normal]": "36:1" +"minecraft:moving_piston[facing=north,type=normal]": "36:2" +"minecraft:moving_piston[facing=south,type=normal]": "36:3" +"minecraft:moving_piston[facing=west,type=normal]": "36:4" +"minecraft:moving_piston[facing=east,type=normal]": "36:5" +"minecraft:moving_piston[facing=down,type=sticky]": "36:8" +"minecraft:moving_piston[facing=up,type=sticky]": "36:9" +"minecraft:moving_piston[facing=north,type=sticky]": "36:10" +"minecraft:moving_piston[facing=south,type=sticky]": "36:11" +"minecraft:moving_piston[facing=west,type=sticky]": "36:12" +"minecraft:moving_piston[facing=east,type=sticky]": "36:13" +"minecraft:dandelion": "37:0" +"minecraft:poppy": "38:0" +"minecraft:blue_orchid": "38:1" +"minecraft:allium": "38:2" +"minecraft:azure_bluet": "38:3" +"minecraft:red_tulip": "38:4" +"minecraft:orange_tulip": "38:5" +"minecraft:white_tulip": "38:6" +"minecraft:pink_tulip": "38:7" +"minecraft:oxeye_daisy": "38:8" +"minecraft:brown_mushroom": "39:0" +"minecraft:red_mushroom": "40:0" +"minecraft:gold_block": "41:0" +"minecraft:iron_block": "42:0" +"minecraft:stone_slab[type=double]": "43:0" +"minecraft:sandstone_slab[type=double]": "43:1" +"minecraft:petrified_oak_slab[type=double]": "43:2" +"minecraft:cobblestone_slab[type=double]": "43:3" +"minecraft:brick_slab[type=double]": "43:4" +"minecraft:stone_brick_slab[type=double]": "43:5" +"minecraft:nether_brick_slab[type=double]": "43:6" +"minecraft:quartz_slab[type=double]": "43:7" +"minecraft:smooth_stone": "43:8" +"minecraft:smooth_sandstone": "43:9" +"minecraft:smooth_quartz": "43:15" +"minecraft:stone_slab[type=bottom]": "44:0" +"minecraft:sandstone_slab[type=bottom]": "44:1" +"minecraft:petrified_oak_slab[type=bottom]": "44:2" +"minecraft:cobblestone_slab[type=bottom]": "44:3" +"minecraft:brick_slab[type=bottom]": "44:4" +"minecraft:stone_brick_slab[type=bottom]": "44:5" +"minecraft:nether_brick_slab[type=bottom]": "44:6" +"minecraft:quartz_slab[type=bottom]": "44:7" +"minecraft:stone_slab[type=top]": "44:8" +"minecraft:sandstone_slab[type=top]": "44:9" +"minecraft:petrified_oak_slab[type=top]": "44:10" +"minecraft:cobblestone_slab[type=top]": "44:11" +"minecraft:brick_slab[type=top]": "44:12" +"minecraft:stone_brick_slab[type=top]": "44:13" +"minecraft:nether_brick_slab[type=top]": "44:14" +"minecraft:quartz_slab[type=top]": "44:15" +"minecraft:bricks": "45:0" +"minecraft:tnt[unstable=false]": "46:0" +"minecraft:tnt[unstable=true]": "46:1" +"minecraft:bookshelf": "47:0" +"minecraft:mossy_cobblestone": "48:0" +"minecraft:obsidian": "49:0" +"minecraft:torch": "50:0" +"minecraft:wall_torch[facing=east]": "50:1" +"minecraft:wall_torch[facing=west]": "50:2" +"minecraft:wall_torch[facing=south]": "50:3" +"minecraft:wall_torch[facing=north]": "50:4" +"minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=0]": "51:0" +"minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=1]": "51:1" +"minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=2]": "51:2" +"minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=3]": "51:3" +"minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=4]": "51:4" +"minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=5]": "51:5" +"minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=6]": "51:6" +"minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=7]": "51:7" +"minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=8]": "51:8" +"minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=9]": "51:9" +"minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=10]": "51:10" +"minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=11]": "51:11" +"minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=12]": "51:12" +"minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=13]": "51:13" +"minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=14]": "51:14" +"minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=15]": "51:15" +"minecraft:spawner": "52:0" +"minecraft:oak_stairs[half=bottom,shape=outer_right,facing=east]": "53:0" +"minecraft:oak_stairs[half=bottom,shape=outer_right,facing=west]": "53:1" +"minecraft:oak_stairs[half=bottom,shape=outer_right,facing=south]": "53:2" +"minecraft:oak_stairs[half=bottom,shape=outer_right,facing=north]": "53:3" +"minecraft:oak_stairs[half=top,shape=outer_right,facing=east]": "53:4" +"minecraft:oak_stairs[half=top,shape=outer_right,facing=west]": "53:5" +"minecraft:oak_stairs[half=top,shape=outer_right,facing=south]": "53:6" +"minecraft:oak_stairs[half=top,shape=outer_right,facing=north]": "53:7" +"minecraft:chest": "54:0" +"minecraft:chest[facing=north,type=single]": "54:2" +"minecraft:chest[facing=south,type=single]": "54:3" +"minecraft:chest[facing=west,type=single]": "54:4" +"minecraft:chest[facing=east,type=single]": "54:5" +"minecraft:chest[facing=north]": "54:10" +"minecraft:chest[facing=south]": "54:11" +"minecraft:chest[facing=west]": "54:12" +"minecraft:chest[facing=east]": "54:13" +"minecraft:redstone_wire[east=none,south=none,north=none,west=none,power=0]": "55:0" +"minecraft:redstone_wire[east=none,south=none,north=none,west=none,power=1]": "55:1" +"minecraft:redstone_wire[east=none,south=none,north=none,west=none,power=2]": "55:2" +"minecraft:redstone_wire[east=none,south=none,north=none,west=none,power=3]": "55:3" +"minecraft:redstone_wire[east=none,south=none,north=none,west=none,power=4]": "55:4" +"minecraft:redstone_wire[east=none,south=none,north=none,west=none,power=5]": "55:5" +"minecraft:redstone_wire[east=none,south=none,north=none,west=none,power=6]": "55:6" +"minecraft:redstone_wire[east=none,south=none,north=none,west=none,power=7]": "55:7" +"minecraft:redstone_wire[east=none,south=none,north=none,west=none,power=8]": "55:8" +"minecraft:redstone_wire[east=none,south=none,north=none,west=none,power=9]": "55:9" +"minecraft:redstone_wire[east=none,south=none,north=none,west=none,power=10]": "55:10" +"minecraft:redstone_wire[east=none,south=none,north=none,west=none,power=11]": "55:11" +"minecraft:redstone_wire[east=none,south=none,north=none,west=none,power=12]": "55:12" +"minecraft:redstone_wire[east=none,south=none,north=none,west=none,power=13]": "55:13" +"minecraft:redstone_wire[east=none,south=none,north=none,west=none,power=14]": "55:14" +"minecraft:redstone_wire[east=none,south=none,north=none,west=none,power=15]": "55:15" +"minecraft:diamond_ore": "56:0" +"minecraft:diamond_block": "57:0" +"minecraft:crafting_table": "58:0" +"minecraft:wheat[age=0]": "59:0" +"minecraft:wheat[age=1]": "59:1" +"minecraft:wheat[age=2]": "59:2" +"minecraft:wheat[age=3]": "59:3" +"minecraft:wheat[age=4]": "59:4" +"minecraft:wheat[age=5]": "59:5" +"minecraft:wheat[age=6]": "59:6" +"minecraft:wheat[age=7]": "59:7" +"minecraft:farmland[moisture=0]": "60:0" +"minecraft:farmland[moisture=1]": "60:1" +"minecraft:farmland[moisture=2]": "60:2" +"minecraft:farmland[moisture=3]": "60:3" +"minecraft:farmland[moisture=4]": "60:4" +"minecraft:farmland[moisture=5]": "60:5" +"minecraft:farmland[moisture=6]": "60:6" +"minecraft:farmland[moisture=7]": "60:7" +"minecraft:furnace": "61:0" +"minecraft:furnace[facing=north,lit=false]": "61:2" +"minecraft:furnace[facing=south,lit=false]": "61:3" +"minecraft:furnace[facing=west,lit=false]": "61:4" +"minecraft:furnace[facing=east,lit=false]": "61:5" +"minecraft:furnace[lit=true]": "62:0" +"minecraft:furnace[facing=north,lit=true]": "62:2" +"minecraft:furnace[facing=south,lit=true]": "62:3" +"minecraft:furnace[facing=west,lit=true]": "62:4" +"minecraft:furnace[facing=east,lit=true]": "62:5" +"minecraft:oak_sign[rotation=0]": "63:0" +"minecraft:oak_sign[rotation=1]": "63:1" +"minecraft:oak_sign[rotation=2]": "63:2" +"minecraft:oak_sign[rotation=3]": "63:3" +"minecraft:oak_sign[rotation=4]": "63:4" +"minecraft:oak_sign[rotation=5]": "63:5" +"minecraft:oak_sign[rotation=6]": "63:6" +"minecraft:oak_sign[rotation=7]": "63:7" +"minecraft:oak_sign[rotation=8]": "63:8" +"minecraft:oak_sign[rotation=9]": "63:9" +"minecraft:oak_sign[rotation=10]": "63:10" +"minecraft:oak_sign[rotation=11]": "63:11" +"minecraft:oak_sign[rotation=12]": "63:12" +"minecraft:oak_sign[rotation=13]": "63:13" +"minecraft:oak_sign[rotation=14]": "63:14" +"minecraft:oak_sign[rotation=15]": "63:15" +"minecraft:oak_door[hinge=right,half=lower,powered=false,facing=east,open=false]": "64:0" +"minecraft:oak_door[hinge=right,half=lower,powered=false,facing=south,open=false]": "64:1" +"minecraft:oak_door[hinge=right,half=lower,powered=false,facing=west,open=false]": "64:2" +"minecraft:oak_door[hinge=right,half=lower,powered=false,facing=north,open=false]": "64:3" +"minecraft:oak_door[hinge=right,half=lower,powered=false,facing=east,open=true]": "64:4" +"minecraft:oak_door[hinge=right,half=lower,powered=false,facing=south,open=true]": "64:5" +"minecraft:oak_door[hinge=right,half=lower,powered=false,facing=west,open=true]": "64:6" +"minecraft:oak_door[hinge=right,half=lower,powered=false,facing=north,open=true]": "64:7" +"minecraft:oak_door[hinge=left,half=upper,powered=false,facing=east,open=false]": "64:8" +"minecraft:oak_door[hinge=right,half=upper,powered=false,facing=east,open=false]": "64:9" +"minecraft:oak_door[hinge=left,half=upper,powered=true,facing=east,open=false]": "64:10" +"minecraft:oak_door[hinge=right,half=upper,powered=true,facing=east,open=false]": "64:11" +"minecraft:ladder": "65:0" +"minecraft:ladder[facing=north]": "65:2" +"minecraft:ladder[facing=south]": "65:3" +"minecraft:ladder[facing=west]": "65:4" +"minecraft:ladder[facing=east]": "65:5" +"minecraft:rail[shape=north_south]": "66:0" +"minecraft:rail[shape=east_west]": "66:1" +"minecraft:rail[shape=ascending_east]": "66:2" +"minecraft:rail[shape=ascending_west]": "66:3" +"minecraft:rail[shape=ascending_north]": "66:4" +"minecraft:rail[shape=ascending_south]": "66:5" +"minecraft:rail[shape=south_east]": "66:6" +"minecraft:rail[shape=south_west]": "66:7" +"minecraft:rail[shape=north_west]": "66:8" +"minecraft:rail[shape=north_east]": "66:9" +"minecraft:cobblestone_stairs[half=bottom,shape=straight,facing=east]": "67:0" +"minecraft:cobblestone_stairs[half=bottom,shape=straight,facing=west]": "67:1" +"minecraft:cobblestone_stairs[half=bottom,shape=straight,facing=south]": "67:2" +"minecraft:cobblestone_stairs[half=bottom,shape=straight,facing=north]": "67:3" +"minecraft:cobblestone_stairs[half=top,shape=straight,facing=east]": "67:4" +"minecraft:cobblestone_stairs[half=top,shape=straight,facing=west]": "67:5" +"minecraft:cobblestone_stairs[half=top,shape=straight,facing=south]": "67:6" +"minecraft:cobblestone_stairs[half=top,shape=straight,facing=north]": "67:7" +"minecraft:oak_wall_sign": "68:0" +"minecraft:oak_wall_sign[facing=north]": "68:2" +"minecraft:oak_wall_sign[facing=south]": "68:3" +"minecraft:oak_wall_sign[facing=west]": "68:4" +"minecraft:oak_wall_sign[facing=east]": "68:5" +"minecraft:lever[powered=false,facing=north,face=ceiling]": "69:0" +"minecraft:lever[powered=false,facing=east,face=wall]": "69:1" +"minecraft:lever[powered=false,facing=west,face=wall]": "69:2" +"minecraft:lever[powered=false,facing=south,face=wall]": "69:3" +"minecraft:lever[powered=false,facing=north,face=wall]": "69:4" +"minecraft:lever[powered=false,facing=east,face=floor]": "69:5" +"minecraft:lever[powered=false,facing=north,face=floor]": "69:6" +"minecraft:lever[powered=false,facing=east,face=ceiling]": "69:7" +"minecraft:lever[powered=true,facing=north,face=ceiling]": "69:8" +"minecraft:lever[powered=true,facing=east,face=wall]": "69:9" +"minecraft:lever[powered=true,facing=west,face=wall]": "69:10" +"minecraft:lever[powered=true,facing=south,face=wall]": "69:11" +"minecraft:lever[powered=true,facing=north,face=wall]": "69:12" +"minecraft:lever[powered=true,facing=east,face=floor]": "69:13" +"minecraft:lever[powered=true,facing=north,face=floor]": "69:14" +"minecraft:lever[powered=true,facing=east,face=ceiling]": "69:15" +"minecraft:stone_pressure_plate[powered=false]": "70:0" +"minecraft:stone_pressure_plate[powered=true]": "70:1" +"minecraft:iron_door[hinge=right,half=lower,powered=false,facing=east,open=false]": "71:0" +"minecraft:iron_door[hinge=right,half=lower,powered=false,facing=south,open=false]": "71:1" +"minecraft:iron_door[hinge=right,half=lower,powered=false,facing=west,open=false]": "71:2" +"minecraft:iron_door[hinge=right,half=lower,powered=false,facing=north,open=false]": "71:3" +"minecraft:iron_door[hinge=right,half=lower,powered=false,facing=east,open=true]": "71:4" +"minecraft:iron_door[hinge=right,half=lower,powered=false,facing=south,open=true]": "71:5" +"minecraft:iron_door[hinge=right,half=lower,powered=false,facing=west,open=true]": "71:6" +"minecraft:iron_door[hinge=right,half=lower,powered=false,facing=north,open=true]": "71:7" +"minecraft:iron_door[hinge=left,half=upper,powered=false,facing=east,open=false]": "71:8" +"minecraft:iron_door[hinge=right,half=upper,powered=false,facing=east,open=false]": "71:9" +"minecraft:iron_door[hinge=left,half=upper,powered=true,facing=east,open=false]": "71:10" +"minecraft:iron_door[hinge=right,half=upper,powered=true,facing=east,open=false]": "71:11" +"minecraft:oak_pressure_plate[powered=false]": "72:0" +"minecraft:oak_pressure_plate[powered=true]": "72:1" +"minecraft:redstone_ore[lit=false]": "73:0" +"minecraft:redstone_ore[lit=true]": "74:0" +"minecraft:redstone_torch[lit=false]": "75:0" +"minecraft:redstone_wall_torch[facing=east,lit=false]": "75:1" +"minecraft:redstone_wall_torch[facing=west,lit=false]": "75:2" +"minecraft:redstone_wall_torch[facing=south,lit=false]": "75:3" +"minecraft:redstone_wall_torch[facing=north,lit=false]": "75:4" +"minecraft:redstone_wall_torch[lit=false]": "75:13" +"minecraft:redstone_torch[lit=true]": "76:0" +"minecraft:redstone_wall_torch[facing=east,lit=true]": "76:1" +"minecraft:redstone_wall_torch[facing=west,lit=true]": "76:2" +"minecraft:redstone_wall_torch[facing=south,lit=true]": "76:3" +"minecraft:redstone_wall_torch[facing=north,lit=true]": "76:4" +"minecraft:redstone_wall_torch[lit=true]": "76:5" +"minecraft:stone_button[powered=false,facing=east,face=ceiling]": "77:0" +"minecraft:stone_button[powered=false,facing=east,face=wall]": "77:1" +"minecraft:stone_button[powered=false,facing=west,face=wall]": "77:2" +"minecraft:stone_button[powered=false,facing=south,face=wall]": "77:3" +"minecraft:stone_button[powered=false,facing=north,face=wall]": "77:4" +"minecraft:stone_button[powered=false,facing=east,face=floor]": "77:5" +"minecraft:stone_button[powered=true,facing=south,face=ceiling]": "77:8" +"minecraft:stone_button[powered=true,facing=east,face=wall]": "77:9" +"minecraft:stone_button[powered=true,facing=west,face=wall]": "77:10" +"minecraft:stone_button[powered=true,facing=south,face=wall]": "77:11" +"minecraft:stone_button[powered=true,facing=north,face=wall]": "77:12" +"minecraft:stone_button[powered=true,facing=south,face=floor]": "77:13" +"minecraft:snow[layers=1]": "78:0" +"minecraft:snow[layers=2]": "78:1" +"minecraft:snow[layers=3]": "78:2" +"minecraft:snow[layers=4]": "78:3" +"minecraft:snow[layers=5]": "78:4" +"minecraft:snow[layers=6]": "78:5" +"minecraft:snow[layers=7]": "78:6" +"minecraft:snow[layers=8]": "78:7" +"minecraft:ice": "79:0" +"minecraft:snow_block": "80:0" +"minecraft:cactus[age=0]": "81:0" +"minecraft:cactus[age=1]": "81:1" +"minecraft:cactus[age=2]": "81:2" +"minecraft:cactus[age=3]": "81:3" +"minecraft:cactus[age=4]": "81:4" +"minecraft:cactus[age=5]": "81:5" +"minecraft:cactus[age=6]": "81:6" +"minecraft:cactus[age=7]": "81:7" +"minecraft:cactus[age=8]": "81:8" +"minecraft:cactus[age=9]": "81:9" +"minecraft:cactus[age=10]": "81:10" +"minecraft:cactus[age=11]": "81:11" +"minecraft:cactus[age=12]": "81:12" +"minecraft:cactus[age=13]": "81:13" +"minecraft:cactus[age=14]": "81:14" +"minecraft:cactus[age=15]": "81:15" +"minecraft:clay": "82:0" +"minecraft:sugar_cane[age=0]": "83:0" +"minecraft:sugar_cane[age=1]": "83:1" +"minecraft:sugar_cane[age=2]": "83:2" +"minecraft:sugar_cane[age=3]": "83:3" +"minecraft:sugar_cane[age=4]": "83:4" +"minecraft:sugar_cane[age=5]": "83:5" +"minecraft:sugar_cane[age=6]": "83:6" +"minecraft:sugar_cane[age=7]": "83:7" +"minecraft:sugar_cane[age=8]": "83:8" +"minecraft:sugar_cane[age=9]": "83:9" +"minecraft:sugar_cane[age=10]": "83:10" +"minecraft:sugar_cane[age=11]": "83:11" +"minecraft:sugar_cane[age=12]": "83:12" +"minecraft:sugar_cane[age=13]": "83:13" +"minecraft:sugar_cane[age=14]": "83:14" +"minecraft:sugar_cane[age=15]": "83:15" +"minecraft:jukebox[has_record=false]": "84:0" +"minecraft:jukebox[has_record=true]": "84:1" +"minecraft:oak_fence[east=false,south=false,north=false,west=false]": "85:0" +"minecraft:carved_pumpkin[facing=south]": "86:0" +"minecraft:carved_pumpkin[facing=west]": "86:1" +"minecraft:carved_pumpkin[facing=north]": "86:2" +"minecraft:carved_pumpkin[facing=east]": "86:3" +"minecraft:netherrack": "87:0" +"minecraft:soul_sand": "88:0" +"minecraft:glowstone": "89:0" +"minecraft:nether_portal": "90:0" +"minecraft:nether_portal[axis=x]": "90:1" +"minecraft:nether_portal[axis=z]": "90:2" +"minecraft:jack_o_lantern[facing=south]": "91:0" +"minecraft:jack_o_lantern[facing=west]": "91:1" +"minecraft:jack_o_lantern[facing=north]": "91:2" +"minecraft:jack_o_lantern[facing=east]": "91:3" +"minecraft:cake[bites=0]": "92:0" +"minecraft:cake[bites=1]": "92:1" +"minecraft:cake[bites=2]": "92:2" +"minecraft:cake[bites=3]": "92:3" +"minecraft:cake[bites=4]": "92:4" +"minecraft:cake[bites=5]": "92:5" +"minecraft:cake[bites=6]": "92:6" +"minecraft:repeater[delay=1,facing=south,locked=false,powered=false]": "93:0" +"minecraft:repeater[delay=1,facing=west,locked=false,powered=false]": "93:1" +"minecraft:repeater[delay=1,facing=north,locked=false,powered=false]": "93:2" +"minecraft:repeater[delay=1,facing=east,locked=false,powered=false]": "93:3" +"minecraft:repeater[delay=2,facing=south,locked=false,powered=false]": "93:4" +"minecraft:repeater[delay=2,facing=west,locked=false,powered=false]": "93:5" +"minecraft:repeater[delay=2,facing=north,locked=false,powered=false]": "93:6" +"minecraft:repeater[delay=2,facing=east,locked=false,powered=false]": "93:7" +"minecraft:repeater[delay=3,facing=south,locked=false,powered=false]": "93:8" +"minecraft:repeater[delay=3,facing=west,locked=false,powered=false]": "93:9" +"minecraft:repeater[delay=3,facing=north,locked=false,powered=false]": "93:10" +"minecraft:repeater[delay=3,facing=east,locked=false,powered=false]": "93:11" +"minecraft:repeater[delay=4,facing=south,locked=false,powered=false]": "93:12" +"minecraft:repeater[delay=4,facing=west,locked=false,powered=false]": "93:13" +"minecraft:repeater[delay=4,facing=north,locked=false,powered=false]": "93:14" +"minecraft:repeater[delay=4,facing=east,locked=false,powered=false]": "93:15" +"minecraft:repeater[delay=1,facing=south,locked=false,powered=true]": "94:0" +"minecraft:repeater[delay=1,facing=west,locked=false,powered=true]": "94:1" +"minecraft:repeater[delay=1,facing=north,locked=false,powered=true]": "94:2" +"minecraft:repeater[delay=1,facing=east,locked=false,powered=true]": "94:3" +"minecraft:repeater[delay=2,facing=south,locked=false,powered=true]": "94:4" +"minecraft:repeater[delay=2,facing=west,locked=false,powered=true]": "94:5" +"minecraft:repeater[delay=2,facing=north,locked=false,powered=true]": "94:6" +"minecraft:repeater[delay=2,facing=east,locked=false,powered=true]": "94:7" +"minecraft:repeater[delay=3,facing=south,locked=false,powered=true]": "94:8" +"minecraft:repeater[delay=3,facing=west,locked=false,powered=true]": "94:9" +"minecraft:repeater[delay=3,facing=north,locked=false,powered=true]": "94:10" +"minecraft:repeater[delay=3,facing=east,locked=false,powered=true]": "94:11" +"minecraft:repeater[delay=4,facing=south,locked=false,powered=true]": "94:12" +"minecraft:repeater[delay=4,facing=west,locked=false,powered=true]": "94:13" +"minecraft:repeater[delay=4,facing=north,locked=false,powered=true]": "94:14" +"minecraft:repeater[delay=4,facing=east,locked=false,powered=true]": "94:15" +"minecraft:white_stained_glass": "95:0" +"minecraft:orange_stained_glass": "95:1" +"minecraft:magenta_stained_glass": "95:2" +"minecraft:light_blue_stained_glass": "95:3" +"minecraft:yellow_stained_glass": "95:4" +"minecraft:lime_stained_glass": "95:5" +"minecraft:pink_stained_glass": "95:6" +"minecraft:gray_stained_glass": "95:7" +"minecraft:light_gray_stained_glass": "95:8" +"minecraft:cyan_stained_glass": "95:9" +"minecraft:purple_stained_glass": "95:10" +"minecraft:blue_stained_glass": "95:11" +"minecraft:brown_stained_glass": "95:12" +"minecraft:green_stained_glass": "95:13" +"minecraft:red_stained_glass": "95:14" +"minecraft:black_stained_glass": "95:15" +"minecraft:oak_trapdoor[half=bottom,facing=north,open=false,powered=false]": "96:0" +"minecraft:oak_trapdoor[half=bottom,facing=south,open=false,powered=false]": "96:1" +"minecraft:oak_trapdoor[half=bottom,facing=west,open=false,powered=false]": "96:2" +"minecraft:oak_trapdoor[half=bottom,facing=east,open=false,powered=false]": "96:3" +"minecraft:oak_trapdoor[half=bottom,facing=north,open=true,powered=true]": "96:4" +"minecraft:oak_trapdoor[half=bottom,facing=south,open=true,powered=true]": "96:5" +"minecraft:oak_trapdoor[half=bottom,facing=west,open=true,powered=true]": "96:6" +"minecraft:oak_trapdoor[half=bottom,facing=east,open=true,powered=true]": "96:7" +"minecraft:oak_trapdoor[half=top,facing=north,open=false,powered=false]": "96:8" +"minecraft:oak_trapdoor[half=top,facing=south,open=false,powered=false]": "96:9" +"minecraft:oak_trapdoor[half=top,facing=west,open=false,powered=false]": "96:10" +"minecraft:oak_trapdoor[half=top,facing=east,open=false,powered=false]": "96:11" +"minecraft:oak_trapdoor[half=top,facing=north,open=true,powered=true]": "96:12" +"minecraft:oak_trapdoor[half=top,facing=south,open=true,powered=true]": "96:13" +"minecraft:oak_trapdoor[half=top,facing=west,open=true,powered=true]": "96:14" +"minecraft:oak_trapdoor[half=top,facing=east,open=true,powered=true]": "96:15" +"minecraft:infested_stone": "97:0" +"minecraft:infested_cobblestone": "97:1" +"minecraft:infested_stone_bricks": "97:2" +"minecraft:infested_mossy_stone_bricks": "97:3" +"minecraft:infested_cracked_stone_bricks": "97:4" +"minecraft:infested_chiseled_stone_bricks": "97:5" +"minecraft:stone_bricks": "98:0" +"minecraft:mossy_stone_bricks": "98:1" +"minecraft:cracked_stone_bricks": "98:2" +"minecraft:chiseled_stone_bricks": "98:3" +"minecraft:brown_mushroom_block[north=false,east=false,south=false,west=false,up=false,down=false]": "99:0" +"minecraft:brown_mushroom_block[north=true,east=false,south=false,west=true,up=true,down=false]": "99:1" +"minecraft:brown_mushroom_block[north=true,east=false,south=false,west=false,up=true,down=false]": "99:2" +"minecraft:brown_mushroom_block[north=true,east=true,south=false,west=false,up=true,down=false]": "99:3" +"minecraft:brown_mushroom_block[north=false,east=false,south=false,west=true,up=true,down=false]": "99:4" +"minecraft:brown_mushroom_block[north=false,east=false,south=false,west=false,up=true,down=false]": "99:5" +"minecraft:brown_mushroom_block[north=false,east=true,south=false,west=false,up=true,down=false]": "99:6" +"minecraft:brown_mushroom_block[north=false,east=false,south=true,west=true,up=true,down=false]": "99:7" +"minecraft:brown_mushroom_block[north=false,east=false,south=true,west=false,up=true,down=false]": "99:8" +"minecraft:brown_mushroom_block[north=false,east=true,south=true,west=false,up=true,down=false]": "99:9" +"minecraft:mushroom_stem[north=true,east=true,south=true,west=true,up=false,down=false]": "99:10" +"minecraft:brown_mushroom_block[north=true,east=true,south=true,west=true,up=true,down=true]": "99:14" +"minecraft:mushroom_stem[north=true,east=true,south=true,west=true,up=true,down=true]": "99:15" +"minecraft:red_mushroom_block[north=false,east=false,south=false,west=false,up=false,down=false]": "100:0" +"minecraft:red_mushroom_block[north=true,east=false,south=false,west=true,up=true,down=false]": "100:1" +"minecraft:red_mushroom_block[north=true,east=false,south=false,west=false,up=true,down=false]": "100:2" +"minecraft:red_mushroom_block[north=true,east=true,south=false,west=false,up=true,down=false]": "100:3" +"minecraft:red_mushroom_block[north=false,east=false,south=false,west=true,up=true,down=false]": "100:4" +"minecraft:red_mushroom_block[north=false,east=false,south=false,west=false,up=true,down=false]": "100:5" +"minecraft:red_mushroom_block[north=false,east=true,south=false,west=false,up=true,down=false]": "100:6" +"minecraft:red_mushroom_block[north=false,east=false,south=true,west=true,up=true,down=false]": "100:7" +"minecraft:red_mushroom_block[north=false,east=false,south=true,west=false,up=true,down=false]": "100:8" +"minecraft:red_mushroom_block[north=false,east=true,south=true,west=false,up=true,down=false]": "100:9" +"minecraft:red_mushroom_block[north=true,east=true,south=true,west=true,up=true,down=true]": "100:14" +"minecraft:iron_bars[east=false,south=false,north=false,west=false]": "101:0" +"minecraft:glass_pane[east=false,south=false,north=false,west=false]": "102:0" +"minecraft:melon": "103:0" +"minecraft:pumpkin_stem[age=0]": "104:0" +"minecraft:pumpkin_stem[age=1]": "104:1" +"minecraft:pumpkin_stem[age=2]": "104:2" +"minecraft:pumpkin_stem[age=3]": "104:3" +"minecraft:pumpkin_stem[age=4]": "104:4" +"minecraft:pumpkin_stem[age=5]": "104:5" +"minecraft:pumpkin_stem[age=6]": "104:6" +"minecraft:pumpkin_stem[age=7]": "104:7" +"minecraft:melon_stem[age=0]": "105:0" +"minecraft:melon_stem[age=1]": "105:1" +"minecraft:melon_stem[age=2]": "105:2" +"minecraft:melon_stem[age=3]": "105:3" +"minecraft:melon_stem[age=4]": "105:4" +"minecraft:melon_stem[age=5]": "105:5" +"minecraft:melon_stem[age=6]": "105:6" +"minecraft:melon_stem[age=7]": "105:7" +"minecraft:vine[east=false,south=false,north=false,west=false,up=false]": "106:0" +"minecraft:vine[east=false,south=true,north=false,west=false,up=false]": "106:1" +"minecraft:vine[east=false,south=false,north=false,west=true,up=false]": "106:2" +"minecraft:vine[east=false,south=true,north=false,west=true,up=false]": "106:3" +"minecraft:vine[east=false,south=false,north=true,west=false,up=false]": "106:4" +"minecraft:vine[east=false,south=true,north=true,west=false,up=false]": "106:5" +"minecraft:vine[east=false,south=false,north=true,west=true,up=false]": "106:6" +"minecraft:vine[east=false,south=true,north=true,west=true,up=false]": "106:7" +"minecraft:vine[east=true,south=false,north=false,west=false,up=false]": "106:8" +"minecraft:vine[east=true,south=true,north=false,west=false,up=false]": "106:9" +"minecraft:vine[east=true,south=false,north=false,west=true,up=false]": "106:10" +"minecraft:vine[east=true,south=true,north=false,west=true,up=false]": "106:11" +"minecraft:vine[east=true,south=false,north=true,west=false,up=false]": "106:12" +"minecraft:vine[east=true,south=true,north=true,west=false,up=false]": "106:13" +"minecraft:vine[east=true,south=false,north=true,west=true,up=false]": "106:14" +"minecraft:vine[east=true,south=true,north=true,west=true,up=false]": "106:15" +"minecraft:oak_fence_gate[in_wall=false,powered=false,facing=south,open=false]": "107:0" +"minecraft:oak_fence_gate[in_wall=false,powered=false,facing=west,open=false]": "107:1" +"minecraft:oak_fence_gate[in_wall=false,powered=false,facing=north,open=false]": "107:2" +"minecraft:oak_fence_gate[in_wall=false,powered=false,facing=east,open=false]": "107:3" +"minecraft:oak_fence_gate[in_wall=false,powered=false,facing=south,open=true]": "107:4" +"minecraft:oak_fence_gate[in_wall=false,powered=false,facing=west,open=true]": "107:5" +"minecraft:oak_fence_gate[in_wall=false,powered=false,facing=north,open=true]": "107:6" +"minecraft:oak_fence_gate[in_wall=false,powered=false,facing=east,open=true]": "107:7" +"minecraft:oak_fence_gate[in_wall=false,powered=true,facing=south,open=false]": "107:8" +"minecraft:oak_fence_gate[in_wall=false,powered=true,facing=west,open=false]": "107:9" +"minecraft:oak_fence_gate[in_wall=false,powered=true,facing=north,open=false]": "107:10" +"minecraft:oak_fence_gate[in_wall=false,powered=true,facing=east,open=false]": "107:11" +"minecraft:oak_fence_gate[in_wall=false,powered=true,facing=south,open=true]": "107:12" +"minecraft:oak_fence_gate[in_wall=false,powered=true,facing=west,open=true]": "107:13" +"minecraft:oak_fence_gate[in_wall=false,powered=true,facing=north,open=true]": "107:14" +"minecraft:oak_fence_gate[in_wall=false,powered=true,facing=east,open=true]": "107:15" +"minecraft:brick_stairs[half=bottom,shape=straight,facing=east]": "108:0" +"minecraft:brick_stairs[half=bottom,shape=straight,facing=west]": "108:1" +"minecraft:brick_stairs[half=bottom,shape=straight,facing=south]": "108:2" +"minecraft:brick_stairs[half=bottom,shape=straight,facing=north]": "108:3" +"minecraft:brick_stairs[half=top,shape=straight,facing=east]": "108:4" +"minecraft:brick_stairs[half=top,shape=straight,facing=west]": "108:5" +"minecraft:brick_stairs[half=top,shape=straight,facing=south]": "108:6" +"minecraft:brick_stairs[half=top,shape=straight,facing=north]": "108:7" +"minecraft:stone_brick_stairs[half=bottom,shape=straight,facing=east]": "109:0" +"minecraft:stone_brick_stairs[half=bottom,shape=straight,facing=west]": "109:1" +"minecraft:stone_brick_stairs[half=bottom,shape=straight,facing=south]": "109:2" +"minecraft:stone_brick_stairs[half=bottom,shape=straight,facing=north]": "109:3" +"minecraft:stone_brick_stairs[half=top,shape=straight,facing=east]": "109:4" +"minecraft:stone_brick_stairs[half=top,shape=straight,facing=west]": "109:5" +"minecraft:stone_brick_stairs[half=top,shape=straight,facing=south]": "109:6" +"minecraft:stone_brick_stairs[half=top,shape=straight,facing=north]": "109:7" +"minecraft:mycelium[snowy=false]": "110:0" +"minecraft:lily_pad": "111:0" +"minecraft:nether_bricks": "112:0" +"minecraft:nether_brick_fence[east=false,south=false,north=false,west=false]": "113:0" +"minecraft:nether_brick_stairs[half=bottom,shape=straight,facing=east]": "114:0" +"minecraft:nether_brick_stairs[half=bottom,shape=straight,facing=west]": "114:1" +"minecraft:nether_brick_stairs[half=bottom,shape=straight,facing=south]": "114:2" +"minecraft:nether_brick_stairs[half=bottom,shape=straight,facing=north]": "114:3" +"minecraft:nether_brick_stairs[half=top,shape=straight,facing=east]": "114:4" +"minecraft:nether_brick_stairs[half=top,shape=straight,facing=west]": "114:5" +"minecraft:nether_brick_stairs[half=top,shape=straight,facing=south]": "114:6" +"minecraft:nether_brick_stairs[half=top,shape=straight,facing=north]": "114:7" +"minecraft:nether_wart[age=0]": "115:0" +"minecraft:nether_wart[age=1]": "115:1" +"minecraft:nether_wart[age=2]": "115:2" +"minecraft:nether_wart[age=3]": "115:3" +"minecraft:enchanting_table": "116:0" +"minecraft:brewing_stand[has_bottle_0=false,has_bottle_1=false,has_bottle_2=false]": "117:0" +"minecraft:brewing_stand[has_bottle_0=true,has_bottle_1=false,has_bottle_2=false]": "117:1" +"minecraft:brewing_stand[has_bottle_0=false,has_bottle_1=true,has_bottle_2=false]": "117:2" +"minecraft:brewing_stand[has_bottle_0=true,has_bottle_1=true,has_bottle_2=false]": "117:3" +"minecraft:brewing_stand[has_bottle_0=false,has_bottle_1=false,has_bottle_2=true]": "117:4" +"minecraft:brewing_stand[has_bottle_0=true,has_bottle_1=false,has_bottle_2=true]": "117:5" +"minecraft:brewing_stand[has_bottle_0=false,has_bottle_1=true,has_bottle_2=true]": "117:6" +"minecraft:brewing_stand[has_bottle_0=true,has_bottle_1=true,has_bottle_2=true]": "117:7" +"minecraft:cauldron[level=0]": "118:0" +"minecraft:cauldron[level=1]": "118:1" +"minecraft:cauldron[level=2]": "118:2" +"minecraft:cauldron[level=3]": "118:3" +"minecraft:end_portal": "119:0" +"minecraft:end_portal_frame[eye=false,facing=south]": "120:0" +"minecraft:end_portal_frame[eye=false,facing=west]": "120:1" +"minecraft:end_portal_frame[eye=false,facing=north]": "120:2" +"minecraft:end_portal_frame[eye=false,facing=east]": "120:3" +"minecraft:end_portal_frame[eye=true,facing=south]": "120:4" +"minecraft:end_portal_frame[eye=true,facing=west]": "120:5" +"minecraft:end_portal_frame[eye=true,facing=north]": "120:6" +"minecraft:end_portal_frame[eye=true,facing=east]": "120:7" +"minecraft:end_stone": "121:0" +"minecraft:dragon_egg": "122:0" +"minecraft:redstone_lamp[lit=false]": "123:0" +"minecraft:redstone_lamp[lit=true]": "124:0" +"minecraft:oak_slab[type=double]": "125:0" +"minecraft:spruce_slab[type=double]": "125:1" +"minecraft:birch_slab[type=double]": "125:2" +"minecraft:jungle_slab[type=double]": "125:3" +"minecraft:acacia_slab[type=double]": "125:4" +"minecraft:dark_oak_slab[type=double]": "125:5" +"minecraft:oak_slab[type=bottom]": "126:0" +"minecraft:spruce_slab[type=bottom]": "126:1" +"minecraft:birch_slab[type=bottom]": "126:2" +"minecraft:jungle_slab[type=bottom]": "126:3" +"minecraft:acacia_slab[type=bottom]": "126:4" +"minecraft:dark_oak_slab[type=bottom]": "126:5" +"minecraft:oak_slab[type=top]": "126:8" +"minecraft:spruce_slab[type=top]": "126:9" +"minecraft:birch_slab[type=top]": "126:10" +"minecraft:jungle_slab[type=top]": "126:11" +"minecraft:acacia_slab[type=top]": "126:12" +"minecraft:dark_oak_slab[type=top]": "126:13" +"minecraft:cocoa[facing=south,age=0]": "127:0" +"minecraft:cocoa[facing=west,age=0]": "127:1" +"minecraft:cocoa[facing=north,age=0]": "127:2" +"minecraft:cocoa[facing=east,age=0]": "127:3" +"minecraft:cocoa[facing=south,age=1]": "127:4" +"minecraft:cocoa[facing=west,age=1]": "127:5" +"minecraft:cocoa[facing=north,age=1]": "127:6" +"minecraft:cocoa[facing=east,age=1]": "127:7" +"minecraft:cocoa[facing=south,age=2]": "127:8" +"minecraft:cocoa[facing=west,age=2]": "127:9" +"minecraft:cocoa[facing=north,age=2]": "127:10" +"minecraft:cocoa[facing=east,age=2]": "127:11" +"minecraft:sandstone_stairs[half=bottom,shape=straight,facing=east]": "128:0" +"minecraft:sandstone_stairs[half=bottom,shape=straight,facing=west]": "128:1" +"minecraft:sandstone_stairs[half=bottom,shape=straight,facing=south]": "128:2" +"minecraft:sandstone_stairs[half=bottom,shape=straight,facing=north]": "128:3" +"minecraft:sandstone_stairs[half=top,shape=straight,facing=east]": "128:4" +"minecraft:sandstone_stairs[half=top,shape=straight,facing=west]": "128:5" +"minecraft:sandstone_stairs[half=top,shape=straight,facing=south]": "128:6" +"minecraft:sandstone_stairs[half=top,shape=straight,facing=north]": "128:7" +"minecraft:emerald_ore": "129:0" +"minecraft:ender_chest": "130:0" +"minecraft:ender_chest[facing=north]": "130:2" +"minecraft:ender_chest[facing=south]": "130:3" +"minecraft:ender_chest[facing=west]": "130:4" +"minecraft:ender_chest[facing=east]": "130:5" +"minecraft:tripwire_hook[powered=false,attached=false,facing=south]": "131:0" +"minecraft:tripwire_hook[powered=false,attached=false,facing=west]": "131:1" +"minecraft:tripwire_hook[powered=false,attached=false,facing=north]": "131:2" +"minecraft:tripwire_hook[powered=false,attached=false,facing=east]": "131:3" +"minecraft:tripwire_hook[powered=false,attached=true,facing=south]": "131:4" +"minecraft:tripwire_hook[powered=false,attached=true,facing=west]": "131:5" +"minecraft:tripwire_hook[powered=false,attached=true,facing=north]": "131:6" +"minecraft:tripwire_hook[powered=false,attached=true,facing=east]": "131:7" +"minecraft:tripwire_hook[powered=true,attached=false,facing=south]": "131:8" +"minecraft:tripwire_hook[powered=true,attached=false,facing=west]": "131:9" +"minecraft:tripwire_hook[powered=true,attached=false,facing=north]": "131:10" +"minecraft:tripwire_hook[powered=true,attached=false,facing=east]": "131:11" +"minecraft:tripwire_hook[powered=true,attached=true,facing=south]": "131:12" +"minecraft:tripwire_hook[powered=true,attached=true,facing=west]": "131:13" +"minecraft:tripwire_hook[powered=true,attached=true,facing=north]": "131:14" +"minecraft:tripwire_hook[powered=true,attached=true,facing=east]": "131:15" +"minecraft:tripwire[disarmed=false,east=false,powered=false,south=false,north=false,west=false,attached=false]": "132:0" +"minecraft:tripwire[disarmed=false,east=false,powered=true,south=false,north=false,west=false,attached=false]": "132:1" +"minecraft:tripwire[disarmed=false,east=false,powered=false,south=false,north=false,west=false,attached=true]": "132:4" +"minecraft:tripwire[disarmed=false,east=false,powered=true,south=false,north=false,west=false,attached=true]": "132:5" +"minecraft:tripwire[disarmed=true,east=false,powered=false,south=false,north=false,west=false,attached=false]": "132:8" +"minecraft:tripwire[disarmed=true,east=false,powered=true,south=false,north=false,west=false,attached=false]": "132:9" +"minecraft:tripwire[disarmed=true,east=false,powered=false,south=false,north=false,west=false,attached=true]": "132:12" +"minecraft:tripwire[disarmed=true,east=false,powered=true,south=false,north=false,west=false,attached=true]": "132:13" +"minecraft:emerald_block": "133:0" +"minecraft:spruce_stairs[half=bottom,shape=straight,facing=east]": "134:0" +"minecraft:spruce_stairs[half=bottom,shape=straight,facing=west]": "134:1" +"minecraft:spruce_stairs[half=bottom,shape=straight,facing=south]": "134:2" +"minecraft:spruce_stairs[half=bottom,shape=straight,facing=north]": "134:3" +"minecraft:spruce_stairs[half=top,shape=straight,facing=east]": "134:4" +"minecraft:spruce_stairs[half=top,shape=straight,facing=west]": "134:5" +"minecraft:spruce_stairs[half=top,shape=straight,facing=south]": "134:6" +"minecraft:spruce_stairs[half=top,shape=straight,facing=north]": "134:7" +"minecraft:birch_stairs[half=bottom,shape=straight,facing=east]": "135:0" +"minecraft:birch_stairs[half=bottom,shape=straight,facing=west]": "135:1" +"minecraft:birch_stairs[half=bottom,shape=straight,facing=south]": "135:2" +"minecraft:birch_stairs[half=bottom,shape=straight,facing=north]": "135:3" +"minecraft:birch_stairs[half=top,shape=straight,facing=east]": "135:4" +"minecraft:birch_stairs[half=top,shape=straight,facing=west]": "135:5" +"minecraft:birch_stairs[half=top,shape=straight,facing=south]": "135:6" +"minecraft:birch_stairs[half=top,shape=straight,facing=north]": "135:7" +"minecraft:jungle_stairs[half=bottom,shape=straight,facing=east]": "136:0" +"minecraft:jungle_stairs[half=bottom,shape=straight,facing=west]": "136:1" +"minecraft:jungle_stairs[half=bottom,shape=straight,facing=south]": "136:2" +"minecraft:jungle_stairs[half=bottom,shape=straight,facing=north]": "136:3" +"minecraft:jungle_stairs[half=top,shape=straight,facing=east]": "136:4" +"minecraft:jungle_stairs[half=top,shape=straight,facing=west]": "136:5" +"minecraft:jungle_stairs[half=top,shape=straight,facing=south]": "136:6" +"minecraft:jungle_stairs[half=top,shape=straight,facing=north]": "136:7" +"minecraft:command_block[conditional=false,facing=down]": "137:0" +"minecraft:command_block[conditional=false,facing=up]": "137:1" +"minecraft:command_block[conditional=false,facing=north]": "137:2" +"minecraft:command_block[conditional=false,facing=south]": "137:3" +"minecraft:command_block[conditional=false,facing=west]": "137:4" +"minecraft:command_block[conditional=false,facing=east]": "137:5" +"minecraft:command_block[conditional=true,facing=down]": "137:8" +"minecraft:command_block[conditional=true,facing=up]": "137:9" +"minecraft:command_block[conditional=true,facing=north]": "137:10" +"minecraft:command_block[conditional=true,facing=south]": "137:11" +"minecraft:command_block[conditional=true,facing=west]": "137:12" +"minecraft:command_block[conditional=true,facing=east]": "137:13" +"minecraft:beacon": "138:0" +"minecraft:cobblestone_wall[east=false,south=false,north=false,west=false,up=false]": "139:0" +"minecraft:mossy_cobblestone_wall[east=false,south=false,north=false,west=false,up=false]": "139:1" +"minecraft:flower_pot": "140:0" +"minecraft:potted_poppy": "140:1" +"minecraft:potted_dandelion": "140:2" +"minecraft:potted_oak_sapling": "140:3" +"minecraft:potted_spruce_sapling": "140:4" +"minecraft:potted_birch_sapling": "140:5" +"minecraft:potted_jungle_sapling": "140:6" +"minecraft:potted_red_mushroom": "140:7" +"minecraft:potted_brown_mushroom": "140:8" +"minecraft:potted_cactus": "140:9" +"minecraft:potted_dead_bush": "140:10" +"minecraft:potted_fern": "140:11" +"minecraft:potted_acacia_sapling": "140:12" +"minecraft:potted_dark_oak_sapling": "140:13" +"minecraft:potted_blue_orchid": "140:14" +"minecraft:potted_allium": "140:15" +"minecraft:carrots[age=0]": "141:0" +"minecraft:carrots[age=1]": "141:1" +"minecraft:carrots[age=2]": "141:2" +"minecraft:carrots[age=3]": "141:3" +"minecraft:carrots[age=4]": "141:4" +"minecraft:carrots[age=5]": "141:5" +"minecraft:carrots[age=6]": "141:6" +"minecraft:carrots[age=7]": "141:7" +"minecraft:potatoes[age=0]": "142:0" +"minecraft:potatoes[age=1]": "142:1" +"minecraft:potatoes[age=2]": "142:2" +"minecraft:potatoes[age=3]": "142:3" +"minecraft:potatoes[age=4]": "142:4" +"minecraft:potatoes[age=5]": "142:5" +"minecraft:potatoes[age=6]": "142:6" +"minecraft:potatoes[age=7]": "142:7" +"minecraft:oak_button[powered=false,facing=east,face=ceiling]": "143:0" +"minecraft:oak_button[powered=false,facing=east,face=wall]": "143:1" +"minecraft:oak_button[powered=false,facing=west,face=wall]": "143:2" +"minecraft:oak_button[powered=false,facing=south,face=wall]": "143:3" +"minecraft:oak_button[powered=false,facing=north,face=wall]": "143:4" +"minecraft:oak_button[powered=false,facing=east,face=floor]": "143:5" +"minecraft:oak_button[powered=true,facing=south,face=ceiling]": "143:8" +"minecraft:oak_button[powered=true,facing=east,face=wall]": "143:9" +"minecraft:oak_button[powered=true,facing=west,face=wall]": "143:10" +"minecraft:oak_button[powered=true,facing=south,face=wall]": "143:11" +"minecraft:oak_button[powered=true,facing=north,face=wall]": "143:12" +"minecraft:oak_button[powered=true,facing=south,face=floor]": "143:13" +"minecraft:skeleton_skull[rotation=0]": "144:0" +"minecraft:skeleton_skull[rotation=4]": "144:1" +"minecraft:skeleton_wall_skull[facing=north]": "144:2" +"minecraft:skeleton_wall_skull[facing=south]": "144:3" +"minecraft:skeleton_wall_skull[facing=west]": "144:4" +"minecraft:skeleton_wall_skull[facing=east]": "144:5" +"minecraft:skeleton_skull[rotation=8]": "144:8" +"minecraft:skeleton_skull[rotation=12]": "144:9" +"minecraft:anvil[facing=south]": "145:0" +"minecraft:anvil[facing=west]": "145:1" +"minecraft:anvil[facing=north]": "145:2" +"minecraft:anvil[facing=east]": "145:3" +"minecraft:chipped_anvil[facing=south]": "145:4" +"minecraft:chipped_anvil[facing=west]": "145:5" +"minecraft:chipped_anvil[facing=north]": "145:6" +"minecraft:chipped_anvil[facing=east]": "145:7" +"minecraft:damaged_anvil[facing=south]": "145:8" +"minecraft:damaged_anvil[facing=west]": "145:9" +"minecraft:damaged_anvil[facing=north]": "145:10" +"minecraft:damaged_anvil[facing=east]": "145:11" +"minecraft:trapped_chest": "146:0" +"minecraft:trapped_chest[facing=north,type=single]": "146:2" +"minecraft:trapped_chest[facing=south,type=single]": "146:3" +"minecraft:trapped_chest[facing=west,type=single]": "146:4" +"minecraft:trapped_chest[facing=east,type=single]": "146:5" +"minecraft:light_weighted_pressure_plate[power=0]": "147:0" +"minecraft:light_weighted_pressure_plate[power=1]": "147:1" +"minecraft:light_weighted_pressure_plate[power=2]": "147:2" +"minecraft:light_weighted_pressure_plate[power=3]": "147:3" +"minecraft:light_weighted_pressure_plate[power=4]": "147:4" +"minecraft:light_weighted_pressure_plate[power=5]": "147:5" +"minecraft:light_weighted_pressure_plate[power=6]": "147:6" +"minecraft:light_weighted_pressure_plate[power=7]": "147:7" +"minecraft:light_weighted_pressure_plate[power=8]": "147:8" +"minecraft:light_weighted_pressure_plate[power=9]": "147:9" +"minecraft:light_weighted_pressure_plate[power=10]": "147:10" +"minecraft:light_weighted_pressure_plate[power=11]": "147:11" +"minecraft:light_weighted_pressure_plate[power=12]": "147:12" +"minecraft:light_weighted_pressure_plate[power=13]": "147:13" +"minecraft:light_weighted_pressure_plate[power=14]": "147:14" +"minecraft:light_weighted_pressure_plate[power=15]": "147:15" +"minecraft:heavy_weighted_pressure_plate[power=0]": "148:0" +"minecraft:heavy_weighted_pressure_plate[power=1]": "148:1" +"minecraft:heavy_weighted_pressure_plate[power=2]": "148:2" +"minecraft:heavy_weighted_pressure_plate[power=3]": "148:3" +"minecraft:heavy_weighted_pressure_plate[power=4]": "148:4" +"minecraft:heavy_weighted_pressure_plate[power=5]": "148:5" +"minecraft:heavy_weighted_pressure_plate[power=6]": "148:6" +"minecraft:heavy_weighted_pressure_plate[power=7]": "148:7" +"minecraft:heavy_weighted_pressure_plate[power=8]": "148:8" +"minecraft:heavy_weighted_pressure_plate[power=9]": "148:9" +"minecraft:heavy_weighted_pressure_plate[power=10]": "148:10" +"minecraft:heavy_weighted_pressure_plate[power=11]": "148:11" +"minecraft:heavy_weighted_pressure_plate[power=12]": "148:12" +"minecraft:heavy_weighted_pressure_plate[power=13]": "148:13" +"minecraft:heavy_weighted_pressure_plate[power=14]": "148:14" +"minecraft:heavy_weighted_pressure_plate[power=15]": "148:15" +"minecraft:comparator[mode=compare,powered=false,facing=south]": "149:0" +"minecraft:comparator[mode=compare,powered=false,facing=west]": "149:1" +"minecraft:comparator[mode=compare,powered=false,facing=north]": "149:2" +"minecraft:comparator[mode=compare,powered=false,facing=east]": "149:3" +"minecraft:comparator[mode=subtract,powered=false,facing=south]": "149:4" +"minecraft:comparator[mode=subtract,powered=false,facing=west]": "149:5" +"minecraft:comparator[mode=subtract,powered=false,facing=north]": "149:6" +"minecraft:comparator[mode=subtract,powered=false,facing=east]": "149:7" +"minecraft:comparator[mode=compare,powered=true,facing=south]": "150:0" +"minecraft:comparator[mode=compare,powered=true,facing=west]": "150:1" +"minecraft:comparator[mode=compare,powered=true,facing=north]": "150:2" +"minecraft:comparator[mode=compare,powered=true,facing=east]": "150:3" +"minecraft:comparator[mode=subtract,powered=true,facing=south]": "150:4" +"minecraft:comparator[mode=subtract,powered=true,facing=west]": "150:5" +"minecraft:comparator[mode=subtract,powered=true,facing=north]": "150:6" +"minecraft:comparator[mode=subtract,powered=true,facing=east]": "150:7" +"minecraft:daylight_detector[inverted=false,power=0]": "151:0" +"minecraft:daylight_detector[inverted=false,power=1]": "151:1" +"minecraft:daylight_detector[inverted=false,power=2]": "151:2" +"minecraft:daylight_detector[inverted=false,power=3]": "151:3" +"minecraft:daylight_detector[inverted=false,power=4]": "151:4" +"minecraft:daylight_detector[inverted=false,power=5]": "151:5" +"minecraft:daylight_detector[inverted=false,power=6]": "151:6" +"minecraft:daylight_detector[inverted=false,power=7]": "151:7" +"minecraft:daylight_detector[inverted=false,power=8]": "151:8" +"minecraft:daylight_detector[inverted=false,power=9]": "151:9" +"minecraft:daylight_detector[inverted=false,power=10]": "151:10" +"minecraft:daylight_detector[inverted=false,power=11]": "151:11" +"minecraft:daylight_detector[inverted=false,power=12]": "151:12" +"minecraft:daylight_detector[inverted=false,power=13]": "151:13" +"minecraft:daylight_detector[inverted=false,power=14]": "151:14" +"minecraft:daylight_detector[inverted=false,power=15]": "151:15" +"minecraft:redstone_block": "152:0" +"minecraft:nether_quartz_ore": "153:0" +"minecraft:hopper[facing=down,enabled=true]": "154:0" +"minecraft:hopper[facing=north,enabled=true]": "154:2" +"minecraft:hopper[facing=south,enabled=true]": "154:3" +"minecraft:hopper[facing=west,enabled=true]": "154:4" +"minecraft:hopper[facing=east,enabled=true]": "154:5" +"minecraft:hopper[facing=down,enabled=false]": "154:8" +"minecraft:hopper[facing=north,enabled=false]": "154:10" +"minecraft:hopper[facing=south,enabled=false]": "154:11" +"minecraft:hopper[facing=west,enabled=false]": "154:12" +"minecraft:hopper[facing=east,enabled=false]": "154:13" +"minecraft:quartz_block": "155:0" +"minecraft:chiseled_quartz_block": "155:1" +"minecraft:quartz_pillar[axis=y]": "155:2" +"minecraft:quartz_pillar[axis=x]": "155:3" +"minecraft:quartz_pillar[axis=z]": "155:4" +"minecraft:quartz_stairs[half=bottom,shape=straight,facing=east]": "156:0" +"minecraft:quartz_stairs[half=bottom,shape=straight,facing=west]": "156:1" +"minecraft:quartz_stairs[half=bottom,shape=straight,facing=south]": "156:2" +"minecraft:quartz_stairs[half=bottom,shape=straight,facing=north]": "156:3" +"minecraft:quartz_stairs[half=top,shape=straight,facing=east]": "156:4" +"minecraft:quartz_stairs[half=top,shape=straight,facing=west]": "156:5" +"minecraft:quartz_stairs[half=top,shape=straight,facing=south]": "156:6" +"minecraft:quartz_stairs[half=top,shape=straight,facing=north]": "156:7" +"minecraft:activator_rail[shape=north_south,powered=false]": "157:0" +"minecraft:activator_rail[shape=east_west,powered=false]": "157:1" +"minecraft:activator_rail[shape=ascending_east,powered=false]": "157:2" +"minecraft:activator_rail[shape=ascending_west,powered=false]": "157:3" +"minecraft:activator_rail[shape=ascending_north,powered=false]": "157:4" +"minecraft:activator_rail[shape=ascending_south,powered=false]": "157:5" +"minecraft:activator_rail[shape=north_south,powered=true]": "157:8" +"minecraft:activator_rail[shape=east_west,powered=true]": "157:9" +"minecraft:activator_rail[shape=ascending_east,powered=true]": "157:10" +"minecraft:activator_rail[shape=ascending_west,powered=true]": "157:11" +"minecraft:activator_rail[shape=ascending_north,powered=true]": "157:12" +"minecraft:activator_rail[shape=ascending_south,powered=true]": "157:13" +"minecraft:dropper[triggered=false,facing=down]": "158:0" +"minecraft:dropper[triggered=false,facing=up]": "158:1" +"minecraft:dropper[triggered=false,facing=north]": "158:2" +"minecraft:dropper[triggered=false,facing=south]": "158:3" +"minecraft:dropper[triggered=false,facing=west]": "158:4" +"minecraft:dropper[triggered=false,facing=east]": "158:5" +"minecraft:dropper[triggered=true,facing=down]": "158:8" +"minecraft:dropper[triggered=true,facing=up]": "158:9" +"minecraft:dropper[triggered=true,facing=north]": "158:10" +"minecraft:dropper[triggered=true,facing=south]": "158:11" +"minecraft:dropper[triggered=true,facing=west]": "158:12" +"minecraft:dropper[triggered=true,facing=east]": "158:13" +"minecraft:white_terracotta": "159:0" +"minecraft:orange_terracotta": "159:1" +"minecraft:magenta_terracotta": "159:2" +"minecraft:light_blue_terracotta": "159:3" +"minecraft:yellow_terracotta": "159:4" +"minecraft:lime_terracotta": "159:5" +"minecraft:pink_terracotta": "159:6" +"minecraft:gray_terracotta": "159:7" +"minecraft:light_gray_terracotta": "159:8" +"minecraft:cyan_terracotta": "159:9" +"minecraft:purple_terracotta": "159:10" +"minecraft:blue_terracotta": "159:11" +"minecraft:brown_terracotta": "159:12" +"minecraft:green_terracotta": "159:13" +"minecraft:red_terracotta": "159:14" +"minecraft:black_terracotta": "159:15" +"minecraft:white_stained_glass_pane[east=false,south=false,north=false,west=false]": "160:0" +"minecraft:orange_stained_glass_pane[east=false,south=false,north=false,west=false]": "160:1" +"minecraft:magenta_stained_glass_pane[east=false,south=false,north=false,west=false]": "160:2" +"minecraft:light_blue_stained_glass_pane[east=false,south=false,north=false,west=false]": "160:3" +"minecraft:yellow_stained_glass_pane[east=false,south=false,north=false,west=false]": "160:4" +"minecraft:lime_stained_glass_pane[east=false,south=false,north=false,west=false]": "160:5" +"minecraft:pink_stained_glass_pane[east=false,south=false,north=false,west=false]": "160:6" +"minecraft:gray_stained_glass_pane[east=false,south=false,north=false,west=false]": "160:7" +"minecraft:light_gray_stained_glass_pane[east=false,south=false,north=false,west=false]": "160:8" +"minecraft:cyan_stained_glass_pane[east=false,south=false,north=false,west=false]": "160:9" +"minecraft:purple_stained_glass_pane[east=false,south=false,north=false,west=false]": "160:10" +"minecraft:blue_stained_glass_pane[east=false,south=false,north=false,west=false]": "160:11" +"minecraft:brown_stained_glass_pane[east=false,south=false,north=false,west=false]": "160:12" +"minecraft:green_stained_glass_pane[east=false,south=false,north=false,west=false]": "160:13" +"minecraft:red_stained_glass_pane[east=false,south=false,north=false,west=false]": "160:14" +"minecraft:black_stained_glass_pane[east=false,south=false,north=false,west=false]": "160:15" +"minecraft:acacia_leaves[persistent=false,distance=1]": "161:0" +"minecraft:dark_oak_leaves[persistent=false,distance=1]": "161:1" +"minecraft:acacia_leaves[persistent=true,distance=1]": "161:4" +"minecraft:dark_oak_leaves[persistent=true,distance=1]": "161:5" +"minecraft:acacia_log[axis=y]": "162:0" +"minecraft:dark_oak_log[axis=y]": "162:1" +"minecraft:acacia_log[axis=x]": "162:4" +"minecraft:dark_oak_log[axis=x]": "162:5" +"minecraft:acacia_log[axis=z]": "162:8" +"minecraft:dark_oak_log[axis=z]": "162:9" +"minecraft:acacia_wood": "162:12" +"minecraft:dark_oak_wood": "162:13" +"minecraft:acacia_stairs[half=bottom,shape=straight,facing=east]": "163:0" +"minecraft:acacia_stairs[half=bottom,shape=straight,facing=west]": "163:1" +"minecraft:acacia_stairs[half=bottom,shape=straight,facing=south]": "163:2" +"minecraft:acacia_stairs[half=bottom,shape=straight,facing=north]": "163:3" +"minecraft:acacia_stairs[half=top,shape=straight,facing=east]": "163:4" +"minecraft:acacia_stairs[half=top,shape=straight,facing=west]": "163:5" +"minecraft:acacia_stairs[half=top,shape=straight,facing=south]": "163:6" +"minecraft:acacia_stairs[half=top,shape=straight,facing=north]": "163:7" +"minecraft:dark_oak_stairs[half=bottom,shape=straight,facing=east]": "164:0" +"minecraft:dark_oak_stairs[half=bottom,shape=straight,facing=west]": "164:1" +"minecraft:dark_oak_stairs[half=bottom,shape=straight,facing=south]": "164:2" +"minecraft:dark_oak_stairs[half=bottom,shape=straight,facing=north]": "164:3" +"minecraft:dark_oak_stairs[half=top,shape=straight,facing=east]": "164:4" +"minecraft:dark_oak_stairs[half=top,shape=straight,facing=west]": "164:5" +"minecraft:dark_oak_stairs[half=top,shape=straight,facing=south]": "164:6" +"minecraft:dark_oak_stairs[half=top,shape=straight,facing=north]": "164:7" +"minecraft:slime_block": "165:0" +"minecraft:barrier": "166:0" +"minecraft:iron_trapdoor[half=bottom,facing=north,open=false]": "167:0" +"minecraft:iron_trapdoor[half=bottom,facing=south,open=false]": "167:1" +"minecraft:iron_trapdoor[half=bottom,facing=west,open=false]": "167:2" +"minecraft:iron_trapdoor[half=bottom,facing=east,open=false]": "167:3" +"minecraft:iron_trapdoor[half=bottom,facing=north,open=true]": "167:4" +"minecraft:iron_trapdoor[half=bottom,facing=south,open=true]": "167:5" +"minecraft:iron_trapdoor[half=bottom,facing=west,open=true]": "167:6" +"minecraft:iron_trapdoor[half=bottom,facing=east,open=true]": "167:7" +"minecraft:iron_trapdoor[half=top,facing=north,open=false]": "167:8" +"minecraft:iron_trapdoor[half=top,facing=south,open=false]": "167:9" +"minecraft:iron_trapdoor[half=top,facing=west,open=false]": "167:10" +"minecraft:iron_trapdoor[half=top,facing=east,open=false]": "167:11" +"minecraft:iron_trapdoor[half=top,facing=north,open=true]": "167:12" +"minecraft:iron_trapdoor[half=top,facing=south,open=true]": "167:13" +"minecraft:iron_trapdoor[half=top,facing=west,open=true]": "167:14" +"minecraft:iron_trapdoor[half=top,facing=east,open=true]": "167:15" +"minecraft:prismarine": "168:0" +"minecraft:prismarine_bricks": "168:1" +"minecraft:dark_prismarine": "168:2" +"minecraft:sea_lantern": "169:0" +"minecraft:hay_block[axis=y]": "170:0" +"minecraft:hay_block[axis=x]": "170:4" +"minecraft:hay_block[axis=z]": "170:8" +"minecraft:white_carpet": "171:0" +"minecraft:orange_carpet": "171:1" +"minecraft:magenta_carpet": "171:2" +"minecraft:light_blue_carpet": "171:3" +"minecraft:yellow_carpet": "171:4" +"minecraft:lime_carpet": "171:5" +"minecraft:pink_carpet": "171:6" +"minecraft:gray_carpet": "171:7" +"minecraft:light_gray_carpet": "171:8" +"minecraft:cyan_carpet": "171:9" +"minecraft:purple_carpet": "171:10" +"minecraft:blue_carpet": "171:11" +"minecraft:brown_carpet": "171:12" +"minecraft:green_carpet": "171:13" +"minecraft:red_carpet": "171:14" +"minecraft:black_carpet": "171:15" +"minecraft:terracotta": "172:0" +"minecraft:coal_block": "173:0" +"minecraft:packed_ice": "174:0" +"minecraft:sunflower[half=lower]": "175:0" +"minecraft:lilac[half=lower]": "175:1" +"minecraft:tall_grass[half=lower]": "175:2" +"minecraft:large_fern[half=lower]": "175:3" +"minecraft:rose_bush[half=lower]": "175:4" +"minecraft:peony[half=lower]": "175:5" +"minecraft:sunflower[half=upper]": "175:8" +"minecraft:lilac[half=upper]": "175:9" +"minecraft:tall_grass[half=upper]": "175:10" +"minecraft:large_fern[half=upper]": "175:11" +"minecraft:rose_bush[half=upper]": "175:12" +"minecraft:peony[half=upper]": "175:13" +"minecraft:white_banner[rotation=0]": "176:0" +"minecraft:white_banner[rotation=1]": "176:1" +"minecraft:white_banner[rotation=2]": "176:2" +"minecraft:white_banner[rotation=3]": "176:3" +"minecraft:white_banner[rotation=4]": "176:4" +"minecraft:white_banner[rotation=5]": "176:5" +"minecraft:white_banner[rotation=6]": "176:6" +"minecraft:white_banner[rotation=7]": "176:7" +"minecraft:white_banner[rotation=8]": "176:8" +"minecraft:white_banner[rotation=9]": "176:9" +"minecraft:white_banner[rotation=10]": "176:10" +"minecraft:white_banner[rotation=11]": "176:11" +"minecraft:white_banner[rotation=12]": "176:12" +"minecraft:white_banner[rotation=13]": "176:13" +"minecraft:white_banner[rotation=14]": "176:14" +"minecraft:white_banner[rotation=15]": "176:15" +"minecraft:white_wall_banner": "177:0" +"minecraft:white_wall_banner[facing=north]": "177:2" +"minecraft:white_wall_banner[facing=south]": "177:3" +"minecraft:white_wall_banner[facing=west]": "177:4" +"minecraft:white_wall_banner[facing=east]": "177:5" +"minecraft:daylight_detector[inverted=true,power=0]": "178:0" +"minecraft:daylight_detector[inverted=true,power=1]": "178:1" +"minecraft:daylight_detector[inverted=true,power=2]": "178:2" +"minecraft:daylight_detector[inverted=true,power=3]": "178:3" +"minecraft:daylight_detector[inverted=true,power=4]": "178:4" +"minecraft:daylight_detector[inverted=true,power=5]": "178:5" +"minecraft:daylight_detector[inverted=true,power=6]": "178:6" +"minecraft:daylight_detector[inverted=true,power=7]": "178:7" +"minecraft:daylight_detector[inverted=true,power=8]": "178:8" +"minecraft:daylight_detector[inverted=true,power=9]": "178:9" +"minecraft:daylight_detector[inverted=true,power=10]": "178:10" +"minecraft:daylight_detector[inverted=true,power=11]": "178:11" +"minecraft:daylight_detector[inverted=true,power=12]": "178:12" +"minecraft:daylight_detector[inverted=true,power=13]": "178:13" +"minecraft:daylight_detector[inverted=true,power=14]": "178:14" +"minecraft:daylight_detector[inverted=true,power=15]": "178:15" +"minecraft:red_sandstone": "179:0" +"minecraft:chiseled_red_sandstone": "179:1" +"minecraft:cut_red_sandstone": "179:2" +"minecraft:red_sandstone_stairs[half=bottom,shape=straight,facing=east]": "180:0" +"minecraft:red_sandstone_stairs[half=bottom,shape=straight,facing=west]": "180:1" +"minecraft:red_sandstone_stairs[half=bottom,shape=straight,facing=south]": "180:2" +"minecraft:red_sandstone_stairs[half=bottom,shape=straight,facing=north]": "180:3" +"minecraft:red_sandstone_stairs[half=top,shape=straight,facing=east]": "180:4" +"minecraft:red_sandstone_stairs[half=top,shape=straight,facing=west]": "180:5" +"minecraft:red_sandstone_stairs[half=top,shape=straight,facing=south]": "180:6" +"minecraft:red_sandstone_stairs[half=top,shape=straight,facing=north]": "180:7" +"minecraft:red_sandstone_slab[type=double]": "181:0" +"minecraft:smooth_red_sandstone": "181:8" +"minecraft:red_sandstone_slab[type=bottom]": "182:0" +"minecraft:red_sandstone_slab[type=top]": "182:8" +"minecraft:spruce_fence_gate[in_wall=false,powered=false,facing=south,open=false]": "183:0" +"minecraft:spruce_fence_gate[in_wall=false,powered=false,facing=west,open=false]": "183:1" +"minecraft:spruce_fence_gate[in_wall=false,powered=false,facing=north,open=false]": "183:2" +"minecraft:spruce_fence_gate[in_wall=false,powered=false,facing=east,open=false]": "183:3" +"minecraft:spruce_fence_gate[in_wall=false,powered=false,facing=south,open=true]": "183:4" +"minecraft:spruce_fence_gate[in_wall=false,powered=false,facing=west,open=true]": "183:5" +"minecraft:spruce_fence_gate[in_wall=false,powered=false,facing=north,open=true]": "183:6" +"minecraft:spruce_fence_gate[in_wall=false,powered=false,facing=east,open=true]": "183:7" +"minecraft:spruce_fence_gate[in_wall=false,powered=true,facing=south,open=false]": "183:8" +"minecraft:spruce_fence_gate[in_wall=false,powered=true,facing=west,open=false]": "183:9" +"minecraft:spruce_fence_gate[in_wall=false,powered=true,facing=north,open=false]": "183:10" +"minecraft:spruce_fence_gate[in_wall=false,powered=true,facing=east,open=false]": "183:11" +"minecraft:spruce_fence_gate[in_wall=false,powered=true,facing=south,open=true]": "183:12" +"minecraft:spruce_fence_gate[in_wall=false,powered=true,facing=west,open=true]": "183:13" +"minecraft:spruce_fence_gate[in_wall=false,powered=true,facing=north,open=true]": "183:14" +"minecraft:spruce_fence_gate[in_wall=false,powered=true,facing=east,open=true]": "183:15" +"minecraft:birch_fence_gate[in_wall=false,powered=false,facing=south,open=false]": "184:0" +"minecraft:birch_fence_gate[in_wall=false,powered=false,facing=west,open=false]": "184:1" +"minecraft:birch_fence_gate[in_wall=false,powered=false,facing=north,open=false]": "184:2" +"minecraft:birch_fence_gate[in_wall=false,powered=false,facing=east,open=false]": "184:3" +"minecraft:birch_fence_gate[in_wall=false,powered=false,facing=south,open=true]": "184:4" +"minecraft:birch_fence_gate[in_wall=false,powered=false,facing=west,open=true]": "184:5" +"minecraft:birch_fence_gate[in_wall=false,powered=false,facing=north,open=true]": "184:6" +"minecraft:birch_fence_gate[in_wall=false,powered=false,facing=east,open=true]": "184:7" +"minecraft:birch_fence_gate[in_wall=false,powered=true,facing=south,open=false]": "184:8" +"minecraft:birch_fence_gate[in_wall=false,powered=true,facing=west,open=false]": "184:9" +"minecraft:birch_fence_gate[in_wall=false,powered=true,facing=north,open=false]": "184:10" +"minecraft:birch_fence_gate[in_wall=false,powered=true,facing=east,open=false]": "184:11" +"minecraft:birch_fence_gate[in_wall=false,powered=true,facing=south,open=true]": "184:12" +"minecraft:birch_fence_gate[in_wall=false,powered=true,facing=west,open=true]": "184:13" +"minecraft:birch_fence_gate[in_wall=false,powered=true,facing=north,open=true]": "184:14" +"minecraft:birch_fence_gate[in_wall=false,powered=true,facing=east,open=true]": "184:15" +"minecraft:jungle_fence_gate[in_wall=false,powered=false,facing=south,open=false]": "185:0" +"minecraft:jungle_fence_gate[in_wall=false,powered=false,facing=west,open=false]": "185:1" +"minecraft:jungle_fence_gate[in_wall=false,powered=false,facing=north,open=false]": "185:2" +"minecraft:jungle_fence_gate[in_wall=false,powered=false,facing=east,open=false]": "185:3" +"minecraft:jungle_fence_gate[in_wall=false,powered=false,facing=south,open=true]": "185:4" +"minecraft:jungle_fence_gate[in_wall=false,powered=false,facing=west,open=true]": "185:5" +"minecraft:jungle_fence_gate[in_wall=false,powered=false,facing=north,open=true]": "185:6" +"minecraft:jungle_fence_gate[in_wall=false,powered=false,facing=east,open=true]": "185:7" +"minecraft:jungle_fence_gate[in_wall=false,powered=true,facing=south,open=false]": "185:8" +"minecraft:jungle_fence_gate[in_wall=false,powered=true,facing=west,open=false]": "185:9" +"minecraft:jungle_fence_gate[in_wall=false,powered=true,facing=north,open=false]": "185:10" +"minecraft:jungle_fence_gate[in_wall=false,powered=true,facing=east,open=false]": "185:11" +"minecraft:jungle_fence_gate[in_wall=false,powered=true,facing=south,open=true]": "185:12" +"minecraft:jungle_fence_gate[in_wall=false,powered=true,facing=west,open=true]": "185:13" +"minecraft:jungle_fence_gate[in_wall=false,powered=true,facing=north,open=true]": "185:14" +"minecraft:jungle_fence_gate[in_wall=false,powered=true,facing=east,open=true]": "185:15" +"minecraft:dark_oak_fence_gate[in_wall=false,powered=false,facing=south,open=false]": "186:0" +"minecraft:dark_oak_fence_gate[in_wall=false,powered=false,facing=west,open=false]": "186:1" +"minecraft:dark_oak_fence_gate[in_wall=false,powered=false,facing=north,open=false]": "186:2" +"minecraft:dark_oak_fence_gate[in_wall=false,powered=false,facing=east,open=false]": "186:3" +"minecraft:dark_oak_fence_gate[in_wall=false,powered=false,facing=south,open=true]": "186:4" +"minecraft:dark_oak_fence_gate[in_wall=false,powered=false,facing=west,open=true]": "186:5" +"minecraft:dark_oak_fence_gate[in_wall=false,powered=false,facing=north,open=true]": "186:6" +"minecraft:dark_oak_fence_gate[in_wall=false,powered=false,facing=east,open=true]": "186:7" +"minecraft:dark_oak_fence_gate[in_wall=false,powered=true,facing=south,open=false]": "186:8" +"minecraft:dark_oak_fence_gate[in_wall=false,powered=true,facing=west,open=false]": "186:9" +"minecraft:dark_oak_fence_gate[in_wall=false,powered=true,facing=north,open=false]": "186:10" +"minecraft:dark_oak_fence_gate[in_wall=false,powered=true,facing=east,open=false]": "186:11" +"minecraft:dark_oak_fence_gate[in_wall=false,powered=true,facing=south,open=true]": "186:12" +"minecraft:dark_oak_fence_gate[in_wall=false,powered=true,facing=west,open=true]": "186:13" +"minecraft:dark_oak_fence_gate[in_wall=false,powered=true,facing=north,open=true]": "186:14" +"minecraft:dark_oak_fence_gate[in_wall=false,powered=true,facing=east,open=true]": "186:15" +"minecraft:acacia_fence_gate[in_wall=false,powered=false,facing=south,open=false]": "187:0" +"minecraft:acacia_fence_gate[in_wall=false,powered=false,facing=west,open=false]": "187:1" +"minecraft:acacia_fence_gate[in_wall=false,powered=false,facing=north,open=false]": "187:2" +"minecraft:acacia_fence_gate[in_wall=false,powered=false,facing=east,open=false]": "187:3" +"minecraft:acacia_fence_gate[in_wall=false,powered=false,facing=south,open=true]": "187:4" +"minecraft:acacia_fence_gate[in_wall=false,powered=false,facing=west,open=true]": "187:5" +"minecraft:acacia_fence_gate[in_wall=false,powered=false,facing=north,open=true]": "187:6" +"minecraft:acacia_fence_gate[in_wall=false,powered=false,facing=east,open=true]": "187:7" +"minecraft:acacia_fence_gate[in_wall=false,powered=true,facing=south,open=false]": "187:8" +"minecraft:acacia_fence_gate[in_wall=false,powered=true,facing=west,open=false]": "187:9" +"minecraft:acacia_fence_gate[in_wall=false,powered=true,facing=north,open=false]": "187:10" +"minecraft:acacia_fence_gate[in_wall=false,powered=true,facing=east,open=false]": "187:11" +"minecraft:acacia_fence_gate[in_wall=false,powered=true,facing=south,open=true]": "187:12" +"minecraft:acacia_fence_gate[in_wall=false,powered=true,facing=west,open=true]": "187:13" +"minecraft:acacia_fence_gate[in_wall=false,powered=true,facing=north,open=true]": "187:14" +"minecraft:acacia_fence_gate[in_wall=false,powered=true,facing=east,open=true]": "187:15" +"minecraft:spruce_fence[east=false,south=false,north=false,west=false]": "188:0" +"minecraft:birch_fence[east=false,south=false,north=false,west=false]": "189:0" +"minecraft:jungle_fence[east=false,south=false,north=false,west=false]": "190:0" +"minecraft:dark_oak_fence[east=false,south=false,north=false,west=false]": "191:0" +"minecraft:acacia_fence[east=false,south=false,north=false,west=false]": "192:0" +"minecraft:spruce_door[hinge=right,half=lower,powered=false,facing=east,open=false]": "193:0" +"minecraft:spruce_door[hinge=right,half=lower,powered=false,facing=south,open=false]": "193:1" +"minecraft:spruce_door[hinge=right,half=lower,powered=false,facing=west,open=false]": "193:2" +"minecraft:spruce_door[hinge=right,half=lower,powered=false,facing=north,open=false]": "193:3" +"minecraft:spruce_door[hinge=right,half=lower,powered=false,facing=east,open=true]": "193:4" +"minecraft:spruce_door[hinge=right,half=lower,powered=false,facing=south,open=true]": "193:5" +"minecraft:spruce_door[hinge=right,half=lower,powered=false,facing=west,open=true]": "193:6" +"minecraft:spruce_door[hinge=right,half=lower,powered=false,facing=north,open=true]": "193:7" +"minecraft:spruce_door[hinge=left,half=upper,powered=false,facing=east,open=false]": "193:8" +"minecraft:spruce_door[hinge=right,half=upper,powered=false,facing=east,open=false]": "193:9" +"minecraft:spruce_door[hinge=left,half=upper,powered=true,facing=east,open=false]": "193:10" +"minecraft:spruce_door[hinge=right,half=upper,powered=true,facing=east,open=false]": "193:11" +"minecraft:birch_door[hinge=right,half=lower,powered=false,facing=east,open=false]": "194:0" +"minecraft:birch_door[hinge=right,half=lower,powered=false,facing=south,open=false]": "194:1" +"minecraft:birch_door[hinge=right,half=lower,powered=false,facing=west,open=false]": "194:2" +"minecraft:birch_door[hinge=right,half=lower,powered=false,facing=north,open=false]": "194:3" +"minecraft:birch_door[hinge=right,half=lower,powered=false,facing=east,open=true]": "194:4" +"minecraft:birch_door[hinge=right,half=lower,powered=false,facing=south,open=true]": "194:5" +"minecraft:birch_door[hinge=right,half=lower,powered=false,facing=west,open=true]": "194:6" +"minecraft:birch_door[hinge=right,half=lower,powered=false,facing=north,open=true]": "194:7" +"minecraft:birch_door[hinge=left,half=upper,powered=false,facing=east,open=false]": "194:8" +"minecraft:birch_door[hinge=right,half=upper,powered=false,facing=east,open=false]": "194:9" +"minecraft:birch_door[hinge=left,half=upper,powered=true,facing=east,open=false]": "194:10" +"minecraft:birch_door[hinge=right,half=upper,powered=true,facing=east,open=false]": "194:11" +"minecraft:jungle_door[hinge=right,half=lower,powered=false,facing=east,open=false]": "195:0" +"minecraft:jungle_door[hinge=right,half=lower,powered=false,facing=south,open=false]": "195:1" +"minecraft:jungle_door[hinge=right,half=lower,powered=false,facing=west,open=false]": "195:2" +"minecraft:jungle_door[hinge=right,half=lower,powered=false,facing=north,open=false]": "195:3" +"minecraft:jungle_door[hinge=right,half=lower,powered=false,facing=east,open=true]": "195:4" +"minecraft:jungle_door[hinge=right,half=lower,powered=false,facing=south,open=true]": "195:5" +"minecraft:jungle_door[hinge=right,half=lower,powered=false,facing=west,open=true]": "195:6" +"minecraft:jungle_door[hinge=right,half=lower,powered=false,facing=north,open=true]": "195:7" +"minecraft:jungle_door[hinge=left,half=upper,powered=false,facing=east,open=false]": "195:8" +"minecraft:jungle_door[hinge=right,half=upper,powered=false,facing=east,open=false]": "195:9" +"minecraft:jungle_door[hinge=left,half=upper,powered=true,facing=east,open=false]": "195:10" +"minecraft:jungle_door[hinge=right,half=upper,powered=true,facing=east,open=false]": "195:11" +"minecraft:acacia_door[hinge=right,half=lower,powered=false,facing=east,open=false]": "196:0" +"minecraft:acacia_door[hinge=right,half=lower,powered=false,facing=south,open=false]": "196:1" +"minecraft:acacia_door[hinge=right,half=lower,powered=false,facing=west,open=false]": "196:2" +"minecraft:acacia_door[hinge=right,half=lower,powered=false,facing=north,open=false]": "196:3" +"minecraft:acacia_door[hinge=right,half=lower,powered=false,facing=east,open=true]": "196:4" +"minecraft:acacia_door[hinge=right,half=lower,powered=false,facing=south,open=true]": "196:5" +"minecraft:acacia_door[hinge=right,half=lower,powered=false,facing=west,open=true]": "196:6" +"minecraft:acacia_door[hinge=right,half=lower,powered=false,facing=north,open=true]": "196:7" +"minecraft:acacia_door[hinge=left,half=upper,powered=false,facing=east,open=false]": "196:8" +"minecraft:acacia_door[hinge=right,half=upper,powered=false,facing=east,open=false]": "196:9" +"minecraft:acacia_door[hinge=left,half=upper,powered=true,facing=east,open=false]": "196:10" +"minecraft:acacia_door[hinge=right,half=upper,powered=true,facing=east,open=false]": "196:11" +"minecraft:dark_oak_door[hinge=right,half=lower,powered=false,facing=east,open=false]": "197:0" +"minecraft:dark_oak_door[hinge=right,half=lower,powered=false,facing=south,open=false]": "197:1" +"minecraft:dark_oak_door[hinge=right,half=lower,powered=false,facing=west,open=false]": "197:2" +"minecraft:dark_oak_door[hinge=right,half=lower,powered=false,facing=north,open=false]": "197:3" +"minecraft:dark_oak_door[hinge=right,half=lower,powered=false,facing=east,open=true]": "197:4" +"minecraft:dark_oak_door[hinge=right,half=lower,powered=false,facing=south,open=true]": "197:5" +"minecraft:dark_oak_door[hinge=right,half=lower,powered=false,facing=west,open=true]": "197:6" +"minecraft:dark_oak_door[hinge=right,half=lower,powered=false,facing=north,open=true]": "197:7" +"minecraft:dark_oak_door[hinge=left,half=upper,powered=false,facing=east,open=false]": "197:8" +"minecraft:dark_oak_door[hinge=right,half=upper,powered=false,facing=east,open=false]": "197:9" +"minecraft:dark_oak_door[hinge=left,half=upper,powered=true,facing=east,open=false]": "197:10" +"minecraft:dark_oak_door[hinge=right,half=upper,powered=true,facing=east,open=false]": "197:11" +"minecraft:end_rod[facing=down]": "198:0" +"minecraft:end_rod[facing=up]": "198:1" +"minecraft:end_rod[facing=north]": "198:2" +"minecraft:end_rod[facing=south]": "198:3" +"minecraft:end_rod[facing=west]": "198:4" +"minecraft:end_rod[facing=east]": "198:5" +"minecraft:chorus_plant[east=false,south=false,north=false,west=false,up=false,down=false]": "199:0" +"minecraft:chorus_flower[age=0]": "200:0" +"minecraft:chorus_flower[age=1]": "200:1" +"minecraft:chorus_flower[age=2]": "200:2" +"minecraft:chorus_flower[age=3]": "200:3" +"minecraft:chorus_flower[age=4]": "200:4" +"minecraft:chorus_flower[age=5]": "200:5" +"minecraft:purpur_block": "201:0" +"minecraft:purpur_pillar[axis=y]": "202:0" +"minecraft:purpur_pillar[axis=x]": "202:4" +"minecraft:purpur_pillar[axis=z]": "202:8" +"minecraft:purpur_stairs[half=bottom,shape=straight,facing=east]": "203:0" +"minecraft:purpur_stairs[half=bottom,shape=straight,facing=west]": "203:1" +"minecraft:purpur_stairs[half=bottom,shape=straight,facing=south]": "203:2" +"minecraft:purpur_stairs[half=bottom,shape=straight,facing=north]": "203:3" +"minecraft:purpur_stairs[half=top,shape=straight,facing=east]": "203:4" +"minecraft:purpur_stairs[half=top,shape=straight,facing=west]": "203:5" +"minecraft:purpur_stairs[half=top,shape=straight,facing=south]": "203:6" +"minecraft:purpur_stairs[half=top,shape=straight,facing=north]": "203:7" +"minecraft:purpur_slab[type=double]": "204:0" +"minecraft:purpur_slab[type=bottom]": "205:0" +"minecraft:purpur_slab[type=top]": "205:8" +"minecraft:end_stone_bricks": "206:0" +"minecraft:beetroots[age=0]": "207:0" +"minecraft:beetroots[age=1]": "207:1" +"minecraft:beetroots[age=2]": "207:2" +"minecraft:beetroots[age=3]": "207:3" +"minecraft:grass_path": "208:0" +"minecraft:end_gateway": "209:0" +"minecraft:repeating_command_block[conditional=false,facing=down]": "210:0" +"minecraft:repeating_command_block[conditional=false,facing=up]": "210:1" +"minecraft:repeating_command_block[conditional=false,facing=north]": "210:2" +"minecraft:repeating_command_block[conditional=false,facing=south]": "210:3" +"minecraft:repeating_command_block[conditional=false,facing=west]": "210:4" +"minecraft:repeating_command_block[conditional=false,facing=east]": "210:5" +"minecraft:repeating_command_block[conditional=true,facing=down]": "210:8" +"minecraft:repeating_command_block[conditional=true,facing=up]": "210:9" +"minecraft:repeating_command_block[conditional=true,facing=north]": "210:10" +"minecraft:repeating_command_block[conditional=true,facing=south]": "210:11" +"minecraft:repeating_command_block[conditional=true,facing=west]": "210:12" +"minecraft:repeating_command_block[conditional=true,facing=east]": "210:13" +"minecraft:chain_command_block[conditional=false,facing=down]": "211:0" +"minecraft:chain_command_block[conditional=false,facing=up]": "211:1" +"minecraft:chain_command_block[conditional=false,facing=north]": "211:2" +"minecraft:chain_command_block[conditional=false,facing=south]": "211:3" +"minecraft:chain_command_block[conditional=false,facing=west]": "211:4" +"minecraft:chain_command_block[conditional=false,facing=east]": "211:5" +"minecraft:chain_command_block[conditional=true,facing=down]": "211:8" +"minecraft:chain_command_block[conditional=true,facing=up]": "211:9" +"minecraft:chain_command_block[conditional=true,facing=north]": "211:10" +"minecraft:chain_command_block[conditional=true,facing=south]": "211:11" +"minecraft:chain_command_block[conditional=true,facing=west]": "211:12" +"minecraft:chain_command_block[conditional=true,facing=east]": "211:13" +"minecraft:frosted_ice[age=0]": "212:0" +"minecraft:frosted_ice[age=1]": "212:1" +"minecraft:frosted_ice[age=2]": "212:2" +"minecraft:frosted_ice[age=3]": "212:3" +"minecraft:magma_block": "213:0" +"minecraft:nether_wart_block": "214:0" +"minecraft:red_nether_bricks": "215:0" +"minecraft:bone_block[axis=y]": "216:0" +"minecraft:bone_block[axis=x]": "216:4" +"minecraft:bone_block[axis=z]": "216:8" +"minecraft:structure_void": "217:0" +"minecraft:observer[powered=false,facing=down]": "218:0" +"minecraft:observer[powered=false,facing=up]": "218:1" +"minecraft:observer[powered=false,facing=north]": "218:2" +"minecraft:observer[powered=false,facing=south]": "218:3" +"minecraft:observer[powered=false,facing=west]": "218:4" +"minecraft:observer[powered=false,facing=east]": "218:5" +"minecraft:observer[powered=true,facing=down]": "218:8" +"minecraft:observer[powered=true,facing=up]": "218:9" +"minecraft:observer[powered=true,facing=north]": "218:10" +"minecraft:observer[powered=true,facing=south]": "218:11" +"minecraft:observer[powered=true,facing=west]": "218:12" +"minecraft:observer[powered=true,facing=east]": "218:13" +"minecraft:white_shulker_box[facing=down]": "219:0" +"minecraft:white_shulker_box[facing=up]": "219:1" +"minecraft:white_shulker_box[facing=north]": "219:2" +"minecraft:white_shulker_box[facing=south]": "219:3" +"minecraft:white_shulker_box[facing=west]": "219:4" +"minecraft:white_shulker_box[facing=east]": "219:5" +"minecraft:orange_shulker_box[facing=down]": "220:0" +"minecraft:orange_shulker_box[facing=up]": "220:1" +"minecraft:orange_shulker_box[facing=north]": "220:2" +"minecraft:orange_shulker_box[facing=south]": "220:3" +"minecraft:orange_shulker_box[facing=west]": "220:4" +"minecraft:orange_shulker_box[facing=east]": "220:5" +"minecraft:magenta_shulker_box[facing=down]": "221:0" +"minecraft:magenta_shulker_box[facing=up]": "221:1" +"minecraft:magenta_shulker_box[facing=north]": "221:2" +"minecraft:magenta_shulker_box[facing=south]": "221:3" +"minecraft:magenta_shulker_box[facing=west]": "221:4" +"minecraft:magenta_shulker_box[facing=east]": "221:5" +"minecraft:light_blue_shulker_box[facing=down]": "222:0" +"minecraft:light_blue_shulker_box[facing=up]": "222:1" +"minecraft:light_blue_shulker_box[facing=north]": "222:2" +"minecraft:light_blue_shulker_box[facing=south]": "222:3" +"minecraft:light_blue_shulker_box[facing=west]": "222:4" +"minecraft:light_blue_shulker_box[facing=east]": "222:5" +"minecraft:yellow_shulker_box[facing=down]": "223:0" +"minecraft:yellow_shulker_box[facing=up]": "223:1" +"minecraft:yellow_shulker_box[facing=north]": "223:2" +"minecraft:yellow_shulker_box[facing=south]": "223:3" +"minecraft:yellow_shulker_box[facing=west]": "223:4" +"minecraft:yellow_shulker_box[facing=east]": "223:5" +"minecraft:lime_shulker_box[facing=down]": "224:0" +"minecraft:lime_shulker_box[facing=up]": "224:1" +"minecraft:lime_shulker_box[facing=north]": "224:2" +"minecraft:lime_shulker_box[facing=south]": "224:3" +"minecraft:lime_shulker_box[facing=west]": "224:4" +"minecraft:lime_shulker_box[facing=east]": "224:5" +"minecraft:pink_shulker_box[facing=down]": "225:0" +"minecraft:pink_shulker_box[facing=up]": "225:1" +"minecraft:pink_shulker_box[facing=north]": "225:2" +"minecraft:pink_shulker_box[facing=south]": "225:3" +"minecraft:pink_shulker_box[facing=west]": "225:4" +"minecraft:pink_shulker_box[facing=east]": "225:5" +"minecraft:gray_shulker_box[facing=down]": "226:0" +"minecraft:gray_shulker_box[facing=up]": "226:1" +"minecraft:gray_shulker_box[facing=north]": "226:2" +"minecraft:gray_shulker_box[facing=south]": "226:3" +"minecraft:gray_shulker_box[facing=west]": "226:4" +"minecraft:gray_shulker_box[facing=east]": "226:5" +"minecraft:light_gray_shulker_box[facing=down]": "227:0" +"minecraft:light_gray_shulker_box[facing=up]": "227:1" +"minecraft:light_gray_shulker_box[facing=north]": "227:2" +"minecraft:light_gray_shulker_box[facing=south]": "227:3" +"minecraft:light_gray_shulker_box[facing=west]": "227:4" +"minecraft:light_gray_shulker_box[facing=east]": "227:5" +"minecraft:cyan_shulker_box[facing=down]": "228:0" +"minecraft:cyan_shulker_box[facing=up]": "228:1" +"minecraft:cyan_shulker_box[facing=north]": "228:2" +"minecraft:cyan_shulker_box[facing=south]": "228:3" +"minecraft:cyan_shulker_box[facing=west]": "228:4" +"minecraft:cyan_shulker_box[facing=east]": "228:5" +"minecraft:purple_shulker_box[facing=down]": "229:0" +"minecraft:purple_shulker_box[facing=up]": "229:1" +"minecraft:purple_shulker_box[facing=north]": "229:2" +"minecraft:purple_shulker_box[facing=south]": "229:3" +"minecraft:purple_shulker_box[facing=west]": "229:4" +"minecraft:purple_shulker_box[facing=east]": "229:5" +"minecraft:blue_shulker_box[facing=down]": "230:0" +"minecraft:blue_shulker_box[facing=up]": "230:1" +"minecraft:blue_shulker_box[facing=north]": "230:2" +"minecraft:blue_shulker_box[facing=south]": "230:3" +"minecraft:blue_shulker_box[facing=west]": "230:4" +"minecraft:blue_shulker_box[facing=east]": "230:5" +"minecraft:brown_shulker_box[facing=down]": "231:0" +"minecraft:brown_shulker_box[facing=up]": "231:1" +"minecraft:brown_shulker_box[facing=north]": "231:2" +"minecraft:brown_shulker_box[facing=south]": "231:3" +"minecraft:brown_shulker_box[facing=west]": "231:4" +"minecraft:brown_shulker_box[facing=east]": "231:5" +"minecraft:green_shulker_box[facing=down]": "232:0" +"minecraft:green_shulker_box[facing=up]": "232:1" +"minecraft:green_shulker_box[facing=north]": "232:2" +"minecraft:green_shulker_box[facing=south]": "232:3" +"minecraft:green_shulker_box[facing=west]": "232:4" +"minecraft:green_shulker_box[facing=east]": "232:5" +"minecraft:red_shulker_box[facing=down]": "233:0" +"minecraft:red_shulker_box[facing=up]": "233:1" +"minecraft:red_shulker_box[facing=north]": "233:2" +"minecraft:red_shulker_box[facing=south]": "233:3" +"minecraft:red_shulker_box[facing=west]": "233:4" +"minecraft:red_shulker_box[facing=east]": "233:5" +"minecraft:black_shulker_box[facing=down]": "234:0" +"minecraft:black_shulker_box[facing=up]": "234:1" +"minecraft:black_shulker_box[facing=north]": "234:2" +"minecraft:black_shulker_box[facing=south]": "234:3" +"minecraft:black_shulker_box[facing=west]": "234:4" +"minecraft:black_shulker_box[facing=east]": "234:5" +"minecraft:white_glazed_terracotta[facing=south]": "235:0" +"minecraft:white_glazed_terracotta[facing=west]": "235:1" +"minecraft:white_glazed_terracotta[facing=north]": "235:2" +"minecraft:white_glazed_terracotta[facing=east]": "235:3" +"minecraft:orange_glazed_terracotta[facing=south]": "236:0" +"minecraft:orange_glazed_terracotta[facing=west]": "236:1" +"minecraft:orange_glazed_terracotta[facing=north]": "236:2" +"minecraft:orange_glazed_terracotta[facing=east]": "236:3" +"minecraft:magenta_glazed_terracotta[facing=south]": "237:0" +"minecraft:magenta_glazed_terracotta[facing=west]": "237:1" +"minecraft:magenta_glazed_terracotta[facing=north]": "237:2" +"minecraft:magenta_glazed_terracotta[facing=east]": "237:3" +"minecraft:light_blue_glazed_terracotta[facing=south]": "238:0" +"minecraft:light_blue_glazed_terracotta[facing=west]": "238:1" +"minecraft:light_blue_glazed_terracotta[facing=north]": "238:2" +"minecraft:light_blue_glazed_terracotta[facing=east]": "238:3" +"minecraft:yellow_glazed_terracotta[facing=south]": "239:0" +"minecraft:yellow_glazed_terracotta[facing=west]": "239:1" +"minecraft:yellow_glazed_terracotta[facing=north]": "239:2" +"minecraft:yellow_glazed_terracotta[facing=east]": "239:3" +"minecraft:lime_glazed_terracotta[facing=south]": "240:0" +"minecraft:lime_glazed_terracotta[facing=west]": "240:1" +"minecraft:lime_glazed_terracotta[facing=north]": "240:2" +"minecraft:lime_glazed_terracotta[facing=east]": "240:3" +"minecraft:pink_glazed_terracotta[facing=south]": "241:0" +"minecraft:pink_glazed_terracotta[facing=west]": "241:1" +"minecraft:pink_glazed_terracotta[facing=north]": "241:2" +"minecraft:pink_glazed_terracotta[facing=east]": "241:3" +"minecraft:gray_glazed_terracotta[facing=south]": "242:0" +"minecraft:gray_glazed_terracotta[facing=west]": "242:1" +"minecraft:gray_glazed_terracotta[facing=north]": "242:2" +"minecraft:gray_glazed_terracotta[facing=east]": "242:3" +"minecraft:light_gray_glazed_terracotta[facing=south]": "243:0" +"minecraft:light_gray_glazed_terracotta[facing=west]": "243:1" +"minecraft:light_gray_glazed_terracotta[facing=north]": "243:2" +"minecraft:light_gray_glazed_terracotta[facing=east]": "243:3" +"minecraft:cyan_glazed_terracotta[facing=south]": "244:0" +"minecraft:cyan_glazed_terracotta[facing=west]": "244:1" +"minecraft:cyan_glazed_terracotta[facing=north]": "244:2" +"minecraft:cyan_glazed_terracotta[facing=east]": "244:3" +"minecraft:purple_glazed_terracotta[facing=south]": "245:0" +"minecraft:purple_glazed_terracotta[facing=west]": "245:1" +"minecraft:purple_glazed_terracotta[facing=north]": "245:2" +"minecraft:purple_glazed_terracotta[facing=east]": "245:3" +"minecraft:blue_glazed_terracotta[facing=south]": "246:0" +"minecraft:blue_glazed_terracotta[facing=west]": "246:1" +"minecraft:blue_glazed_terracotta[facing=north]": "246:2" +"minecraft:blue_glazed_terracotta[facing=east]": "246:3" +"minecraft:brown_glazed_terracotta[facing=south]": "247:0" +"minecraft:brown_glazed_terracotta[facing=west]": "247:1" +"minecraft:brown_glazed_terracotta[facing=north]": "247:2" +"minecraft:brown_glazed_terracotta[facing=east]": "247:3" +"minecraft:green_glazed_terracotta[facing=south]": "248:0" +"minecraft:green_glazed_terracotta[facing=west]": "248:1" +"minecraft:green_glazed_terracotta[facing=north]": "248:2" +"minecraft:green_glazed_terracotta[facing=east]": "248:3" +"minecraft:red_glazed_terracotta[facing=south]": "249:0" +"minecraft:red_glazed_terracotta[facing=west]": "249:1" +"minecraft:red_glazed_terracotta[facing=north]": "249:2" +"minecraft:red_glazed_terracotta[facing=east]": "249:3" +"minecraft:black_glazed_terracotta[facing=south]": "250:0" +"minecraft:black_glazed_terracotta[facing=west]": "250:1" +"minecraft:black_glazed_terracotta[facing=north]": "250:2" +"minecraft:black_glazed_terracotta[facing=east]": "250:3" +"minecraft:white_concrete": "251:0" +"minecraft:orange_concrete": "251:1" +"minecraft:magenta_concrete": "251:2" +"minecraft:light_blue_concrete": "251:3" +"minecraft:yellow_concrete": "251:4" +"minecraft:lime_concrete": "251:5" +"minecraft:pink_concrete": "251:6" +"minecraft:gray_concrete": "251:7" +"minecraft:light_gray_concrete": "251:8" +"minecraft:cyan_concrete": "251:9" +"minecraft:purple_concrete": "251:10" +"minecraft:blue_concrete": "251:11" +"minecraft:brown_concrete": "251:12" +"minecraft:green_concrete": "251:13" +"minecraft:red_concrete": "251:14" +"minecraft:black_concrete": "251:15" +"minecraft:white_concrete_powder": "252:0" +"minecraft:orange_concrete_powder": "252:1" +"minecraft:magenta_concrete_powder": "252:2" +"minecraft:light_blue_concrete_powder": "252:3" +"minecraft:yellow_concrete_powder": "252:4" +"minecraft:lime_concrete_powder": "252:5" +"minecraft:pink_concrete_powder": "252:6" +"minecraft:gray_concrete_powder": "252:7" +"minecraft:light_gray_concrete_powder": "252:8" +"minecraft:cyan_concrete_powder": "252:9" +"minecraft:purple_concrete_powder": "252:10" +"minecraft:blue_concrete_powder": "252:11" +"minecraft:brown_concrete_powder": "252:12" +"minecraft:green_concrete_powder": "252:13" +"minecraft:red_concrete_powder": "252:14" +"minecraft:black_concrete_powder": "252:15" +"minecraft:structure_block[mode=save]": "255:0" +"minecraft:structure_block[mode=load]": "255:1" +"minecraft:structure_block[mode=corner]": "255:2" +"minecraft:structure_block[mode=data]": "255:3" \ No newline at end of file diff --git a/SpigotCore/SpigotCore_9/build.gradle.kts b/SpigotCore/SpigotCore_9/build.gradle.kts new file mode 100644 index 00000000..8a73a394 --- /dev/null +++ b/SpigotCore/SpigotCore_9/build.gradle.kts @@ -0,0 +1,57 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2024 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +plugins { + id("java") + id("base") +} + +group = "de.steamwar" +version = "" + +tasks.compileJava { + options.encoding = "UTF-8" +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +sourceSets { + main { + java { + srcDirs("src/") + } + resources { + srcDirs("src/") + exclude("**/*.java", "**/*.kt") + } + } +} + +dependencies { + compileOnly("org.projectlombok:lombok:1.18.32") + annotationProcessor("org.projectlombok:lombok:1.18.32") + + compileOnly(project(":SpigotCore:SpigotCore_Main")) + compileOnly(project(":SpigotCore:SpigotCore_8")) + + compileOnly("de.steamwar:spigot:1.9") +} diff --git a/SpigotCore/SpigotCore_9/src/de/steamwar/core/BountifulWrapper9.java b/SpigotCore/SpigotCore_9/src/de/steamwar/core/BountifulWrapper9.java new file mode 100644 index 00000000..2832f2f4 --- /dev/null +++ b/SpigotCore/SpigotCore_9/src/de/steamwar/core/BountifulWrapper9.java @@ -0,0 +1,95 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + */ + +package de.steamwar.core; + +import com.comphenix.tinyprotocol.Reflection; +import com.viaversion.viaversion.api.Via; +import net.md_5.bungee.api.ChatMessageType; +import net.md_5.bungee.api.chat.BaseComponent; +import org.bukkit.Sound; +import org.bukkit.entity.Player; + +import java.util.UUID; + +public class BountifulWrapper9 implements BountifulWrapper.IBountifulWrapper { + + @Override + public void playPling(Player player) { + player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1, 1); + } + + @Override + public void sendMessage(Player player, ChatMessageType type, BaseComponent... msg) { + if(type == ChatMessageType.CHAT && Via.getAPI().getPlayerVersion(player.getUniqueId()) >= 759) + type = ChatMessageType.SYSTEM; + + player.spigot().sendMessage(type, msg); + } + + private static final Class dataWatcherObject = Reflection.getClass("{nms.network.syncher}.DataWatcherObject"); + private static final Class dataWatcherRegistry = Reflection.getClass("{nms.network.syncher}.DataWatcherRegistry"); + private static final Class dataWatcherSerializer = Reflection.getClass("{nms.network.syncher}.DataWatcherSerializer"); + private static final Reflection.ConstructorInvoker dataWatcherObjectConstructor = Reflection.getConstructor(dataWatcherObject, int.class, dataWatcherSerializer); + @Override + public Object getDataWatcherObject(int index, Class type) { + return dataWatcherObjectConstructor.invoke(index, Reflection.getField(dataWatcherRegistry, dataWatcherSerializer, 0, type).get(null)); + } + + private static final Class item = Reflection.getClass("{nms.network.syncher}.DataWatcher$Item"); + private static final Reflection.ConstructorInvoker itemConstructor = Reflection.getConstructor(item, dataWatcherObject, Object.class); + @Override + public Object getDataWatcherItem(Object dwo, Object value) { + return itemConstructor.invoke(dwo, value); + } + + @Override + public BountifulWrapper.PositionSetter getPositionSetter(Class packetClass, int fieldOffset) { + Reflection.FieldAccessor posX = Reflection.getField(packetClass, double.class, fieldOffset); + Reflection.FieldAccessor posY = Reflection.getField(packetClass, double.class, fieldOffset+1); + Reflection.FieldAccessor posZ = Reflection.getField(packetClass, double.class, fieldOffset+2); + + return (packet, x, y, z) -> { + posX.set(packet, x); + posY.set(packet, y); + posZ.set(packet, z); + }; + } + + @Override + public BountifulWrapper.PositionSetter getRelMoveSetter(Class packetClass) { + Class type = Core.getVersion() > 12 ? short.class : int.class; + Reflection.FieldAccessor moveX = Reflection.getField(packetClass, "b", type); + Reflection.FieldAccessor moveY = Reflection.getField(packetClass, "c", type); + Reflection.FieldAccessor moveZ = Reflection.getField(packetClass, "d", type); + + return (packet, x, y, z) -> { + moveX.set(packet, (short)(x*4096)); + moveY.set(packet, (short)(y*4096)); + moveZ.set(packet, (short)(z*4096)); + }; + } + + @Override + public BountifulWrapper.UUIDSetter getUUIDSetter(Class packetClass) { + Reflection.FieldAccessor uuidField = Reflection.getField(packetClass, UUID.class, 0); + + return uuidField::set; + } +} diff --git a/SpigotCore/SpigotCore_9/src/de/steamwar/core/CraftbukkitWrapper9.java b/SpigotCore/SpigotCore_9/src/de/steamwar/core/CraftbukkitWrapper9.java new file mode 100644 index 00000000..8d111e31 --- /dev/null +++ b/SpigotCore/SpigotCore_9/src/de/steamwar/core/CraftbukkitWrapper9.java @@ -0,0 +1,37 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.core; + +import com.comphenix.tinyprotocol.Reflection; +import com.comphenix.tinyprotocol.TinyProtocol; +import org.bukkit.entity.Player; + +public class CraftbukkitWrapper9 implements CraftbukkitWrapper.ICraftbukkitWrapper { + + private static final Class chunk = Reflection.getClass("{nms.world.level.chunk}.Chunk"); + private static final Class packetPlayOutMapChunk = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutMapChunk"); + private static final Reflection.ConstructorInvoker newPacketPlayOutMapChunk = Reflection.getConstructor(packetPlayOutMapChunk, chunk, int.class); + private static final Reflection.MethodInvoker getHandle = Reflection.getMethod("{obc}.CraftChunk", "getHandle"); + + @Override + public void sendChunk(Player p, int chunkX, int chunkZ) { + TinyProtocol.instance.sendPacket(p, newPacketPlayOutMapChunk.invoke(getHandle.invoke(p.getWorld().getChunkAt(chunkX, chunkZ)), 65535)); + } +} diff --git a/SpigotCore/SpigotCore_9/src/de/steamwar/techhider/ChunkHider9.java b/SpigotCore/SpigotCore_9/src/de/steamwar/techhider/ChunkHider9.java new file mode 100644 index 00000000..59e279f5 --- /dev/null +++ b/SpigotCore/SpigotCore_9/src/de/steamwar/techhider/ChunkHider9.java @@ -0,0 +1,169 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2021 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + */ + +package de.steamwar.techhider; + +import com.comphenix.tinyprotocol.Reflection; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.bukkit.entity.Player; + +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; + +public class ChunkHider9 extends ChunkHider8 { + + private static final UnaryOperator mapChunkCloner = ProtocolUtils.shallowCloneGenerator(mapChunkPacket); + + private static final Reflection.FieldAccessor mapChunkX = Reflection.getField(mapChunkPacket, int.class, 0); + private static final Reflection.FieldAccessor mapChunkZ = Reflection.getField(mapChunkPacket, int.class, 1); + private static final Reflection.FieldAccessor mapChunkBitMask = Reflection.getField(mapChunkPacket, int.class, 2); + private static final Reflection.FieldAccessor mapChunkBlockEntities = Reflection.getField(mapChunkPacket, List.class, 0); + private static final Reflection.FieldAccessor mapChunkData = Reflection.getField(mapChunkPacket, byte[].class, 0); + + private static final Class nbtTagCompound = Reflection.getClass("{nms.nbt}.NBTTagCompound"); + private static final Reflection.MethodInvoker nbtTagGetString = Reflection.getTypedMethod(nbtTagCompound, null, String.class, String.class); + + @Override + public BiFunction chunkHiderGenerator(TechHider.LocationEvaluator locationEvaluator, int obfuscationTarget, Set obfuscate, Set hiddenBlockEntities) { + return (p, packet) -> { + int chunkX = mapChunkX.get(packet); + int chunkZ = mapChunkZ.get(packet); + if (locationEvaluator.skipChunk(p, chunkX, chunkZ)) + return packet; + + packet = mapChunkCloner.apply(packet); + mapChunkBlockEntities.set(packet, ((List)mapChunkBlockEntities.get(packet)).stream().filter( + nbttag -> !hiddenBlockEntities.contains((String) nbtTagGetString.invoke(nbttag, "id")) + ).collect(Collectors.toList())); + + int xOffset = 16*chunkX; + int zOffset = 16*chunkZ; + int primaryBitMask = mapChunkBitMask.get(packet); + ByteBuf in = Unpooled.wrappedBuffer(mapChunkData.get(packet)); + ByteBuf out = Unpooled.buffer(in.readableBytes() + 64); + for(int chunkY = 0; chunkY < p.getWorld().getMaxHeight()/16; chunkY++) { + if(((1 << chunkY) & primaryBitMask) == 0) + continue; + + int yOffset = 16*chunkY; + dataHider((x, y, z) -> locationEvaluator.check(p, x+xOffset, y+yOffset, z+zOffset), obfuscationTarget, obfuscate, in, out, locationEvaluator.skipChunkSection(p, chunkX, chunkY, chunkZ), locationEvaluator.blockPrecise(p, chunkX, chunkY, chunkZ)); + } + byte[] data = new byte[out.readableBytes()]; + out.readBytes(data); + mapChunkData.set(packet, data); + + return packet; + }; + } + + protected void dataHider(PosEvaluator locationEvaluator, int obfuscationTarget, Set obfuscate, ByteBuf in, ByteBuf out, boolean skip, boolean blockPrecise) { + byte bitsPerBlock = in.readByte(); + out.writeByte(bitsPerBlock); + + int paletteTarget = ChunkHider.processPalette(obfuscationTarget, skip ? Collections.emptySet() : obfuscate, in, out); + if(bitsPerBlock < 13) { + obfuscationTarget = paletteTarget; + obfuscate = Collections.emptySet(); + } + + processDataArray(locationEvaluator, obfuscationTarget, obfuscate, in, out, bitsPerBlock, skip || (!blockPrecise && bitsPerBlock < 9)); + + out.writeBytes(in, 4096); //Skylight (Not in Nether/End!!!) 2048 + Blocklight 2048 + } + + protected void processDataArray(PosEvaluator locationEvaluator, int obfuscationTarget, Set obfuscate, ByteBuf in, ByteBuf out, int bitsPerBlock, boolean skip) { + int dataArrayLength = ProtocolUtils.readVarInt(in); + ProtocolUtils.writeVarInt(out, dataArrayLength); + + if(skip) { + out.writeBytes(in, dataArrayLength*8); + return; + } + + VariableValueArray values = new VariableValueArray(bitsPerBlock, dataArrayLength, in); + for (int y = 0; y < 16; y++) { + for (int z = 0; z < 16; z++) { + for (int x = 0; x < 16; x++) { + int pos = (((y * 16) + z) * 16) + x; + + switch (locationEvaluator.test(x, y, z)) { + case SKIP: + break; + case CHECK: + if(!obfuscate.contains(values.get(pos))) + break; + default: + values.set(pos, obfuscationTarget); + } + } + } + } + + for(long l : values.backing) + out.writeLong(l); + } + + private static class VariableValueArray { + private final long[] backing; + private final int bitsPerValue; + private final long valueMask; + + public VariableValueArray(int bitsPerEntry, int dataArrayLength, ByteBuf in) { + this.bitsPerValue = bitsPerEntry; + this.valueMask = (1L << this.bitsPerValue) - 1; + + this.backing = new long[dataArrayLength]; + for(int i = 0; i < dataArrayLength; i++) + backing[i] = in.readLong(); + } + + public int get(int index) { + index *= bitsPerValue; + int i0 = index >> 6; + int i1 = index & 0x3f; + + long value = backing[i0] >>> i1; + + // The value is divided over two long values + if (i1 + bitsPerValue > 64) + value |= backing[++i0] << 64 - i1; + + return (int) (value & valueMask); + } + + public void set(int index, int value) { + index *= bitsPerValue; + int i0 = index >> 6; + int i1 = index & 0x3f; + + backing[i0] = backing[i0] & ~(valueMask << i1) | (value & valueMask) << i1; + int i2 = i1 + bitsPerValue; + // The value is divided over two long values + if (i2 > 64) { + i0++; + backing[i0] = backing[i0] & -(1L << i2 - 64) | value >> 64 - i1; + } + } + } +} diff --git a/SpigotCore/SpigotCore_Main/build.gradle.kts b/SpigotCore/SpigotCore_Main/build.gradle.kts new file mode 100644 index 00000000..95e30238 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/build.gradle.kts @@ -0,0 +1,65 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2024 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +plugins { + id("java") + id("base") +} + +group = "de.steamwar" +version = "" + +tasks.compileJava { + options.encoding = "UTF-8" +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +sourceSets { + main { + java { + srcDirs("src/") + } + resources { + srcDirs("src/") + exclude("**/*.java", "**/*.kt") + } + } +} + +dependencies { + compileOnly("org.projectlombok:lombok:1.18.32") + annotationProcessor("org.projectlombok:lombok:1.18.32") + + compileOnly(project(":CommonCore")) + compileOnly(project(":CommandFramework")) + + compileOnly("de.steamwar:worldedit:1.12") + + compileOnly("org.spigotmc:spigot-api:1.19-R0.1-SNAPSHOT") + compileOnly("io.netty:netty-all:4.1.68.Final") + compileOnly("com.mojang:authlib:1.5.25") + compileOnly("mysql:mysql-connector-java:5.1.49") + compileOnly("com.viaversion:viaversion-api:4.3.1") + compileOnly("it.unimi.dsi:fastutil:8.5.6") + implementation("net.wesjd:anvilgui:1.7.0-SNAPSHOT") +} diff --git a/SpigotCore/SpigotCore_Main/src/src/SpigotCore.properties b/SpigotCore/SpigotCore_Main/src/src/SpigotCore.properties new file mode 100644 index 00000000..04425f0a --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/SpigotCore.properties @@ -0,0 +1,107 @@ +# +# This file is a part of the SteamWar software. +# +# Copyright (C) 2022 SteamWar.de-Serverteam +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +LOCAL_CHAT=§eLocal §r{0}§8»§7 {1} + +COMMAND_SYSTEM_ERROR = §cError executing the command! + +SWLISINV_NEXT_PAGE_ACTIVE = §eNext page +SWLISINV_NEXT_PAGE_INACTIVE = §7Next page +SWLISINV_PREVIOUS_PAGE_ACTIVE = §ePrevious page +SWLISINV_PREVIOUS_PAGE_INACTIVE = §7Previous page + +SCHEM_SELECTOR_TITLE={0} selection: {1} +SCHEM_SELECTOR_BACK=§eBack +SCHEM_SELECTOR_DIR=§9Directory +SCHEM_SELECTOR_RANK=§8Rank {0} +SCHEM_SELECTOR_OWN=§7Own schematics +SCHEM_SELECTOR_PUB=§7Public schematics +SCHEM_SELECTOR_SEL_DIR=§7Select directory +SCHEM_SELECTOR_NEW_DIR=§7New directory +SCHEM_SELECTOR_FILTER=§7Filter +SCHEM_SELECTOR_SORTING=§7Order by +SCHEM_SELECTOR_SORTING_CURRENT=§7Current: §e{0} +SCHEM_SELECTOR_SORTING_NAME=Name +SCHEM_SELECTOR_SORTING_TYPE=Schematic type +SCHEM_SELECTOR_SORTING_UPDATE=Last update +SCHEM_SELECTOR_SORTING_DIRECTION=§e{0} §7order +SCHEM_SELECTOR_SORTING_ASC=Ascending +SCHEM_SELECTOR_SORTING_DSC=Descending +SCHEM_SELECTOR_CLICK_BACK=§7Click to go back + +SCHEM_SELECTOR_ITEM_NAME=§e{0} +SCHEM_SELECTOR_ITEM_NAME_FILTER=§7{0} +SCHEM_SELECTOR_ITEM_REPLACE=§e{0}§7 +SCHEM_SELECTOR_ITEM_LORE_TYPE=§7{0} + +SCHEM_SELECTOR_CREATE_DIR_TITLE=Create directory + +SCHEM_SELECTOR_FILTER_TITLE=Filter +SCHEM_SELECTOR_FILTER_ENTER_NAME=Insert name +SCHEM_SELECTOR_FILTER_NAME=§7Search by name... +SCHEM_SELECTOR_FILTER_NAME_SEARCH=§7Search term: §e{0} +SCHEM_SELECTOR_FILTER_ENTER_OWNER=Choose owner +SCHEM_SELECTOR_FILTER_OWNER=§7Search by owner... +SCHEM_SELECTOR_FILTER_OWNER_SEARCH=§7Owner: §e{0} +SCHEM_SELECTOR_FILTER_SEL_TYPE=Choose type... +SCHEM_SELECTOR_FILTER_TYPE=§7Search by type... +SCHEM_SELECTOR_FILTER_TYPE_SEARCH=§7Type: §e{0} +SCHEM_SELECTOR_FILTER_MAT=§7Filter by item... +SCHEM_SELECTOR_FILTER_MAT_SEARCH=§7Item: §e{0} +SCHEM_SELECTOR_CANCEL=§eCancel +SCHEM_SELECTOR_GO=§eSearch... +SCHEM_SELECTOR_SCHEMATIC=Schematic +SCHEM_SELECTOR_DIRECTORY=Directory +SCHEM_SELECTOR_SCHEMATIC_NODE=Schematic/Directory + +SCHEM_SELECTOR_FILTER_TITLE_SINGLE=§eSingle Filter +SCHEM_SELECTOR_FILTER_TITLE_MULTI=§e{0} Filters +SCHEM_SELECTOR_FILTER_TITLE_EMPTY=§eNo Filters +SCHEM_SELECTOR_FILTER_EMPTY=§7Empty + +MATERIAL_SELECTOR_TITLE=Select material + +BAN_TEAM={0} §e{1} §7was §e§lbanned§7 by §e{2} {3}§8: §f{4} +BAN_PERMA=§7You are §e§lbanned §epermanently§8: §e{0} +BAN_UNTIL=§7You are §e§lbanned §euntil {0}§8: §e{1} +UNBAN_ERROR=§cThe player isn't banned. +UNBAN=§7You have §e§lunbanned §e{0}. + +MUTE_TEAM={0} §e{1} §7was §e§lmuted§7 by §e{2} {3}§8: §f{4} +MUTE_PERMA=§7You are §epermanently §e§lmuted§8: §e{0} +MUTE_UNTIL=§7You are §e§lmuted §euntil {0}§8: §e{1} +UNMUTE_ERROR=§cThe player isn't muted. +UNMUTE=§7You have §e§lunmuted §e{0}. + +NOSCHEMRECEIVING_TEAM={0} §e{1} §7was excluded from §e{2} {3} §7from §e§lrecieving schematics§8: §f{4} +NOSCHEMRECEIVING_PERMA=§7You are §epermanently§7 excluded from receiving §e§lschematics§8: §e{0} +NOSCHEMRECEIVING_UNTIL=§7You are excluded from receiving §e§lschematics §euntil {0}§8: §e{1} +UNNOSCHEMRECEIVING_ERROR=§cThe player is not excluded from receiving schematics. +UNNOSCHEMRECEIVING=§e{0} §7may now receive §e§lschematics§7 again§8. + +NOSCHEMSHARING_TEAM={0} §e{1} §7was excluded from §e{2} {3} §7from §e§lsharing schematics§8: §f{4} +NOSCHEMSHARING_PERMA=§7You are §epermanently§7 excluded from sharing §e§lschematics§8: §e{0} +NOSCHEMSHARING_UNTIL=§7You are excluded from sharing §e§lschematics §euntil {0}§8: §e{1} +UNNOSCHEMSHARING_ERROR=§cThe player is not excluded from sharing schematics. +UNNOSCHEMSHARING=§e{0} §7may now share §e§lschematics§7 again§8. + +NOSCHEMSUBMITTING_TEAM={0} §e{1} §7was excluded from §e{2} {3} §7from §e§lsubmitting schematics§8: §f{4} +NOSCHEMSUBMITTING_PERMA=§7You are §epermanently§7 excluded from submitting §e§lschematics§8: §e{0} +NOSCHEMSUBMITTING_UNTIL=§7You are excluded from submitting §e§lschematics §euntil {0}§8: §e{1} +UNNOSCHEMSUBMITTING_ERROR=§cThe player is not excluded from submitting schematics. +UNNOSCHEMSUBMITTING=§e{0} §7may now submit §e§lschematics§7 again§8. diff --git a/SpigotCore/SpigotCore_Main/src/src/SpigotCore_de.properties b/SpigotCore/SpigotCore_Main/src/src/SpigotCore_de.properties new file mode 100644 index 00000000..967190be --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/SpigotCore_de.properties @@ -0,0 +1,102 @@ +# +# This file is a part of the SteamWar software. +# +# Copyright (C) 2022 SteamWar.de-Serverteam +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +LOCAL_CHAT=§eLokal §r{0}§8»§7 {1} + +COMMAND_SYSTEM_ERROR = §cFehler beim Ausführen des Befehls! + +SWLISINV_NEXT_PAGE_ACTIVE = §eSeite vor +SWLISINV_NEXT_PAGE_INACTIVE = §7Seite vor +SWLISINV_PREVIOUS_PAGE_ACTIVE = §eSeite zurück +SWLISINV_PREVIOUS_PAGE_INACTIVE = §7Seite zurück + +SCHEM_SELECTOR_TITLE={0} auswählen: {1} +SCHEM_SELECTOR_BACK=§eZurück +SCHEM_SELECTOR_DIR=§9Ordner +SCHEM_SELECTOR_RANK=§8Rang {0} +SCHEM_SELECTOR_OWN=§7Eigene Schematics +SCHEM_SELECTOR_PUB=§7Public Schematics +SCHEM_SELECTOR_SEL_DIR=§7Ordner auswählen +SCHEM_SELECTOR_NEW_DIR=§7Neuer Ordner +SCHEM_SELECTOR_FILTER=§7Filter +SCHEM_SELECTOR_SORTING=§7Sortierung +SCHEM_SELECTOR_SORTING_CURRENT=§7Aktuell: §e{0} +SCHEM_SELECTOR_SORTING_NAME=Name +SCHEM_SELECTOR_SORTING_TYPE=Schematic-Typ +SCHEM_SELECTOR_SORTING_UPDATE=Letztes Update +SCHEM_SELECTOR_SORTING_DIRECTION=§7Richtung: §e{0} +SCHEM_SELECTOR_SORTING_ASC=Aufsteigend +SCHEM_SELECTOR_SORTING_DSC=Absteigend +SCHEM_SELECTOR_CLICK_BACK=§7Klicke hier, um zurückzugehen + +SCHEM_SELECTOR_CREATE_DIR_TITLE=Ordner erstellen + +SCHEM_SELECTOR_FILTER_TITLE=Filter +SCHEM_SELECTOR_FILTER_ENTER_NAME=Name eingeben +SCHEM_SELECTOR_FILTER_NAME=§7Nach Namen suchen... +SCHEM_SELECTOR_FILTER_NAME_SEARCH=§7Suchbegriff: §e{0} +SCHEM_SELECTOR_FILTER_ENTER_OWNER=Besitzer eingeben +SCHEM_SELECTOR_FILTER_OWNER=§7Nach Besitzer suchen... +SCHEM_SELECTOR_FILTER_OWNER_SEARCH=§7Besitzer: §e{0} +SCHEM_SELECTOR_FILTER_SEL_TYPE=Typ wählen... +SCHEM_SELECTOR_FILTER_TYPE=§7Nach Typ filtern... +SCHEM_SELECTOR_FILTER_TYPE_SEARCH=§7Typ: §e{0} +SCHEM_SELECTOR_FILTER_MAT=§7Nach Item filtern... +SCHEM_SELECTOR_FILTER_MAT_SEARCH=§7Item: §e{0} +SCHEM_SELECTOR_CANCEL=§eAbbrechen +SCHEM_SELECTOR_GO=§eSuchen... +SCHEM_SELECTOR_SCHEMATIC=Schematic +SCHEM_SELECTOR_DIRECTORY=Ordner +SCHEM_SELECTOR_SCHEMATIC_NODE=Schematic/Ordner + +SCHEM_SELECTOR_FILTER_TITLE_SINGLE=§eEinzelfilter +SCHEM_SELECTOR_FILTER_TITLE_MULTI=§e{0} §e§lMehrfachfilter +SCHEM_SELECTOR_FILTER_TITLE_EMPTY=§eKeine Filter +SCHEM_SELECTOR_FILTER_EMPTY=§7Leer + +MATERIAL_SELECTOR_TITLE=Material auswählen + +BAN_TEAM={0} §e{1} §7wurde von §e{2} {3} §e§lgebannt§8: §f{4} +BAN_PERMA=§7Du bist §epermanent §e§lgebannt§8: §e{0} +BAN_UNTIL=§7Du bist §ebis zum {0} §e§lgebannt§8: §e{1} +UNBAN_ERROR=§cDer Spieler ist nicht gebannt. +UNBAN=§7Du hast §e{0} §e§lentbannt. + +MUTE_TEAM={0} §e{1} §7wurde von §e{2} {3} §e§lgemuted§8: §f{4} +MUTE_PERMA=§7Du bist §epermanent §e§lgemuted§8: §e{0} +MUTE_UNTIL=§7Du bist §ebis zum {0} §e§lgemuted§8: §e{1} +UNMUTE_ERROR=§cDer Spieler ist nicht gemuted. +UNMUTE=§7Du hast §e{0} §e§lentmuted. + +NOSCHEMRECEIVING_TEAM={0} §e{1} §7wurde von §e{2} {3} §7vom §e§lSchematicerhalten§7 ausgeschlossen§8: §f{4} +NOSCHEMRECEIVING_PERMA=§7Du bist §epermanent §7vom Erhalten von §e§lSchematics§7 ausgeschlossen§8: §e{0} +NOSCHEMRECEIVING_UNTIL=§7Du bist §ebis zum {0} §7vom Erhalten von §e§lSchematics§7 ausgeschlossen§8: §e{1} +UNNOSCHEMRECEIVING_ERROR=§cDer Spieler ist nicht vom Erhalten von Schematics ausgeschlossen. +UNNOSCHEMRECEIVING=§e{0} §7darf nun wieder §e§lSchematics§7 erhalten§8. + +NOSCHEMSHARING_TEAM={0} §e{1} §7wurde von §e{2} {3} §7vom §e§lSchematicverteilen§7 ausgeschlossen§8: §f{4} +NOSCHEMSHARING_PERMA=§7Du bist §epermanent §7vom §e§lVerteilen von Schematics§7 ausgeschlossen§8: §e{0} +NOSCHEMSHARING_UNTIL=§7Du bist §ebis zum {0} §7vom §e§lVerteilen von Schematics§7 ausgeschlossen§8: §e{1} +UNNOSCHEMSHARING_ERROR=§cDer Spieler ist nicht vom Verteilen von Schematics ausgeschlossen. +UNNOSCHEMSHARING=§e{0} §7darf nun wieder §e§lSchematics§7 verteilen§8. + +NOSCHEMSUBMITTING_TEAM={0} §e{1} §7wurde von §e{2} {3} §7vom §e§lSchematiceinsenden§7 ausgeschlossen§8: §f{4} +NOSCHEMSUBMITTING_PERMA=§7Du bist §epermanent §7vom §e§lEinsenden von Schematics§7 ausgeschlossen§8: §e{0} +NOSCHEMSUBMITTING_UNTIL=§7Du bist §ebis zum {0} §7vom §e§lEinsenden von Schematics§7 ausgeschlossen§8: §e{1} +UNNOSCHEMSUBMITTING_ERROR=§cDer Spieler ist nicht vom Einsenden von Schematics ausgeschlossen. +UNNOSCHEMSUBMITTING=§e{0} §7darf nun wieder §e§lSchematis§7 einsenden§8. \ No newline at end of file diff --git a/SpigotCore/SpigotCore_Main/src/src/com/comphenix/tinyprotocol/Reflection.java b/SpigotCore/SpigotCore_Main/src/src/com/comphenix/tinyprotocol/Reflection.java new file mode 100644 index 00000000..fc1e8c1d --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/com/comphenix/tinyprotocol/Reflection.java @@ -0,0 +1,397 @@ +package com.comphenix.tinyprotocol; + +import de.steamwar.core.Core; +import jdk.internal.misc.Unsafe; +import org.bukkit.Bukkit; + +import java.lang.reflect.*; +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * An utility class that simplifies reflection in Bukkit plugins. + * + * @author Kristian + */ +public final class Reflection { + /** + * An interface for invoking a specific constructor. + */ + public interface ConstructorInvoker { + /** + * Invoke a constructor for a specific class. + * + * @param arguments - the arguments to pass to the constructor. + * @return The constructed object. + */ + Object invoke(Object... arguments); + } + + /** + * An interface for invoking a specific method. + */ + public interface MethodInvoker { + /** + * Invoke a method on a specific target object. + * + * @param target - the target object, or NULL for a static method. + * @param arguments - the arguments to pass to the method. + * @return The return value, or NULL if is void. + */ + Object invoke(Object target, Object... arguments); + } + + /** + * An interface for retrieving the field content. + * + * @param - field type. + */ + public interface FieldAccessor { + /** + * Retrieve the content of a field. + * + * @param target - the target object, or NULL for a static field. + * @return The value of the field. + */ + T get(Object target); + + /** + * Set the content of a field. + * + * @param target - the target object, or NULL for a static field. + * @param value - the new value of the field. + */ + void set(Object target, Object value); + + /** + * Determine if the given object has this field. + * + * @param target - the object to test. + * @return TRUE if it does, FALSE otherwise. + */ + boolean hasField(Object target); + } + + // Deduce the net.minecraft.server.v* package + private static final String OBC_PREFIX = Bukkit.getServer().getClass().getPackage().getName(); + private static final String NMS_PREFIX = OBC_PREFIX.replace("org.bukkit.craftbukkit", "net.minecraft.server"); + private static final String VERSION = OBC_PREFIX.replace("org.bukkit.craftbukkit", "").replace(".", ""); + + // Variable replacement + private static final Pattern MATCH_VARIABLE = Pattern.compile("\\{([^\\}]+)\\}"); + + private Reflection() { + // Seal class + } + + /** + * Retrieve a field accessor for a specific field type and name. + * + * @param target - the target type. + * @param name - the name of the field, or NULL to ignore. + * @param fieldType - a compatible field type. + * @return The field accessor. + */ + public static FieldAccessor getField(Class target, String name, Class fieldType) { + return getField(target, name, fieldType, 0); + } + + /** + * Retrieve a field accessor for a specific field type and name. + * + * @param className - lookup name of the class, see {@link #getClass(String)}. + * @param name - the name of the field, or NULL to ignore. + * @param fieldType - a compatible field type. + * @return The field accessor. + */ + public static FieldAccessor getField(String className, String name, Class fieldType) { + return getField(getClass(className), name, fieldType, 0); + } + + /** + * Retrieve a field accessor for a specific field type and name. + * + * @param target - the target type. + * @param fieldType - a compatible field type. + * @param index - the number of compatible fields to skip. + * @return The field accessor. + */ + public static FieldAccessor getField(Class target, Class fieldType, int index) { + return getField(target, null, fieldType, index); + } + + /** + * Retrieve a field accessor for a specific field type and name. + * + * @param className - lookup name of the class, see {@link #getClass(String)}. + * @param fieldType - a compatible field type. + * @param index - the number of compatible fields to skip. + * @return The field accessor. + */ + public static FieldAccessor getField(String className, Class fieldType, int index) { + return getField(getClass(className), fieldType, index); + } + + public static FieldAccessor getField(Class target, Class fieldType, int index, Class... parameters) { + return getField(target, null, fieldType, index, parameters); + } + + private static FieldAccessor getField(Class target, String name, Class fieldType, int index, Class... parameters) { + for (final Field field : target.getDeclaredFields()) { + if(matching(field, name, fieldType, parameters) && index-- <= 0) { + field.setAccessible(true); + + return new FieldAccessor() { + @Override + @SuppressWarnings("unchecked") + public T get(Object target) { + try { + return (T) field.get(target); + } catch (IllegalAccessException e) { + throw new IllegalArgumentException("Cannot access reflection.", e); + } + } + + @Override + public void set(Object target, Object value) { + try { + field.set(target, value); + } catch (IllegalAccessException e) { + throw new IllegalArgumentException("Cannot access reflection.", e); + } + } + + @Override + public boolean hasField(Object target) { + // target instanceof DeclaringClass + return field.getDeclaringClass().isAssignableFrom(target.getClass()); + } + }; + } + } + + // Search in parent classes + if (target.getSuperclass() != null) + return getField(target.getSuperclass(), name, fieldType, index); + + throw new IllegalArgumentException("Cannot find field with type " + fieldType); + } + + private static boolean matching(Field field, String name, Class fieldType, Class... parameters) { + if(name != null && !field.getName().equals(name)) + return false; + + if(!fieldType.isAssignableFrom(field.getType())) + return false; + + if(parameters.length > 0) { + Type[] arguments = ((ParameterizedType)field.getGenericType()).getActualTypeArguments(); + + for(int i = 0; i < parameters.length; i++) { + if(arguments[i] instanceof ParameterizedType ? ((ParameterizedType) arguments[i]).getRawType() != parameters[i] : arguments[i] != parameters[i]) + return false; + } + } + return true; + } + + /** + * Search for the first publicly and privately defined method of the given name and parameter count. + * + * @param className - lookup name of the class, see {@link #getClass(String)}. + * @param methodName - the method name, or NULL to skip. + * @param params - the expected parameters. + * @return An object that invokes this specific method. + * @throws IllegalStateException If we cannot find this method. + */ + public static MethodInvoker getMethod(String className, String methodName, Class... params) { + return getTypedMethod(getClass(className), methodName, null, params); + } + + /** + * Search for the first publicly and privately defined method of the given name and parameter count. + * + * @param clazz - a class to start with. + * @param methodName - the method name, or NULL to skip. + * @param params - the expected parameters. + * @return An object that invokes this specific method. + * @throws IllegalStateException If we cannot find this method. + */ + public static MethodInvoker getMethod(Class clazz, String methodName, Class... params) { + return getTypedMethod(clazz, methodName, null, params); + } + + /** + * Search for the first publicly and privately defined method of the given name and parameter count. + * + * @param clazz - a class to start with. + * @param methodName - the method name, or NULL to skip. + * @param returnType - the expected return type, or NULL to ignore. + * @param params - the expected parameters. + * @return An object that invokes this specific method. + * @throws IllegalStateException If we cannot find this method. + */ + public static MethodInvoker getTypedMethod(Class clazz, String methodName, Class returnType, Class... params) { + for (final Method method : clazz.getDeclaredMethods()) { + if ((methodName == null || method.getName().equals(methodName)) + && (returnType == null || method.getReturnType().equals(returnType)) + && Arrays.equals(method.getParameterTypes(), params)) { + method.setAccessible(true); + + return (target, arguments) -> { + try { + return method.invoke(target, arguments); + } catch (Exception e) { + throw new IllegalArgumentException("Cannot invoke method " + method, e); + } + }; + } + } + + // Search in every superclass + if (clazz.getSuperclass() != null) + return getTypedMethod(clazz.getSuperclass(), methodName, returnType, params); + + throw new IllegalStateException(String.format("Unable to find method %s (%s).", methodName, Arrays.asList(params))); + } + + /** + * Search for the first publically and privately defined constructor of the given name and parameter count. + * + * @param className - lookup name of the class, see {@link #getClass(String)}. + * @param params - the expected parameters. + * @return An object that invokes this constructor. + * @throws IllegalStateException If we cannot find this method. + */ + public static ConstructorInvoker getConstructor(String className, Class... params) { + return getConstructor(getClass(className), params); + } + + /** + * Search for the first publically and privately defined constructor of the given name and parameter count. + * + * @param clazz - a class to start with. + * @param params - the expected parameters. + * @return An object that invokes this constructor. + * @throws IllegalStateException If we cannot find this method. + */ + public static ConstructorInvoker getConstructor(Class clazz, Class... params) { + for (final Constructor constructor : clazz.getDeclaredConstructors()) { + if (Arrays.equals(constructor.getParameterTypes(), params)) { + constructor.setAccessible(true); + + return arguments -> { + try { + return constructor.newInstance(arguments); + } catch (Exception e) { + throw new IllegalArgumentException("Cannot invoke constructor " + constructor, e); + } + }; + } + } + + throw new IllegalStateException(String.format("Unable to find constructor for %s (%s).", clazz, Arrays.asList(params))); + } + + /** + * Retrieve a class from its full name, without knowing its type on compile time. + *

+ * This is useful when looking up fields by a NMS or OBC type. + *

+ * + * @param lookupName - the class name with variables. + * @return The class. + */ + public static Class getUntypedClass(String lookupName) { + @SuppressWarnings({ "rawtypes", "unchecked" }) + Class clazz = (Class) getClass(lookupName); + return clazz; + } + + /** + * Retrieve a class from its full name. + *

+ * Strings enclosed with curly brackets - such as {TEXT} - will be replaced according to the following table: + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
VariableContent
{nms}Actual package name of net.minecraft.server.VERSION
{obc}Actual pacakge name of org.bukkit.craftbukkit.VERSION
{version}The current Minecraft package VERSION, if any.
+ * + * @param lookupName - the class name with variables. + * @return The looked up class. + * @throws IllegalArgumentException If a variable or class could not be found. + */ + public static Class getClass(String lookupName) { + try { + return Class.forName(expandVariables(lookupName)); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException("Cannot find " + expandVariables(lookupName), e); + } + } + + /** + * Expand variables such as "{nms}" and "{obc}" to their corresponding packages. + * + * @param name - the full name of the class. + * @return The expanded string. + */ + private static String expandVariables(String name) { + StringBuffer output = new StringBuffer(); + Matcher matcher = MATCH_VARIABLE.matcher(name); + + while (matcher.find()) { + String variable = matcher.group(1); + String replacement; + + // Expand all detected variables + if (variable.startsWith("nms")) { + if(Core.getVersion() >= 17) + replacement = "net.minecraft" + variable.substring(3); + else + replacement = NMS_PREFIX; + } else if ("obc".equals(variable)) + replacement = OBC_PREFIX; + else if ("version".equals(variable)) + replacement = VERSION; + else + throw new IllegalArgumentException("Unknown variable: " + variable); + + // Assume the expanded variables are all packages, and append a dot + if (replacement.length() > 0 && matcher.end() < name.length() && name.charAt(matcher.end()) != '.') + replacement += "."; + matcher.appendReplacement(output, Matcher.quoteReplacement(replacement)); + } + + matcher.appendTail(output); + return output.toString(); + } + + @SuppressWarnings("deprecation") + public static Object newInstance(Class clazz) { + try { + if (Core.getVersion() > 15) { + return Unsafe.getUnsafe().allocateInstance(clazz); + } else { + return clazz.newInstance(); + } + } catch (InstantiationException | IllegalAccessException e) { + throw new SecurityException("Could not create object", e); + } + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/com/comphenix/tinyprotocol/TinyProtocol.java b/SpigotCore/SpigotCore_Main/src/src/com/comphenix/tinyprotocol/TinyProtocol.java new file mode 100644 index 00000000..caa2bb89 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/com/comphenix/tinyprotocol/TinyProtocol.java @@ -0,0 +1,207 @@ +package com.comphenix.tinyprotocol; + +import com.comphenix.tinyprotocol.Reflection.FieldAccessor; +import de.steamwar.core.Core; +import io.netty.channel.Channel; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerLoginEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.server.PluginDisableEvent; +import org.bukkit.plugin.Plugin; + +import java.util.*; +import java.util.function.BiFunction; +import java.util.logging.Level; + +public class TinyProtocol implements Listener { + + private static final Class craftServer = Reflection.getClass("{obc}.CraftServer"); + private static final Class dedicatedPlayerList = Reflection.getClass("{nms.server.dedicated}.DedicatedPlayerList"); + private static final FieldAccessor getPlayerList = Reflection.getField(craftServer, dedicatedPlayerList, 0); + private static final Class playerList = Reflection.getClass("{nms.server.players}.PlayerList"); + private static final Class minecraftServer = Reflection.getClass("{nms.server}.MinecraftServer"); + private static final FieldAccessor getMinecraftServer = Reflection.getField(playerList, minecraftServer, 0); + public static final Class serverConnection = Reflection.getClass("{nms.server.network}.ServerConnection"); + private static final FieldAccessor getServerConnection = Reflection.getField(minecraftServer, serverConnection, 0); + public static Object getServerConnection(Plugin plugin) { + return getServerConnection.get(getMinecraftServer.get(getPlayerList.get(plugin.getServer()))); + } + private static final Class networkManager = Reflection.getClass("{nms.network}.NetworkManager"); + public static final FieldAccessor networkManagers = Reflection.getField(serverConnection, List.class, 0, networkManager); + + public static final TinyProtocol instance = new TinyProtocol(Core.getInstance()); + private static int id = 0; + + public static void init() { + //enforce init + } + + private final Plugin plugin; + private final String handlerName; + private final List connections; + private boolean closed; + + private final Map, List>> packetFilters = new HashMap<>(); + private final Map playerInterceptors = new HashMap<>(); + + private TinyProtocol(final Plugin plugin) { + this.plugin = plugin; + this.handlerName = "tiny-" + plugin.getName() + "-" + ++id; + this.connections = networkManagers.get(getServerConnection(plugin)); + + plugin.getServer().getPluginManager().registerEvents(this, plugin); + + for (Player player : plugin.getServer().getOnlinePlayers()) { + new PacketInterceptor(player); + } + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerLogin(PlayerLoginEvent e) { + if(closed) + return; + new PacketInterceptor(e.getPlayer()); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerDisconnect(PlayerQuitEvent e) { + getInterceptor(e.getPlayer()).ifPresent(PacketInterceptor::close); + } + + @EventHandler + public void onPluginDisable(PluginDisableEvent e) { + if (e.getPlugin().equals(plugin)) { + close(); + } + } + + public void addFilter(Class packetType, BiFunction filter) { + packetFilters.computeIfAbsent(packetType, c -> Collections.synchronizedList(new ArrayList<>(1))).add(filter); + } + + public void removeFilter(Class packetType, BiFunction filter) { + packetFilters.getOrDefault(packetType, Collections.emptyList()).remove(filter); + } + + public void sendPacket(Player player, Object packet) { + getInterceptor(player).ifPresent(i -> i.sendPacket(packet)); + } + + public void receivePacket(Player player, Object packet) { + getInterceptor(player).ifPresent(i -> i.receivePacket(packet)); + } + + public final void close() { + if(closed) + return; + closed = true; + + HandlerList.unregisterAll(this); + + for (Player player : plugin.getServer().getOnlinePlayers()) { + getInterceptor(player).ifPresent(PacketInterceptor::close); + } + } + + private Optional getInterceptor(Player player) { + synchronized (playerInterceptors) { + return Optional.ofNullable(playerInterceptors.get(player)); + } + } + + private static final FieldAccessor getChannel = Reflection.getField(networkManager, Channel.class, 0); + private static final FieldAccessor getUUID = Reflection.getField(networkManager, UUID.class, 0); + + private final class PacketInterceptor extends ChannelDuplexHandler { + private final Player player; + private final Channel channel; + + private PacketInterceptor(Player player) { + this.player = player; + + channel = getChannel.get(connections.stream().filter(connection -> player.getUniqueId().equals(getUUID.get(connection))).findAny().orElseThrow(() -> { + player.kickPlayer("An injection failure happend."); + return new SecurityException("Could not find channel for player " + player.getName()); + })); + + if(!channel.isActive()) + return; + + synchronized (playerInterceptors) { + playerInterceptors.put(player, this); + } + + channel.pipeline().addBefore("packet_handler", handlerName, this); + } + + private void sendPacket(Object packet) { + channel.pipeline().writeAndFlush(packet); + } + + private void receivePacket(Object packet) { + channel.pipeline().context("encoder").fireChannelRead(packet); + } + + private void close() { + if(channel.isActive()) { + channel.eventLoop().execute(() -> { + try { + channel.pipeline().remove(handlerName); + } catch (NoSuchElementException e) { + // ignore + } + }); + } + + synchronized (playerInterceptors) { + playerInterceptors.remove(player, this); + } + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + try { + msg = filterPacket(player, msg); + } catch (Exception e) { + plugin.getLogger().log(Level.SEVERE, "Error during incoming packet processing", e); + } + + if (msg != null) { + super.channelRead(ctx, msg); + } + } + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + try { + msg = filterPacket(player, msg); + } catch (Exception e) { + plugin.getLogger().log(Level.SEVERE, "Error during outgoing packet processing", e); + } + + if (msg != null) { + super.write(ctx, msg, promise); + } + } + + private Object filterPacket(Player player, Object packet) { + List> filters = packetFilters.getOrDefault(packet.getClass(), Collections.emptyList()); + + for(BiFunction filter : filters) { + packet = filter.apply(player, packet); + + if(packet == null) + break; + } + + return packet; + } + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/command/CaseInsensitiveCommandsListener.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/command/CaseInsensitiveCommandsListener.java new file mode 100644 index 00000000..a829308a --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/command/CaseInsensitiveCommandsListener.java @@ -0,0 +1,35 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.command; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; + +public class CaseInsensitiveCommandsListener implements Listener { + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) { + String[] strings = event.getMessage().split(" "); + strings[0] = strings[0].toLowerCase(); + event.setMessage(String.join(" ", strings)); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/command/CommandRegistering.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/command/CommandRegistering.java new file mode 100644 index 00000000..143ea2aa --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/command/CommandRegistering.java @@ -0,0 +1,65 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.command; + +import lombok.experimental.UtilityClass; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandMap; +import org.bukkit.command.SimpleCommandMap; + +import java.lang.reflect.Field; +import java.util.Map; + +@UtilityClass +class CommandRegistering { + + private static final CommandMap commandMap; + private static final Map knownCommandMap; + + static { + try { + final Field commandMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap"); + commandMapField.setAccessible(true); + commandMap = (CommandMap) commandMapField.get(Bukkit.getServer()); + } catch (NoSuchFieldException | IllegalAccessException exception) { + Bukkit.shutdown(); + throw new SecurityException("Oh shit. Commands cannot be registered.", exception); + } + try { + final Field knownCommandsField = SimpleCommandMap.class.getDeclaredField("knownCommands"); + knownCommandsField.setAccessible(true); + knownCommandMap = (Map) knownCommandsField.get(commandMap); + } catch (NoSuchFieldException | IllegalAccessException exception) { + Bukkit.shutdown(); + throw new SecurityException("Oh shit. Commands cannot be registered.", exception); + } + } + + static void unregister(Command command) { + knownCommandMap.remove(command.getName()); + command.getAliases().forEach(knownCommandMap::remove); + command.unregister(commandMap); + } + + static void register(Command command) { + commandMap.register("steamwar", command); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/command/SWCommand.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/command/SWCommand.java new file mode 100644 index 00000000..5094da45 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/command/SWCommand.java @@ -0,0 +1,160 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.command; + +import de.steamwar.core.Core; +import de.steamwar.message.Message; +import lombok.Setter; +import net.md_5.bungee.api.chat.ClickEvent; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; +import java.util.logging.Level; + +public class SWCommand extends AbstractSWCommand { + + static { + TypeUtils.init(); + } + + private Command command; + + @Setter + private Message message = null; + private List defaultHelpMessages = new ArrayList<>(); + + protected SWCommand(String command) { + super(CommandSender.class, command); + } + + protected SWCommand(String command, String... aliases) { + super(CommandSender.class, command, aliases); + } + + @Override + protected void createAndSafeCommand(String command, String[] aliases) { + this.command = new Command(command, "", "/" + command, Arrays.asList(aliases)) { + @Override + public boolean execute(CommandSender sender, String alias, String[] args) { + SWCommand.this.execute(sender, alias, args); + return false; + } + + @Override + public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { + return SWCommand.this.tabComplete(sender, alias, args); + } + }; + } + + @Override + public void unregister() { + CommandRegistering.unregister(this.command); + } + + @Override + public void register() { + CommandRegistering.register(this.command); + } + + @Override + protected void commandSystemError(CommandSender sender, CommandFrameworkException e) { + Bukkit.getLogger().log(Level.SEVERE, e.getMessage(), e); + Core.MESSAGE.sendPrefixless("COMMAND_SYSTEM_ERROR", sender); + } + + @Override + protected void commandSystemWarning(Supplier message) { + Bukkit.getLogger().log(Level.WARNING, message); + } + + public void addDefaultHelpMessage(String message) { + defaultHelpMessages.add(message); + } + + @Override + protected void sendMessage(CommandSender sender, String message, Object[] args) { + this.message.send(message, sender, args); + } + + @Override + protected void initialisePartOf(AbstractSWCommand parent) { + if (parent instanceof SWCommand) { + this.message = ((SWCommand) parent).message; + } + } + + @Register(help = true) + public void internalHelp(Player p, String... args) { + if (message == null) { + return; + } + try { + message.sendPrefixless("COMMAND_HELP_HEAD", p, command.getName().substring(0, 1).toUpperCase() + command.getName().substring(1)); + defaultHelpMessages.forEach(s -> message.sendPrefixless(s, p)); + } catch (Exception e) { + Bukkit.getLogger().log(Level.WARNING, "Failed to send help message", e); + return; + } + AtomicInteger atomicInteger = new AtomicInteger(); + if (args.length != 0) { + commandList.forEach(subCommand -> { + List tabCompletes = subCommand.tabComplete(p, args); + if (tabCompletes == null || tabCompletes.isEmpty()) { + atomicInteger.incrementAndGet(); + return; + } + boolean hasTabCompletes = tabCompletes.stream() + .anyMatch(s -> s.toLowerCase().startsWith(args[args.length - 1].toLowerCase())); + if (hasTabCompletes) { + send(p, subCommand); + } else { + atomicInteger.incrementAndGet(); + } + }); + } + if (args.length == 0 || atomicInteger.get() == commandList.size()) { + commandList.forEach(subCommand -> { + if (subCommand.validator == null || subCommand.validator.validate(p, p, (s, args1) -> {})) { + send(p, subCommand); + } + }); + } + } + + private void send(Player p, SubCommand subCommand) { + try { + for (String s : subCommand.description) { + String hover = "§8/§e" + command.getName() + " " + String.join(" ", subCommand.subCommand); + String suggest = "/" + command.getName() + " " + String.join(" ", subCommand.subCommand); + message.sendPrefixless(s, p, hover, new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, suggest)); + } + } catch (Exception e) { + Bukkit.getLogger().log(Level.WARNING, "Failed to send description of registered method '" + subCommand.method + "' with description '" + subCommand.description + "'", e); + } + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/command/TypeMapper.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/command/TypeMapper.java new file mode 100644 index 00000000..08c99cd0 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/command/TypeMapper.java @@ -0,0 +1,40 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.command; + +import org.bukkit.command.CommandSender; + +import java.util.List; + +public interface TypeMapper extends AbstractTypeMapper { + /** + * The CommandSender can be null! + */ + default T map(CommandSender commandSender, String[] previousArguments, String s) { + return map(previousArguments, s); + } + + // For backwards compatibility, can be removed later on + // SINCE="Use the other map Function without calling super!" + @Deprecated + default T map(String[] previousArguments, String s) { + throw new SecurityException(); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/command/TypeUtils.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/command/TypeUtils.java new file mode 100644 index 00000000..f4db4f9e --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/command/TypeUtils.java @@ -0,0 +1,88 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.command; + +import de.steamwar.sql.BauweltMember; +import de.steamwar.sql.SchematicNode; +import de.steamwar.sql.SteamwarUser; +import lombok.experimental.UtilityClass; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.stream.Collectors; + +@UtilityClass +public class TypeUtils { + + static void init() { + SWCommandUtils.addMapper(Player.class, SWCommandUtils.createMapper(Bukkit::getPlayer, s -> Bukkit.getOnlinePlayers().stream().map(Player::getName).collect(Collectors.toList()))); + SWCommandUtils.addMapper(GameMode.class, SWCommandUtils.createMapper(s -> { + s = s.toLowerCase(); + if (s.equals("s") || s.equals("survival") || s.equals("0")) return GameMode.SURVIVAL; + if (s.equals("c") || s.equals("creative") || s.equals("1")) return GameMode.CREATIVE; + if (s.equals("sp") || s.equals("spectator") || s.equals("3")) return GameMode.SPECTATOR; + if (s.equals("a") || s.equals("adventure") || s.equals("2")) return GameMode.ADVENTURE; + return null; + }, s -> Arrays.asList("s", "survival", "0", "c", "creative", "1", "sp", "spectator", "3", "a", "adventure", "2"))); + SWCommandUtils.addMapper(SteamwarUser.class, SWCommandUtils.createMapper(SteamwarUser::get, s -> Bukkit.getOnlinePlayers().stream().map(Player::getName).collect(Collectors.toList()))); + SWCommandUtils.addMapper(SchematicNode.class, new TypeMapper() { + @Override + public SchematicNode map(CommandSender commandSender, String[] previousArguments, String s) { + return SchematicNode.getNodeFromPath(SteamwarUser.get(((Player) commandSender).getUniqueId()), s); + } + + @Override + public Collection tabCompletes(CommandSender sender, String[] previousArguments, String s) { + return SchematicNode.getNodeTabcomplete(SteamwarUser.get(((Player) sender).getUniqueId()), s); + } + }); + SWCommandUtils.addMapper(BauweltMember.class, new TypeMapper() { + @Override + public BauweltMember map(CommandSender commandSender, String[] previousArguments, String s) { + if (!(commandSender instanceof Player)) { + return null; + } + Player player = (Player) commandSender; + return BauweltMember.getMembers(player.getUniqueId()) + .stream() + .filter(member -> SteamwarUser.get(member.getMemberID()).getUserName().equalsIgnoreCase(s)) + .findAny() + .orElse(null); + } + + @Override + public Collection tabCompletes(CommandSender sender, String[] previousArguments, String s) { + if (!(sender instanceof Player)) { + return new ArrayList<>(); + } + Player player = (Player) sender; + return BauweltMember.getMembers(player.getUniqueId()) + .stream() + .map(m -> SteamwarUser.get(m.getMemberID()).getUserName()) + .collect(Collectors.toList()); + } + }); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/command/TypeValidator.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/command/TypeValidator.java new file mode 100644 index 00000000..a07f11eb --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/command/TypeValidator.java @@ -0,0 +1,25 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.command; + +import org.bukkit.command.CommandSender; + +public interface TypeValidator extends AbstractValidator { +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/BountifulWrapper.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/BountifulWrapper.java new file mode 100644 index 00000000..2e532908 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/BountifulWrapper.java @@ -0,0 +1,53 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.core; + +import net.md_5.bungee.api.ChatMessageType; +import net.md_5.bungee.api.chat.BaseComponent; +import org.bukkit.entity.Player; + +import java.util.UUID; + +public class BountifulWrapper { + private BountifulWrapper() {} + + public static final IBountifulWrapper impl = VersionDependent.getVersionImpl(Core.getInstance()); + + public interface IBountifulWrapper { + void playPling(Player player); + + void sendMessage(Player player, ChatMessageType type, BaseComponent... msg); + + Object getDataWatcherObject(int index, Class type); + Object getDataWatcherItem(Object dataWatcherObject, Object value); + + PositionSetter getPositionSetter(Class packetClass, int fieldOffset); + PositionSetter getRelMoveSetter(Class packetClass); + UUIDSetter getUUIDSetter(Class packetClass); + } + + public interface PositionSetter { + void set(Object packet, double x, double y, double z); + } + + public interface UUIDSetter { + void set(Object packet, UUID uuid); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/ChatWrapper.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/ChatWrapper.java new file mode 100644 index 00000000..8c7d1868 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/ChatWrapper.java @@ -0,0 +1,29 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.core; + +public interface ChatWrapper { + ChatWrapper impl = VersionDependent.getVersionImpl(Core.getInstance()); + + + Object stringToChatComponent(String text); + + Object getDataWatcherPacket(int entityId, Object... dataWatcherKeyValues); +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/CheckpointUtils.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/CheckpointUtils.java new file mode 100644 index 00000000..7b3b4b76 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/CheckpointUtils.java @@ -0,0 +1,147 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2024 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.core; + +import com.comphenix.tinyprotocol.Reflection; +import com.comphenix.tinyprotocol.TinyProtocol; +import com.viaversion.viaversion.api.Via; +import de.steamwar.sql.BauweltMember; +import de.steamwar.sql.internal.Statement; +import io.netty.channel.ChannelFuture; +import org.bukkit.Bukkit; +import org.eclipse.openj9.criu.CRIUSupport; +import org.eclipse.openj9.criu.JVMCRIUException; +import sun.misc.Signal; + +import java.io.DataInputStream; +import java.io.File; +import java.io.IOException; +import java.net.InetAddress; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.List; +import java.util.logging.Level; +import java.util.stream.Stream; + +public class CheckpointUtils { + private CheckpointUtils() {} + + public static void signalHandler() { + Signal.handle(new Signal("USR1"), signal -> Bukkit.getScheduler().runTask(Core.getInstance(), CheckpointUtils::freeze)); + } + + public static void freeze() { + String checkpointFile = System.getProperty("checkpoint"); + if(!CRIUSupport.isCheckpointAllowed() || checkpointFile == null) { + Bukkit.shutdown(); + return; + } + + Bukkit.getOnlinePlayers().forEach(player -> player.kickPlayer(null)); + + List networkManagers = TinyProtocol.networkManagers.get(TinyProtocol.getServerConnection(Core.getInstance())); + if(!Bukkit.getOnlinePlayers().isEmpty() || !networkManagers.isEmpty()) { + Core.getInstance().getLogger().log(Level.INFO, "Waiting for players to disconnect for checkpointing"); + Bukkit.getScheduler().runTaskLater(Core.getInstance(), CheckpointUtils::freeze, 1); + return; + } + + Path path = FileSystems.getDefault().getPath(checkpointFile); + + try { + freezeInternal(path); + } catch (Exception e) { + String message = e.getMessage() != null ? e.getMessage() : ""; + if(message.contains("Connected TCP socket")) { + Core.getInstance().getLogger().log(Level.INFO, "Connected TCP socket, waiting for checkpointing"); + Bukkit.getScheduler().runTaskLater(Core.getInstance(), CheckpointUtils::freeze, 1); + return; + } + + Bukkit.shutdown(); + + if(!message.contains("Can't dump ghost file") && !message.contains("Can't create link remap")) // File/Jar has been updated + throw new SecurityException(e); + } finally { + // Delete checkpoint + try (Stream stream = Files.walk(path)) { + stream.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + } catch (IOException e) { + //ignore + } + } + } + + private static final Reflection.FieldAccessor channelFutures = Reflection.getField(TinyProtocol.serverConnection, List.class, 0, ChannelFuture.class); + private static final Reflection.MethodInvoker bind = Reflection.getMethod(TinyProtocol.serverConnection, null, InetAddress.class, int.class); + private static void freezeInternal(Path path) throws Exception { + Bukkit.getWorlds().forEach(FlatteningWrapper.impl::syncSave); + Statement.closeAll(); + + // Close socket + Via.getManager().getInjector().uninject(); + Object serverConnection = TinyProtocol.getServerConnection(Core.getInstance()); + List channels = channelFutures.get(serverConnection); + for(Object future : channels) { + ((ChannelFuture) future).channel().close().syncUninterruptibly(); + } + channels.clear(); + + System.runFinalization(); + System.gc(); + + // Do the checkpoint + path.toFile().mkdirs(); + CRIUSupport criu = new CRIUSupport(path); + criu.setAutoDedup(true); + criu.setFileLocks(true); + criu.setShellJob(true); + criu.setLogFile("criu.log"); + try { + criu.checkpointJVM(); + } catch (JVMCRIUException e) { + Path logfile = path.resolve("criu.log"); + if(logfile.toFile().exists()) + throw new IllegalStateException("Could not create checkpoint, criu log:\n" + new String(Files.readAllBytes(logfile)), e); + + throw e; + } + + // Get new port + int port; + try (DataInputStream stream = new DataInputStream(Files.newInputStream(path.resolve("port").toFile().toPath()))) { + port = stream.readInt(); + } + + // Reopen socket + bind.invoke(serverConnection, InetAddress.getLoopbackAddress(), port); + if(Core.getVersion() > 12) { + for(Object future : channels) { + ((ChannelFuture) future).channel().config().setAutoRead(true); + } + } + Via.getManager().getInjector().inject(); + + BauweltMember.clear(); + Core.getInstance().getLogger().log(Level.INFO, "Checkpoint restored"); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/CommandRemover.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/CommandRemover.java new file mode 100644 index 00000000..bbce1378 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/CommandRemover.java @@ -0,0 +1,40 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.core; + +import com.comphenix.tinyprotocol.Reflection; +import lombok.experimental.UtilityClass; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.SimpleCommandMap; + +import java.util.Map; + +@UtilityClass +public class CommandRemover { + private static final Reflection.FieldAccessor commandMap = Reflection.getField("{obc}.CraftServer", "commandMap", SimpleCommandMap.class); + private static final Reflection.FieldAccessor knownCommands = Reflection.getField(SimpleCommandMap.class, "knownCommands", Map.class); + public static void removeAll(String... cmds) { + Map knownCmds = knownCommands.get(commandMap.get(Bukkit.getServer())); + for (String cmd : cmds) { + knownCmds.remove(cmd.toLowerCase()); + } + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/Core.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/Core.java new file mode 100644 index 00000000..7e0b3719 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/Core.java @@ -0,0 +1,127 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.core; + +import com.comphenix.tinyprotocol.TinyProtocol; +import de.steamwar.command.*; +import de.steamwar.core.authlib.AuthlibInjector; +import de.steamwar.core.events.*; +import de.steamwar.message.Message; +import de.steamwar.network.NetworkReceiver; +import de.steamwar.network.handlers.ServerDataHandler; +import de.steamwar.sql.SchematicNode; +import de.steamwar.sql.SteamwarUser; +import de.steamwar.sql.internal.Statement; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.event.Listener; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Collection; +import java.util.logging.Level; + +public class Core extends JavaPlugin{ + + public static final Message MESSAGE = new Message("SpigotCore", Core.class.getClassLoader()); + + private static final int VERSION = Integer.parseInt(Bukkit.getServer().getClass().getPackage().getName().split("_", 3)[1]); + + public static int getVersion(){ + return VERSION; + } + + private static JavaPlugin instance; + public static JavaPlugin getInstance() { + return instance; + } + public static void setInstance(JavaPlugin instance) { + Core.instance = instance; + } + + private ErrorHandler errorHandler; + private CrashDetector crashDetector; + + @Override + public void onLoad() { + setInstance(this); + } + + @Override + public void onEnable() { + errorHandler = new ErrorHandler(); + crashDetector = new CrashDetector(); + + SWCommandUtils.init((SWTypeMapperCreator, CommandSender, Object>) (mapper, tabCompleter) -> new TypeMapper() { + @Override + public Object map(CommandSender commandSender, String[] previousArguments, String s) { + return mapper.apply(s); + } + + @Override + public Collection tabCompletes(CommandSender sender, String[] previousArguments, String s) { + return tabCompleter.apply(sender, s); + } + }); + + for(Listener listener : new Listener[]{new CaseInsensitiveCommandsListener(), new PlayerJoinedEvent(), new ChattingEvent(), new WorldLoadEvent(), RecipeDiscoverWrapper.impl}) { + getServer().getPluginManager().registerEvents(listener, this); + } + if(!Statement.productionDatabase()) { + getServer().getPluginManager().registerEvents(LocaleChangeWrapper.impl, this); + } + + getServer().getMessenger().registerIncomingPluginChannel(this, "sw:bridge", new NetworkReceiver()); + getServer().getMessenger().registerOutgoingPluginChannel(this, "sw:bridge"); + + if(Core.getVersion() < 19) + AuthlibInjector.inject(); + + TinyProtocol.init(); + CheckpointUtils.signalHandler(); + new AntiNocom(); + + if(Core.getVersion() < 17 && Bukkit.getPluginManager().getPlugin("ViaVersion") != null) + new PartialChunkFixer(); + + if(Core.getVersion() >= 19) + new ServerDataHandler(); + + Bukkit.getScheduler().runTaskTimer(this, TabCompletionCache::invalidateOldEntries, 20, 20); + Bukkit.getScheduler().runTaskTimer(Core.getInstance(), SteamwarUser::clear, 72000, 72000); + Bukkit.getScheduler().runTaskTimer(Core.getInstance(), SchematicNode::clear, 20L * 30, 20L * 30); + + try { + getLogger().log(Level.INFO, "Running on: " + new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec("hostname").getInputStream())).readLine()); + } catch (IOException e) { + throw new SecurityException("Could not load Hostname", e); + } + } + + @Override + public void onDisable() { + TinyProtocol.instance.close(); + errorHandler.unregister(); + if(crashDetector.onMainThread()) + Statement.closeAll(); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/CraftbukkitWrapper.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/CraftbukkitWrapper.java new file mode 100644 index 00000000..13cd06a7 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/CraftbukkitWrapper.java @@ -0,0 +1,32 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2021 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.core; + +import org.bukkit.entity.Player; + +public class CraftbukkitWrapper { + private CraftbukkitWrapper() {} + + public static final ICraftbukkitWrapper impl = VersionDependent.getVersionImpl(Core.getInstance()); + + public interface ICraftbukkitWrapper { + void sendChunk(Player p, int chunkX, int chunkZ); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/CrashDetector.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/CrashDetector.java new file mode 100644 index 00000000..543eccad --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/CrashDetector.java @@ -0,0 +1,91 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.core; + +import de.steamwar.sql.SWException; +import de.steamwar.sql.internal.Statement; +import org.bukkit.Bukkit; + +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +public class CrashDetector { + + private static final long TIMEOUT = 20_000_000_000L; + private final AtomicLong lastTick = new AtomicLong(Long.MAX_VALUE); + private final AtomicLong lastMessage = new AtomicLong(Long.MAX_VALUE); + private final Thread mainThread = Thread.currentThread(); + private final Thread watchdog; + + private boolean run = true; + + public CrashDetector () { + Bukkit.getScheduler().runTaskTimer(Core.getInstance(), () -> { + lastTick.set(System.nanoTime()); + lastMessage.set(System.nanoTime()); + }, 0, 1); + watchdog = new Thread(this::run, "SteamWar Watchdog"); + watchdog.setDaemon(true); + watchdog.start(); + } + + public void stop() { + run = false; + watchdog.interrupt(); + } + + public boolean onMainThread() { + return Thread.currentThread() == mainThread; + } + + private void run() { + SWException.init(); + while (run) { + long curTime = System.nanoTime(); + if(curTime - 4*TIMEOUT >= lastTick.get()) { + SWException.log("Server did not recover in " + ((curTime - lastTick.get()) / 1000000.0) + "ms, unclean server stop", ""); + hardStop(); + } else if(curTime - TIMEOUT > lastMessage.get()) { + if(mainThread.isAlive()) { + SWException.log("Server hung for " + ((curTime - lastTick.get()) / 1000000.0) + "ms", Arrays.stream(mainThread.getStackTrace()).map(StackTraceElement::toString).collect(Collectors.joining("\n"))); + } else { + SWException.log("Server thread already dead, unclean server stop", "Core enabled: " + Core.getInstance().isEnabled()); + hardStop(); + } + lastMessage.set(curTime); + } + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + + private void hardStop() { + if(Core.getInstance().isEnabled()) { + Core.getInstance().onDisable(); + } + Statement.closeAll(); + //System.exit(0); Does freeze, potential freezing issues: ConsoleRestoreHook, ApplicationShutdownHooks or DeleteOnExitHook + Runtime.getRuntime().halt(0); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/ErrorHandler.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/ErrorHandler.java new file mode 100644 index 00000000..c9585847 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/ErrorHandler.java @@ -0,0 +1,163 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.core; + +import com.comphenix.tinyprotocol.Reflection; +import de.steamwar.sql.SWException; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +public class ErrorHandler extends Handler { + + private final long watchdogThreadId; + + public ErrorHandler(){ + Logger.getLogger("").addHandler(this); + + Class watchdogThread = Reflection.getClass("org.spigotmc.WatchdogThread"); + Reflection.FieldAccessor getInstance = Reflection.getField(watchdogThread, watchdogThread, 0); + Thread watchdog = (Thread) getInstance.get(null); + watchdogThreadId = watchdog.getId(); + } + + void unregister() { + Logger.getLogger("").removeHandler(this); + } + + @Override + @SuppressWarnings("deprecation") + public void publish(LogRecord logRecord) { + if(logRecord.getLevel().intValue() < Level.WARNING.intValue()) + return; + + if(watchdogThreadId == logRecord.getThreadID()) + return; + + String message = logRecord.getMessage() != null ? logRecord.getMessage() : ""; + for(String reason : ignoreStartsWith) + if(message.startsWith(reason)) + return; + for(String reason : ignoreContains) + if(message.contains(reason)) + return; + + ByteArrayOutputStream stacktraceOutput = new ByteArrayOutputStream(); + if(logRecord.getThrown() != null) + logRecord.getThrown().printStackTrace(new PrintStream(stacktraceOutput)); + String stacktrace = stacktraceOutput.toString(); + if(stacktrace.contains("POI data mismatch") || stacktrace.contains("Newer version! Server downgrades are not supported!")) + return; + + if(message.isEmpty() && stacktrace.isEmpty()) + return; + + try { + SWException.log(message, stacktrace); + } catch (SecurityException e) { + Core.getInstance().getLogger().log(Level.INFO, "Could not log error in database", e); + } + } + + @Override + public void flush() { + //This is task of the database + } + + @Override + public void close() throws SecurityException { + //Done in the database + } + + private static final List ignoreStartsWith; + private static final List ignoreContains; + + static { + List startsWith = new ArrayList<>(); + startsWith.add("Could not save the list after adding a user."); + startsWith.add("Could not save spigot.yml"); + startsWith.add("Failed to save operators list:"); + startsWith.add("Block at"); + startsWith.add("POI data mismatch"); + startsWith.add("handleDisconnection() called twice"); + startsWith.add("**** SERVER IS RUNNING IN OFFLINE/INSECURE MODE!"); + startsWith.add("The server will make no attempt to authenticate usernames. Beware."); + startsWith.add("Whilst this makes it possible to use BungeeCord,"); + startsWith.add("Please see http://www.spigotmc.org/wiki/firewall-guide/ for further information."); + startsWith.add("To change this, set \"online-mode\" to \"true\" in the server.properties file."); + startsWith.add("This crash report has been saved to:"); + startsWith.add("Could not pass event PlayerQuitEvent to WorldEditSUI"); + startsWith.add("[ViaVersion] "); + startsWith.add("[ViaBackwards] "); + startsWith.add("Something went wrong upgrading!"); + startsWith.add("Tried to load unrecognized recipe"); + startsWith.add("Invalid BlockState in palette:"); + startsWith.add("Could not register alias"); + startsWith.add("Can't keep up! Is the server overloaded?"); + startsWith.add("\tat "); + startsWith.add("java.lang.Exception"); + startsWith.add("An exceptionCaught()"); + startsWith.add("Exception verifying"); + startsWith.add("[WorldEditSUI]"); + startsWith.add("Unsupported key:"); + startsWith.add("ThrownPotion entity"); + startsWith.add("Couldn't load custom particle"); + startsWith.add("Chunk file at ["); + startsWith.add("Ignoring unknown attribute"); + startsWith.add("Skipping player strafe phase because no player was found"); + startsWith.add("Couldn't save chunk; already in use by another instance of Minecraft?"); + startsWith.add("Failed to save player data for "); + startsWith.add("Failed to check session lock for world located at"); + startsWith.add("Saving oversized chunk "); + startsWith.add("Ignoring plugin channel"); + startsWith.add("Ignoring incoming plugin"); + startsWith.add("This version of Minecraft is extremely outdated"); + startsWith.add("Custom worlds heights are NOT SUPPORTED for 1.16 players and older"); + startsWith.add("You have min/max set to -64/384"); + startsWith.add("You have min/max set to -64/256"); + startsWith.add("This version of ViaBackwards does not fully support 1.19 servers."); + startsWith.add("Biome with id"); + startsWith.add("1.16 and 1.16.1 clients are only partially supported"); + startsWith.add("Unable to parse CustomName from "); + startsWith.add("java.util.ConcurrentModificationException"); + startsWith.add("com.destroystokyo.paper.exception.ServerInternalException: Attempted to place a tile entity"); + startsWith.add("World: minecraft:overworld"); + startsWith.add("Chunk coordinates: "); + startsWith.add("Failed to save history"); + startsWith.add("\t... "); + startsWith.add("ERROR IN Protocol"); + ignoreStartsWith = Collections.unmodifiableList(startsWith); + + List contains = new ArrayList<>(); + contains.add("moved too quickly!"); + contains.add("moved wrongly!"); + contains.add("was kicked for floating too long!"); + contains.add("just tried to change non-editable sign"); + contains.add("about their usage of System.out/err.print"); + ignoreContains = Collections.unmodifiableList(contains); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/FlatteningWrapper.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/FlatteningWrapper.java new file mode 100644 index 00000000..479760cf --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/FlatteningWrapper.java @@ -0,0 +1,61 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.core; + +import com.comphenix.tinyprotocol.Reflection; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +public class FlatteningWrapper { + private FlatteningWrapper() {} + + public static final Class scoreboardObjective = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutScoreboardObjective"); + public static final Class scoreboardScore = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutScoreboardScore"); + + public static final IFlatteningWrapper impl = VersionDependent.getVersionImpl(Core.getInstance()); + + public interface IFlatteningWrapper { + void setScoreboardTitle(Object packet, String title); + void setScoreAction(Object packet); + + Material getMaterial(String material); + Material getDye(int colorCode); + ItemStack setSkullOwner(String player); + + Object getPose(EntityPose pose); + void setNamedSpawnPacketDataWatcher(Object packet); + Object formatDisplayName(String displayName); + + void setSpawnPacketType(Object packet, EntityType type); + + int getViewDistance(Player player); + + void syncSave(World world); + } + + public enum EntityPose { + NORMAL, + SNEAKING, + SWIMMING; + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/LocaleChangeWrapper.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/LocaleChangeWrapper.java new file mode 100644 index 00000000..6db67b79 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/LocaleChangeWrapper.java @@ -0,0 +1,26 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.core; + +import org.bukkit.event.Listener; + +public interface LocaleChangeWrapper extends Listener { + LocaleChangeWrapper impl = VersionDependent.getVersionImpl(Core.getInstance()); +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/ProtocolWrapper.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/ProtocolWrapper.java new file mode 100644 index 00000000..e6787497 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/ProtocolWrapper.java @@ -0,0 +1,57 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2021 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + */ + +package de.steamwar.core; + +import com.comphenix.tinyprotocol.Reflection; +import com.mojang.authlib.GameProfile; +import org.bukkit.GameMode; + +import java.util.function.LongSupplier; + +public interface ProtocolWrapper { + + Class itemStack = Reflection.getClass("{nms.world.item}.ItemStack"); + Class spawnPacket = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutSpawnEntity"); + Class spawnLivingPacket = Core.getVersion() > 18 ? ProtocolWrapper.spawnPacket : Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutSpawnEntityLiving"); + Class equipmentPacket = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutEntityEquipment"); + + Class enumGamemode = Reflection.getClass(Core.getVersion() > 9 ? "{nms.world.level}.EnumGamemode" : "{nms}.WorldSettings$EnumGamemode"); + Reflection.MethodInvoker getGameModeById = Reflection.getTypedMethod(enumGamemode, null, enumGamemode, int.class); + + // 0: hand, 1: offhand, 2: feet, 3: legs, 4: chest, 5: head + Object[] itemSlots = Core.getVersion() > 8 ? Reflection.getClass("{nms.world.entity}.EnumItemSlot").getEnumConstants() : new Integer[]{0, 0, 1, 2, 3, 4}; + + ProtocolWrapper impl = VersionDependent.getVersionImpl(Core.getInstance()); + + void setEquipmentPacketStack(Object packet, Object slot, Object stack); + + Object playerInfoPacketConstructor(PlayerInfoAction action, GameProfile profile, GameMode mode); + + default void initTPSWarp(LongSupplier longSupplier) { + Class systemUtils = Reflection.getClass("{nms}.SystemUtils"); + Reflection.getField(systemUtils, LongSupplier.class, 0).set(systemUtils, (LongSupplier) () -> System.nanoTime() + longSupplier.getAsLong()); + } + + enum PlayerInfoAction { + ADD, + GAMEMODE, + REMOVE + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/RecipeDiscoverWrapper.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/RecipeDiscoverWrapper.java new file mode 100644 index 00000000..0d6b9da4 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/RecipeDiscoverWrapper.java @@ -0,0 +1,26 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.core; + +import org.bukkit.event.Listener; + +public interface RecipeDiscoverWrapper extends Listener { + RecipeDiscoverWrapper impl = VersionDependent.getVersionImpl(Core.getInstance()); +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/TPSWarpUtils.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/TPSWarpUtils.java new file mode 100644 index 00000000..3550a991 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/TPSWarpUtils.java @@ -0,0 +1,56 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.core; + +import com.comphenix.tinyprotocol.Reflection; +import de.steamwar.core.Core; +import lombok.experimental.UtilityClass; +import org.bukkit.Bukkit; +import org.bukkit.scheduler.BukkitTask; + +import java.util.function.LongSupplier; + +@UtilityClass +public class TPSWarpUtils { + + private static long nanoOffset = 0; + private static long nanoDOffset = 0; + private static BukkitTask bukkitTask = null; + + static { + ProtocolWrapper.impl.initTPSWarp(() -> nanoOffset); + } + + public static void warp(double tps) { + double d = 50 - (50 / (tps / 20.0)); + nanoDOffset = Math.max(0, (long) (d * 1000000)); + if (nanoDOffset == 0) { + if (bukkitTask == null) return; + bukkitTask.cancel(); + bukkitTask = null; + } else if (bukkitTask == null) { + bukkitTask = Bukkit.getScheduler().runTaskTimer(Core.getInstance(), () -> nanoOffset += nanoDOffset, 1, 1); + } + } + + public static boolean isWarping() { + return nanoDOffset > 0; + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/TPSWatcher.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/TPSWatcher.java new file mode 100644 index 00000000..54a5f7e2 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/TPSWatcher.java @@ -0,0 +1,100 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.core; + +import com.comphenix.tinyprotocol.Reflection; +import org.bukkit.Bukkit; + +public class TPSWatcher { + + private static final double TICK_DEFAULT = 20.0; + + private static final TPSWatcher tps_OneSecond = new TPSWatcher(1000); + private static final TPSWatcher tps_TenSecond = new TPSWatcher(10000); + + private long lastTime = System.currentTimeMillis(); + private double tps = TICK_DEFAULT; + + private TPSWatcher(long timeInterval) { + Bukkit.getScheduler().runTaskTimer(Core.getInstance(), () -> { + long currentTime = System.currentTimeMillis(); + + if (currentTime > lastTime) + tps = (timeInterval / (double) (currentTime - lastTime)) * TICK_DEFAULT; + + lastTime = currentTime; + }, timeInterval / 50, timeInterval / 50); + } + + public static double getTPS() { + return getTPS(TPSType.ONE_SECOND); + } + + public static double getTPS(double limit) { + return getTPS(TPSType.ONE_SECOND, limit); + } + + public static double getTPSUnlimited() { + return getTPSUnlimited(TPSType.ONE_SECOND); + } + + public static double getTPS(TPSType tpsType) { + return getTPS(tpsType, TICK_DEFAULT); + } + + public static double getTPS(TPSType tpsType, double limit) { + return Math.min(getTPSUnlimited(tpsType), limit); + } + + public static double getTPSUnlimited(TPSType tpsType) { + switch (tpsType) { + case TEN_SECONDS: + return round(tps_TenSecond.tps); + case ONE_MINUTE: + return round(getSpigotTPS()[0]); + case FIVE_MINUTES: + return round(getSpigotTPS()[1]); + case TEN_MINUTES: + return round(getSpigotTPS()[2]); + case ONE_SECOND: + default: + return round(tps_OneSecond.tps); + } + } + + private static final Class minecraftServer = Reflection.getClass("{nms.server}.MinecraftServer"); + private static final Reflection.MethodInvoker getServer = Reflection.getTypedMethod(minecraftServer, "getServer", minecraftServer); + private static final Reflection.FieldAccessor recentTps = Reflection.getField(minecraftServer, "recentTps", double[].class); + private static double[] getSpigotTPS() { + return recentTps.get(getServer.invoke(null)); + } + + private static double round(double d) { + return Math.round(d * 10) / 10.0; + } + + public enum TPSType { + ONE_SECOND, + TEN_SECONDS, + ONE_MINUTE, + FIVE_MINUTES, + TEN_MINUTES + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/VersionDependent.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/VersionDependent.java new file mode 100644 index 00000000..5e8ec241 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/VersionDependent.java @@ -0,0 +1,46 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2021 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.core; + +import org.bukkit.plugin.Plugin; + +import java.lang.reflect.InvocationTargetException; + +public class VersionDependent { + private VersionDependent() {} + + public static T getVersionImpl(Plugin plugin) { + return getVersionImpl(plugin, (new Exception()).getStackTrace()[1].getClassName()); + } + + public static T getVersionImpl(Plugin plugin, String className){ + ClassLoader loader = plugin.getClass().getClassLoader(); + for(int version = Core.getVersion(); version >= 8; version--) { + try { + return ((Class) Class.forName(className + version, true, loader)).getDeclaredConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new SecurityException("Could not load version dependent class", e); + } catch (ClassNotFoundException e) { + // try next version + } + } + throw new SecurityException("Unable to find version dependent implementation for " + className); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/WorldEditWrapper.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/WorldEditWrapper.java new file mode 100644 index 00000000..269f4266 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/WorldEditWrapper.java @@ -0,0 +1,25 @@ +package de.steamwar.core; + +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.extent.clipboard.Clipboard; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.io.IOException; +import java.io.InputStream; + +public class WorldEditWrapper { + private WorldEditWrapper() {} + + public static final IWorldEditWrapper impl = VersionDependent.getVersionImpl(Core.getInstance()); + + public interface IWorldEditWrapper { + InputStream getPlayerClipboard(Player player, boolean schemFormat); + void setPlayerClipboard(Player player, InputStream is, boolean schemFormat); + Clipboard getClipboard(InputStream is, boolean schemFormat) throws IOException; + } + + public static WorldEditPlugin getWorldEditPlugin() { + return (WorldEditPlugin) Bukkit.getPluginManager().getPlugin("WorldEdit"); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/authlib/AuthlibInjector.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/authlib/AuthlibInjector.java new file mode 100644 index 00000000..0c9c188b --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/authlib/AuthlibInjector.java @@ -0,0 +1,35 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.core.authlib; + +import com.comphenix.tinyprotocol.Reflection; +import com.mojang.authlib.GameProfileRepository; +import com.mojang.authlib.yggdrasil.YggdrasilGameProfileRepository; + +public class AuthlibInjector { + private AuthlibInjector() {} + + public static void inject() { + Class minecraftServerClass = Reflection.getClass("{nms.server}.MinecraftServer"); + Reflection.FieldAccessor gameProfile = Reflection.getField(minecraftServerClass, GameProfileRepository.class, 0); + Object minecraftServer = Reflection.getTypedMethod(minecraftServerClass, "getServer", minecraftServerClass).invoke(null); + gameProfile.set(minecraftServer, new SteamwarGameProfileRepository((YggdrasilGameProfileRepository) gameProfile.get(minecraftServer))); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/authlib/SteamwarGameProfileRepository.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/authlib/SteamwarGameProfileRepository.java new file mode 100644 index 00000000..9045de17 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/authlib/SteamwarGameProfileRepository.java @@ -0,0 +1,60 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.core.authlib; + +import com.mojang.authlib.Agent; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.GameProfileRepository; +import com.mojang.authlib.ProfileLookupCallback; +import com.mojang.authlib.yggdrasil.YggdrasilGameProfileRepository; +import de.steamwar.sql.SteamwarUser; + +import java.util.ArrayList; +import java.util.List; + +public class SteamwarGameProfileRepository implements GameProfileRepository { + + private final YggdrasilGameProfileRepository fallback; + + public SteamwarGameProfileRepository(YggdrasilGameProfileRepository repository) { + fallback = repository; + } + + @Override + public void findProfilesByNames(String[] strings, Agent agent, ProfileLookupCallback profileLookupCallback) { + if(agent == Agent.SCROLLS) { + fallback.findProfilesByNames(strings, agent, profileLookupCallback); + } else { + List unknownNames = new ArrayList<>(); + for (String name:strings) { + SteamwarUser user = SteamwarUser.get(name); + if(user == null) { + unknownNames.add(name); + continue; + } + + profileLookupCallback.onProfileLookupSucceeded(new GameProfile(user.getUUID(), user.getUserName())); + } + if(!unknownNames.isEmpty()) { + fallback.findProfilesByNames(unknownNames.toArray(new String[0]), agent, profileLookupCallback); + } + } + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/events/AntiNocom.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/events/AntiNocom.java new file mode 100644 index 00000000..ea7759da --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/events/AntiNocom.java @@ -0,0 +1,96 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.core.events; + +import com.comphenix.tinyprotocol.Reflection; +import com.comphenix.tinyprotocol.TinyProtocol; +import de.steamwar.core.Core; +import de.steamwar.sql.SWException; +import de.steamwar.techhider.ProtocolUtils; +import de.steamwar.techhider.TechHider; +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.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +public class AntiNocom implements Listener { + + private final Map flags = new ConcurrentHashMap<>(); + + public AntiNocom() { + Bukkit.getServer().getPluginManager().registerEvents(this, Core.getInstance()); + TinyProtocol.instance.addFilter(blockDig, this::onDig); + + if(Core.getVersion() > 8) { + registerUseItem(); + } + } + + @EventHandler + public void onQuit(PlayerQuitEvent e) { + flags.remove(e.getPlayer()); + } + + private void registerUseItem() { + Class useItem = Reflection.getClass("{nms.network.protocol.game}.PacketPlayInUseItem"); + + Function getPosition; + if(Core.getVersion() > 12) { + Class movingObjectPositionBlock = Reflection.getClass("{nms.world.phys}.MovingObjectPositionBlock"); + Reflection.FieldAccessor useItemPosition = Reflection.getField(useItem, movingObjectPositionBlock, 0); + Reflection.FieldAccessor movingBlockPosition = Reflection.getField(movingObjectPositionBlock, TechHider.blockPosition, 0); + + getPosition = (packet) -> movingBlockPosition.get(useItemPosition.get(packet)); + } else { + getPosition = Reflection.getField(useItem, TechHider.blockPosition, 0)::get; + } + + TinyProtocol.instance.addFilter(useItem, (player, packet) -> { + Object pos = getPosition.apply(packet); + return isValid(player, "UseItem", TechHider.blockPositionX.get(pos), TechHider.blockPositionZ.get(pos)) ? packet : null; + }); + } + + private static final Class blockDig = Reflection.getClass("{nms.network.protocol.game}.PacketPlayInBlockDig"); + private static final Reflection.FieldAccessor digPosition = Reflection.getField(blockDig, TechHider.blockPosition, 0); + private Object onDig(Player player, Object packet) { + Object pos = digPosition.get(packet); + return isValid(player, "Dig", TechHider.blockPositionX.get(pos), TechHider.blockPositionZ.get(pos)) ? packet : null; + } + + private boolean isValid(Player player, String type, int x, int z) { + if((x == 0 && z == 0) || player.getWorld().isChunkLoaded(ProtocolUtils.posToChunk(x), ProtocolUtils.posToChunk(z))) + return true; + + int amount = flags.compute(player, (p, a) -> a == null ? 1 : a+1); + if(amount % 8 == 0) { // Only after 8 and every 8 to reduce false flags and spam + if(amount == 8) // Schedule player kick only once + Bukkit.getScheduler().runTask(Core.getInstance(), () -> player.kickPlayer(null)); + + SWException.log(player.getName() + " kicked for potential NoCom-DOS attack", x + " " + z + " " + type + " " + amount); + } + return false; + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/events/ChattingEvent.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/events/ChattingEvent.java new file mode 100644 index 00000000..1de582f2 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/events/ChattingEvent.java @@ -0,0 +1,35 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.core.events; + +import de.steamwar.core.Core; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerChatEvent; + +public class ChattingEvent implements Listener { + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + private void onChat(AsyncPlayerChatEvent event) { + event.setCancelled(true); + Core.MESSAGE.broadcastPrefixless("LOCAL_CHAT", event.getPlayer().getDisplayName(), event.getMessage()); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/events/PartialChunkFixer.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/events/PartialChunkFixer.java new file mode 100644 index 00000000..6efab3c5 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/events/PartialChunkFixer.java @@ -0,0 +1,84 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.core.events; + +import com.comphenix.tinyprotocol.Reflection; +import com.comphenix.tinyprotocol.TinyProtocol; +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.ViaAPI; +import de.steamwar.core.Core; +import de.steamwar.core.CraftbukkitWrapper; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; + +/** + * TinyProtocol can't translate BlockEntities during 1.16 to 1.17 conversions du to removed partial chunk update support. This class cancels PartialChunkUpdates for this players and sends them a complete chunk instead. + * This class can only be loaded on 1.9 to 1.15 with active ViaVersion. + **/ +public class PartialChunkFixer { + + private static final int PROTOCOL1_17 = 755; + private static final Class mapChunk = Reflection.getClass("{nms}.PacketPlayOutMapChunk"); + private static final Reflection.FieldAccessor fullChunkFlag = Reflection.getField(mapChunk, boolean.class, 0); + private static final Reflection.FieldAccessor chunkX = Reflection.getField(mapChunk, int.class, 0); + private static final Reflection.FieldAccessor chunkZ = Reflection.getField(mapChunk, int.class, 1); + + private final ViaAPI via = Via.getAPI(); + + private final List chunksToResend = new ArrayList<>(); + + public PartialChunkFixer() { + TinyProtocol.instance.addFilter(mapChunk, this::chunkFilter); + Bukkit.getScheduler().runTaskTimer(Core.getInstance(), () -> { + synchronized (chunksToResend) { + for(ResendChunk chunk : chunksToResend) { + CraftbukkitWrapper.impl.sendChunk(chunk.player, chunk.x, chunk.z); + } + chunksToResend.clear(); + } + }, 1, 1); + } + + private Object chunkFilter(Player player, Object packet) { + if(via.getPlayerVersion(player) >= PROTOCOL1_17 && !fullChunkFlag.get(packet)) { + // partial chunk update + synchronized (chunksToResend) { + chunksToResend.add(new ResendChunk(player, chunkX.get(packet), chunkZ.get(packet))); + } + return null; + } + return packet; + } + + private static class ResendChunk { + private final Player player; + private final int x; + private final int z; + + private ResendChunk(Player player, int x, int z) { + this.player = player; + this.x = x; + this.z = z; + } + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/events/PlayerJoinedEvent.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/events/PlayerJoinedEvent.java new file mode 100644 index 00000000..8936de9a --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/events/PlayerJoinedEvent.java @@ -0,0 +1,55 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.core.events; + +import de.steamwar.sql.SteamwarUser; +import de.steamwar.sql.UserPerm; +import de.steamwar.sql.internal.Statement; +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; + + +public class PlayerJoinedEvent implements Listener{ + + @EventHandler(priority = EventPriority.LOWEST) + private void onJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + SteamwarUser user = Statement.productionDatabase() ? SteamwarUser.get(player.getUniqueId()) : SteamwarUser.getOrCreate(player.getUniqueId(), player.getName(), uuid -> {}, (oldName, newName) -> {}); + + UserPerm.Prefix prefix = user.prefix(); + if(prefix != UserPerm.emptyPrefix) { + player.setDisplayName(prefix.getColorCode() + prefix.getChatPrefix() + " " + player.getName() + "§r"); + } else + player.setDisplayName(prefix.getColorCode() + player.getName() + "§r"); + + event.setJoinMessage("§a§l» §r" + player.getDisplayName()); + } + + @EventHandler + private void onQuit(PlayerQuitEvent event) { + Player player = event.getPlayer(); + + event.setQuitMessage("§c§l« §r" + player.getDisplayName()); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/events/WorldLoadEvent.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/events/WorldLoadEvent.java new file mode 100644 index 00000000..c28f998e --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/core/events/WorldLoadEvent.java @@ -0,0 +1,33 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.core.events; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.world.WorldInitEvent; + +public class WorldLoadEvent implements Listener { + + @EventHandler + public void onWorldInit(WorldInitEvent e){ + if(Integer.parseInt(System.getProperty("fightID", "0")) != -1) // On testarenas not + e.getWorld().setKeepSpawnInMemory(false); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/entity/RArmorStand.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/entity/RArmorStand.java new file mode 100644 index 00000000..420c6f47 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/entity/RArmorStand.java @@ -0,0 +1,81 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.entity; + +import de.steamwar.core.BountifulWrapper; +import de.steamwar.core.Core; +import lombok.Getter; +import org.bukkit.Location; +import org.bukkit.entity.EntityType; + +import java.util.function.Consumer; + +public class RArmorStand extends REntity { + + private static int sizeIndex() { + switch(Core.getVersion()) { + case 8: + case 9: + return 10; + case 10: + case 12: + return 11; + case 14: + return 13; + case 15: + return 14; + case 18: + case 19: + default: + return 15; + } + } + + private static final Object sizeWatcher = BountifulWrapper.impl.getDataWatcherObject(sizeIndex(), Byte.class); + + @Getter + private final Size size; + + public RArmorStand(REntityServer server, Location location, Size size) { + super(server, EntityType.ARMOR_STAND, location, 0); + this.size = size; + server.addEntity(this); + } + + @Override + void spawn(Consumer packetSink) { + super.spawn(packetSink); + + if(size != null && size != Size.NORMAL) + packetSink.accept(getDataWatcherPacket(sizeWatcher, size.value)); + } + + public enum Size { + NORMAL((byte) 0x00), + SMALL((byte) 0x01), + MARKER((byte) 0x10); + + private final byte value; + + Size(byte value) { + this.value = value; + } + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/entity/REntity.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/entity/REntity.java new file mode 100644 index 00000000..5f97b0df --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/entity/REntity.java @@ -0,0 +1,491 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.entity; + +import com.comphenix.tinyprotocol.Reflection; +import de.steamwar.core.*; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import lombok.Getter; +import org.bukkit.Location; +import org.bukkit.entity.EntityType; +import org.bukkit.inventory.ItemStack; + +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Function; + +public class REntity { + + private static final Object entityStatusWatcher = BountifulWrapper.impl.getDataWatcherObject(0, Byte.class); + private static final Object sneakingDataWatcher = BountifulWrapper.impl.getDataWatcherObject(Core.getVersion() > 12 ? 6 : 0, FlatteningWrapper.impl.getPose(FlatteningWrapper.EntityPose.NORMAL).getClass()); + private static final Object bowDrawnWatcher = BountifulWrapper.impl.getDataWatcherObject(Core.getVersion() > 12 ? 7 : 6, Byte.class); + private static final Object nameWatcher = BountifulWrapper.impl.getDataWatcherObject(2, Core.getVersion() > 12 ? Optional.class : String.class); // Optional + private static final Object nameVisibleWatcher = BountifulWrapper.impl.getDataWatcherObject(3, Boolean.class); + + private static final Object noGravityDataWatcher = BountifulWrapper.impl.getDataWatcherObject(5,Boolean.class); + + private static int entityIdCounter = -1; + private static final Random random = new Random(); + + private final REntityServer server; + private final EntityType entityType; + @Getter + protected final int entityId; + @Getter + protected final UUID uuid; + + @Getter + protected double x; + @Getter + protected double y; + @Getter + protected double z; + private byte yaw; + private byte pitch; + private byte headYaw; + + @Getter + private boolean hidden; + @Getter + private boolean invisible; + @Getter + private FlatteningWrapper.EntityPose pose = FlatteningWrapper.EntityPose.NORMAL; + @Getter + private boolean bowDrawn; + @Getter + private boolean noGravity; + private boolean isGlowing; + private int fireTick; + + private final int objectData; + @Getter + private String displayName; + protected final Map itemSlots; + + public REntity(REntityServer server, EntityType entityType, Location location) { + this(server,entityType,location,0); + server.addEntity(this); + } + + protected REntity(REntityServer server, EntityType entityType, Location location,int objectData) { + this(server, entityType, new UUID(random.nextLong() & -61441L | 16384L, random.nextLong() & 4611686018427387903L | -9223372036854775808L), location,objectData); + } + + protected REntity(REntityServer server, EntityType entityType, UUID uuid, Location location,int objectData) { + this.server = server; + this.entityType = entityType; + this.entityId = entityIdCounter--; + this.uuid = uuid; + + this.x = location.getX(); + this.y = location.getY(); + this.z = location.getZ(); + this.headYaw = this.yaw = rotToByte(location.getYaw()); + this.pitch = rotToByte(location.getPitch()); + + this.itemSlots = entityType == EntityType.PLAYER ? new HashMap<>() : null; + + this.noGravity = false; + this.isGlowing = false; + + this.objectData = objectData; + } + + public void hide(boolean hide) { + if(hidden == hide) + return; + + if(hide) { + despawn(packet -> server.updateEntity(this, packet)); + hidden = true; + } else { + hidden = false; + spawn(packet -> server.updateEntity(this, packet)); + } + } + + public void move(Location location) { + move(location.getX(), location.getY(), location.getZ(), location.getPitch(), location.getYaw(), rotToByte(location.getYaw())); + } + + private static final double MAX_REL_MOVE = Core.getVersion() > 8 ? 8.0 : 4.0; + public void move(double locX, double locY, double locZ, float pitch, float yaw, byte headYaw) { + server.preEntityMove(this, locX, locZ); + + double fromX = this.x; + double fromZ = this.z; + double diffX = locX - x; + double diffY = locY - y; + double diffZ = locZ - z; + boolean rotEq = this.yaw == yaw && this.pitch == pitch; + + this.x = locX; + this.y = locY; + this.z = locZ; + this.yaw = rotToByte(yaw); + this.pitch = rotToByte(pitch); + + if(Math.abs(diffX) < MAX_REL_MOVE && Math.abs(diffY) < MAX_REL_MOVE && Math.abs(diffZ) < MAX_REL_MOVE) { + Object packet = getMoveLookPacket(diffX, diffY, diffZ,rotEq); + + if(packet != null) + server.updateEntity(this, packet); + } else { + server.updateEntity(this, getTeleportPacket()); + } + + if(this.headYaw != headYaw) { + this.headYaw = headYaw; + server.updateEntity(this, getHeadRotationPacket()); + } + + server.postEntityMove(this, fromX, fromZ); + } + + private static final Class animationPacket = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutAnimation"); + private static final Reflection.FieldAccessor animationEntity = Reflection.getField(animationPacket, int.class, Core.getVersion() > 15 ? (Core.getVersion() > 19 ? 5 : 6) : 0); + private static final Reflection.FieldAccessor animationAnimation = Reflection.getField(animationPacket, int.class, Core.getVersion() > 15 ? (Core.getVersion() > 19 ? 6 : 7) : 1); + public void showAnimation(byte animation) { + Object packet = Reflection.newInstance(animationPacket); + animationEntity.set(packet, entityId); + animationAnimation.set(packet, (int) animation); + server.updateEntity(this, packet); + } + + private static final Class velocityPacket = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutEntityVelocity"); + private static final Reflection.FieldAccessor velocityEntity = Reflection.getField(velocityPacket, int.class, 0); + private static final Reflection.FieldAccessor velocityX = Reflection.getField(velocityPacket, int.class, 1); + private static final Reflection.FieldAccessor velocityY = Reflection.getField(velocityPacket, int.class, 2); + private static final Reflection.FieldAccessor velocityZ = Reflection.getField(velocityPacket, int.class, 3); + public void setVelocity(double dX, double dY, double dZ) { + Object packet = Reflection.newInstance(velocityPacket); + velocityEntity.set(packet, entityId); + velocityX.set(packet, calcVelocity(dX)); + velocityY.set(packet, calcVelocity(dY)); + velocityZ.set(packet, calcVelocity(dZ)); + server.updateEntity(this, packet); + } + + private static final Class statusPacket = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutEntityStatus"); + private static final Reflection.FieldAccessor statusEntity = Reflection.getField(statusPacket, int.class, 0); + private static final Reflection.FieldAccessor statusStatus = Reflection.getField(statusPacket, byte.class, 0); + public void showDamage() { + Object packet = Reflection.newInstance(statusPacket); + statusEntity.set(packet, entityId); + statusStatus.set(packet, (byte) 2); + server.updateEntity(this, packet); + } + + public void setPose(FlatteningWrapper.EntityPose pose) { + this.pose = pose; + if(Core.getVersion() > 12) { + server.updateEntity(this, getDataWatcherPacket(sneakingDataWatcher, FlatteningWrapper.impl.getPose(pose))); + } else { + server.updateEntity(this, getDataWatcherPacket(entityStatusWatcher, getEntityStatus())); + } + } + + public void setOnFire(boolean perma) { + fireTick = perma ? -1 : 21; + server.updateEntity(this, getDataWatcherPacket(entityStatusWatcher, getEntityStatus())); + } + + public boolean isOnFire() { + return fireTick == -1 || fireTick > 0; + } + + public void setInvisible(boolean invisible) { + this.invisible = invisible; + server.updateEntity(this, getDataWatcherPacket(entityStatusWatcher, getEntityStatus())); + } + + public void setBowDrawn(boolean drawn, boolean offHand) { + bowDrawn = drawn; + if(Core.getVersion() > 8){ + server.updateEntity(this, getDataWatcherPacket(bowDrawnWatcher, (byte) ((drawn ? 1 : 0) + (offHand ? 2 : 0)))); + }else{ + server.updateEntity(this, getDataWatcherPacket(entityStatusWatcher, getEntityStatus())); + } + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + server.updateEntity(this, getDataWatcherPacket( + nameWatcher, FlatteningWrapper.impl.formatDisplayName(displayName), + nameVisibleWatcher, displayName != null + )); + } + + public void setItem(Object slot, ItemStack stack) { + itemSlots.put(slot, stack); + server.updateEntity(this, getEquipmentPacket(slot, stack)); + } + + public void die() { + server.removeEntity(this); + } + + public void setNoGravity(boolean noGravity) { + this.noGravity = noGravity; + if(Core.getVersion() > 8) + server.updateEntity(this,getDataWatcherPacket(noGravityDataWatcher,noGravity)); + } + + public void setGlowing(boolean glowing) { + this.isGlowing = glowing; + if(Core.getVersion() > 8) { + server.updateEntity(this,getDataWatcherPacket(entityStatusWatcher,getEntityStatus())); + } + } + + public boolean isGlowing() { + return isGlowing; + } + + private static int spawnPacketOffset() { + switch (Core.getVersion()) { + case 8: + case 18: + return 1; + case 9: + case 10: + case 12: + case 14: + case 15: + return 0; + case 19: + default: + return 2; + } + } + private static final Function spawnPacketGenerator = entitySpawnPacketGenerator(ProtocolWrapper.spawnPacket, spawnPacketOffset()); + private static int objectDataOffset() { + switch (Core.getVersion()) { + case 8: + return 9; + case 9: + case 14: + case 12: + case 10: + case 15: + case 18: + return 6; + case 19: + default: + return 4; + } + } + private static final Reflection.FieldAccessor additionalData = Reflection.getField(ProtocolWrapper.spawnPacket, int.class, objectDataOffset()); + private Object spawnPacketGenerator() { + Object packet = spawnPacketGenerator.apply(this); + additionalData.set(packet, objectData); + return packet; + } + + void list(Consumer packetSink) { + // empty for regular entity + } + + private static final Function livingSpawnPacketGenerator = Core.getVersion() >= 19 ? REntity::spawnPacketGenerator : entitySpawnPacketGenerator(ProtocolWrapper.spawnLivingPacket, Core.getVersion() == 8 ? 2 : 0); + void spawn(Consumer packetSink) { + if(entityType.isAlive()) { + packetSink.accept(livingSpawnPacketGenerator.apply(this)); + } else { + packetSink.accept(spawnPacketGenerator()); + } + + postSpawn(packetSink); + } + + protected void postSpawn(Consumer packetSink) { + if(headYaw != 0) { + packetSink.accept(getHeadRotationPacket()); + } + + if(Core.getVersion() > 12 && pose != FlatteningWrapper.EntityPose.NORMAL) { + packetSink.accept(getDataWatcherPacket(sneakingDataWatcher, FlatteningWrapper.impl.getPose(pose))); + } + + byte status = getEntityStatus(); + if(status != 0) { + packetSink.accept(getDataWatcherPacket(entityStatusWatcher, getEntityStatus())); + } + + if(displayName != null) { + packetSink.accept(getDataWatcherPacket(nameWatcher, FlatteningWrapper.impl.formatDisplayName(displayName), nameVisibleWatcher, true)); + } + + if(Core.getVersion() > 8 && noGravity) + packetSink.accept(getDataWatcherPacket(noGravityDataWatcher, true)); + } + + void tick() { + if(fireTick > 0) { + fireTick--; + if(fireTick == 0) { + server.updateEntity(this, getDataWatcherPacket(entityStatusWatcher, getEntityStatus())); + } + } + } + + private static final Class destroyPacket = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutEntityDestroy"); + private static final Reflection.FieldAccessor destroyEntities; + static { + if(Core.getVersion() > 15) + destroyEntities = Reflection.getField(destroyPacket, IntList.class, 0); + else + destroyEntities = Reflection.getField(destroyPacket, int[].class, 0); + } + void despawn(Consumer packetSink){ + Object packet = Reflection.newInstance(destroyPacket); + destroyEntities.set(packet, Core.getVersion() > 15 ? new IntArrayList(new int[]{entityId}) : new int[]{entityId}); + packetSink.accept(packet); + } + + void delist(Consumer packetSink) { + // empty for regular entity + } + + double x() { + return x; + } + + double z() { + return z; + } + + private byte getEntityStatus() { + byte status = 0; + + if(fireTick != 0) + status |= 1; + if(pose == FlatteningWrapper.EntityPose.SNEAKING) + status |= 2; + if(Core.getVersion() == 8 && bowDrawn) + status |= 0x10; + if(invisible) + status |= 0x20; + if(Core.getVersion() > 8 && isGlowing) + status |= 0x40; + + return status; + } + + protected Object getDataWatcherPacket(Object... dataWatcherKeyValues) { + return ChatWrapper.impl.getDataWatcherPacket(entityId, dataWatcherKeyValues); + } + + private static final Class teleportPacket = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutEntityTeleport"); + private static final Reflection.FieldAccessor teleportEntity = Reflection.getField(teleportPacket, int.class, 0); + private static final BountifulWrapper.PositionSetter teleportPosition = BountifulWrapper.impl.getPositionSetter(teleportPacket, Core.getVersion() == 8 ? 1 : 0); + private static final Reflection.FieldAccessor teleportYaw = Reflection.getField(teleportPacket, byte.class, 0); + private static final Reflection.FieldAccessor teleportPitch = Reflection.getField(teleportPacket, byte.class, 1); + private Object getTeleportPacket(){ + Object packet = Reflection.newInstance(teleportPacket); + teleportEntity.set(packet, entityId); + teleportPosition.set(packet, x, y, z); + teleportYaw.set(packet, yaw); + teleportPitch.set(packet, pitch); + return packet; + } + + private static final Class entityPacket = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutEntity"); + private static final Reflection.FieldAccessor moveEntityId = Reflection.getField(entityPacket, int.class, 0); + private static final BountifulWrapper.PositionSetter movePosition = BountifulWrapper.impl.getRelMoveSetter(entityPacket); + private static final Reflection.FieldAccessor lookYaw = Reflection.getField(entityPacket, "e", byte.class); + private static final Reflection.FieldAccessor lookPitch = Reflection.getField(entityPacket, "f", byte.class); + private static final Class lookPacket = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutEntity$PacketPlayOutEntityLook"); + private static final Class movePacket = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutEntity$PacketPlayOutRelEntityMove"); + private static final Class moveLookPacket = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutEntity$PacketPlayOutRelEntityMoveLook"); + private Object getMoveLookPacket(double diffX, double diffY, double diffZ, boolean rotEq) { + Class clazz; + if(diffX == 0 && diffY == 0 && diffZ == 0) { + if(rotEq) + return null; + + clazz = lookPacket; + } else if (rotEq) { + clazz = movePacket; + } else { + clazz = moveLookPacket; + } + + Object packet = Reflection.newInstance(clazz); + moveEntityId.set(packet, entityId); + movePosition.set(packet, diffX, diffY, diffZ); + lookYaw.set(packet, yaw); + lookPitch.set(packet, pitch); + return packet; + } + + private static final Class headRotationPacket = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutEntityHeadRotation"); + private static final Reflection.FieldAccessor headRotationEntity = Reflection.getField(headRotationPacket, int.class, 0); + private static final Reflection.FieldAccessor headRotationYaw = Reflection.getField(headRotationPacket, byte.class, 0); + private Object getHeadRotationPacket(){ + Object packet = Reflection.newInstance(headRotationPacket); + headRotationEntity.set(packet, entityId); + headRotationYaw.set(packet, headYaw); + return packet; + } + + private static final Reflection.FieldAccessor equipmentEntity = Reflection.getField(ProtocolWrapper.equipmentPacket, int.class, 0); + + private static final Class craftItemStack = Reflection.getClass("{obc}.inventory.CraftItemStack"); + private static final Reflection.MethodInvoker asNMSCopy = Reflection.getTypedMethod(REntity.craftItemStack, "asNMSCopy", ProtocolWrapper.itemStack, ItemStack.class); + protected Object getEquipmentPacket(Object slot, ItemStack stack){ + Object packet = Reflection.newInstance(ProtocolWrapper.equipmentPacket); + equipmentEntity.set(packet, entityId); + ProtocolWrapper.impl.setEquipmentPacketStack(packet, slot, asNMSCopy.invoke(null, stack)); + return packet; + } + + private static Function entitySpawnPacketGenerator(Class spawnPacket, int posOffset) { + BountifulWrapper.UUIDSetter uuid = BountifulWrapper.impl.getUUIDSetter(spawnPacket); + Function packetGenerator = spawnPacketGenerator(spawnPacket, posOffset); + + return entity -> { + Object packet = packetGenerator.apply(entity); + uuid.set(packet, entity.uuid); + FlatteningWrapper.impl.setSpawnPacketType(packet, entity.entityType); + return packet; + }; + } + + protected static Function spawnPacketGenerator(Class spawnPacket, int posOffset) { + Reflection.FieldAccessor entityId = Reflection.getField(spawnPacket, int.class, 0); + BountifulWrapper.PositionSetter position = BountifulWrapper.impl.getPositionSetter(spawnPacket, posOffset); + + return entity -> { + Object packet = Reflection.newInstance(spawnPacket); + entityId.set(packet, entity.entityId); + position.set(packet, entity.x, entity.y, entity.z); + return packet; + }; + } + + private byte rotToByte(float rot) { + return (byte)((int)(rot * 256.0F / 360.0F)); + } + + private int calcVelocity(double value) { + return (int)(Math.max(-3.9, Math.min(value, 3.9)) * 8000); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/entity/REntityServer.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/entity/REntityServer.java new file mode 100644 index 00000000..442d05dd --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/entity/REntityServer.java @@ -0,0 +1,329 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.entity; + +import com.comphenix.tinyprotocol.Reflection; +import com.comphenix.tinyprotocol.TinyProtocol; +import de.steamwar.core.Core; +import de.steamwar.core.FlatteningWrapper; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; + + +public class REntityServer implements Listener { + + private static final HashSet emptyEntities = new HashSet<>(0); + private static final HashSet emptyPlayers = new HashSet<>(0); + + private static final Class useEntity = Reflection.getClass("{nms.network.protocol.game}.PacketPlayInUseEntity"); + private static final Reflection.FieldAccessor useEntityTarget = Reflection.getField(useEntity, int.class, 0); + private static final Class useEntityEnumAction = Reflection.getClass("{nms.network.protocol.game}.PacketPlayInUseEntity$EnumEntityUseAction"); + private static final Reflection.FieldAccessor useEntityAction = Reflection.getField(useEntity, useEntityEnumAction, 0); + private static final Function getEntityAction; + static { + if(Core.getVersion() > 15) { + Reflection.MethodInvoker useEntityGetAction = Reflection.getMethod(useEntityEnumAction, "a"); + getEntityAction = value -> ((Enum) useEntityGetAction.invoke(value)).ordinal(); + } else { + getEntityAction = value -> ((Enum) value).ordinal(); + } + } + + private final ConcurrentHashMap entityMap = new ConcurrentHashMap<>(); + private final HashMap> entities = new HashMap<>(); + private final HashMap> players = new HashMap<>(); + private final HashMap lastLocation = new HashMap<>(); + private final HashMap viewDistance = new HashMap<>(); + + private EntityActionListener callback = null; + private final Set playersThatClicked = Collections.synchronizedSet(new HashSet<>()); + + private final BiFunction filter = (player, packet) -> { + REntity entity = entityMap.get(useEntityTarget.get(packet)); + if (entity == null) + return packet; + + if (playersThatClicked.contains(player)) + return null; + + playersThatClicked.add(player); + EntityAction action = getEntityAction.apply(useEntityAction.get(packet)) == 1 ? EntityAction.ATTACK : EntityAction.INTERACT; + Bukkit.getScheduler().runTask(Core.getInstance(), () -> { + playersThatClicked.remove(player); + callback.onAction(player, entity, action); + }); + return null; + }; + + public REntityServer() { + Core.getInstance().getServer().getPluginManager().registerEvents(this, Core.getInstance()); + } + + public void setCallback(EntityActionListener callback) { + boolean uninitialized = this.callback == null; + this.callback = callback; + + if(uninitialized) + TinyProtocol.instance.addFilter(useEntity, filter); + } + + public void addPlayer(Player player) { + Location location = player.getLocation(); + lastLocation.put(player, location); + viewDistance.put(player, viewRadius(player)); + forChunkInView(player, location, (x, z) -> addPlayerToChunk(player, x, z)); + } + + public void removePlayer(Player player) { + if (!viewDistance.containsKey(player)) { + return; + } + forChunkInView(player, lastLocation.remove(player), (x, z) -> removePlayerFromChunk(player, x, z)); + viewDistance.remove(player); + } + + public List getPlayers() { + return new ArrayList<>(viewDistance.keySet()); + } + + public void close() { + TinyProtocol.instance.removeFilter(useEntity, filter); + for(Player player : lastLocation.keySet().toArray(new Player[0])) { + removePlayer(player); + } + HandlerList.unregisterAll(this); + } + + void addEntity(REntity entity) { + entityMap.put(entity.entityId, entity); + addEntityToChunk(entity); + entity.list(packet -> updateEntity(entity, packet)); + entity.spawn(packet -> updateEntity(entity, packet)); + } + + void preEntityMove(REntity entity, double toX, double toZ) { + long fromId = entityToId(entity); + long toId = posToId(toX, toZ); + if(fromId == toId) + return; + + if(!entity.isHidden()) + onMissing(players.get(fromId), players.get(toId), entity::despawn); + onMissing(players.get(fromId), players.get(toId), entity::delist); + removeEntityFromChunk(entity); + } + + void postEntityMove(REntity entity, double fromX, double fromZ) { + long fromId = posToId(fromX, fromZ); + long toId = entityToId(entity); + if(fromId == toId) + return; + + addEntityToChunk(entity); + onMissing(players.get(toId), players.get(fromId), entity::list); + if(!entity.isHidden()) + onMissing(players.get(toId), players.get(fromId), entity::spawn); + } + + void updateEntity(REntity entity, Object packet) { + if(entity.isHidden()) + return; + + for(Player player : players.getOrDefault(entityToId(entity), emptyPlayers)) { + TinyProtocol.instance.sendPacket(player, packet); + } + } + + void removeEntity(REntity entity) { + entity.despawn(packet -> updateEntity(entity, packet)); + removeEntityFromChunk(entity); + entity.delist(packet -> updateEntity(entity, packet)); + entityMap.remove(entity.entityId); + } + + public List getEntities() { + return new ArrayList<>(entityMap.values()); + } + + public List getEntitiesByType(Class clazz) { + return entityMap.values().stream().filter(clazz::isInstance).map(clazz::cast).collect(Collectors.toList()); + } + + private void addEntityToChunk(REntity entity) { + entities.computeIfAbsent(entityToId(entity), i -> new HashSet<>()).add(entity); + } + + private void removeEntityFromChunk(REntity entity) { + long id = entityToId(entity); + HashSet entitiesInChunk = entities.get(id); + entitiesInChunk.remove(entity); + if(entitiesInChunk.isEmpty()) + entities.remove(id); + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onMove(PlayerMoveEvent e) { + Player player = e.getPlayer(); + Location from = lastLocation.get(player); + Location to = e.getTo(); + if(from == null || to == null) + return; + + int fromX = posToChunk(from.getX()); + int fromZ = posToChunk(from.getZ()); + int toX = posToChunk(to.getX()); + int toZ = posToChunk(to.getZ()); + if(fromX == toX && fromZ == toZ) + return; + + lastLocation.put(player, to); + + int toViewDistance = viewRadius(player); + forChunkInView(player, from, (x, z) -> { + if(Math.abs(x - toX) > toViewDistance || Math.abs(z - toZ) > toViewDistance) { + removePlayerFromChunk(player, x, z); + } + }); + + int fromViewDistance = this.viewDistance.put(player, toViewDistance); + forChunkInView(player, to, (x, z) -> { + if(Math.abs(x - fromX) > fromViewDistance || Math.abs(z - fromZ) > fromViewDistance) { + addPlayerToChunk(player, x, z); + } + }); + } + + @EventHandler + public void onQuit(PlayerQuitEvent e) { + Player player = e.getPlayer(); + Location location = lastLocation.remove(player); + if(location == null) + return; + + forChunkInView(player, location, (x, z) -> { + long id = chunkToId(x, z); + Set playersInChunk = players.get(id); + playersInChunk.remove(player); + if(playersInChunk.isEmpty()) + players.remove(id); + }); + viewDistance.remove(player); + } + + private void onMissing(Set of, Set in, Consumer> packetProvider) { + if(of == null) + return; + + for(Player player : of) { + if(in == null || !in.contains(player)) { + packetProvider.accept(packet -> TinyProtocol.instance.sendPacket(player, packet)); + } + } + } + + private void forChunkInView(Player player, Location location, BiConsumer func) { + int chunkX = posToChunk(location.getX()); + int chunkZ = posToChunk(location.getZ()); + int viewDistance = this.viewDistance.get(player); + + for(int x = chunkX - viewDistance; x <= chunkX + viewDistance; x++) { + for(int z = chunkZ - viewDistance; z <= chunkZ + viewDistance; z++) { + func.accept(x, z); + } + } + } + + private void addPlayerToChunk(Player player, int x, int z) { + long id = chunkToId(x, z); + players.computeIfAbsent(id, i -> new HashSet<>()).add(player); + for(REntity entity : entities.getOrDefault(id, emptyEntities)) { + entity.list(packet -> TinyProtocol.instance.sendPacket(player, packet)); + if(!entity.isHidden()) + entity.spawn(packet -> TinyProtocol.instance.sendPacket(player, packet)); + } + } + + private void removePlayerFromChunk(Player player, int x, int z) { + long id = chunkToId(x, z); + + Set playersInChunk = players.get(id); + playersInChunk.remove(player); + if(playersInChunk.isEmpty()) + players.remove(id); + + for(REntity entity : entities.getOrDefault(id, emptyEntities)) { + if(!entity.isHidden()) + entity.despawn(packet -> TinyProtocol.instance.sendPacket(player, packet)); + entity.delist(packet -> TinyProtocol.instance.sendPacket(player, packet)); + } + } + + public void tick() { + for(HashSet entitiesInChunk : entities.values()) { + for(REntity entity : entitiesInChunk) { + entity.tick(); + } + } + } + + private int posToChunk(double coord) { + return (int)(coord / 16) - (coord < 0 ? 1 : 0); + } + + private int viewRadius(Player player) { + return FlatteningWrapper.impl.getViewDistance(player) / 2; + } + + private long entityToId(REntity entity) { + return posToId(entity.x(), entity.z()); + } + + private long posToId(double x, double z) { + return chunkToId(posToChunk(x), posToChunk(z)); + } + + private long chunkToId(int x, int z) { + return ((long) x << 32) + z; + } + + public enum EntityAction { + INTERACT, + ATTACK, + } + + public interface EntityActionListener { + void onAction(Player player, REntity entity, EntityAction action); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/entity/RFallingBlockEntity.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/entity/RFallingBlockEntity.java new file mode 100644 index 00000000..ae70775a --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/entity/RFallingBlockEntity.java @@ -0,0 +1,39 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.entity; + +import de.steamwar.core.Core; +import de.steamwar.techhider.BlockIds; +import lombok.Getter; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.EntityType; + +@Getter +public class RFallingBlockEntity extends REntity{ + + private final Material material; + + public RFallingBlockEntity(REntityServer server, Location location, Material material) { + super(server, EntityType.FALLING_BLOCK, location, BlockIds.impl.materialToId(material) >> (Core.getVersion() <= 12 ? 4 : 0)); + this.material = material; + server.addEntity(this); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/entity/RPlayer.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/entity/RPlayer.java new file mode 100644 index 00000000..1aa10d85 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/entity/RPlayer.java @@ -0,0 +1,103 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.entity; + +import com.comphenix.tinyprotocol.Reflection; +import com.mojang.authlib.GameProfile; +import de.steamwar.core.BountifulWrapper; +import de.steamwar.core.Core; +import de.steamwar.core.FlatteningWrapper; +import de.steamwar.core.ProtocolWrapper; +import lombok.Getter; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.entity.EntityType; +import org.bukkit.inventory.ItemStack; + +import java.util.Map; +import java.util.UUID; +import java.util.function.Consumer; +import java.util.function.Function; + +@Getter +public class RPlayer extends REntity { + + private static int skinPartsIndex() { + switch(Core.getVersion()) { + case 8: + return 10; + case 9: + return 12; + case 10: + case 12: + return 13; + case 14: + return 15; + case 15: + return 16; + case 18: + case 19: + default: + return 17; + } + } + + private static final Object skinPartsDataWatcher = BountifulWrapper.impl.getDataWatcherObject(skinPartsIndex(), Byte.class); + + private final String name; + + public RPlayer(REntityServer server, UUID uuid, String name, Location location) { + super(server, EntityType.PLAYER, uuid, location,0); + this.name = name; + server.addEntity(this); + } + + @Override + void list(Consumer packetSink) { + packetSink.accept(ProtocolWrapper.impl.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.ADD, new GameProfile(uuid, name), GameMode.CREATIVE)); + } + + @Override + void spawn(Consumer packetSink) { + packetSink.accept(getNamedSpawnPacket()); + packetSink.accept(getDataWatcherPacket(skinPartsDataWatcher, (byte) 0x7F)); + + for (Map.Entry entry : itemSlots.entrySet()) { + packetSink.accept(getEquipmentPacket(entry.getKey(), entry.getValue())); + } + + postSpawn(packetSink); + } + + @Override + void delist(Consumer packetSink) { + packetSink.accept(ProtocolWrapper.impl.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.REMOVE, new GameProfile(uuid, name), GameMode.CREATIVE)); + } + + private static final Class namedSpawnPacket = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutNamedEntitySpawn"); + private static final Function namedSpawnPacketGenerator = spawnPacketGenerator(namedSpawnPacket, Core.getVersion() == 8 ? 1 : 0); + private static final Reflection.FieldAccessor namedSpawnUUID = Reflection.getField(namedSpawnPacket, UUID.class, 0); + private Object getNamedSpawnPacket() { + Object packet = namedSpawnPacketGenerator.apply(this); + namedSpawnUUID.set(packet, uuid); + FlatteningWrapper.impl.setNamedSpawnPacketDataWatcher(packet); + return packet; + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/InvCallback.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/InvCallback.java new file mode 100644 index 00000000..0ac23234 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/InvCallback.java @@ -0,0 +1,26 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.inventory; + +import org.bukkit.event.inventory.ClickType; + +public interface InvCallback { + void clicked(ClickType click); +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/SWAnvilInv.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/SWAnvilInv.java new file mode 100644 index 00000000..3ff1aa1e --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/SWAnvilInv.java @@ -0,0 +1,99 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.inventory; + +import de.steamwar.core.Core; +import net.wesjd.anvilgui.AnvilGUI; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; + +public class SWAnvilInv { + + private final AnvilGUI.Builder builder; + private final Player player; + private final String defaultText; + + private Runnable leftCallback; + private Consumer callback; + + public SWAnvilInv(Player p, String t) { + this(p, t, ""); + } + + public SWAnvilInv(Player p, String t, String defaultText) { + builder = new AnvilGUI.Builder().plugin(Core.getInstance()).title(t).text("»" + defaultText).onClick(this::onResult); + this.defaultText = defaultText; + player = p; + } + + public void setItem(Material m) { + setItem(m, new ArrayList<>(), false); + } + + public void setItem(Material m, byte meta) { + setItem(m, meta, new ArrayList<>(), false); + } + + public void setItem(Material m, List lore, boolean e) { + setItem(m, (byte)0, lore, e); + } + + public void setItem(Material m, byte meta, List lore, boolean e) { + builder.itemLeft(new SWItem(m, meta, "»" + defaultText, lore, e, null).getItemStack()); + } + + public void setCallback(Consumer callback) { + this.callback = callback; + } + + public void addLeftCallback(Runnable callback) { + leftCallback = callback; + } + + public void addCloseCallback(Runnable callback) { + builder.onClose(p -> callback.run()); + } + + public void open() { + player.setLevel(1); + builder.open(player); + } + + private List onResult(Integer slot, AnvilGUI.StateSnapshot state) { + if(slot != AnvilGUI.Slot.OUTPUT) { + if(slot == AnvilGUI.Slot.INPUT_LEFT && leftCallback != null) + leftCallback.run(); + + return Collections.emptyList(); + } + + String s = state.getText(); + if(s.startsWith("»")) + s = s.substring(1); + callback.accept(s); + player.setLevel(0); + return Collections.singletonList(AnvilGUI.ResponseAction.close()); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/SWInventory.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/SWInventory.java new file mode 100644 index 00000000..94a8cf3a --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/SWInventory.java @@ -0,0 +1,173 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.inventory; + +import de.steamwar.core.Core; +import org.bukkit.Bukkit; +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.InventoryCloseEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class SWInventory implements Listener { + + final Player player; + final Map> callbacks = new HashMap<>(); + final Inventory inventory; + String title; + boolean open = false; + + public SWInventory(Player p, int size, String t) { + player = p; + inventory = Bukkit.createInventory(p, size, t); + } + + public SWInventory(Player p, int size, String t, Map items) { + this(p, size, t); + items.forEach(this::setItem); + open(); + } + + public SWInventory(Player p, Supplier inventoryConstructor) { + player = p; + try { + inventory = inventoryConstructor.get(); + } catch (Exception e) { + throw new SecurityException("Could not construct inventory", e); + } + } + + public void addCloseCallback(InvCallback c) { + callbacks.put(-1, inventoryClickEvent -> c.clicked(null)); + } + + public void addCloseRunnable(Runnable c) { + callbacks.put(-1, inventoryClickEvent -> c.run()); + } + + public void setItem(int pos, ItemStack itemStack, InvCallback c) { + inventory.setItem(pos, itemStack); + if(c != null) { + callbacks.put(pos, inventoryClickEvent -> c.clicked(inventoryClickEvent.getClick())); + } else { + callbacks.remove(pos); + } + } + + public void setItem(int pos, SWItem item) { + setItem(pos, item.getItemStack(), item.getCallback()); + } + + public void setItem(int pos, Material m, String name, InvCallback c){ + setItem(pos, m, name, new ArrayList<>(), false, c); + } + + public void setItem(int pos, Material m, byte meta, String name, InvCallback c){ + setItem(pos, m, meta, name, new ArrayList<>(), false, c); + } + + public void setItem(int pos, Material m, String name, List lore, boolean e, InvCallback c) { + setItem(pos, m, (byte) 0, name, lore, e, c); + } + + public void setItem(int pos, Material m, byte meta, String name, List lore, boolean e, InvCallback c) { + SWItem item = new SWItem(m, meta, name, lore, e, c); + setItem(pos, item); + } + + public void setCallback(int pos, InvCallback c) { + callbacks.put(pos, inventoryClickEvent -> c.clicked(inventoryClickEvent == null ? null : inventoryClickEvent.getClick())); + } + + public void setItemEvent(int pos, ItemStack itemStack, Consumer c) { + inventory.setItem(pos, itemStack); + callbacks.put(pos, c); + } + + public void setItemEvent(int pos, Material m, String name, Consumer c) { + setItemEvent(pos, m, name, new ArrayList<>(), false, c); + } + + public void setItemEvent(int pos, Material m, byte meta, String name, Consumer c) { + setItemEvent(pos, m, meta, name, new ArrayList<>(), false, c); + } + + public void setItemEvent(int pos, Material m, String name, List lore, boolean e, Consumer c) { + setItemEvent(pos, m, (byte) 0, name, lore, e, c); + } + + public void setItemEvent(int pos, Material m, byte meta, String name, List lore, boolean e, Consumer c) { + SWItem item = new SWItem(m, meta, name, lore, e, click -> { + }); + setItem(pos, item); + setEventCallback(pos, c); + } + + public void setEventCallback(int pos, Consumer c) { + callbacks.put(pos, c); + } + + public void open() { + InventoryView view = player.openInventory(inventory); + title = view.getTitle(); + Core.getInstance().getLogger().info("[SWINV] Opened " + title + " for " + player.getName()); + if(!open) { + Bukkit.getPluginManager().registerEvents(this, Core.getInstance()); + open = true; + } + } + + @EventHandler + public void onInventoryClick(InventoryClickEvent e) { + if (!player.equals(e.getWhoClicked())) + return; + + if (callbacks.containsKey(e.getRawSlot()) && callbacks.get(e.getRawSlot()) != null) { + e.setCancelled(true); + Core.getInstance().getLogger().info("[SWINV] " + e.getWhoClicked().getName() + " " + e.getClick().name() + " clicked " + e.getRawSlot() + " on " + (e.getCurrentItem() != null ? e.getCurrentItem().getItemMeta().getDisplayName() : "[EMPTY]") + " in " + e.getView().getTitle()); + callbacks.get(e.getRawSlot()).accept(e); + } + } + + @EventHandler + public void onInventoryClose(InventoryCloseEvent e){ + if(!player.equals(e.getPlayer())) + return; + + InventoryClickEvent.getHandlerList().unregister(this); + InventoryCloseEvent.getHandlerList().unregister(this); + Core.getInstance().getLogger().info("[SWINV] " + player.getName() + " closed " + title); + if(callbacks.containsKey(-1)) + callbacks.get(-1).accept(null); + open = false; + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/SWItem.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/SWItem.java new file mode 100644 index 00000000..d9f9c23a --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/SWItem.java @@ -0,0 +1,201 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.inventory; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import de.steamwar.core.FlatteningWrapper; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.ArrayList; +import java.util.List; + +public class SWItem { + + private ItemStack itemStack; + private ItemMeta itemMeta; + private InvCallback callback; + + public static SWItem getPlayerSkull(OfflinePlayer player){ + return getPlayerSkull(player.getName()); + } + + public static SWItem getPlayerSkull(String playerName){ + SWItem p = new SWItem(); + ItemStack head = FlatteningWrapper.impl.setSkullOwner(playerName); + p.setItemStack(head); + return p; + } + + public static Material getMaterial(String material){ + try{ + Material m = FlatteningWrapper.impl.getMaterial(material); + if(m == null) + return Material.BARRIER; + + return m; + }catch(IllegalArgumentException e){ + return Material.STONE; + } + } + + public static Material getDye(int colorCode){ + return FlatteningWrapper.impl.getDye(colorCode); + } + + public SWItem() { + itemStack = new ItemStack(Material.AIR); + itemMeta = itemStack.getItemMeta(); + hideAttributes(); + } + + public SWItem(Material material, String name){ + this(material, (byte)0, name, new ArrayList<>(), false, null); + } + + public SWItem(Material material, String name, InvCallback c){ + this(material, (byte)0, name, new ArrayList<>(), false, c); + } + + public SWItem(Material material, byte meta, String name){ + this(material, meta, name, new ArrayList<>(), false, null); + } + + public SWItem(Material material, byte meta, String name, InvCallback c){ + this(material, meta, name, new ArrayList<>(), false, c); + } + + public SWItem(Material material, String name, List lore, boolean enchanted, InvCallback c) { + this(material, (byte)0, name, lore, enchanted, c); + } + + @SuppressWarnings("deprecation") + public SWItem(Material material, byte meta, String name, List lore, boolean enchanted, InvCallback c) { + + try { + itemStack = new ItemStack(material, 1, (short)0, meta); + } catch (IllegalArgumentException e) { + itemStack = new ItemStack(material, 1); + } + + itemMeta = itemStack.getItemMeta(); + + if (itemMeta != null) { + hideAttributes(); + + itemMeta.setDisplayName(name); + if (lore != null && !lore.isEmpty()) itemMeta.setLore(lore); + if (enchanted) itemMeta.addEnchant(Enchantment.DURABILITY , 10, true); + itemStack.setItemMeta(itemMeta); + } + callback = c; + } + + public static SWItem getItemFromJson(JsonObject itemJson) { + SWItem item = null; + try { + if(itemJson.has("color")) { + item = new SWItem(SWItem.getDye(itemJson.get("color").getAsInt()), + itemJson.has("color")?itemJson.get("color").getAsByte():0, + itemJson.get("title").getAsString()); + }else { + item = new SWItem(SWItem.getMaterial(itemJson.get("material").getAsString()), itemJson.get("title").getAsString()); + } + }catch (IllegalArgumentException e) { + item = new SWItem(Material.STONE, itemJson.get("title").getAsString()); + } + if(itemJson.has("skullOwner")) { + item = SWItem.getPlayerSkull(itemJson.get("skullOwner").getAsString()); + item.setName(itemJson.get("title").getAsString()); + } + + if(itemJson.has("enchanted")) + item.setEnchanted(true); + if(itemJson.has("lore")) { + List lore = new ArrayList<>(); + JsonArray loreArray = itemJson.getAsJsonArray("lore"); + loreArray.forEach(jsonElement -> lore.add(jsonElement.getAsString())); + item.setLore(lore); + } + return item; + } + + private void hideAttributes() { + if (itemMeta == null) return; + itemMeta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES); + itemMeta.addItemFlags(ItemFlag.HIDE_DESTROYS); + itemMeta.addItemFlags(ItemFlag.HIDE_UNBREAKABLE); + itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS); + itemMeta.addItemFlags(ItemFlag.HIDE_PLACED_ON); + itemMeta.addItemFlags(ItemFlag.HIDE_POTION_EFFECTS); + } + + public ItemStack getItemStack() { + return itemStack; + } + + public void setItemStack(ItemStack itemStack) { + this.itemStack = itemStack; + itemMeta = itemStack.getItemMeta(); + hideAttributes(); + } + + public ItemMeta getItemMeta() { + return itemMeta; + } + + public void setItemMeta(ItemMeta itemMeta) { + this.itemMeta = itemMeta; + itemStack.setItemMeta(itemMeta); + hideAttributes(); + } + + public InvCallback getCallback() { + return callback; + } + + public void setCallback(InvCallback callback) { + this.callback = callback; + } + + public void setName(String name) { + itemMeta.setDisplayName(name); + itemStack.setItemMeta(itemMeta); + } + + public void setLore(List lore) { + itemMeta.setLore(lore); + itemStack.setItemMeta(itemMeta); + } + + public void setEnchanted(boolean enchanted) { + if (enchanted){ + itemMeta.addEnchant(Enchantment.DURABILITY , 10, true); + } else { + itemMeta.removeEnchant(Enchantment.DURABILITY); + } + itemStack.setItemMeta(itemMeta); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/SWListInv.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/SWListInv.java new file mode 100644 index 00000000..8851081f --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/SWListInv.java @@ -0,0 +1,183 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.inventory; + +import de.steamwar.core.Core; +import de.steamwar.sql.SchematicNode; +import de.steamwar.sql.SchematicType; +import org.bukkit.Bukkit; +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 SWListInv extends SWInventory { + + private final List> elements; + + private final Map customItems = new HashMap<>(); + private final boolean dynamicSize; + private ListCallback callback; + private int page; + private boolean opened = false; + + public SWListInv(Player p, String t, List> l, ListCallback c){ + this(p, t, true, l, c); + } + + public SWListInv(Player p, String t, boolean dynamicSize, List> l, ListCallback c){ + super(p, dynamicSize ? dynamicSize(l.size()) : 54, t); + callback = c; + elements = l; + page = 0; + this.dynamicSize = dynamicSize; + } + + @Override + public void open(){ + opened = true; + inventory.clear(); + callbacks.keySet().stream().filter(i -> i >= 0).collect(Collectors.toList()).forEach(callbacks::remove); + if(!callbacks.containsKey(-999)) { + setCallback(-999, (ClickType click) -> player.closeInventory()); + } + + if (sizeBiggerMax()) { + if (page != 0) { + setItem(45, SWItem.getDye(10), (byte) 10, Core.MESSAGE.parse("SWLISINV_PREVIOUS_PAGE_ACTIVE", player), (ClickType click) -> { + page--; + open(); + }); + } else { + setItem(45, SWItem.getDye(8), (byte) 8, Core.MESSAGE.parse("SWLISINV_PREVIOUS_PAGE_INACTIVE", player), (ClickType click) -> { + }); + } + if (page < elements.size() / 45 - (elements.size() % 45 == 0 ? 1 : 0)) { + setItem(53, SWItem.getDye(10), (byte) 10, Core.MESSAGE.parse("SWLISINV_NEXT_PAGE_ACTIVE", player), (ClickType click) -> { + page++; + open(); + }); + } else { + setItem(53, SWItem.getDye(8), (byte) 8, Core.MESSAGE.parse("SWLISINV_NEXT_PAGE_INACTIVE", player), (ClickType click) -> { + }); + } + } else if (!dynamicSize) { + setItem(45, SWItem.getDye(8), (byte) 8, Core.MESSAGE.parse("SWLISINV_PREVIOUS_PAGE_INACTIVE", player), (ClickType click) -> { + }); + setItem(53, SWItem.getDye(8), (byte) 8, Core.MESSAGE.parse("SWLISINV_NEXT_PAGE_INACTIVE", player), (ClickType click) -> { + }); + } + + int ipageLimit = elements.size() - page * 45; + if (ipageLimit > 45 && sizeBiggerMax()) { + ipageLimit = 45; + } + int i = page * 45; + for (int ipage = 0; ipage < ipageLimit; ipage++) { + SWItem e = elements.get(i).getItem(); + + final int pos = i; + setItem(ipage, e); + setCallback(ipage, (ClickType click) -> callback.clicked(click, elements.get(pos).getObject())); + i++; + } + + for (Map.Entry customItem : customItems.entrySet()) { + setItem(customItem.getKey(), customItem.getValue()); + } + super.open(); + } + + @Override + public void setItem(int pos, SWItem item){ + super.setItem(pos, item); + if(!opened) + customItems.put(pos, item); + } + + public void setCallback(ListCallback c){ + callback = c; + } + + public static List> createPlayerList(UUID without){ + List> onlinePlayers = new ArrayList<>(); + for(Player player : Bukkit.getOnlinePlayers()){ + if(without != null && player.getUniqueId().equals(without)) + continue; + + onlinePlayers.add(new SWListEntry<>(SWItem.getPlayerSkull(player), player.getUniqueId())); + } + return onlinePlayers; + } + + public static List> getSchemnodeList(SchematicType type, int steamwarUserId){ + List> schemList = new ArrayList<>(); + + List schems; + if(type == null) + schems = SchematicNode.getAllSchematicsAccessibleByUser(steamwarUserId); + else + schems = SchematicNode.getAllAccessibleSchematicsOfType(steamwarUserId, type.toDB()); + + for(SchematicNode s : schems){ + Material m; + if(s.getItem().isEmpty()) + m = SWItem.getMaterial("CAULDRON_ITEM"); + else + m = SWItem.getMaterial(s.getItem()); + SWItem item = new SWItem(m,"§e" + s.getName()); + item.setEnchanted(s.isDir()); + schemList.add(new SWListEntry<>(item, s)); + } + return schemList; + } + + private boolean sizeBiggerMax(){ + return dynamicSize ? elements.size() > 54 : elements.size() > 45; + } + + private static int dynamicSize(int size){ + return (size>45) ? 54 : (size + 9-size%9); + } + + public interface ListCallback{ + void clicked(ClickType click, T element); + } + + public static class SWListEntry{ + final SWItem item; + final T object; + + public SWListEntry(SWItem item, T object){ + this.item = item; + this.object = object; + } + + public SWItem getItem(){ + return item; + } + + public T getObject(){ + return object; + } + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/SchematicSelector.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/SchematicSelector.java new file mode 100644 index 00000000..73c45259 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/SchematicSelector.java @@ -0,0 +1,656 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2021 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.inventory; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import de.steamwar.core.Core; +import de.steamwar.sql.*; +import lombok.*; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.text.MessageFormat; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +public class SchematicSelector { + + private static final Sorting[] all_sortings = Sorting.values(); + private static final Map filterCache = new HashMap<>(); + + @Getter + private final Player player; + @Getter + private SteamwarUser user; + @Getter + @Setter + private Consumer callback; + private final SelectorTarget target; + @Getter + private SelectorFilter filter; + private SchematicSelectorInjectable injectable = SchematicSelectorInjectable.DEFAULT; + @Setter + @Getter + private PublicMode publicMode = PublicMode.ALL; + @Setter + @Getter + private boolean singleDirOpen; + private boolean sdoTrigger = false; + @Getter + @Setter + private int depth = 0; + private Sorting sorting = Sorting.NAME; + private boolean invertSorting = false; + @Getter + private SchematicNode lastParent; + + public SchematicSelector(Player player, SelectorTarget target, Consumer callback) { + this.player = player; + this.user = SteamwarUser.get(player.getUniqueId()); + this.target = target; + this.callback = callback; + this.singleDirOpen = !target.target.dirs; + } + + public SchematicSelector(Player player, SelectorTarget target, SchematicSelectorInjectable injectable, Consumer callback) { + this(player, target, callback); + this.injectable = injectable; + } + + public void open() { + injectable.onSelectorCreate(this); + if(publicMode == PublicMode.PUBLIC_ONLY) { + this.user = SteamwarUser.get(0); + } + openList(null); + injectable.onSelectorOpen(this, SchematicSelectorInjectable.OpenFrom.FRESH); + } + + public void reOpen() { + openList(lastParent); + injectable.onSelectorOpen(this, SchematicSelectorInjectable.OpenFrom.REOPEN); + } + + public void reOpenDirUp() { + depth--; + openList(dirUp(lastParent)); + injectable.onSelectorOpen(this, SchematicSelectorInjectable.OpenFrom.REOPEN); + } + + private void openList(SchematicNode parent) { + lastParent = parent; + List nodes = applySorting(filter != null?getFilteredSchematics():getSchematicList(parent)); + + if(sdoTrigger) { + sdoTrigger = false; + openList(nodes.get(0)); + return; + } + + List> list = new ArrayList<>(); + + if(depth != 0) { + list.add(new SWListInv.SWListEntry<>(new SWItem(Material.ARROW, Core.MESSAGE.parse("SCHEM_SELECTOR_BACK", player), clickType -> {}), null)); + } + + for (SchematicNode node : nodes) { + if(node.getName().equals("//copy")) continue; + list.add(renderItem(node)); + } + + SWListInv inv = new SWListInv<>(player, MessageFormat.format(injectable.createTitle(player), target.target.getName(player), (filter == null || filter.getName() == null)?(parent == null?"/":parent.generateBreadcrumbs()):filter.getName()), false, list, (clickType, node) -> handleClick(node, parent)); + if(publicMode == PublicMode.ALL) { + if(user.getId() == 0) { + inv.setItem(48, Material.BUCKET, Core.MESSAGE.parse("SCHEM_SELECTOR_OWN", player), clickType -> { + this.user = SteamwarUser.get(player.getUniqueId()); + openList(null); + }); + } else { + inv.setItem(48, Material.GLASS, Core.MESSAGE.parse("SCHEM_SELECTOR_PUB", player), clickType -> { + this.user = SteamwarUser.get(0); + openList(null); + }); + } + } + if(target.target.dirs) { + inv.setItem(49, SWItem.getDye(10), Core.MESSAGE.parse("SCHEM_SELECTOR_SEL_DIR", player), clickType -> { + player.closeInventory(); + callback.accept(parent); + }); + } + if(user.getId() != 0) { + inv.setItem(50, Material.CHEST, Core.MESSAGE.parse("SCHEM_SELECTOR_NEW_DIR", player), clickType -> createFolderIn(parent)); + } + inv.setItem(51, Material.NAME_TAG, Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER", player), clickType -> openFilter()); + inv.setItem(47, sorting.mat, Core.MESSAGE.parse("SCHEM_SELECTOR_SORTING", player), Arrays.asList( + Core.MESSAGE.parse("SCHEM_SELECTOR_SORTING_CURRENT", player, sorting.parseName(player)), + Core.MESSAGE.parse("SCHEM_SELECTOR_SORTING_DIRECTION", player, Core.MESSAGE.parse(invertSorting?"SCHEM_SELECTOR_SORTING_DSC":"SCHEM_SELECTOR_SORTING_ASC", player)) + ), invertSorting, click -> { + if(click.isLeftClick()) { + cycleSorting(); + } else { + invertSorting = !invertSorting; + } + openList(parent); + }); + + injectable.onListRender(this, inv, parent); + inv.open(); + } + + private SchematicNode dirUp(SchematicNode parent) { + if(parent == null) { + // Gracefully handle unexpected Updir in Root Folder + depth = 0; + return null; + } + if(!singleDirOpen) { + if(NodeMember.getNodeMember(parent.getId(), user.getId()) != null) { + return NodeMember.getNodeMember(parent.getId(), user.getId()).getParent().map(integer -> SchematicNode.byIdAndUser(user, integer)).orElse(null); + } else { + return getParent(parent).orElse(null); + } + } else { + Optional currentParent = Optional.of(parent); + do { + sdoTrigger = false; + currentParent = currentParent.flatMap(SchematicSelector::getParent); + if(!currentParent.isPresent()) { + break; + } + getSchematicList(currentParent.get()); + } while (sdoTrigger); + return currentParent.orElse(null); + } + } + + private void handleClick(SchematicNode node, SchematicNode parent) { + if(node == null) { + depth--; + openList(dirUp(parent)); + return; + } + if(node.isDir()) { + if(filter != null && target.target.dirs) { + player.closeInventory(); + callback.accept(node); + return; + } + filter = null; + depth++; + openList(node); + return; + } + player.closeInventory(); + callback.accept(node); + } + + private void cycleSorting() { + int next = sorting.ordinal() + 1; + if(next >= all_sortings.length) { + next = 0; + } + sorting = all_sortings[next]; + } + + private List applySorting(List nodes) { + if(sorting == Sorting.NAME && !invertSorting) { + return nodes; + } + + Comparator comparator = sorting.comparator; + if(invertSorting) { + comparator = comparator.reversed(); + } + + nodes.sort(comparator); + return nodes; + } + + private SWListInv.SWListEntry renderItem(SchematicNode node) { + Material m = SWItem.getMaterial(node.getItem()); + + String name = Core.MESSAGE.parse((filter != null && filter.name == null)?"SCHEM_SELECTOR_ITEM_NAME":"SCHEM_SELECTOR_ITEM_NAME_FILTER", player, node.getName()); + + if(filter != null && filter.getName() != null) { + name = name.replace(filter.getName(), Core.MESSAGE.parse("SCHEM_SELECTOR_ITEM_REPLACE", player, filter.getName())); + } + + SWItem item = new SWItem(m, name, Collections.singletonList(node.isDir() ? (Core.MESSAGE.parse("SCHEM_SELECTOR_DIR", player)) : Core.MESSAGE.parse("SCHEM_SELECTOR_ITEM_LORE_TYPE", player, node.getSchemtype().name())), !node.isDir() && !node.getSchemtype().writeable(), click -> { + }); + if(!node.isDir() && node.getRank() > 0) { + item.setLore(Arrays.asList(Core.MESSAGE.parse("SCHEM_SELECTOR_ITEM_LORE_TYPE", player, node.getSchemtype().name()), Core.MESSAGE.parse("SCHEM_SELECTOR_RANK", player, node.getRank()))); + } + return new SWListInv.SWListEntry<>(item, node); + } + + private void addLeftCloseAction(SWAnvilInv inv, Runnable runnable) { + AtomicBoolean wasLeft = new AtomicBoolean(false); + inv.addCloseCallback(() -> { + if(injectable.onAnvilInvCloseAction(this) == SchematicSelectorInjectable.AnvilInvCloseAction.REOPEN && !wasLeft.get()) { + player.closeInventory(); + Bukkit.getScheduler().runTaskLater(Core.getInstance(), runnable, 1); + } + }); + inv.addLeftCallback(() -> { + wasLeft.set(true); + runnable.run(); + }); + } + + private void createFolderIn(SchematicNode parent) { + SWAnvilInv inv = new SWAnvilInv(player, Core.MESSAGE.parse("SCHEM_SELECTOR_CREATE_DIR_TITLE", player)); + inv.setItem(Material.CHEST, Collections.singletonList(Core.MESSAGE.parse("SCHEM_SELECTOR_CLICK_BACK", player)), false); + inv.setCallback(s -> { + if(!SchematicNode.invalidSchemName(new String[] {s})) { + if(injectable.onFolderCreate(this, s)) { + SchematicNode.createSchematicDirectory(user.getId(), s, parent==null?0:parent.getId()); + openList(parent); + } + return; + } + player.closeInventory(); + }); + addLeftCloseAction(inv, this::reOpen); + inv.open(); + } + + private void openFilter() { + if (filter == null) { + filter = new SelectorFilter(null, null, null, null); + } + + SWInventory inv = new SWInventory(player, 9 * 2, Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_TITLE", player)); + InvCallback nameCallback = clickType -> { + if(clickType.isRightClick()) { + filter = filter.withName(null); + openFilter(); + } else { + SWAnvilInv swAnvilInv = new SWAnvilInv(player, Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_ENTER_NAME", player)); + swAnvilInv.setItem(Material.NAME_TAG, Collections.singletonList(Core.MESSAGE.parse("SCHEM_SELECTOR_CLICK_BACK", player)), false); + swAnvilInv.setCallback(s -> { + filter = filter.withName(s); + openFilter(); + }); + addLeftCloseAction(swAnvilInv, this::openFilter); + swAnvilInv.open(); + } + }; + if(filter.getName() == null) { + inv.setItem(0, Material.NAME_TAG, Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_NAME", player), nameCallback); + } else { + inv.setItem(0, Material.NAME_TAG, Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_NAME", player), Collections.singletonList(Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_NAME_SEARCH", player, filter.getName())), true, nameCallback); + } + + InvCallback ownerCallback = clickType -> { + if(clickType.isRightClick()) { + filter = filter.withOwner(null); + openFilter(); + } else { + SWAnvilInv swAnvilInv = new SWAnvilInv(player, Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_ENTER_OWNER", player)); + swAnvilInv.setItem(SWItem.getMaterial("SKULL_ITEM"), (byte) 3, Collections.singletonList(Core.MESSAGE.parse("SCHEM_SELECTOR_CLICK_BACK", player)), false); + swAnvilInv.setCallback(s -> { + if(SteamwarUser.get(s) != null) { + filter = filter.withOwner(SteamwarUser.get(s).getId()); + } + openFilter(); + }); + addLeftCloseAction(swAnvilInv, this::openFilter); + swAnvilInv.open(); + } + }; + if(filter.getOwner() == null) { + inv.setItem(1, SWItem.getMaterial("SKULL_ITEM"), (byte) 3, Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_OWNER", player), ownerCallback); + } else { + SteamwarUser tUser = SteamwarUser.get(filter.getOwner()); + SWItem item = SWItem.getPlayerSkull(tUser.getUserName()); + item.setName(Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_OWNER", player)); + item.setEnchanted(true); + item.setLore(Collections.singletonList(Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_OWNER_SEARCH", player, tUser.getUserName()))); + item.setCallback(ownerCallback); + inv.setItem(1, item); + } + + if(target.target != Target.SCHEMATIC_TYPE) { + InvCallback schemTypeCallback = clickType -> { + if(clickType.isRightClick()) { + filter = filter.withType(null); + openFilter(); + } else { + List> types = new ArrayList<>(); + SchematicType.values().forEach(schematicType -> types.add(new SWListInv.SWListEntry<>(new SWItem(SWItem.getMaterial(schematicType.getMaterial()), "§e" + schematicType.name(), Collections.emptyList(), schematicType.fightType(), n -> {}), schematicType))); + SWListInv listInv = new SWListInv<>(player, Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_SEL_TYPE", player), types, (clickType1, schematicType) -> { + filter = filter.withType(schematicType); + openFilter(); + }); + listInv.open(); + } + }; + + if(filter.getType() == null) { + inv.setItem(2, SWItem.getMaterial(SchematicType.Normal.getMaterial()), Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_TYPE", player), schemTypeCallback); + } else { + inv.setItem(2, SWItem.getMaterial(filter.getType().getMaterial()), Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_TYPE", player), Collections.singletonList(Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_TYPE_SEARCH", player, filter.getType().name())), true, schemTypeCallback); + } + } + + InvCallback materialCallback = clickType -> { + if(clickType.isRightClick()) { + filter = filter.withItem(null); + openFilter(); + } else { + UtilGui.openMaterialSelector(player, material -> { + filter = filter.withItem(material); + openFilter(); + }); + } + }; + + final int iSlot = target.target == Target.SCHEMATIC_TYPE?2:3; + + if(filter.getItem() == null) { + inv.setItem(iSlot, Material.STONE, Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_MAT", player), materialCallback); + } else { + inv.setItem(iSlot, filter.getItem(), Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_MAT", player), Collections.singletonList(Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_MAT_SEARCH", player, filter.getItem().name())), true, materialCallback); + } + + if (!filterCache.containsKey(player)) { + filterCache.put(player, new SelectorFilter[9]); + + String cfg = UserConfig.getConfig(user.getId(), "selector:filters"); + + if (cfg != null) { + JsonArray array = JsonParser.parseString(cfg).getAsJsonArray(); + for (int i = 0; i < array.size(); i++) { + JsonObject object = array.get(i).getAsJsonObject(); + filterCache.get(player)[i] = new SelectorFilter(object); + } + } + } + + SelectorFilter[] filters = filterCache.get(player); + + for (int i = 0; i < filters.length; i++) { + SelectorFilter filterCached = filters[i]; + if (filterCached == null) { + inv.setItem(i + 9, new SWItem(Material.BARRIER, Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_EMPTY", player), click -> {})); + } else { + SWItem item = filterCached.toItemStack(player); + item.setEnchanted(filterCached.equals(filter)); + inv.setItem(i + 9, item.getItemStack(), click -> { + filter = filterCached; + openFilter(); + }); + } + } + + inv.setItem(7, SWItem.getDye(1), Core.MESSAGE.parse("SCHEM_SELECTOR_CANCEL", player), clickType -> { + filter = null; + depth = 0; + openList(null); + }); + inv.setItem(8, SWItem.getDye(10), Core.MESSAGE.parse("SCHEM_SELECTOR_GO", player), clickType -> { + if (!filter.equals(filters[0])) { + for (int i = filters.length - 1; i > 0; i--) { + filters[i] = filters[i-1]; + if (filter.equals(filters[i])) { + filters[i] = null; + for (int j = i; j < filters.length - 1; j++) { + filters[j] = filters[j+1]; + } + filters[filters.length - 1] = null; + } + } + + filters[0] = filter; + + filterCache.put(player, filters); + JsonArray array = new JsonArray(); + for (SelectorFilter f : filters) { + if (f != null) { + array.add(f.toJson()); + } + } + + UserConfig.updatePlayerConfig(user.getId(), "selector:filters", array.toString()); + } + + injectable.onFilterApply(this); + depth = 0; + openList(null); + }); + + injectable.onFilterRender(this, inv); + + inv.open(); + } + + private List getFilteredSchematics() { + List nodes = new ArrayList<>(SchematicNode.getAll(user)); + nodes.removeIf(node -> { + injectable.onNodeFilter(this, node); + return !filter.matches(node); + }); + if(target.target == Target.DIRECTORY) { + nodes.removeIf(node -> !node.isDir()); + } + if(target.target == Target.SCHEMATIC_TYPE) { + nodes.removeIf(node -> node.isDir() || !node.getSchemtype().equals(target.type)); + } + return nodes; + } + + private List getSchematicList(SchematicNode parent) { + List nodes = new ArrayList<>(); + switch (target.target) { + case DIRECTORY: + nodes.addAll(SchematicNode.list(user, Optional.ofNullable(parent).map(SchematicNode::getId).orElse(null))); + nodes.removeIf(node -> !node.isDir()); + break; + case SCHEMATIC_TYPE: + nodes.addAll(SchematicNode.accessibleByUserTypeParent(user, target.type, parent==null?null:parent.getId())); + break; + default: + nodes.addAll(SchematicNode.list(user, parent == null?null:parent.getId())); + } + + if(singleDirOpen && nodes.size() == 1 && nodes.get(0).isDir()) { + sdoTrigger = true; + } + return nodes; + } + + private static Optional getParent(SchematicNode node) { + return node.getOptionalParent().map(integer -> SchematicNode.byIdAndUser(SteamwarUser.get(node.getEffectiveOwner()), integer)); + } + + public static SelectorTarget selectSchematic() { + return new SelectorTarget(Target.SCHEMATIC, null, -1); + } + + public static SelectorTarget selectDirectory() { + return new SelectorTarget(Target.DIRECTORY, null, -1); + } + + public static SelectorTarget selectSchematicNode() { + return new SelectorTarget(Target.SCHEMATIC_NODE, null, -1); + } + + public static SelectorTarget selectSchematicType(SchematicType type) { + return new SelectorTarget(Target.SCHEMATIC_TYPE, type, -1); + } + + public static SelectorTarget selectSchematicTypeWithRank(SchematicType type, int rank) { + return new SelectorTarget(Target.SCHEMATIC_TYPE, type, rank); + } + + @AllArgsConstructor + public static class SelectorTarget { + + private final Target target; + private final SchematicType type; + @Deprecated + private final int rank; + } + + @AllArgsConstructor + private enum Target { + SCHEMATIC("SCHEM_SELECTOR_SCHEMATIC", false), + DIRECTORY("SCHEM_SELECTOR_DIRECTORY", true), + SCHEMATIC_NODE("SCHEM_SELECTOR_SCHEMATIC_NODE", true), + SCHEMATIC_TYPE("SCHEM_SELECTOR_SCHEMATIC", false); + + private final String rawName; + private final boolean dirs; + + private String getName(Player player) { + return Core.MESSAGE.parse(rawName, player); + } + } + + @Getter + @Setter + @AllArgsConstructor + @With + @EqualsAndHashCode + public static class SelectorFilter { + private final String name; + private final Integer owner; + private final SchematicType type; + private final Material item; + + public SelectorFilter(JsonObject object) { + this.name = object.get("name").isJsonNull() ? null : object.get("name").getAsString(); + this.owner = object.get("owner").isJsonNull() ? null : object.get("owner").getAsInt(); + this.type = object.get("type").isJsonNull() ? null : SchematicType.fromDB(object.get("type").getAsString()); + this.item = object.get("item").isJsonNull() ? null : Material.valueOf(object.get("item").getAsString()); + } + + public boolean matches(SchematicNode node) { + boolean matches = name == null || node.getName().contains(name); + + if(owner != null && node.getOwner() != owner) { + matches = false; + } + + if(type != null && (node.isDir() || !node.getSchemtype().equals(type))) { + matches = false; + } + + if(item != null) { + String i; + if(node.getItem().isEmpty()) { + i = node.isDir()?"CHEST":"CAULDRON"; + } else { + i = node.getItem(); + } + if(!item.name().equals(i)) { + matches = false; + } + } + return matches; + } + + public long getCount() { + return Arrays.stream(new Object[]{name, owner, type, item}).filter(Objects::nonNull).count(); + } + + public List getItemLore(Player player) { + List lore = new ArrayList<>(4); + if(name != null) { + lore.add(Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_NAME_SEARCH", player, name)); + } + if(owner != null) { + lore.add(Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_OWNER_SEARCH", player, SteamwarUser.get(owner).getUserName())); + } + if(type != null) { + lore.add(Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_TYPE_SEARCH", player, type.name())); + } + if(item != null) { + lore.add(Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_MAT_SEARCH", player, item.name())); + } + + return lore; + } + + public SWItem toItemStack(Player player) { + long count = getCount(); + String itemName = count != 1 ? Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_TITLE_MULTI", player, count) : Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_TITLE_SINGLE", player); + + List lore = getItemLore(player); + + if (name != null) { + return new SWItem(Material.NAME_TAG, itemName, lore, false, click -> {}); + } else if (owner != null) { + SWItem playerSkull = SWItem.getPlayerSkull(SteamwarUser.get(owner).getUserName()); + playerSkull.setName(itemName); + playerSkull.setLore(lore); + return playerSkull; + } else if (type != null) { + return new SWItem(SWItem.getMaterial(type.getMaterial()), itemName, lore, false, n -> {}); + } else if (item != null) { + return new SWItem(item, itemName, lore, false, click -> {}); + } else { + return new SWItem(Material.BARRIER, Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_TITLE_EMPTY", player), Collections.emptyList(), false, click -> {}); + } + } + + public JsonObject toJson() { + JsonObject object = new JsonObject(); + object.addProperty("name", name); + object.addProperty("owner", owner); + object.addProperty("type", type == null?null:type.toDB()); + object.addProperty("item", item == null?null:item.name()); + + return object; + } + } + + public enum PublicMode { + ALL, + PRIVATE_ONLY, + PUBLIC_ONLY + } + + @AllArgsConstructor + private enum Sorting { + NAME(Material.PAPER, "SCHEM_SELECTOR_SORTING_NAME", Comparator.comparing(SchematicNode::getName)), + TYPE(Material.CAULDRON, "SCHEM_SELECTOR_SORTING_TYPE", (o1, o2) -> { + if(o1.isDir() || o2.isDir()) { + return Boolean.compare(o1.isDir(), o2.isDir()); + } else { + return o1.getSchemtype().name().compareTo(o2.getSchemtype().name()); + } + }), + LAST_UPDATED(SWItem.getMaterial("WATCH"), "SCHEM_SELECTOR_SORTING_UPDATE", Comparator.comparing(SchematicNode::getLastUpdate)); + + private final Material mat; + private final String name; + private final Comparator comparator; + + private String parseName(Player player) { + return Core.MESSAGE.parse(name, player); + } + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/SchematicSelectorInjectable.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/SchematicSelectorInjectable.java new file mode 100644 index 00000000..235dda4f --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/SchematicSelectorInjectable.java @@ -0,0 +1,59 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2021 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.inventory; + +import de.steamwar.core.Core; +import de.steamwar.sql.SchematicNode; +import org.bukkit.entity.Player; + +public interface SchematicSelectorInjectable { + + SchematicSelectorInjectable DEFAULT = new SchematicSelectorInjectable() {}; + + default String createTitle(Player player) { + return Core.MESSAGE.parse("SCHEM_SELECTOR_TITLE", player); + } + + default void onSelectorCreate(SchematicSelector selector) {} + + default void onListRender(SchematicSelector selector, SWListInv inv, SchematicNode parent) {} + + default void onFilterRender(SchematicSelector selector, SWInventory inventory) {} + + default void onFilterApply(SchematicSelector selector) {} + + default boolean onFolderCreate(SchematicSelector selector, String name) {return true;} + + default void onNodeFilter(SchematicSelector selector, SchematicNode node) {} + + default void onSelectorOpen(SchematicSelector selector, OpenFrom from) {} + + default AnvilInvCloseAction onAnvilInvCloseAction(SchematicSelector selector) {return AnvilInvCloseAction.CLOSE;} + + enum OpenFrom { + FRESH, + REOPEN + } + + enum AnvilInvCloseAction { + CLOSE, + REOPEN + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/UtilGui.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/UtilGui.java new file mode 100644 index 00000000..298d0c0f --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/inventory/UtilGui.java @@ -0,0 +1,54 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2021 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.inventory; + +import de.steamwar.core.Core; +import de.steamwar.inventory.SWItem; +import de.steamwar.inventory.SWListInv; +import lombok.experimental.UtilityClass; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.util.LinkedList; +import java.util.List; +import java.util.function.Consumer; + +@UtilityClass +public class UtilGui { + + public static void openMaterialSelector(Player player, Consumer callback) { + openMaterialSelector(player, Core.MESSAGE.parse("MATERIAL_SELECTOR_TITLE", player), callback); + } + + public static void openMaterialSelector(Player player, String title, Consumer callback) { + List> materials = new LinkedList<>(); + for(Material material : Material.values()){ + if(material.name().startsWith(Material.LEGACY_PREFIX)) + continue; + SWItem item = new SWItem(material, "§7" + material.name()); + if(item.getItemMeta() != null && material.isItem()) { + materials.add(new SWListInv.SWListEntry<>(item, material)); + } + } + + SWListInv swListInv = new SWListInv<>(player, title, materials, (clickType3, material) -> callback.accept(material)); + swListInv.open(); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/message/Message.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/message/Message.java new file mode 100644 index 00000000..eca895f4 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/message/Message.java @@ -0,0 +1,158 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.message; + +import de.steamwar.core.BountifulWrapper; +import de.steamwar.core.Core; +import de.steamwar.sql.SteamwarUser; +import net.md_5.bungee.api.ChatMessageType; +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.command.CommandSender; +import org.bukkit.entity.Player; + +import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; +import java.util.Locale; +import java.util.ResourceBundle; + +public class Message { + private final String resourceBundleName; + private final ClassLoader classLoader; + + public Message(String resourceBundleName, ClassLoader classLoader){ + this.resourceBundleName = resourceBundleName; + this.classLoader = classLoader; + } + + /* Parsing input to a message */ + + public TextComponent parseToComponent(String message, boolean prefixed, CommandSender sender, Object... params){ + return new TextComponent(TextComponent.fromLegacyText(parse(message, prefixed, sender, params))); + } + + public String parsePrefixed(String message, CommandSender sender, Object... params){ + return parse(message, true, sender, params); + } + + public String parse(String message, CommandSender sender, Object... params){ + return parse(message, false, sender, params); + } + + private String parse(String message, boolean prefixed, CommandSender sender, Object... params){ + Locale locale; + if(sender instanceof Player) + locale = getLocale((Player) sender); + else + locale = Locale.getDefault(); + + ResourceBundle resourceBundle = ResourceBundle.getBundle(resourceBundleName, locale, classLoader); + String pattern = ""; + if(prefixed) + pattern = fromRB(resourceBundle, "PREFIX") + " "; + pattern += fromRB(resourceBundle, message); + + return new MessageFormat(pattern, locale).format(params); + } + + private String fromRB(ResourceBundle bundle, String key) { + String result = bundle.getString(key); + if(Core.getVersion() < 12) + result = new String(result.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8); + return result; + } + + private Locale getLocale(Player player){ + return SteamwarUser.get(player.getUniqueId()).getLocale(); + } + + /* Send a message to one player */ + + public void send(String message, CommandSender sender, Object... params){ + send(message, true, sender, ChatMessageType.SYSTEM, null, null, params); + } + + public void sendPrefixless(String message, CommandSender sender, Object... params){ + send(message, false, sender, ChatMessageType.SYSTEM, null, null, params); + } + + public void send(String message, CommandSender sender, ChatMessageType type, Object... params){ + send(message, true, sender, type, null, null, params); + } + + public void sendPrefixless(String message, CommandSender sender, ChatMessageType type, Object... params){ + send(message, false, sender, type, null, null, params); + } + + public void send(String message, CommandSender sender, String onHover, ClickEvent onClick, Object... params){ + send(message, true, sender, ChatMessageType.SYSTEM, onHover, onClick, params); + } + + public void sendPrefixless(String message, CommandSender sender, String onHover, ClickEvent onClick, Object... params){ + send(message, false, sender, ChatMessageType.SYSTEM, onHover, onClick, params); + } + + public void send(String message, boolean prefixed, CommandSender sender, ChatMessageType type, String onHover, ClickEvent onClick, Object... params){ + TextComponent msg = parseToComponent(message, prefixed, sender, params); + if(onHover != null) + msg.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.fromLegacyText(onHover))); + if(onClick != null) + msg.setClickEvent(onClick); + + if(sender instanceof Player) + BountifulWrapper.impl.sendMessage((Player)sender, type, msg); + else + sender.sendMessage(msg.toPlainText()); + } + + /* Send message to all players */ + + public void broadcastPrefixless(String message, String onHover, ClickEvent onClick, Object... params){ + for(Player player : Bukkit.getOnlinePlayers()) + sendPrefixless(message, player, parse(onHover, false, player), onClick, params); + } + + public void broadcastPrefixless(String message, Object... params){ + for(Player player : Bukkit.getOnlinePlayers()) + sendPrefixless(message, player, ChatMessageType.SYSTEM, params); + } + + public void broadcastActionbar(String message, Object... params){ + for(Player player : Bukkit.getOnlinePlayers()) + send(message, player, ChatMessageType.ACTION_BAR, params); + } + + public void broadcast(String message, String onHover, ClickEvent onClick, Object... params){ + for(Player player : Bukkit.getOnlinePlayers()) + send(message, player, parse(onHover, false, player), onClick, params); + } + + public void broadcast(String message, Object... params){ + for(Player player : Bukkit.getOnlinePlayers()) + send(message, player, ChatMessageType.SYSTEM, params); + } + + public void chat(String message, Object... params){ + for(Player player : Bukkit.getOnlinePlayers()) + sendPrefixless(message, player, ChatMessageType.CHAT, params); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/network/CoreNetworkHandler.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/network/CoreNetworkHandler.java new file mode 100644 index 00000000..bf74682a --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/network/CoreNetworkHandler.java @@ -0,0 +1,71 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.network; + +import de.steamwar.core.BountifulWrapper; +import de.steamwar.network.handlers.InventoryHandler; +import de.steamwar.network.packets.PacketHandler; +import de.steamwar.network.packets.server.*; +import de.steamwar.sql.BauweltMember; +import de.steamwar.sql.SteamwarUser; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.UUID; + +public class CoreNetworkHandler extends PacketHandler { + + public CoreNetworkHandler() { + super(); + new InventoryHandler().register(); + } + + @Handler + public void handleBaumemberUpdatePacket(BaumemberUpdatePacket packet) { + BauweltMember.clear(); + } + + @Handler + public void handleCloseInventoryPacket(CloseInventoryPacket packet) { + Player player = Bukkit.getPlayer(SteamwarUser.get(packet.getPlayerId()).getUUID()); + if (player != null) { + player.closeInventory(); + } + } + + @Handler + public void handleInventoryPacket(InventoryPacket packet) { + InventoryHandler.handleInventoryPacket(packet); + } + + @Handler + public void handlePingPacket(PingPacket packet) { + UUID uuid = SteamwarUser.get(packet.getId()).getUUID(); + if(Bukkit.getPlayer(uuid) != null) { + Player player = Bukkit.getPlayer(uuid); + BountifulWrapper.impl.playPling(player); + } + } + + @Handler + public void handleLocaleChange(LocaleInvalidationPacket packet) { + SteamwarUser.invalidate(packet.getPlayerId()); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/network/NetworkReceiver.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/network/NetworkReceiver.java new file mode 100644 index 00000000..c93046cd --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/network/NetworkReceiver.java @@ -0,0 +1,36 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.network; + +import de.steamwar.network.packets.NetworkPacket; +import org.bukkit.entity.Player; +import org.bukkit.plugin.messaging.PluginMessageListener; + +public class NetworkReceiver implements PluginMessageListener { + + public NetworkReceiver() { + new CoreNetworkHandler().register(); + } + + @Override + public void onPluginMessageReceived(String channel, Player player, byte[] message) { + NetworkPacket.handle(message); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/network/NetworkSender.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/network/NetworkSender.java new file mode 100644 index 00000000..10a387d8 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/network/NetworkSender.java @@ -0,0 +1,38 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.network; + +import de.steamwar.core.Core; +import de.steamwar.network.packets.NetworkPacket; +import lombok.SneakyThrows; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +public class NetworkSender { + + public static void send(NetworkPacket packet) { + Bukkit.getOnlinePlayers().stream().findAny().ifPresent(player -> send(packet, player)); + } + + @SneakyThrows + public static void send(NetworkPacket packet, Player player) { + player.sendPluginMessage(Core.getInstance(), "sw:bridge", packet.serialize()); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/network/handlers/InventoryHandler.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/network/handlers/InventoryHandler.java new file mode 100644 index 00000000..a8a378a0 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/network/handlers/InventoryHandler.java @@ -0,0 +1,56 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.network.handlers; + +import com.google.gson.JsonParser; +import de.steamwar.inventory.SWInventory; +import de.steamwar.inventory.SWItem; +import de.steamwar.network.NetworkSender; +import de.steamwar.network.packets.PacketHandler; +import de.steamwar.network.packets.client.InventoryCallbackPacket; +import de.steamwar.network.packets.server.InventoryPacket; +import de.steamwar.sql.SteamwarUser; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; + +import java.util.HashMap; +import java.util.Map; + +public class InventoryHandler extends PacketHandler { + + @Handler + public static void handleInventoryPacket(InventoryPacket packet) { + Player player = Bukkit.getPlayer(SteamwarUser.get(packet.getPlayer()).getUUID()); + Map items = new HashMap<>(); + packet.getItems().forEach((i, item) -> { + @SuppressWarnings("deprecation") SWItem it = SWItem.getItemFromJson(new JsonParser().parse(item).getAsJsonObject()); + it.setCallback(click -> NetworkSender.send(InventoryCallbackPacket.builder().owner(packet.getPlayer()).position(i).type(InventoryCallbackPacket.CallbackType.CLICK).clickType(InventoryCallbackPacket.ClickType.getByName(click.name())).build(), player)); + items.put(i, it); + }); + + SWInventory inventory = new SWInventory(player, packet.getSize(), packet.getTitle(), items); + inventory.addCloseCallback(click -> { + if(player.getOpenInventory().getType() != InventoryType.CHEST) + NetworkSender.send(InventoryCallbackPacket.builder().owner(packet.getPlayer()).type(InventoryCallbackPacket.CallbackType.CLOSE).build(), player); + }); + inventory.open(); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/network/handlers/ServerDataHandler.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/network/handlers/ServerDataHandler.java new file mode 100644 index 00000000..de5615b7 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/network/handlers/ServerDataHandler.java @@ -0,0 +1,12 @@ +package de.steamwar.network.handlers; + +import com.comphenix.tinyprotocol.Reflection; +import com.comphenix.tinyprotocol.TinyProtocol; + +public class ServerDataHandler { + + public ServerDataHandler() { + TinyProtocol.instance.addFilter(Reflection.getClass("{nms.network.protocol.game}.ClientboundServerDataPacket"), (p, o) -> null); + TinyProtocol.instance.addFilter(Reflection.getClass("{nms.network.protocol.game}.ServerboundChatSessionUpdatePacket"), (player, packet) -> null); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/providers/BauServerInfo.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/providers/BauServerInfo.java new file mode 100644 index 00000000..b7de60b1 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/providers/BauServerInfo.java @@ -0,0 +1,47 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.providers; + +import de.steamwar.sql.SteamwarUser; +import org.bukkit.Bukkit; + +import java.util.UUID; + +public class BauServerInfo { + private static Integer bauOwner = null; + + static { + try { + bauOwner = Integer.parseInt(Bukkit.getWorlds().get(0).getName()); + } catch (NumberFormatException e) { + try { + bauOwner = SteamwarUser.get(UUID.fromString(Bukkit.getWorlds().get(0).getName())).getId(); + } catch (IllegalArgumentException ignored) {} + } + } + + public static Integer getOwnerId() { + return bauOwner; + } + + public static boolean isBauServer() { + return bauOwner != null; + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/scoreboard/SWScoreboard.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/scoreboard/SWScoreboard.java new file mode 100644 index 00000000..440e29a0 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/scoreboard/SWScoreboard.java @@ -0,0 +1,117 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.scoreboard; + +import com.comphenix.tinyprotocol.Reflection; +import com.comphenix.tinyprotocol.TinyProtocol; +import de.steamwar.core.Core; +import de.steamwar.core.FlatteningWrapper; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; + +public class SWScoreboard { + private SWScoreboard() {} + + private static final Reflection.FieldAccessor scoreboardName = Reflection.getField(FlatteningWrapper.scoreboardObjective, String.class, 0); + private static final Reflection.FieldAccessor scoreboardAction = Reflection.getField(FlatteningWrapper.scoreboardObjective, int.class, Core.getVersion() > 15 ? 3 : 0); + private static final Class scoreboardDisplayEnum = Reflection.getClass("{nms.world.scores.criteria}.IScoreboardCriteria$EnumScoreboardHealthDisplay"); + private static final Reflection.FieldAccessor scoreboardDisplayType = Reflection.getField(FlatteningWrapper.scoreboardObjective, scoreboardDisplayEnum, 0); + private static final Object displayTypeIntegers = scoreboardDisplayEnum.getEnumConstants()[0]; + + private static final Reflection.FieldAccessor scoreName = Reflection.getField(FlatteningWrapper.scoreboardScore, String.class, 0); + private static final Reflection.FieldAccessor scoreScoreboardName = Reflection.getField(FlatteningWrapper.scoreboardScore, String.class, 1); + private static final Reflection.FieldAccessor scoreValue = Reflection.getField(FlatteningWrapper.scoreboardScore, int.class, 0); + + private static final HashMap playerBoards = new HashMap<>(); //Object -> Scoreboard | Alle Versionen in der Map! + private static int toggle = 0; // Scoreboard 0 updates while scoreboard 1 is presenting. toggle marks the current active scoreboard + + private static final String SIDEBAR = "Sidebar"; + private static final Object[] DELETE_SCOREBOARD = new Object[2]; + private static final Object[] DISPLAY_SIDEBAR = new Object[2]; + + static { + Class scoreboardDisplayObjective = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutScoreboardDisplayObjective"); + Reflection.FieldAccessor scoreboardDisplayName = Reflection.getField(scoreboardDisplayObjective, String.class, 0); + Reflection.FieldAccessor scoreboardDisplaySlot = Reflection.getField(scoreboardDisplayObjective, int.class, 0); + for(int id = 0; id < 2; id++) { + DELETE_SCOREBOARD[id] = Reflection.newInstance(FlatteningWrapper.scoreboardObjective); + scoreboardName.set(DELETE_SCOREBOARD[id], SIDEBAR + id); + scoreboardAction.set(DELETE_SCOREBOARD[id], 1); //1 to remove + + DISPLAY_SIDEBAR[id] = Reflection.newInstance(scoreboardDisplayObjective); + scoreboardDisplayName.set(DISPLAY_SIDEBAR[id], SIDEBAR + id); + scoreboardDisplaySlot.set(DISPLAY_SIDEBAR[id], 1); // 1 = Sidebar + } + + Bukkit.getScheduler().runTaskTimer(Core.getInstance(), () -> { + toggle ^= 1; // Toggle between 0 and 1 + + for(Map.Entry scoreboard : playerBoards.entrySet()) { + Player player = scoreboard.getKey(); + ScoreboardCallback callback = scoreboard.getValue(); + + TinyProtocol.instance.sendPacket(player, DELETE_SCOREBOARD[toggle]); + TinyProtocol.instance.sendPacket(player, createSidebarPacket(callback.getTitle())); + for(Map.Entry score : callback.getData().entrySet()){ + TinyProtocol.instance.sendPacket(player, createScorePacket(score.getKey(), score.getValue())); + } + + Bukkit.getScheduler().runTaskLater(Core.getInstance(), () -> { + if(!player.isOnline()) + return; + TinyProtocol.instance.sendPacket(player, DISPLAY_SIDEBAR[toggle]); + }, 2); + } + }, 10, 5); + } + + public static boolean createScoreboard(Player player, ScoreboardCallback callback) { + playerBoards.put(player, callback); + return true; + } + + public static void removeScoreboard(Player player) { + if(playerBoards.remove(player) == null || !player.isOnline()) + return; + + TinyProtocol.instance.sendPacket(player, DELETE_SCOREBOARD[toggle]); + } + + private static Object createSidebarPacket(String name){ + Object packet = Reflection.newInstance(FlatteningWrapper.scoreboardObjective); + scoreboardName.set(packet, SIDEBAR + toggle); + scoreboardAction.set(packet, 0); //0 to create + FlatteningWrapper.impl.setScoreboardTitle(packet, name); + scoreboardDisplayType.set(packet, displayTypeIntegers); + return packet; + } + + private static Object createScorePacket(String name, int value){ + Object packet = Reflection.newInstance(FlatteningWrapper.scoreboardScore); + scoreName.set(packet, name); + scoreScoreboardName.set(packet, SIDEBAR + toggle); + scoreValue.set(packet, value); + FlatteningWrapper.impl.setScoreAction(packet); + return packet; + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/scoreboard/ScoreboardCallback.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/scoreboard/ScoreboardCallback.java new file mode 100644 index 00000000..11e403ba --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/scoreboard/ScoreboardCallback.java @@ -0,0 +1,29 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.scoreboard; + +import java.util.HashMap; + +public interface ScoreboardCallback { + + HashMap getData(); + + String getTitle(); +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/sql/PersonalKit.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/sql/PersonalKit.java new file mode 100644 index 00000000..fcc5a092 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/sql/PersonalKit.java @@ -0,0 +1,143 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.Field; +import de.steamwar.sql.internal.SelectStatement; +import de.steamwar.sql.internal.Statement; +import de.steamwar.sql.internal.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.inventory.ItemStack; + +import java.io.StringReader; +import java.util.List; +import java.util.Objects; + +@AllArgsConstructor +public class PersonalKit { + + private static final Table table = new Table<>(PersonalKit.class); + private static final SelectStatement getKits = table.selectFields("UserID", "GameMode"); + private static final SelectStatement getKit = table.select(Table.PRIMARY); + private static final SelectStatement getKitInUse = table.selectFields("UserID", "GameMode", "InUse"); + private static final Statement update = table.insertAll(); + private static final Statement delete = table.delete(Table.PRIMARY); + + @Getter + @Field(keys = {Table.PRIMARY}) + private final int userID; + @Getter + @Field(keys = {Table.PRIMARY}) + private final String gamemode; + @Getter + @Field(keys = {Table.PRIMARY}) + private final String name; + @Field + private String inventory; + @Field + private String armor; + @Getter + @Field(def = "1") + private boolean inUse; + + public String getRawInventory() { + return inventory; + } + + public String getRawArmor() { + return armor; + } + + public ItemStack[] getInventory(){ + YamlConfiguration config = YamlConfiguration.loadConfiguration(new StringReader(inventory)); + return Objects.requireNonNull(config.getList("Inventory")).toArray(new ItemStack[0]); + } + + public ItemStack[] getArmor(){ + YamlConfiguration config = YamlConfiguration.loadConfiguration(new StringReader(armor)); + return Objects.requireNonNull(config.getList("Armor")).toArray(new ItemStack[0]); + } + + public void setInUse() { + PersonalKit kit = getKitInUse(userID, gamemode); + if(kit != null) + kit.setUse(false); + setUse(true); + } + + private void setUse(boolean inUse) { + this.inUse = inUse; + update(); + } + + public void setInventory(ItemStack[] inventory) { + this.inventory = saveInvConfig("Inventory", inventory); + update(); + } + + public void setArmor(ItemStack[] armor) { + this.armor = saveInvConfig("Armor", armor); + update(); + } + + public void setContainer(ItemStack[] inventory, ItemStack[] armor) { + this.armor = saveInvConfig("Armor", armor); + this.inventory = saveInvConfig("Inventory", inventory); + update(); + } + + public void delete() { + delete.update(userID, gamemode, name); + } + + private void update() { + update.update(userID, gamemode, name, inventory, armor, inUse); + } + + public static List get(int userID, String gamemode){ + return getKits.listSelect(userID, gamemode); + } + + public static PersonalKit get(int userID, String gamemode, String name) { + return getKit.select(userID, gamemode, name); + } + + public static PersonalKit create(int userID, String gamemode, String name, ItemStack[] inventory, ItemStack[] armor){ + if(armor == null) { + armor = new ItemStack[]{null, null, null, null}; + } + PersonalKit kit = new PersonalKit(userID, gamemode, name, saveInvConfig("Inventory", inventory), saveInvConfig("Armor", armor), true); + kit.update(); + return kit; + } + + public static PersonalKit getKitInUse(int userID, String gamemode) { + return getKitInUse.select(userID, gamemode, true); + } + + private static String saveInvConfig(String name, ItemStack[] inv) { + YamlConfiguration armorConfig = new YamlConfiguration(); + armorConfig.set(name, inv); + + return armorConfig.saveToString(); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/sql/SQLConfigImpl.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/sql/SQLConfigImpl.java new file mode 100644 index 00000000..dd54f35d --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/sql/SQLConfigImpl.java @@ -0,0 +1,37 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import de.steamwar.core.Core; +import de.steamwar.sql.internal.SQLConfig; + +import java.util.logging.Logger; + +public class SQLConfigImpl implements SQLConfig { + @Override + public Logger getLogger() { + return Core.getInstance().getLogger(); + } + + @Override + public int maxConnections() { + return 1; + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/sql/SQLWrapperImpl.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/sql/SQLWrapperImpl.java new file mode 100644 index 00000000..7428574e --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/sql/SQLWrapperImpl.java @@ -0,0 +1,81 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import de.steamwar.core.Core; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; + +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class SQLWrapperImpl implements SQLWrapper { + + @Override + public void loadSchemTypes(List tmpTypes, Map tmpFromDB) { + File folder = new File(Core.getInstance().getDataFolder().getParentFile(), "FightSystem"); + if(folder.exists()) { + for(File configFile : Arrays.stream(folder.listFiles((file, name) -> name.endsWith(".yml") && !name.endsWith(".kits.yml"))).sorted().collect(Collectors.toList())) { + YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); + if(!config.isConfigurationSection("Schematic")) + continue; + + String type = config.getString("Schematic.Type"); + assert type != null; + String shortcut = config.getString("Schematic.Shortcut"); + if(tmpFromDB.containsKey(type.toLowerCase())) + continue; + + SchematicType checktype = null; + String material = config.getString("Schematic.Material", "STONE_BUTTON"); + + if(!config.getStringList("CheckQuestions").isEmpty()) { + checktype = new SchematicType("C" + type, "C" + shortcut, SchematicType.Type.CHECK_TYPE, null, material, true); + tmpTypes.add(checktype); + tmpFromDB.put(checktype.toDB(), checktype); + } + + boolean manualCheck = config.getBoolean("Schematic.ManualCheck", true); + + SchematicType current = new SchematicType(type, shortcut, config.isConfigurationSection("Server") ? SchematicType.Type.FIGHT_TYPE : SchematicType.Type.NORMAL, checktype, material, manualCheck); + tmpTypes.add(current); + tmpFromDB.put(type.toLowerCase(), current); + } + } + } + + private static final String SERVER_VERSION = Bukkit.getServer().getVersion(); + + @Override + public void additionalExceptionMetadata(StringBuilder builder) { + builder.append("\nPlayers: "); + for(Player player : Bukkit.getOnlinePlayers()) + builder.append(player.getName()).append(" "); + builder.append("\nWorlds: "); + for(World world : Bukkit.getWorlds()) + builder.append(world.getName()).append(" "); + builder.append("\nServer: ").append(SERVER_VERSION); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/sql/SchematicData.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/sql/SchematicData.java new file mode 100644 index 00000000..ecbf86aa --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/sql/SchematicData.java @@ -0,0 +1,75 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import com.sk89q.worldedit.extent.clipboard.Clipboard; +import de.steamwar.core.Core; +import de.steamwar.core.WorldEditWrapper; +import de.steamwar.sql.internal.SqlTypeMapper; +import de.steamwar.sql.internal.Statement; +import org.bukkit.entity.Player; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PipedInputStream; +import java.sql.Blob; +import java.sql.PreparedStatement; +import java.util.zip.GZIPInputStream; + +public class SchematicData { + + public static Clipboard clipboardFromStream(InputStream is, boolean schemFormat) { + try { + return WorldEditWrapper.impl.getClipboard(is, schemFormat); + } catch (IOException e) { + throw new SecurityException("Could not read schem", e); + } + } + + private final NodeData data; + + public SchematicData(SchematicNode node) { + this.data = NodeData.get(node); + if(node.isDir()) + throw new SecurityException("Node is Directory"); + } + + public Clipboard load() throws IOException, NoClipboardException { + return WorldEditWrapper.impl.getClipboard(data.schemData(), data.getNodeFormat()); + } + + public void loadToPlayer(Player player) throws IOException, NoClipboardException { + WorldEditWrapper.impl.setPlayerClipboard(player, data.schemData(), data.getNodeFormat()); + } + + public void saveFromPlayer(Player player) throws IOException, NoClipboardException { + saveFromPlayer(player, Core.getVersion() > 12); + } + + public void saveFromPlayer(Player player, boolean newFormat) throws IOException, NoClipboardException { + data.saveFromStream(WorldEditWrapper.impl.getPlayerClipboard(player, newFormat), newFormat); + } + + @Deprecated + public void saveFromBytes(byte[] bytes, boolean newFormat) { + data.saveFromStream(new ByteArrayInputStream(bytes), newFormat); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/techhider/BlockIds.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/techhider/BlockIds.java new file mode 100644 index 00000000..8e129a1d --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/techhider/BlockIds.java @@ -0,0 +1,33 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2021 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + */ + +package de.steamwar.techhider; + +import de.steamwar.core.Core; +import de.steamwar.core.VersionDependent; +import org.bukkit.Material; + +import java.util.Set; + +public interface BlockIds { + BlockIds impl = VersionDependent.getVersionImpl(Core.getInstance()); + + int materialToId(Material material); + Set materialToAllIds(Material material); +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/techhider/ChunkHider.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/techhider/ChunkHider.java new file mode 100644 index 00000000..0a392450 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/techhider/ChunkHider.java @@ -0,0 +1,59 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2022 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + */ + +package de.steamwar.techhider; + +import de.steamwar.core.Core; +import de.steamwar.core.VersionDependent; +import io.netty.buffer.ByteBuf; +import org.bukkit.entity.Player; + +import java.util.Set; +import java.util.function.BiFunction; + +public interface ChunkHider { + ChunkHider impl = VersionDependent.getVersionImpl(Core.getInstance()); + + Class mapChunkPacket(); + BiFunction chunkHiderGenerator(TechHider.LocationEvaluator locationEvaluator, int obfuscationTarget, Set obfuscate, Set hiddenBlockEntities); + + static int processPalette(int obfuscationTarget, Set obfuscate, ByteBuf in, ByteBuf out) { + int paletteLength = ProtocolUtils.readVarInt(in); + ProtocolUtils.writeVarInt(out, paletteLength); + + int paletteTarget = 0; + + for(int i = 0; i < paletteLength; i++) { + int entry = ProtocolUtils.readVarInt(in); + if(obfuscate.contains(entry)) + entry = obfuscationTarget; + + if(entry == obfuscationTarget) + paletteTarget = i; + + ProtocolUtils.writeVarInt(out, entry); + } + + return paletteTarget; + } + + interface PosEvaluator { + TechHider.State test(int x, int y, int z); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/techhider/ProtocolUtils.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/techhider/ProtocolUtils.java new file mode 100644 index 00000000..43bf0435 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/techhider/ProtocolUtils.java @@ -0,0 +1,198 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2022 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + */ + +package de.steamwar.techhider; + +import com.comphenix.tinyprotocol.Reflection; +import com.comphenix.tinyprotocol.TinyProtocol; +import com.google.common.primitives.Bytes; +import io.netty.buffer.ByteBuf; +import org.bukkit.Bukkit; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.UnaryOperator; + +public class ProtocolUtils { + private ProtocolUtils() {} + + public static void broadcastPacket(Object packet) { + Bukkit.getOnlinePlayers().forEach(player -> TinyProtocol.instance.sendPacket(player, packet)); + } + + @Deprecated + public static BiFunction, Object> arrayCloneGenerator(Class elementClass) { + return (array, worker) -> { + int length = Array.getLength(array); + Object result = Array.newInstance(elementClass, length); + + for(int i = 0; i < length; i++) + Array.set(result, i, worker.apply(Array.get(array, i))); + + return result; + }; + } + + public static UnaryOperator shallowCloneGenerator(Class clazz) { + BiConsumer filler = shallowFill(clazz); + + return source -> { + Object clone = Reflection.newInstance(clazz); + filler.accept(source, clone); + return clone; + }; + } + + private static BiConsumer shallowFill(Class clazz) { + if(clazz == null) + return (source, clone) -> {}; + + BiConsumer superFiller = shallowFill(clazz.getSuperclass()); + + Field[] fds = clazz.getDeclaredFields(); + List fields = new ArrayList<>(); + for(Field field : fds) { + if (Modifier.isStatic(field.getModifiers())) + continue; + + field.setAccessible(true); + fields.add(field); + } + + return (source, clone) -> { + superFiller.accept(source, clone); + try { + for(Field field : fields) { + field.set(clone, field.get(source)); + } + } catch (IllegalAccessException e) { + throw new IllegalStateException("Could not set field", e); + } + + }; + } + + public static int posToChunk(int c){ + int chunk = c / 16; + if(c < 0) + chunk--; + return chunk; + } + + @Deprecated + public static int readVarInt(byte[] array, int startPos) { + int numRead = 0; + int result = 0; + byte read; + do { + read = array[startPos + numRead]; + int value = (read & 0b01111111); + result |= (value << (7 * numRead)); + + numRead++; + if (numRead > 5) { + break; + } + } while ((read & 0b10000000) != 0); + + return result; + } + + public static int readVarInt(ByteBuf buf) { + int numRead = 0; + int result = 0; + byte read; + do { + read = buf.readByte(); + int value = (read & 0b01111111); + result |= (value << (7 * numRead)); + + if (++numRead > 5) + throw new SecurityException("VarInt too long"); + } while ((read & 0b10000000) != 0); + + return result; + } + + public static void writeVarInt(ByteBuf buf, int value) { + do { + int temp = value & 0b01111111; + // Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone + value >>>= 7; + if (value != 0) { + temp |= 0b10000000; + } + buf.writeByte(temp); + } while (value != 0); + } + + @Deprecated + public static int readVarIntLength(byte[] array, int startPos) { + int numRead = 0; + byte read; + do { + read = array[startPos + numRead]; + numRead++; + if (numRead > 5) { + break; + } + } while ((read & 0b10000000) != 0); + + return numRead; + } + + @Deprecated + public static byte[] writeVarInt(int value) { + List buffer = new ArrayList<>(5); + do { + byte temp = (byte)(value & 0b01111111); + // Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone + value >>>= 7; + if (value != 0) { + temp |= 0b10000000; + } + buffer.add(temp); + } while (value != 0); + return Bytes.toArray(buffer); + } + + @Deprecated + public static class ChunkPos{ + final int x; + final int z; + + public ChunkPos(int x, int z){ + this.x = x; + this.z = z; + } + + public final int x(){ + return x; + } + + public final int z(){ + return z; + } + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/techhider/ProtocolWrapper.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/techhider/ProtocolWrapper.java new file mode 100644 index 00000000..489e11b7 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/techhider/ProtocolWrapper.java @@ -0,0 +1,39 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2021 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + */ + +package de.steamwar.techhider; + +import de.steamwar.core.Core; +import de.steamwar.core.VersionDependent; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.util.Set; +import java.util.function.BiFunction; + +public interface ProtocolWrapper { + ProtocolWrapper impl = VersionDependent.getVersionImpl(Core.getInstance()); + + + boolean unfilteredTileEntityDataAction(Object packet); + + BiFunction blockBreakHiderGenerator(Class blockBreakPacket, Object obfuscationTarget, Set obfuscate, TechHider.LocationEvaluator locationEvaluator); + + BiFunction multiBlockChangeGenerator(Object obfuscationTarget, Set obfuscate, TechHider.LocationEvaluator locationEvaluator); +} diff --git a/SpigotCore/SpigotCore_Main/src/src/de/steamwar/techhider/TechHider.java b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/techhider/TechHider.java new file mode 100644 index 00000000..24fbb27a --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/de/steamwar/techhider/TechHider.java @@ -0,0 +1,168 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2021 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + */ + +package de.steamwar.techhider; + +import com.comphenix.tinyprotocol.Reflection; +import com.comphenix.tinyprotocol.TinyProtocol; +import de.steamwar.core.Core; +import org.bukkit.GameMode; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.util.*; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; + +public class TechHider { + + public static final Class blockPosition = Reflection.getClass("{nms.core}.BlockPosition"); + private static final Class baseBlockPosition = Reflection.getClass("{nms.core}.BaseBlockPosition"); + public static final Reflection.FieldAccessor blockPositionX = Reflection.getField(baseBlockPosition, int.class, 0); + public static final Reflection.FieldAccessor blockPositionY = Reflection.getField(baseBlockPosition, int.class, 1); + public static final Reflection.FieldAccessor blockPositionZ = Reflection.getField(baseBlockPosition, int.class, 2); + + public static final Class iBlockData = Reflection.getClass("{nms.world.level.block.state}.IBlockData"); + public static final Class block = Reflection.getClass("{nms.world.level.block}.Block"); + private static final Reflection.MethodInvoker getBlockDataByBlock = Reflection.getTypedMethod(block, null, iBlockData); + + public static final Class craftMagicNumbers = Reflection.getClass("{obc}.util.CraftMagicNumbers"); + private static final Reflection.MethodInvoker getBlockByMaterial = Reflection.getTypedMethod(craftMagicNumbers, "getBlock", block, Material.class); + + private static final Reflection.MethodInvoker getBlockByBlockData = Reflection.getTypedMethod(iBlockData, null, block); + private static final Reflection.MethodInvoker getMaterialByBlock = Reflection.getTypedMethod(craftMagicNumbers, "getMaterial", Material.class, block); + public static boolean iBlockDataHidden(Set obfuscate, Object iBlockData) { + return obfuscate.contains((Material) getMaterialByBlock.invoke(null, getBlockByBlockData.invoke(iBlockData))); + } + + private final Map, BiFunction> techhiders = new HashMap<>(); + private final LocationEvaluator locationEvaluator; + private final Object obfuscationTarget; + private final Set obfuscate; + + @Deprecated + public TechHider(BypassEvaluator bypass, Material obfuscationTarget, Set obfuscate, Set hiddenBlockEntities) { + this((LocationEvaluator) (player, x, z) -> bypass.bypass(player, ProtocolUtils.posToChunk(x), ProtocolUtils.posToChunk(z)), obfuscationTarget, obfuscate, hiddenBlockEntities); + } + + public TechHider(LocationEvaluator locationEvaluator, Material obfuscationTarget, Set obfuscate, Set hiddenBlockEntities) { + this.locationEvaluator = locationEvaluator; + this.obfuscate = obfuscate; + this.obfuscationTarget = getBlockDataByBlock.invoke(getBlockByMaterial.invoke(null, obfuscationTarget)); + + techhiders.put(blockActionPacket, this::blockActionHider); + techhiders.put(blockChangePacket, this::blockChangeHider); + techhiders.put(tileEntityDataPacket, this::tileEntityDataHider); + techhiders.put(multiBlockChangePacket, ProtocolWrapper.impl.multiBlockChangeGenerator(this.obfuscationTarget, obfuscate, locationEvaluator)); + techhiders.put(ChunkHider.impl.mapChunkPacket(), ChunkHider.impl.chunkHiderGenerator(locationEvaluator, BlockIds.impl.materialToId(obfuscationTarget), obfuscate.stream().flatMap(m -> BlockIds.impl.materialToAllIds(m).stream()).collect(Collectors.toSet()), hiddenBlockEntities)); + + if(Core.getVersion() > 12 && Core.getVersion() < 19) { + Class blockBreakClass = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutBlockBreak"); + techhiders.put(blockBreakClass, ProtocolWrapper.impl.blockBreakHiderGenerator(blockBreakClass, this.obfuscationTarget, obfuscate, locationEvaluator)); + } + + if(Core.getVersion() > 8){ + techhiders.put(Reflection.getClass("{nms.network.protocol.game}.PacketPlayInUseItem"), (p, packet) -> p.getGameMode() == GameMode.SPECTATOR ? null : packet); + } + techhiders.put(Reflection.getClass("{nms.network.protocol.game}.PacketPlayInUseEntity"), (p, packet) -> p.getGameMode() == GameMode.SPECTATOR ? null : packet); + + } + + public void enable() { + techhiders.forEach(TinyProtocol.instance::addFilter); + } + + public void disable() { + techhiders.forEach(TinyProtocol.instance::removeFilter); + } + + public static final Class multiBlockChangePacket = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutMultiBlockChange"); + public static final UnaryOperator multiBlockChangeCloner = ProtocolUtils.shallowCloneGenerator(TechHider.multiBlockChangePacket); + + private static final Class blockChangePacket = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutBlockChange"); + private static final Function blockChangeCloner = ProtocolUtils.shallowCloneGenerator(blockChangePacket); + private static final Reflection.FieldAccessor blockChangePosition = Reflection.getField(blockChangePacket, blockPosition, 0); + private static final Reflection.FieldAccessor blockChangeBlockData = Reflection.getField(blockChangePacket, iBlockData, 0); + private Object blockChangeHider(Player p, Object packet) { + switch (locationEvaluator.checkBlockPos(p, blockChangePosition.get(packet))) { + case SKIP: + return packet; + case CHECK: + if(!iBlockDataHidden(obfuscate, blockChangeBlockData.get(packet))) + return packet; + default: + packet = blockChangeCloner.apply(packet); + blockChangeBlockData.set(packet, obfuscationTarget); + return packet; + } + } + + private static final Class blockActionPacket = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutBlockAction"); + private static final Reflection.FieldAccessor blockActionPosition = Reflection.getField(blockActionPacket, blockPosition, 0); + private Object blockActionHider(Player p, Object packet) { + if (locationEvaluator.checkBlockPos(p, blockActionPosition.get(packet)) == State.SKIP) + return packet; + return null; + } + + public static final Class tileEntityDataPacket = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutTileEntityData"); + private static final Reflection.FieldAccessor tileEntityDataPosition = Reflection.getField(tileEntityDataPacket, blockPosition, 0); + private Object tileEntityDataHider(Player p, Object packet) { + switch (locationEvaluator.checkBlockPos(p, tileEntityDataPosition.get(packet))) { + case SKIP: + return packet; + case CHECK: + if(ProtocolWrapper.impl.unfilteredTileEntityDataAction(packet)) + return packet; + default: + return null; + } + } + + @Deprecated + public interface BypassEvaluator { + boolean bypass(Player p, int chunkX, int chunkZ); + } + + public enum State { + SKIP, + CHECK, + HIDE + } + + public interface LocationEvaluator { + boolean skipChunk(Player player, int x, int z); + default boolean skipChunkSection(Player player, int x, int y, int z) { + return skipChunk(player, x, z); + } + default State check(Player player, int x, int y, int z) { + return skipChunkSection(player, ProtocolUtils.posToChunk(x), ProtocolUtils.posToChunk(y), ProtocolUtils.posToChunk(z)) ? State.SKIP : State.CHECK; + } + + default State checkBlockPos(Player player, Object pos) { + return check(player, blockPositionX.get(pos), blockPositionY.get(pos), blockPositionZ.get(pos)); + } + + default boolean blockPrecise(Player player, int x, int y, int z) { + return false; + } + } +} diff --git a/SpigotCore/SpigotCore_Main/src/src/plugin.yml b/SpigotCore/SpigotCore_Main/src/src/plugin.yml new file mode 100644 index 00000000..75652668 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/src/plugin.yml @@ -0,0 +1,12 @@ +name: SpigotCore +version: "2.0" +author: Lixfel +api-version: "1.13" +load: STARTUP +softdepend: + - ViaVersion + - WorldEdit + +main: de.steamwar.core.Core +libraries: + - mysql:mysql-connector-java:5.1.49 diff --git a/SpigotCore/build.gradle.kts b/SpigotCore/build.gradle.kts new file mode 100644 index 00000000..96fd3ae9 --- /dev/null +++ b/SpigotCore/build.gradle.kts @@ -0,0 +1,72 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2024 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +plugins { + id("base") + id("application") + + id("com.github.johnrengelman.shadow") +} + +group = "de.steamwar" +version = "" + +tasks.build { + finalizedBy(tasks.shadowJar) +} + +tasks.compileJava { + options.encoding = "UTF-8" +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +sourceSets { + main { + java { + srcDirs("src/") + } + resources { + srcDirs("src/") + exclude("**/*.java", "**/*.kt") + } + } +} + +dependencies { + compileOnly("org.projectlombok:lombok:1.18.32") + annotationProcessor("org.projectlombok:lombok:1.18.32") + + compileOnly(project(":SpigotCore:SpigotCore_Main")) + compileOnly(project(":SpigotCore:SpigotCore_8")) + compileOnly(project(":SpigotCore:SpigotCore_9")) + compileOnly(project(":SpigotCore:SpigotCore_10")) + compileOnly(project(":SpigotCore:SpigotCore_12")) + compileOnly(project(":SpigotCore:SpigotCore_14")) + compileOnly(project(":SpigotCore:SpigotCore_15")) + compileOnly(project(":SpigotCore:SpigotCore_18")) + compileOnly(project(":SpigotCore:SpigotCore_19")) + compileOnly(project(":SpigotCore:SpigotCore_20")) + + implementation(project(":CommonCore")) + implementation(project(":CommandFramework")) +} diff --git a/build.gradle.kts b/build.gradle.kts index 97d6657c..dee0078e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -33,24 +33,28 @@ version = "" allprojects { repositories { mavenCentral() + maven { url = URI("https://repo.papermc.io/repository/maven-public/") content { includeGroup("com.velocitypowered") } } + maven { url = URI("https://m2.dv8tion.net/releases") content { includeGroup("net.dv8tion") } } + maven { url = URI("https://repo.lunarclient.dev") content { includeGroup("com.lunarclient") } } + maven { url = URI("https://steamwar.de/maven/") credentials { @@ -61,5 +65,21 @@ allprojects { password = swProps.getProperty("maven.password") } } + + maven { + url = URI("https://repo.codemc.io/repository/maven-snapshots/") + } + + maven { + url = URI("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") + } + + maven { + url = URI("https://libraries.minecraft.net") + } + + maven { + url = URI("https://repo.viaversion.com") + } } } diff --git a/gradle.properties b/gradle.properties index afac46e8..d272a585 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,4 +19,4 @@ org.gradle.daemon = true org.gradle.parallel = true -org.gradle.workers.max = 4 +org.gradle.workers.max = 8 diff --git a/settings.gradle.kts b/settings.gradle.kts index 9bad9b06..67a46f0f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -21,5 +21,18 @@ rootProject.name = "SteamWar" include("CommandFramework") include("CommonCore") + +include("SpigotCore") +include("SpigotCore:SpigotCore_8") +include("SpigotCore:SpigotCore_9") +include("SpigotCore:SpigotCore_10") +include("SpigotCore:SpigotCore_12") +include("SpigotCore:SpigotCore_14") +include("SpigotCore:SpigotCore_15") +include("SpigotCore:SpigotCore_18") +include("SpigotCore:SpigotCore_19") +include("SpigotCore:SpigotCore_20") +include("SpigotCore:SpigotCore_Main") + include("VelocityCore") include("VelocityCore:Persistent")