From 10145032e2b2dc24afecfd1e829e3a6230d02b52 Mon Sep 17 00:00:00 2001 From: Kalle Struik Date: Tue, 21 Jun 2022 15:46:17 +0200 Subject: [PATCH] feat: Add Better Bundles, Hammers, No Spawn Zones, No Melting, and More Sandstone tweaks --- .gitignore | 5 + build.gradle.kts | 17 +- .../nl/kallestruik/dtweaks/BundleColor.kt | 25 ++ .../nl/kallestruik/dtweaks/BundleVariant.kt | 15 + .../kotlin/nl/kallestruik/dtweaks/Const.kt | 1 + .../kotlin/nl/kallestruik/dtweaks/DTweaks.kt | 40 +- .../dtweaks/commands/CommandBundle.kt | 40 ++ .../dtweaks/commands/CommandPlayer.kt | 65 +-- .../dtweaks/commands/CommandSpawnzones.kt | 101 +++++ .../dtweaks/fakeplayer/FakeConnection.kt | 46 +- .../dtweaks/fakeplayer/FakePlayer.kt | 202 ++++----- .../fakeplayer/FakePlayerConnection.kt | 67 ++- .../dtweaks/fakeplayer/FakePlayerList.kt | 415 +++++++----------- .../dtweaks/managers/FakePlayerManager.kt | 246 +++++++++-- .../dtweaks/managers/NoSpawnZoneManager.kt | 70 +++ .../craftingtweaks/CraftableChainmail.kt | 51 +++ .../tweaks/craftingtweaks/MoreSandstone.kt | 41 ++ .../tweaks/craftingtweaks/RedyeTerracotta.kt | 2 +- .../miscellaneoustweaks/BetterBundles.kt | 345 +++++++++++++++ .../miscellaneoustweaks/DyeableNetherite.kt | 109 +++++ .../tweaks/miscellaneoustweaks/FakePlayers.kt | 63 ++- .../tweaks/miscellaneoustweaks/Hammers.kt | 134 ++++++ .../tweaks/miscellaneoustweaks/NoMelting.kt | 36 ++ .../dtweaks/tweaks/mobtweaks/NoSpawnZones.kt | 44 ++ src/main/resources/plugin.yml | 5 + src/main/resources/tweak_states.yml | 11 +- textures/make_bundles.py | 106 +++++ textures/src/bundles/colors/black_bundle.png | Bin 0 -> 412 bytes .../bundles/colors/black_bundle_filled.png | Bin 0 -> 367 bytes textures/src/bundles/colors/blue_bundle.png | Bin 0 -> 428 bytes .../src/bundles/colors/blue_bundle_filled.png | Bin 0 -> 372 bytes textures/src/bundles/colors/brown_bundle.png | Bin 0 -> 424 bytes .../bundles/colors/brown_bundle_filled.png | Bin 0 -> 370 bytes textures/src/bundles/colors/cyan_bundle.png | Bin 0 -> 435 bytes .../src/bundles/colors/cyan_bundle_filled.png | Bin 0 -> 371 bytes textures/src/bundles/colors/gray_bundle.png | Bin 0 -> 424 bytes .../src/bundles/colors/gray_bundle_filled.png | Bin 0 -> 371 bytes textures/src/bundles/colors/green_bundle.png | Bin 0 -> 415 bytes .../bundles/colors/green_bundle_filled.png | Bin 0 -> 360 bytes .../src/bundles/colors/light_blue_bundle.png | Bin 0 -> 447 bytes .../colors/light_blue_bundle_filled.png | Bin 0 -> 374 bytes .../src/bundles/colors/light_gray_bundle.png | Bin 0 -> 439 bytes .../colors/light_gray_bundle_filled.png | Bin 0 -> 374 bytes textures/src/bundles/colors/lime_bundle.png | Bin 0 -> 431 bytes .../src/bundles/colors/lime_bundle_filled.png | Bin 0 -> 371 bytes .../src/bundles/colors/magenta_bundle.png | Bin 0 -> 437 bytes .../bundles/colors/magenta_bundle_filled.png | Bin 0 -> 370 bytes textures/src/bundles/colors/orange_bundle.png | Bin 0 -> 431 bytes .../bundles/colors/orange_bundle_filled.png | Bin 0 -> 369 bytes textures/src/bundles/colors/pink_bundle.png | Bin 0 -> 442 bytes .../src/bundles/colors/pink_bundle_filled.png | Bin 0 -> 373 bytes textures/src/bundles/colors/purple_bundle.png | Bin 0 -> 435 bytes .../bundles/colors/purple_bundle_filled.png | Bin 0 -> 375 bytes textures/src/bundles/colors/red_bundle.png | Bin 0 -> 421 bytes .../src/bundles/colors/red_bundle_filled.png | Bin 0 -> 363 bytes textures/src/bundles/colors/white_bundle.png | Bin 0 -> 435 bytes .../bundles/colors/white_bundle_filled.png | Bin 0 -> 371 bytes textures/src/bundles/colors/yellow_bundle.png | Bin 0 -> 434 bytes .../bundles/colors/yellow_bundle_filled.png | Bin 0 -> 370 bytes .../src/bundles/variants/cord_diamond.png | Bin 0 -> 432 bytes .../bundles/variants/cord_diamond_filled.png | Bin 0 -> 288 bytes textures/src/bundles/variants/cord_gold.png | Bin 0 -> 433 bytes .../src/bundles/variants/cord_gold_filled.png | Bin 0 -> 288 bytes textures/src/bundles/variants/cord_iron.png | Bin 0 -> 428 bytes .../src/bundles/variants/cord_iron_filled.png | Bin 0 -> 282 bytes .../src/bundles/variants/cord_netherite.png | Bin 0 -> 455 bytes .../variants/cord_netherite_filled.png | Bin 0 -> 304 bytes textures/src/bundles/variants/cord_normal.png | Bin 0 -> 476 bytes .../bundles/variants/cord_normal_filled.png | Bin 0 -> 347 bytes textures/src/hammers/diamond_hammer.json | 6 + textures/src/hammers/diamond_hammer.png | Bin 0 -> 716 bytes textures/src/hammers/diamond_pickaxe.json | 14 + 72 files changed, 1803 insertions(+), 519 deletions(-) create mode 100644 .gitignore create mode 100644 src/main/kotlin/nl/kallestruik/dtweaks/BundleColor.kt create mode 100644 src/main/kotlin/nl/kallestruik/dtweaks/BundleVariant.kt create mode 100644 src/main/kotlin/nl/kallestruik/dtweaks/commands/CommandBundle.kt create mode 100644 src/main/kotlin/nl/kallestruik/dtweaks/commands/CommandSpawnzones.kt create mode 100644 src/main/kotlin/nl/kallestruik/dtweaks/managers/NoSpawnZoneManager.kt create mode 100644 src/main/kotlin/nl/kallestruik/dtweaks/tweaks/craftingtweaks/CraftableChainmail.kt create mode 100644 src/main/kotlin/nl/kallestruik/dtweaks/tweaks/craftingtweaks/MoreSandstone.kt create mode 100644 src/main/kotlin/nl/kallestruik/dtweaks/tweaks/miscellaneoustweaks/BetterBundles.kt create mode 100644 src/main/kotlin/nl/kallestruik/dtweaks/tweaks/miscellaneoustweaks/DyeableNetherite.kt create mode 100644 src/main/kotlin/nl/kallestruik/dtweaks/tweaks/miscellaneoustweaks/Hammers.kt create mode 100644 src/main/kotlin/nl/kallestruik/dtweaks/tweaks/miscellaneoustweaks/NoMelting.kt create mode 100644 src/main/kotlin/nl/kallestruik/dtweaks/tweaks/mobtweaks/NoSpawnZones.kt create mode 100644 textures/make_bundles.py create mode 100644 textures/src/bundles/colors/black_bundle.png create mode 100644 textures/src/bundles/colors/black_bundle_filled.png create mode 100644 textures/src/bundles/colors/blue_bundle.png create mode 100644 textures/src/bundles/colors/blue_bundle_filled.png create mode 100644 textures/src/bundles/colors/brown_bundle.png create mode 100644 textures/src/bundles/colors/brown_bundle_filled.png create mode 100644 textures/src/bundles/colors/cyan_bundle.png create mode 100644 textures/src/bundles/colors/cyan_bundle_filled.png create mode 100644 textures/src/bundles/colors/gray_bundle.png create mode 100644 textures/src/bundles/colors/gray_bundle_filled.png create mode 100644 textures/src/bundles/colors/green_bundle.png create mode 100644 textures/src/bundles/colors/green_bundle_filled.png create mode 100644 textures/src/bundles/colors/light_blue_bundle.png create mode 100644 textures/src/bundles/colors/light_blue_bundle_filled.png create mode 100644 textures/src/bundles/colors/light_gray_bundle.png create mode 100644 textures/src/bundles/colors/light_gray_bundle_filled.png create mode 100644 textures/src/bundles/colors/lime_bundle.png create mode 100644 textures/src/bundles/colors/lime_bundle_filled.png create mode 100644 textures/src/bundles/colors/magenta_bundle.png create mode 100644 textures/src/bundles/colors/magenta_bundle_filled.png create mode 100644 textures/src/bundles/colors/orange_bundle.png create mode 100644 textures/src/bundles/colors/orange_bundle_filled.png create mode 100644 textures/src/bundles/colors/pink_bundle.png create mode 100644 textures/src/bundles/colors/pink_bundle_filled.png create mode 100644 textures/src/bundles/colors/purple_bundle.png create mode 100644 textures/src/bundles/colors/purple_bundle_filled.png create mode 100644 textures/src/bundles/colors/red_bundle.png create mode 100644 textures/src/bundles/colors/red_bundle_filled.png create mode 100644 textures/src/bundles/colors/white_bundle.png create mode 100644 textures/src/bundles/colors/white_bundle_filled.png create mode 100644 textures/src/bundles/colors/yellow_bundle.png create mode 100644 textures/src/bundles/colors/yellow_bundle_filled.png create mode 100644 textures/src/bundles/variants/cord_diamond.png create mode 100644 textures/src/bundles/variants/cord_diamond_filled.png create mode 100644 textures/src/bundles/variants/cord_gold.png create mode 100644 textures/src/bundles/variants/cord_gold_filled.png create mode 100644 textures/src/bundles/variants/cord_iron.png create mode 100644 textures/src/bundles/variants/cord_iron_filled.png create mode 100644 textures/src/bundles/variants/cord_netherite.png create mode 100644 textures/src/bundles/variants/cord_netherite_filled.png create mode 100644 textures/src/bundles/variants/cord_normal.png create mode 100644 textures/src/bundles/variants/cord_normal_filled.png create mode 100644 textures/src/hammers/diamond_hammer.json create mode 100644 textures/src/hammers/diamond_hammer.png create mode 100644 textures/src/hammers/diamond_pickaxe.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ce8d918 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/.gradle/ +/.idea/ +/build/ +/run/ +/textures/out/ diff --git a/build.gradle.kts b/build.gradle.kts index ae74679..e863638 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,7 +2,8 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { kotlin("jvm") version "1.5.31" - id("com.github.johnrengelman.shadow") version "5.2.0" + id("com.github.johnrengelman.shadow") version "7.1.2" + id("xyz.jpenilla.run-paper") version "1.0.6" // Adds runServer and runMojangMappedServer tasks for testing id("io.papermc.paperweight.userdev") version "1.3.4" } @@ -24,18 +25,13 @@ dependencies { compileOnly("com.comphenix.protocol:ProtocolLib:4.6.0") compileOnly(kotlin("stdlib-jdk8")) - compileOnly("nl.kallestruik:DLib:1.3.5") + compileOnly("nl.kallestruik:DLib:1.3.6") testImplementation(kotlin("test-junit5")) testImplementation("org.junit.jupiter:junit-jupiter-api:5.6.0") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.6.0") } -tasks.shadowJar { - relocate("co.aikar.commands", "nl.kallestruik.dtweaks.acf") - relocate("co.aikar.locales", "nl.kallestruik.dtweaks.locales") -} - tasks.build { dependsOn(tasks.shadowJar) } @@ -49,6 +45,11 @@ tasks { useJUnitPlatform() } + shadowJar { + relocate("co.aikar.commands", "nl.kallestruik.dtweaks.acf") + relocate("co.aikar.locales", "nl.kallestruik.dtweaks.locales") + } + compileJava { options.compilerArgs.add("-parameters") } @@ -56,7 +57,7 @@ tasks { kotlinOptions.javaParameters = true } withType { - kotlinOptions.jvmTarget = "11" + kotlinOptions.jvmTarget = "16" } processResources { diff --git a/src/main/kotlin/nl/kallestruik/dtweaks/BundleColor.kt b/src/main/kotlin/nl/kallestruik/dtweaks/BundleColor.kt new file mode 100644 index 0000000..c508b26 --- /dev/null +++ b/src/main/kotlin/nl/kallestruik/dtweaks/BundleColor.kt @@ -0,0 +1,25 @@ +package nl.kallestruik.dtweaks + +import org.bukkit.Material + +enum class BundleColor( + val modelData: Int, + val dye: Material, +) { + BLACK(0, Material.BLACK_DYE), + BLUE(1, Material.BLUE_DYE), + BROWN(2, Material.BROWN_DYE), + CYAN(3, Material.CYAN_DYE), + GRAY(4, Material.GRAY_DYE), + GREEN(5, Material.GREEN_DYE), + LIGHT_BLUE(6, Material.LIGHT_BLUE_DYE), + LIGHT_GRAY(7, Material.LIGHT_GRAY_DYE), + LIME(8, Material.LIME_DYE), + MAGENTA(9, Material.MAGENTA_DYE), + ORANGE(10, Material.ORANGE_DYE), + PINK(11, Material.PINK_DYE), + PURPLE(12, Material.PURPLE_DYE), + RED(13, Material.RED_DYE), + WHITE(14, Material.WHITE_DYE), + YELLOW(15, Material.YELLOW_DYE) +} \ No newline at end of file diff --git a/src/main/kotlin/nl/kallestruik/dtweaks/BundleVariant.kt b/src/main/kotlin/nl/kallestruik/dtweaks/BundleVariant.kt new file mode 100644 index 0000000..aadefc5 --- /dev/null +++ b/src/main/kotlin/nl/kallestruik/dtweaks/BundleVariant.kt @@ -0,0 +1,15 @@ +package nl.kallestruik.dtweaks + +import org.bukkit.Material + +enum class BundleVariant( + val modelDataStart: Int, + val maxWeight: Int, + val material: Material?, +) { + NORMAL(0, 1 * 64, null), + IRON(100, 2 * 64, Material.IRON_INGOT), + GOLD(200, 4 * 64, Material.GOLD_INGOT), + DIAMOND(300, 6 * 64, Material.DIAMOND), + NETHERITE(400, 8 * 64, Material.NETHERITE_INGOT) +} \ No newline at end of file diff --git a/src/main/kotlin/nl/kallestruik/dtweaks/Const.kt b/src/main/kotlin/nl/kallestruik/dtweaks/Const.kt index d6dbdf1..33ab919 100644 --- a/src/main/kotlin/nl/kallestruik/dtweaks/Const.kt +++ b/src/main/kotlin/nl/kallestruik/dtweaks/Const.kt @@ -3,4 +3,5 @@ package nl.kallestruik.dtweaks object Const { const val TRAMPLE_ENABLED_FILE_NAME = "tramplestore.yml" const val TWEAK_STATE_FILE_NAME = "tweak_states.yml" + const val NO_SPAWN_ZONE_FILE_NAME = "no_spawn_store.yml" } \ No newline at end of file diff --git a/src/main/kotlin/nl/kallestruik/dtweaks/DTweaks.kt b/src/main/kotlin/nl/kallestruik/dtweaks/DTweaks.kt index 136f05e..c2202bb 100644 --- a/src/main/kotlin/nl/kallestruik/dtweaks/DTweaks.kt +++ b/src/main/kotlin/nl/kallestruik/dtweaks/DTweaks.kt @@ -5,18 +5,19 @@ import co.aikar.commands.PaperCommandManager import nl.kallestruik.dlib.DUtil import nl.kallestruik.dlib.MathHelper import nl.kallestruik.dlib.ReflectionUtil +import nl.kallestruik.dtweaks.commands.CommandBundle +import nl.kallestruik.dtweaks.commands.CommandPlayer +import nl.kallestruik.dtweaks.commands.CommandSpawnzones import nl.kallestruik.dtweaks.commands.CommandToggletrample +import nl.kallestruik.dtweaks.managers.FakePlayerManager +import nl.kallestruik.dtweaks.managers.NoSpawnZoneManager import nl.kallestruik.dtweaks.managers.TrampleManager import nl.kallestruik.dtweaks.managers.TweakManager import nl.kallestruik.dtweaks.tweaks.craftingtweaks.* import nl.kallestruik.dtweaks.tweaks.croptweaks.* import nl.kallestruik.dtweaks.tweaks.dispsensertweaks.DispensersCanPlantSaplings -import nl.kallestruik.dtweaks.tweaks.miscellaneoustweaks.ArmorStandArmorSwapping -import nl.kallestruik.dtweaks.tweaks.miscellaneoustweaks.CarpetBlockPlacingProtocol -import nl.kallestruik.dtweaks.tweaks.mobtweaks.NoCreeperGrief -import nl.kallestruik.dtweaks.tweaks.mobtweaks.NoDoorBreaking -import nl.kallestruik.dtweaks.tweaks.mobtweaks.NoEndermanGrief -import nl.kallestruik.dtweaks.tweaks.mobtweaks.VillagerInfo +import nl.kallestruik.dtweaks.tweaks.miscellaneoustweaks.* +import nl.kallestruik.dtweaks.tweaks.mobtweaks.* import org.bukkit.plugin.java.JavaPlugin import java.io.File import java.util.* @@ -31,7 +32,8 @@ class DTweaks: JavaPlugin() { lateinit var reflectionUtil: ReflectionUtil private lateinit var trampleManager: TrampleManager private lateinit var commandManager: PaperCommandManager -// private lateinit var fakePlayerManager: FakePlayerManager + private lateinit var fakePlayerManager: FakePlayerManager + private lateinit var noSpawnZoneManager: NoSpawnZoneManager // private lateinit var pocketDimensionManager: PocketDimensionManager } @@ -46,7 +48,8 @@ class DTweaks: JavaPlugin() { reflectionUtil = ReflectionUtil() trampleManager = TrampleManager() commandManager = PaperCommandManager(this) -// fakePlayerManager = FakePlayerManager() + fakePlayerManager = FakePlayerManager(this) + noSpawnZoneManager = NoSpawnZoneManager() // pocketDimensionManager = PocketDimensionManager(mathHelper) // Enable brigadier support on ACF @@ -54,19 +57,24 @@ class DTweaks: JavaPlugin() { registerCommandConditions() + val betterBundles = BetterBundles(this) + /* * Commands */ // commandManager.registerCommand(CommandMobcaps(reflectionUtil)) -// commandManager.registerCommand(CommandPlayer(fakePlayerManager, tweakManager)) + commandManager.registerCommand(CommandPlayer(fakePlayerManager)) // commandManager.registerCommand(CommandPocketdim(pocketDimensionManager)) commandManager.registerCommand(CommandToggletrample(trampleManager)) + commandManager.registerCommand(CommandBundle(betterBundles)) + commandManager.registerCommand(CommandSpawnzones(noSpawnZoneManager)) /* * Load data from disk */ tweakManager.loadFromFile(File(dataFolder, Const.TWEAK_STATE_FILE_NAME)) trampleManager.loadFromFile(File(dataFolder, Const.TRAMPLE_ENABLED_FILE_NAME)) + noSpawnZoneManager.loadFromFile(File(dataFolder, Const.NO_SPAWN_ZONE_FILE_NAME)) // pocketDimensionManager.loadData() /* @@ -80,8 +88,10 @@ class DTweaks: JavaPlugin() { tweakManager.registerTweak(CraftableSponge(this)) tweakManager.registerTweak(IceDecompression(this)) tweakManager.registerTweak(LogsToChests(this)) + tweakManager.registerTweak(MoreSandstone(this)) tweakManager.registerTweak(RedyeTerracotta(this)) tweakManager.registerTweak(WoolToString(this)) + tweakManager.registerTweak(CraftableChainmail(this)) /* * Crop Tweaks @@ -102,8 +112,12 @@ class DTweaks: JavaPlugin() { * Miscellaneous Tweaks */ tweakManager.registerTweak(ArmorStandArmorSwapping(this)) - tweakManager.registerTweak(CarpetBlockPlacingProtocol(this, mathHelper)) -// tweakManager.registerTweak(FakePlayers(fakePlayerManager)) +// tweakManager.registerTweak(CarpetBlockPlacingProtocol(this, mathHelper)) + tweakManager.registerTweak(betterBundles) +// tweakManager.registerTweak(DyeableNetherite(this)) + tweakManager.registerTweak(FakePlayers(fakePlayerManager, this)) + tweakManager.registerTweak(Hammers(this)) + tweakManager.registerTweak(NoMelting(this)) // tweakManager.registerTweak(SpaceTimePockets(this, pocketDimensionManager)) /* @@ -113,6 +127,7 @@ class DTweaks: JavaPlugin() { tweakManager.registerTweak(NoDoorBreaking(this)) tweakManager.registerTweak(NoEndermanGrief(this)) tweakManager.registerTweak(VillagerInfo(this)) + tweakManager.registerTweak(NoSpawnZones(this, noSpawnZoneManager)) } override fun onDisable() { @@ -121,6 +136,7 @@ class DTweaks: JavaPlugin() { */ tweakManager.saveToFile(File(dataFolder, Const.TWEAK_STATE_FILE_NAME)) trampleManager.saveToFile(File(dataFolder, Const.TRAMPLE_ENABLED_FILE_NAME)) + noSpawnZoneManager.saveToFile() // pocketDimensionManager.saveData() } @@ -130,4 +146,6 @@ class DTweaks: JavaPlugin() { throw ConditionFailedException("The tweak ${context.getConfigValue("tweak", "")} is not enabled!") } } + + fun getNamespace(): String = this.name.lowercase(Locale.ROOT) } \ No newline at end of file diff --git a/src/main/kotlin/nl/kallestruik/dtweaks/commands/CommandBundle.kt b/src/main/kotlin/nl/kallestruik/dtweaks/commands/CommandBundle.kt new file mode 100644 index 0000000..444fd51 --- /dev/null +++ b/src/main/kotlin/nl/kallestruik/dtweaks/commands/CommandBundle.kt @@ -0,0 +1,40 @@ +package nl.kallestruik.dtweaks.commands + +import co.aikar.commands.BaseCommand +import co.aikar.commands.annotation.* +import nl.kallestruik.dtweaks.BundleColor +import nl.kallestruik.dtweaks.BundleVariant +import nl.kallestruik.dtweaks.tweaks.miscellaneoustweaks.BetterBundles +import org.bukkit.Material +import org.bukkit.entity.Player +import org.bukkit.persistence.PersistentDataType + +@CommandAlias("bundle") +@Conditions("tweakEnabled:tweak=BetterBundles") +class CommandBundle( + private val betterBundles: BetterBundles, +): BaseCommand() { + @Subcommand("variant") + fun onVariant(sender: Player, variant: BundleVariant) { + val bundle = sender.inventory.itemInMainHand + if (bundle.type != Material.BUNDLE) + return + + val bundleMeta = bundle.itemMeta + bundleMeta.persistentDataContainer.set(betterBundles.VARIANT_KEY, PersistentDataType.STRING, variant.toString()) + bundle.itemMeta = bundleMeta + betterBundles.updateBundle(bundle) + } + + @Subcommand("color") + fun onVariant(sender: Player, color: BundleColor) { + val bundle = sender.inventory.itemInMainHand + if (bundle.type != Material.BUNDLE) + return + + val bundleMeta = bundle.itemMeta + bundleMeta.persistentDataContainer.set(betterBundles.COLOR_KEY, PersistentDataType.STRING, color.toString()) + bundle.itemMeta = bundleMeta + betterBundles.updateBundle(bundle) + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/kallestruik/dtweaks/commands/CommandPlayer.kt b/src/main/kotlin/nl/kallestruik/dtweaks/commands/CommandPlayer.kt index d8c630a..d813fef 100644 --- a/src/main/kotlin/nl/kallestruik/dtweaks/commands/CommandPlayer.kt +++ b/src/main/kotlin/nl/kallestruik/dtweaks/commands/CommandPlayer.kt @@ -1,28 +1,37 @@ -//package nl.kallestruik.dtweaks.commands -// -//import co.aikar.commands.BaseCommand -//import co.aikar.commands.annotation.* -//import nl.kallestruik.dtweaks.managers.FakePlayerManager -//import nl.kallestruik.dtweaks.managers.TweakManager -//import org.bukkit.command.CommandSender -//import org.bukkit.entity.Player -// -//@CommandAlias("player") -//@Conditions("tweakEnabled:tweak=FakePlayers") -//class CommandPlayer( -// private val fakePlayerManager: FakePlayerManager, -// private val tweakManager: TweakManager -//): BaseCommand() { -// -// @Subcommand("spawn") -// @CommandCompletion("@players") -// fun onSpawn(sender: Player, @Single playerName: String) { -// fakePlayerManager.spawnFakePlayer(sender.location, playerName) -// } -// -// @Subcommand("kill") -// @CommandCompletion("@players") -// fun onKill(sender: Player, @Single playerName: String) { -// fakePlayerManager.killFakePlayer(playerName) -// } -//} \ No newline at end of file +package nl.kallestruik.dtweaks.commands + +import co.aikar.commands.BaseCommand +import co.aikar.commands.annotation.* +import nl.kallestruik.dtweaks.managers.FakePlayerManager +import org.bukkit.entity.Player + +@CommandAlias("player") +@Conditions("tweakEnabled:tweak=FakePlayers") +class CommandPlayer( + private val fakePlayerManager: FakePlayerManager +): BaseCommand() { + + @Subcommand("spawn") + @CommandCompletion("@players") + fun onSpawn(sender: Player, @Single playerName: String) { + fakePlayerManager.spawnFakePlayer(sender.location, playerName) + } + + @Subcommand("kill") + @CommandCompletion("@players") + fun onKill(sender: Player, @Single playerName: String) { + fakePlayerManager.killFakePlayer(playerName) + } + + @Subcommand("attack") + @CommandCompletion("@players") + fun onAttack(sender: Player, @Single playerName: String) { + fakePlayerManager.attack(playerName) + } + + @Subcommand("use") + @CommandCompletion("@players") + fun onUse(sender: Player, @Single playerName: String) { + fakePlayerManager.use(playerName) + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/kallestruik/dtweaks/commands/CommandSpawnzones.kt b/src/main/kotlin/nl/kallestruik/dtweaks/commands/CommandSpawnzones.kt new file mode 100644 index 0000000..c9758b4 --- /dev/null +++ b/src/main/kotlin/nl/kallestruik/dtweaks/commands/CommandSpawnzones.kt @@ -0,0 +1,101 @@ +package nl.kallestruik.dtweaks.commands + +import co.aikar.commands.BaseCommand +import co.aikar.commands.annotation.CommandAlias +import co.aikar.commands.annotation.Conditions +import co.aikar.commands.annotation.Subcommand +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.NamedTextColor +import nl.kallestruik.dtweaks.managers.NoSpawnZoneManager +import org.bukkit.entity.Player + + +@CommandAlias("spawnzones") +@Conditions("tweakEnabled:tweak=NoSpawnZones") +class CommandSpawnzones( + private val noSpawnZoneManager: NoSpawnZoneManager +): BaseCommand() { + + @Subcommand("claim") + inner class CommandClaim: BaseCommand() { + + @Subcommand("one") + fun onClaimOne(sender: Player) { + noSpawnZoneManager.claimChunk(sender.location.chunk.x, sender.location.chunk.z, sender.world.key) + noSpawnZoneManager.saveToFile() + } + + @Subcommand("square") + fun onClaimSquare(sender: Player, radius: Int) { + val centerX = sender.location.chunk.x + val centerZ = sender.location.chunk.z + + + for (x in centerX-radius..centerX+radius) + for (z in centerZ-radius..centerZ+radius) + noSpawnZoneManager.claimChunk(x, z, sender.world.key) + + noSpawnZoneManager.saveToFile() + } + } + + @Subcommand("unclaim") + inner class CommandUnclaim: BaseCommand() { + + @Subcommand("one") + fun onClaimOne(sender: Player) { + noSpawnZoneManager.unclaimChunk(sender.location.chunk.x, sender.location.chunk.z, sender.world.key) + noSpawnZoneManager.saveToFile() + } + + @Subcommand("square") + fun onClaimSquare(sender: Player, radius: Int) { + val centerX = sender.location.chunk.x + val centerZ = sender.location.chunk.z + + for (x in centerX-radius..centerX+radius) + for (z in centerZ-radius..centerZ+radius) + noSpawnZoneManager.unclaimChunk(x, z, sender.world.key) + + noSpawnZoneManager.saveToFile() + } + } + + @Subcommand("map") + fun onMap(sender: Player) { + // Print the header of the faction map. + sender.sendMessage(Component + .empty() + .color(NamedTextColor.GRAY) + .append(Component.text("---------------[")) + .append(Component + .text(" No Spawn Zones ") + .color(NamedTextColor.AQUA)) + .append(Component.text("]---------------")) + ) + + val centerX = sender.location.chunk.x + val centerZ = sender.location.chunk.z + + // Create and send the claim map. + for (x in -5..5) { + var row = Component.empty() + for (z in -6..6) { + row = if (x == 0 && z == 0) { + row.append(Component.text(" ■").color(NamedTextColor.YELLOW)) + } else if (noSpawnZoneManager.isChunkClaimed(centerX + x, centerZ + z, sender.world.key)) { + row.append(Component.text(" ■").color(NamedTextColor.AQUA)) + } else { + row.append(Component.text(" ■").color(NamedTextColor.GRAY)) + } + } + sender.sendMessage(row) + } + + // Print the footer of the faction map. + sender.sendMessage(Component + .text("---------------------------------------") + .color(NamedTextColor.GRAY) + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/kallestruik/dtweaks/fakeplayer/FakeConnection.kt b/src/main/kotlin/nl/kallestruik/dtweaks/fakeplayer/FakeConnection.kt index 8711b99..b177740 100644 --- a/src/main/kotlin/nl/kallestruik/dtweaks/fakeplayer/FakeConnection.kt +++ b/src/main/kotlin/nl/kallestruik/dtweaks/fakeplayer/FakeConnection.kt @@ -1,27 +1,19 @@ -//package nl.kallestruik.dtweaks.fakeplayer -// -//import io.netty.util.concurrent.Future -//import io.netty.util.concurrent.GenericFutureListener -// -//class FakeConnection( -// enumProtocolDirection: EnumProtocolDirection -//): NetworkManager(enumProtocolDirection) { -// var open = true -// -// // isOpen() -// override fun isConnected(): Boolean { -// return open -// } -// -// // hasChannel() -// override fun i(): Boolean { -// return false -// } -// -// override fun sendPacket(packet: Packet<*>?, genericfuturelistener: GenericFutureListener?>?) {} -// -// // disableAutoRead() -// override fun stopReading() {} -// -// override fun handleDisconnection() {} -//} \ No newline at end of file +package nl.kallestruik.dtweaks.fakeplayer + +import io.netty.channel.embedded.EmbeddedChannel +import net.minecraft.network.Connection +import net.minecraft.network.protocol.PacketFlow + + +class FakeConnection( + packetFlow: PacketFlow +): Connection(packetFlow) { + + init { + this.channel = EmbeddedChannel() + } + + override fun setReadOnly() {} + + override fun handleDisconnection() {} +} \ No newline at end of file diff --git a/src/main/kotlin/nl/kallestruik/dtweaks/fakeplayer/FakePlayer.kt b/src/main/kotlin/nl/kallestruik/dtweaks/fakeplayer/FakePlayer.kt index a1f3d1d..bfe82cd 100644 --- a/src/main/kotlin/nl/kallestruik/dtweaks/fakeplayer/FakePlayer.kt +++ b/src/main/kotlin/nl/kallestruik/dtweaks/fakeplayer/FakePlayer.kt @@ -1,79 +1,68 @@ -//package nl.kallestruik.dtweaks.fakeplayer -// -//import com.mojang.authlib.GameProfile -//import net.minecraft.server.v1_16_R3.* -//import nl.kallestruik.dtweaks.DTweaks -//import org.bukkit.Location -//import org.bukkit.craftbukkit.v1_16_R3.CraftWorld -// -//class FakePlayer( -// minecraftServer: MinecraftServer?, -// worldServer: WorldServer?, -// gameProfile: GameProfile?, -// playerInteractManager: PlayerInteractManager? -//): EntityPlayer(minecraftServer, worldServer, gameProfile, playerInteractManager) { -// val locale = "en_US" -// private var hasStartingPos = false -// private var startingX = 0.0 -// private var startingY = 0.0 -// private var startingZ = 0.0 -// private var startingYaw = 0f -// private var startingPitch = 0f -// -// private constructor( -// server: MinecraftServer, -// worldIn: WorldServer, -// profile: GameProfile?, -// interactionManagerIn: PlayerInteractManager, -// x: Double, -// y: Double, -// z: Double, -// yaw: Float, -// pitch: Float -// ) : this(server, worldIn, profile, interactionManagerIn) { -// this.hasStartingPos = true -// this.startingX = x -// this.startingY = y -// this.startingZ = z -// this.startingYaw = yaw -// this.startingPitch = pitch -// applyStartingPosition() -// } -// -// companion object { -// fun atLocation(location: Location, name: String?): FakePlayer? { -// // Split the location into its parts for use later. -// val x = location.x -// val y = location.y -// val z = location.z -// val yaw = location.yaw.toDouble() -// val pitch = location.pitch.toDouble() -// try { -// // Get the minecraft server instance. -// val minecraftServer = MinecraftServer.getServer() -// // Create an empty variable for the world server. -// val worldServer = (location.world as CraftWorld).handle -// // Get the game profile from the cache (or retrieve it if it is not cached) -// var gameProfile = minecraftServer.userCache.getProfile(name) ?: return null -// if (gameProfile.properties.containsKey("textures")) { -// gameProfile = TileEntitySkull.b(gameProfile, null, true).get() -// } -// -// // Create a player interact manager using the world server. -// val playerInteractManager = PlayerInteractManager(worldServer) -// -// // Create an instance of our fake player. -// val instance = FakePlayer( -// minecraftServer, worldServer, gameProfile, playerInteractManager, x, y, z, -// yaw.toFloat(), -// pitch.toFloat() -// ) -// // Create a fake connection for our fake player. -// val connection = FakeConnection(EnumProtocolDirection.SERVERBOUND) -// -// // Set access to connected channels so we can add our own connection. -// val serverConnection = minecraftServer.serverConnection -// +package nl.kallestruik.dtweaks.fakeplayer + +import com.mojang.authlib.GameProfile +import net.minecraft.network.chat.TextComponent +import net.minecraft.network.protocol.PacketFlow +import net.minecraft.network.protocol.game.ClientboundRotateHeadPacket +import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket +import net.minecraft.server.MinecraftServer +import net.minecraft.server.TickTask +import net.minecraft.server.level.ServerLevel +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.damagesource.DamageSource +import net.minecraft.world.entity.Entity +import net.minecraft.world.food.FoodData +import net.minecraft.world.level.block.entity.SkullBlockEntity +import org.bukkit.Location +import org.bukkit.craftbukkit.v1_18_R1.CraftWorld +import java.util.concurrent.atomic.AtomicReference + +class FakePlayer( + server: MinecraftServer, + val serverLevel: ServerLevel, + gameProfile: GameProfile +): ServerPlayer(server, serverLevel, gameProfile) { + val locale = "en_US" + + companion object { + fun atLocation(location: Location, name: String): FakePlayer? { + // Get the minecraft server instance. + val minecraftServer = MinecraftServer.getServer() + // Get the world + val serverLevel = (location.world as CraftWorld).handle + // Get the game profile from the cache (or retrieve it if it is not cached) + var gameProfile = minecraftServer.profileCache.get(name).takeIf { it.isPresent }?.get() ?: return null + if (gameProfile.properties.containsKey("textures")) { + val result = AtomicReference() + SkullBlockEntity.updateGameprofile(gameProfile, result::set) + gameProfile = result.get() + } + + // Create an instance of our fake player. + val instance = FakePlayer(minecraftServer, serverLevel, gameProfile) + + // Create a fake connection for our fake player. + val connection = FakeConnection(PacketFlow.SERVERBOUND) + FakePlayerList.placeNewPlayer(minecraftServer.playerList, minecraftServer, connection, instance) + + instance.respawn() + instance.teleportTo(serverLevel, location.x, location.y, location.z, location.yaw, location.pitch) + instance.dead = false + instance.heal(instance.maxHealth) + instance.unsetRemoved() + instance.maxUpStep = 0.6F + + minecraftServer.playerList.broadcastAll( + ClientboundRotateHeadPacket( + instance, + (instance.yHeadRot * 256 / 360).toInt().toByte() + ), serverLevel.dimension()) + + minecraftServer.playerList.broadcastAll(ClientboundTeleportEntityPacket(instance), serverLevel.dimension()) + + instance.entityData.set(DATA_PLAYER_MODE_CUSTOMISATION, 0x7f) + return instance + // (DTweaks.reflectionUtil.getValueFromField( // serverConnection!!, // "connectedChannels" @@ -126,43 +115,36 @@ // instance.worldServer.getChunkProvider().movePlayer(instance) // // bp == PLAYER_MODEL_PARTS // instance.datawatcher.set(bj, 0x7f.toByte()) // show all model layers (incl. capes) -// return instance -// } catch (e: Exception) { -// e.printStackTrace() -// } -// return null -// } -// } -// -// -// fun applyStartingPosition() { -// if (hasStartingPos) { -// this.setPositionRotation(startingX, startingY, startingZ, startingYaw, startingPitch) -// mot = Vec3D(0.0, 0.0, 0.0) -// } -// } -// -// -// override fun killEntity() { -// server.a(TickTask(server.ai()) { playerConnection.a(ChatComponentText("Killed")) }) -// } -// -// override fun tick() { -// super.tick() -// if (H()) { -// I() -// } -// movementTick() -// if (server.ai() % 10 == 0) { -// playerConnection.syncPosition() -// this.worldServer.getChunkProvider().movePlayer(this) -// } -// } -// + } + } + + override fun kill() { + server.tell(TickTask(server.tickCount) {connection.onDisconnect(TextComponent("Killed"))}) + } + + override fun die(source: DamageSource) { + super.die(source) + heal(maxHealth) + foodData = FoodData(this) + kill() + } + + override fun tick() { + super.tick() + + if (server.tickCount % 10 == 0) { + connection.resetPosition() + serverLevel.chunkSource.move(this) + hasChangedDimension() + } + } + // override fun die(cause: DamageSource?) { // super.die(cause) // health = 20f // foodData = FoodMetaData(this) // killEntity() // } -//} \ No newline at end of file + + override fun getIpAddress() = "127.0.0.1" +} \ No newline at end of file diff --git a/src/main/kotlin/nl/kallestruik/dtweaks/fakeplayer/FakePlayerConnection.kt b/src/main/kotlin/nl/kallestruik/dtweaks/fakeplayer/FakePlayerConnection.kt index 0c9a81f..3d95a4b 100644 --- a/src/main/kotlin/nl/kallestruik/dtweaks/fakeplayer/FakePlayerConnection.kt +++ b/src/main/kotlin/nl/kallestruik/dtweaks/fakeplayer/FakePlayerConnection.kt @@ -1,36 +1,31 @@ -//package nl.kallestruik.dtweaks.fakeplayer -// -//import net.minecraft.server.v1_16_R3.* -//import nl.kallestruik.dtweaks.DTweaks -//import java.lang.reflect.Field -// -//class FakePlayerConnection( -// minecraftServer: MinecraftServer, -// networkManager: NetworkManager, -// entityPlayer: EntityPlayer -//): PlayerConnection(minecraftServer, networkManager, entityPlayer) { -// override fun sendPacket(packet: Packet<*>?) { -// if (packet is PacketPlayOutKeepAlive) { -// val pong = PacketPlayInKeepAlive() -// try { -// val pingId: Field = DTweaks.reflectionUtil.getField(PacketPlayOutKeepAlive::class.java, "a") -// val pongId: Field = DTweaks.reflectionUtil.getField(PacketPlayInKeepAlive::class.java, "a") -// pingId.isAccessible = true -// pongId.isAccessible = true -// pongId[pong] = pingId[packet] -// } catch (e: Exception) { -// e.printStackTrace() -// } -// this.a(pong) -// } -// } -// -// override fun disconnect(message: String?) { -// player.killEntity() -// } -// -// override fun a(disconnectReason: IChatBaseComponent?) { -// super.a(disconnectReason) -// (this.a() as FakeConnection).open = false -// } -//} \ No newline at end of file +package nl.kallestruik.dtweaks.fakeplayer + +import io.netty.util.concurrent.Future +import io.netty.util.concurrent.GenericFutureListener +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.TranslatableComponent +import net.minecraft.network.protocol.Packet +import net.minecraft.server.MinecraftServer +import net.minecraft.server.network.ServerGamePacketListenerImpl +import javax.annotation.Nullable + +class FakePlayerConnection( + minecraftServer: MinecraftServer, + connection: FakeConnection, + player: FakePlayer +): ServerGamePacketListenerImpl(minecraftServer, connection, player) { + override fun send(packetIn: Packet<*>?) {} + override fun send(packet: Packet<*>, listener: GenericFutureListener>?) {} + + override fun disconnect(message: Component) { + if (message is TranslatableComponent && (message.key.equals("multiplayer.disconnect.idling") || message.equals("multiplayer.disconnect.duplicate_login"))) + { + player.kill() + } + } + + override fun tick() { + println("Tick for ${player.name}") + super.tick() + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/kallestruik/dtweaks/fakeplayer/FakePlayerList.kt b/src/main/kotlin/nl/kallestruik/dtweaks/fakeplayer/FakePlayerList.kt index edd622b..75e97df 100644 --- a/src/main/kotlin/nl/kallestruik/dtweaks/fakeplayer/FakePlayerList.kt +++ b/src/main/kotlin/nl/kallestruik/dtweaks/fakeplayer/FakePlayerList.kt @@ -1,258 +1,165 @@ -//package nl.kallestruik.dtweaks.fakeplayer -// -//import com.mojang.serialization.DataResult -//import com.mojang.serialization.Dynamic -//import io.netty.buffer.Unpooled -//import net.minecraft.server.v1_16_R3.* -//import nl.kallestruik.dtweaks.DTweaks -//import org.apache.logging.log4j.Logger -//import org.bukkit.Bukkit -//import org.bukkit.craftbukkit.v1_16_R3.CraftWorld -//import org.bukkit.craftbukkit.v1_16_R3.util.CraftChatMessage -//import org.bukkit.entity.Player -//import org.spigotmc.event.player.PlayerSpawnLocationEvent -//import java.util.* -// -//object FakePlayerList { -// fun a(playerList: PlayerList, networkManager: NetworkManager, entityPlayer: EntityPlayer) { -// // Setup what are normally class level variables -// val server = DTweaks.reflectionUtil.getValueFromField(playerList, "server") as MinecraftServer -// val LOGGER = DTweaks.reflectionUtil.getValueFromField(playerList, "LOGGER") as Logger -// -// val gameProfile = entityPlayer.profile -// val userCache = server.userCache -// val oldGameProfile = userCache.getProfile(gameProfile.id) -// var oldName = if (oldGameProfile == null) gameProfile.name else oldGameProfile.name -// -// userCache.a(gameProfile) -// val playerData = playerList.a(entityPlayer) -// if (playerData != null && playerData.hasKey("bukkit")) { -// val bukkit = playerData.getCompound("bukkit") -// oldName = if (bukkit.hasKeyOfType("lastKnownName", 8)) bukkit.getString("lastKnownName") else oldName -// } -// val worldKey: ResourceKey = if (playerData != null) { -// val dataResult: DataResult> = DimensionManager.a( -// Dynamic( -// DynamicOpsNBT.a, -// playerData["Dimension"] -// ) -// ) -// dataResult.resultOrPartial { o -> LOGGER.error(o) } -// .orElse(World.OVERWORLD) as ResourceKey -// } else { -// World.OVERWORLD -// } -// var worldServer = server.getWorldServer(worldKey) ?: server.E() -// -// entityPlayer.spawnIn(worldServer) -// entityPlayer.playerInteractManager.a(entityPlayer.world as WorldServer) -// var s1 = "local" -// if (networkManager.getSocketAddress() != null) { -// s1 = networkManager.getSocketAddress().toString() -// } -// val bukkitPlayer: Player = entityPlayer.bukkitEntity -// val ev = PlayerSpawnLocationEvent(bukkitPlayer, bukkitPlayer.location) -// Bukkit.getPluginManager().callEvent(ev) -// val loc = ev.spawnLocation -// worldServer = (loc.world as CraftWorld).handle -// entityPlayer.spawnIn(worldServer) -// entityPlayer.playerInteractManager.a(entityPlayer.world as WorldServer) -// entityPlayer.setPosition(loc.x, loc.y, loc.z) -// -// //entityplayer.setYawPitch(loc.getYaw(), loc.getPitch()); -// try { -// val setYawPitch = Entity::class.java.getDeclaredMethod( -// "setYawPitch", -// Float::class.javaPrimitiveType, -// Float::class.javaPrimitiveType -// ) -// setYawPitch.isAccessible = true -// setYawPitch.invoke(entityPlayer, loc.yaw, loc.pitch) -// } catch (e: Exception) { -// e.printStackTrace() -// } -// val worldData = worldServer.getWorldData() -// -// //playerList.a(entityplayer, (EntityPlayer)null, worldserver1); -// try { -// val a = PlayerList::class.java.getDeclaredMethod( -// "a", -// EntityPlayer::class.java, -// EntityPlayer::class.java, -// WorldServer::class.java -// ) -// a.isAccessible = true -// a.invoke(playerList, entityPlayer, null, worldServer) -// } catch (e: Exception) { -// e.printStackTrace() -// } -// -// //PlayerConnection playerconnection = new PlayerConnection(server, networkmanager, entityplayer); -// val playerConnection: PlayerConnection = -// (entityPlayer as? FakePlayer)?.let { FakePlayerConnection(server, networkManager, it) } -// ?: PlayerConnection(server, networkManager, entityPlayer) -// val gameRules = worldServer.gameRules -// val doImmediateRespawn = gameRules.getBoolean(GameRules.DO_IMMEDIATE_RESPAWN) -// val reducedDebugInfo = gameRules.getBoolean(GameRules.REDUCED_DEBUG_INFO) -// try { -// playerConnection.sendPacket( -// PacketPlayOutLogin( -// entityPlayer.id, -// entityPlayer.playerInteractManager.gameMode, -// entityPlayer.playerInteractManager.c(), -// BiomeManager.a( -// worldServer.seed -// ), -// worldData.isHardcore, -// server.F(), -// DTweaks.reflectionUtil.getValueFromField(playerList, "s") as IRegistryCustom.Dimension, -// worldServer.dimensionManager, -// worldServer.dimensionKey, -// playerList.maxPlayers, -// worldServer.spigotConfig.viewDistance, -// reducedDebugInfo, -// !doImmediateRespawn, -// worldServer.isDebugWorld, -// worldServer.isFlatWorld -// ) -// ) -// } catch (e: Exception) { -// e.printStackTrace() -// } -// entityPlayer.bukkitEntity.sendSupportedChannels() -// playerConnection.sendPacket( -// PacketPlayOutCustomPayload( -// PacketPlayOutCustomPayload.a, -// PacketDataSerializer(Unpooled.buffer()).a(playerList.server.serverModName) -// ) -// ) -// playerConnection.sendPacket(PacketPlayOutServerDifficulty(worldData.difficulty, worldData.isDifficultyLocked)) -// playerConnection.sendPacket(PacketPlayOutAbilities(entityPlayer.abilities)) -// playerConnection.sendPacket(PacketPlayOutHeldItemSlot(entityPlayer.inventory.itemInHandIndex)) -// playerConnection.sendPacket(PacketPlayOutRecipeUpdate(server.craftingManager.b())) -// playerConnection.sendPacket(PacketPlayOutTags(server.tagRegistry)) -// playerList.d(entityPlayer) -// entityPlayer.statisticManager.c() -// entityPlayer.recipeBook.a(entityPlayer) -// playerList.sendScoreboard(worldServer.scoreboard, entityPlayer) -// server.invalidatePingSample() -// val chatMessage = if (entityPlayer.profile.name.equals(oldName, ignoreCase = true)) { -// ChatMessage("multiplayer.player.joined", entityPlayer.scoreboardDisplayName) -// } else { -// ChatMessage("multiplayer.player.joined.renamed", entityPlayer.scoreboardDisplayName, oldName) -// } -// chatMessage.a(EnumChatFormat.YELLOW) -// var joinMessage = CraftChatMessage.fromComponent(chatMessage) -// playerConnection.a( -// entityPlayer.locX(), -// entityPlayer.locY(), -// entityPlayer.locZ(), -// entityPlayer.yaw, -// entityPlayer.pitch -// ) -// playerList.players.add(entityPlayer) -// -// //playerList.playersByName.put(entityplayer.getName().toLowerCase(Locale.ROOT), entityplayer); -// val playersByName = DTweaks.reflectionUtil.getValueFromField(playerList, "playersByName") as HashMap -// playersByName[entityPlayer.name.toLowerCase(Locale.ROOT)] = entityPlayer -// -// //playerList.j.put(entityplayer.getUniqueID(), entityplayer); -// val playersByUUID = DTweaks.reflectionUtil.getValueFromField(playerList, "j") as HashMap -// playersByUUID[entityPlayer.uniqueID] = entityPlayer -// -// if (entityPlayer.playerConnection.networkManager.isConnected) { -// var i: Int -// if (joinMessage != null && joinMessage.isNotEmpty()) { -// var var27: Array -// val var26 = CraftChatMessage.fromString(joinMessage).also { var27 = it }.size -// i = 0 -// while (i < var26) { -// val line = var27[i] -// server.playerList.sendAll(PacketPlayOutChat(line, ChatMessageType.SYSTEM, SystemUtils.b)) -// ++i -// } -// } -// val packet = PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, entityPlayer) -// for (player in playerList.players) { -// if (player.bukkitEntity.canSee(entityPlayer.bukkitEntity)) { -// player.playerConnection.sendPacket(packet) -// } -// if (entityPlayer.bukkitEntity.canSee(player.bukkitEntity)) { -// entityPlayer.playerConnection.sendPacket( -// PacketPlayOutPlayerInfo( -// PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, -// player -// ) +package nl.kallestruik.dtweaks.fakeplayer + +import com.destroystokyo.paper.PaperConfig +import com.destroystokyo.paper.event.player.PlayerInitialSpawnEvent +import com.mojang.authlib.GameProfile +import com.mojang.datafixers.util.Either +import com.mojang.serialization.Dynamic +import io.netty.buffer.Unpooled +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.NbtOps +import net.minecraft.network.Connection +import net.minecraft.network.FriendlyByteBuf +import net.minecraft.network.protocol.game.* +import net.minecraft.resources.ResourceKey +import net.minecraft.server.MinecraftServer +import net.minecraft.server.level.* +import net.minecraft.server.level.ChunkHolder.ChunkLoadingFailure +import net.minecraft.server.network.ServerGamePacketListenerImpl +import net.minecraft.server.players.GameProfileCache +import net.minecraft.server.players.PlayerList +import net.minecraft.world.level.ChunkPos +import net.minecraft.world.level.GameRules +import net.minecraft.world.level.Level +import net.minecraft.world.level.biome.BiomeManager +import net.minecraft.world.level.chunk.ChunkAccess +import net.minecraft.world.level.dimension.DimensionType +import nl.kallestruik.dlib.ReflectionUtil +import nl.kallestruik.dtweaks.DTweaks +import org.apache.logging.log4j.Logger +import org.bukkit.Bukkit +import org.bukkit.craftbukkit.v1_18_R1.CraftWorld +import org.bukkit.entity.Player +import org.spigotmc.event.player.PlayerSpawnLocationEvent +import java.util.* +import java.util.concurrent.CompletableFuture +import java.util.function.Consumer + +object FakePlayerList { + fun placeNewPlayer(playerList: PlayerList, server: MinecraftServer, connection: FakeConnection, player: FakePlayer) { + player.isRealPlayer = true // Paper - Chunk priority + + player.networkManager = connection // Paper + + player.loginTime = System.currentTimeMillis() // Paper + + val gameProfile: GameProfile = player.getGameProfile() + val userCache: GameProfileCache = server.getProfileCache() + val optional = userCache[gameProfile.id] + var s: String? = optional.map { obj: GameProfile -> obj.name }.orElse(gameProfile.name) + + userCache.add(gameProfile) + val nbtTagCompound: CompoundTag? = playerList.load(player) + // CraftBukkit start - Better rename detection + // CraftBukkit start - Better rename detection + if (nbtTagCompound != null && nbtTagCompound.contains("bukkit")) { + val bukkit = nbtTagCompound.getCompound("bukkit") + s = if (bukkit.contains("lastKnownName", 8)) bukkit.getString("lastKnownName") else s + } + val lastKnownName = s // Paper + + // CraftBukkit end + + // Paper start - move logic in Entity to here, to use bukkit supplied world UUID. + // CraftBukkit end + + // Paper start - move logic in Entity to here, to use bukkit supplied world UUID. + val resourceKey: ResourceKey = if (nbtTagCompound != null && nbtTagCompound.contains("WorldUUIDMost") && nbtTagCompound.contains("WorldUUIDLeast")) { + val uid = UUID(nbtTagCompound.getLong("WorldUUIDMost"), nbtTagCompound.getLong("WorldUUIDLeast")) + val bWorld = Bukkit.getServer().getWorld(uid) + if (bWorld != null) { + (bWorld as CraftWorld).handle.dimension() + } else { + Level.OVERWORLD + } + } else if (nbtTagCompound != null) { + // Vanilla migration support + // Paper end + val dataResult = DimensionType.parseLegacy( + Dynamic( + NbtOps.INSTANCE, + nbtTagCompound["Dimension"] + ) + ) + dataResult.result().orElse(Level.OVERWORLD) + } else { + Level.OVERWORLD + } + + var worldServer: ServerLevel = server.getLevel(resourceKey) ?: server.overworld() + + if (nbtTagCompound == null) player.fudgeSpawnLocation(worldServer) // Paper - only move to spawn on first login, otherwise, stay where you are.... + + player.setLevel(worldServer) + + + // Spigot start - spawn location event + val spawnPlayer: Player = player.bukkitEntity + val ev: PlayerSpawnLocationEvent = + PlayerInitialSpawnEvent(spawnPlayer, spawnPlayer.location) // Paper use our duplicate event + + Bukkit.getServer().pluginManager.callEvent(ev) + + val loc = ev.spawnLocation + worldServer = (loc.world as CraftWorld).handle + + player.spawnIn(worldServer) + player.gameMode.setLevel(player.level as ServerLevel) + // Paper start - set raw so we aren't fully joined to the world (not added to chunk or world) + // Paper start - set raw so we aren't fully joined to the world (not added to chunk or world) + player.setPosRaw(loc.x, loc.y, loc.z) + player.setRot(loc.yaw, loc.pitch) + // Paper end + // Spigot end + + // CraftBukkit - Moved message to after join + // PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ({}, {}, {})", entityplayer.getName().getString(), s1, entityplayer.getId(), entityplayer.getX(), entityplayer.getY(), entityplayer.getZ()); + // Paper end + // Spigot end + + // CraftBukkit - Moved message to after join + // PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ({}, {}, {})", entityplayer.getName().getString(), s1, entityplayer.getId(), entityplayer.getX(), entityplayer.getY(), entityplayer.getZ()); + val worlddata = worldServer.getLevelData() + + player.loadGameTypes(nbtTagCompound) + val playerconnection = FakePlayerConnection(server, connection, player) + val gamerules = worldServer.gameRules + + player.bukkitEntity.sendSupportedChannels() // CraftBukkit + + player.stats.markAllDirty() + player.recipeBook.sendInitialRecipeBook(player) + playerList.updateEntireScoreboard(worldServer.scoreboard, player) + server.invalidateStatus() + // Paper start - async load spawn in chunk + // Paper start - async load spawn in chunk + val finalWorldserver = worldServer + val chunkX = loc.blockX shr 4 + val chunkZ = loc.blockZ shr 4 + val pos = ChunkPos(chunkX, chunkZ) + val playerChunkMap = worldServer.getChunkSource().chunkMap + val distanceManager: DistanceManager = playerChunkMap.distanceManager + distanceManager.addTicketAtLevel(TicketType.LOGIN, pos, 31, pos.toLong()) + worldServer.getChunkSource().markAreaHighPriority(pos, 28, 3) // Paper - Chunk priority + + val postChunkLoadJoin = playerList::class.java.superclass.getDeclaredMethod("postChunkLoadJoin", ServerPlayer::class.java, ServerLevel::class.java, Connection::class.java, ServerGamePacketListenerImpl::class.java, CompoundTag::class.java, String::class.java, String::class.java) + postChunkLoadJoin.isAccessible = true + postChunkLoadJoin.invoke(playerList, player, finalWorldserver, connection, playerconnection, + nbtTagCompound, "127.0.0.1", lastKnownName) + + +// worldServer.getChunkSource().getChunkAtAsynchronously(chunkX, chunkZ, true, false) +// .thenApply { chunk: Either -> // Paper - Chunk priority +// val updatingChunk = playerChunkMap.getUpdatingChunkIfPresent(pos.toLong()) +// if (updatingChunk != null) { +// return@thenApply updatingChunk.entityTickingChunkFuture +// } else { +// return@thenApply CompletableFuture.completedFuture( +// chunk // ) // } -// } -// entityPlayer.sentListPacket = true -// entityPlayer.playerConnection.sendPacket( -// PacketPlayOutEntityMetadata( -// entityPlayer.id, -// DTweaks.reflectionUtil.getValueFromField(entityPlayer, "datawatcher") as DataWatcher, true -// ) -// ) +// }.thenAccept { // -// if (entityPlayer.world === worldServer && !worldServer.getPlayers().contains(entityPlayer)) { -// worldServer.addPlayerJoin(entityPlayer) -// server.bossBattleCustomData.a(entityPlayer) -// } -// -// worldServer = entityPlayer.worldServer -// playerList.a(entityPlayer, worldServer) -// if (server.resourcePack.isNotEmpty()) { -// entityPlayer.setResourcePack(server.resourcePack, server.resourcePackHash) -// } -// -// for (mobEffect in entityPlayer.getEffects()) { -// playerConnection.sendPacket(PacketPlayOutEntityEffect(entityPlayer.id, mobEffect)) -// } -// -// if (playerData != null && playerData.hasKeyOfType("RootVehicle", 10)) { -// val rootVehicleData = playerData.getCompound("RootVehicle") -// val entity = EntityTypes.a( -// rootVehicleData.getCompound("Entity"), worldServer -// ) { entity1x: Entity? -> -// if (!worldServer.addEntitySerialized(entity1x) -// ) null else entity1x -// } -// if (entity != null) { -// val uuid: UUID? = if (rootVehicleData.b("Attach")) { -// rootVehicleData.a("Attach") -// } else { -// null -// } -// if (entity.uniqueID == uuid) { -// entityPlayer.a(entity, true) -// } else { -// for (passenger in entity.allPassengers) { -// if (passenger.uniqueID == uuid) { -// entityPlayer.a(passenger, true) -// break -// } -// } -// } -// if (!entityPlayer.isPassenger) { -// LOGGER.warn("Couldn't reattach entity to player") -// worldServer.removeEntity(entity) -// for (passenger in entity.allPassengers) { -// worldServer.removeEntity(passenger) -// } -// } -// } -// } -// entityPlayer.syncInventory() -// LOGGER.info( -// "{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", -// entityPlayer.getDisplayName().string, -// s1, -// entityPlayer.id, -// worldServer?.worldDataServer?.name, -// entityPlayer.locX(), -// entityPlayer.locY(), -// entityPlayer.locZ() -// ) // } -// } -//} \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/kallestruik/dtweaks/managers/FakePlayerManager.kt b/src/main/kotlin/nl/kallestruik/dtweaks/managers/FakePlayerManager.kt index 0c926e3..e552c4b 100644 --- a/src/main/kotlin/nl/kallestruik/dtweaks/managers/FakePlayerManager.kt +++ b/src/main/kotlin/nl/kallestruik/dtweaks/managers/FakePlayerManager.kt @@ -1,30 +1,222 @@ -//package nl.kallestruik.dtweaks.managers +package nl.kallestruik.dtweaks.managers + +import net.minecraft.core.Direction +import net.minecraft.world.InteractionHand +import net.minecraft.world.entity.Entity +import net.minecraft.world.level.ClipContext +import net.minecraft.world.phys.* +import nl.kallestruik.dlib.ReflectionUtil +import nl.kallestruik.dtweaks.DTweaks +import nl.kallestruik.dtweaks.fakeplayer.FakePlayer +import org.bukkit.Bukkit +import org.bukkit.Location +import org.bukkit.craftbukkit.v1_18_R1.entity.CraftEntity +import org.bukkit.craftbukkit.v1_18_R1.entity.CraftPlayer +import org.bukkit.scheduler.BukkitRunnable +import org.bukkit.scheduler.BukkitTask +import java.util.function.Predicate + + +/* + A lot of code in this file is inspired by code from carpet mod. + Link: https://github.com/gnembon/fabric-carpet + */ +class FakePlayerManager( + val plugin: DTweaks, +) { + + init { + // Tick all fake players. + FakePlayerTickTask().runTaskTimer(plugin, 10, 10) + } + + fun getPlayerByName(name: String): FakePlayer? { + val player = Bukkit.getPlayer(name) + if (player == null || !player.isOnline) return null + return (player as CraftPlayer).handle as? FakePlayer + } + + fun toFakePlayer(entity: org.bukkit.entity.Entity): FakePlayer? { + return (entity as CraftEntity).handle as? FakePlayer + } + + fun spawnFakePlayer(loc: Location, name: String) { + val player = Bukkit.getPlayer(name) + if (player != null && player.isOnline) return + FakePlayer.atLocation(loc, name) + } + + fun killFakePlayer(name: String) { + getPlayerByName(name)?.kill() + } + + fun killAllFakePlayers() { + for (player in Bukkit.getOnlinePlayers()) { + if (!player.isOnline) continue + val fakePlayer = (player as CraftPlayer).handle as? FakePlayer ?: continue + fakePlayer.kill() + } + } + + private fun tickAll() { + for (player in Bukkit.getOnlinePlayers()) { + if (!player.isOnline) continue + val fakePlayer = (player as CraftPlayer).handle as? FakePlayer ?: continue + fakePlayer.doTick() + } + } + +// fun attackInterval(name: String, interval: Int) { // -//import nl.kallestruik.dtweaks.fakeplayer.FakePlayer -//import org.bukkit.Bukkit -//import org.bukkit.Location -//import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer // -//class FakePlayerManager { -// -// fun spawnFakePlayer(loc: Location, name: String) { -// val player = Bukkit.getPlayer(name) -// if (player != null && player.isOnline) return -// FakePlayer.atLocation(loc, name) +// fakePlayer.swing() // } -// -// fun killFakePlayer(name: String) { -// val player = Bukkit.getPlayer(name) -// if (player == null || !player.isOnline) return -// val entityPlayer = (player as CraftPlayer).handle as? FakePlayer ?: return -// entityPlayer.killEntity() -// } -// -// fun killAllFakePlayers() { -// for (player in Bukkit.getOnlinePlayers()) { -// if (!player.isOnline) continue -// val entityPlayer = (player as CraftPlayer).handle as? FakePlayer ?: continue -// entityPlayer.killEntity() -// } -// } -//} \ No newline at end of file + + fun attack(name: String) { + val player = getPlayerByName(name) ?: return + val target = getTarget(player) ?: return + + when (target.type) { + HitResult.Type.BLOCK -> {/*TODO: Not implemented*/} + HitResult.Type.ENTITY -> { + if (target !is EntityHitResult) return + + player.attack(target.entity); + player.swing(InteractionHand.MAIN_HAND); + player.resetLastActionTime(); + } + else -> return + } + } + + fun use(name: String) { + val player = getPlayerByName(name) ?: return + val target = getTarget(player) ?: return + + for (hand in InteractionHand.values()) { + when (target.type) { + HitResult.Type.BLOCK -> { + if (target !is BlockHitResult) return + + player.resetLastActionTime() + val world = player.getLevel() + val pos = target.blockPos + val side = target.direction + if (pos.y < player.getLevel().maxBuildHeight - (if (side === Direction.UP) 1 else 0) + && world.mayInteract(player, pos)) { + val result = player.gameMode.useItemOn(player, world, player.getItemInHand(hand), hand, target) + if (result.consumesAction()) { + if (result.shouldSwing()) player.swing(hand) + return + } + } + } + HitResult.Type.ENTITY -> { + if (target !is EntityHitResult) return + + player.resetLastActionTime() + val entity = target.entity + val relativeHitPos: Vec3 = target.getLocation().subtract(entity.x, entity.y, entity.z) + + entity.interactAt(player, relativeHitPos, hand).consumesAction() + + // fix for SS itemframe always returns CONSUME even if no action is performed + player.interactOn(entity, hand).consumesAction() + } + } + + player.gameMode.useItem(player, player.getLevel(), player.getItemInHand(hand), hand) + } + } + + // + // Ray tracing code borrowed from carpet mod: https://github.com/gnembon/fabric-carpet/blob/master/src/main/java/carpet/helpers/Tracer.java + // + + fun getTarget(player: FakePlayer): HitResult? { + val reach: Double = if (player.gameMode.isCreative) 5.0 else 4.5f.toDouble() + return rayTrace(player, 1F, reach, false) + } + + fun rayTrace(source: FakePlayer, partialTicks: Float, reach: Double, fluids: Boolean): HitResult? { + val blockHit = rayTraceBlocks(source, partialTicks, reach, fluids) + var maxSqDist = reach * reach + if (blockHit != null) { + maxSqDist = blockHit.location.distanceToSqr(source.getEyePosition(partialTicks)) + } + val entityHit = rayTraceEntities(source, partialTicks, reach, maxSqDist) + return entityHit ?: blockHit + } + + fun rayTraceBlocks(source: FakePlayer, partialTicks: Float, reach: Double, fluids: Boolean): BlockHitResult? { + val pos: Vec3 = source.getEyePosition(partialTicks) + val rotation: Vec3 = source.getViewVector(partialTicks) + val reachEnd = pos.add(rotation.x * reach, rotation.y * reach, rotation.z * reach) + return source.level.clip( + ClipContext( + pos, + reachEnd, + ClipContext.Block.OUTLINE, + if (fluids) ClipContext.Fluid.ANY else ClipContext.Fluid.NONE, + source + ) + ) + } + + fun rayTraceEntities(source: FakePlayer, partialTicks: Float, reach: Double, maxSqDist: Double): EntityHitResult? { + val pos = source.getEyePosition(partialTicks) + val reachVec = source.getViewVector(partialTicks).scale(reach) + val box = source.boundingBox.expandTowards(reachVec).inflate(1.0) + return rayTraceEntities(source, pos, pos.add(reachVec), box, maxSqDist) { + e -> !e.isSpectator && e.isPickable + } + } + + fun rayTraceEntities( + source: FakePlayer, + start: Vec3, + end: Vec3, + box: AABB, + maxSqDistance: Double, + predicate: Predicate, + ): EntityHitResult? { + val world = source.level + var targetDistance = maxSqDistance + var target: Entity? = null + var targetHitPos: Vec3? = null + for (current in world.getEntities(source, box, predicate)) { + val currentBox = current.boundingBox.inflate(current.pickRadius.toDouble()) + val currentHit = currentBox.clip(start, end) + if (currentBox.contains(start)) { + if (targetDistance >= 0) { + target = current + targetHitPos = currentHit.orElse(start) + targetDistance = 0.0 + } + } else if (currentHit.isPresent) { + val currentHitPos: Vec3 = currentHit.get() + val currentDistance = start.distanceToSqr(currentHitPos) + if (currentDistance < targetDistance || targetDistance == 0.0) { + if (current.rootVehicle === source.rootVehicle) { + if (targetDistance == 0.0) { + target = current + targetHitPos = currentHitPos + } + } else { + target = current + targetHitPos = currentHitPos + targetDistance = currentDistance + } + } + } + } + return if (target == null) null else EntityHitResult(target, targetHitPos!!) + } + + inner class FakePlayerTickTask: BukkitRunnable() { + override fun run() { + tickAll() + } + + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/kallestruik/dtweaks/managers/NoSpawnZoneManager.kt b/src/main/kotlin/nl/kallestruik/dtweaks/managers/NoSpawnZoneManager.kt new file mode 100644 index 0000000..568d70f --- /dev/null +++ b/src/main/kotlin/nl/kallestruik/dtweaks/managers/NoSpawnZoneManager.kt @@ -0,0 +1,70 @@ +package nl.kallestruik.dtweaks.managers + +import org.bukkit.NamespacedKey +import org.bukkit.configuration.InvalidConfigurationException +import org.bukkit.configuration.file.YamlConfiguration +import java.io.File +import java.io.IOException + +class NoSpawnZoneManager { + lateinit var file: File + var noSpawnZones: MutableMap>> = mutableMapOf() + + @Throws(IOException::class, InvalidConfigurationException::class) + fun loadFromFile(file: File) { + this.file = file + file.parentFile.mkdirs() + file.createNewFile() + val config = YamlConfiguration() + config.load(file) + noSpawnZones.clear() + + for (world in config.getKeys(false)) { + val worldKey = NamespacedKey.fromString(world)!! + noSpawnZones[worldKey] = mutableSetOf() + for (item in config.getStringList(world)) { + val parts = item.split(":") + if (parts.size != 2) + continue + + noSpawnZones[worldKey]?.add(Pair(parts[0].toInt(), parts[1].toInt())) + } + } + } + + @Throws(IOException::class, InvalidConfigurationException::class) + fun saveToFile() { + file.parentFile.mkdirs() + file.createNewFile() + val config = YamlConfiguration() + config.load(file) + + for (entry in noSpawnZones) { + val world = entry.key.toString() + val list = mutableListOf() + for (chunk in entry.value) { + list.add("${chunk.first}:${chunk.second}") + } + + config[world] = list + } + + config.save(file) + } + + fun isChunkClaimed(x: Int, z: Int, world: NamespacedKey): Boolean { + return noSpawnZones[world]?.contains(Pair(x, z)) ?: false + } + + fun claimChunk(x: Int, z: Int, world: NamespacedKey) { + val chunks = noSpawnZones.getOrDefault(world, mutableSetOf()) + chunks.add(Pair(x, z)) + noSpawnZones[world] = chunks + } + + fun unclaimChunk(x: Int, z: Int, world: NamespacedKey) { + val chunks = noSpawnZones.getOrDefault(world, mutableSetOf()) + chunks.remove(Pair(x, z)) + noSpawnZones[world] = chunks + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/craftingtweaks/CraftableChainmail.kt b/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/craftingtweaks/CraftableChainmail.kt new file mode 100644 index 0000000..4102e89 --- /dev/null +++ b/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/craftingtweaks/CraftableChainmail.kt @@ -0,0 +1,51 @@ +package nl.kallestruik.dtweaks.tweaks.craftingtweaks + +import nl.kallestruik.dtweaks.tweaks.ITweak +import org.bukkit.Bukkit +import org.bukkit.Material +import org.bukkit.NamespacedKey +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.ShapedRecipe +import org.bukkit.plugin.java.JavaPlugin + +class CraftableChainmail( + private val plugin: JavaPlugin +): ITweak { + private var helmetRecipeKey = NamespacedKey(plugin, "chainmail_helmet") + private var chestplateRecipeKey = NamespacedKey(plugin, "chainmail_chestplate") + private var leggingsRecipeKey = NamespacedKey(plugin, "chainmail_leggings") + private var bootsRecipeKey = NamespacedKey(plugin, "chainmail_boots") + + override fun getIdentifier(): String = "CraftableChainmail" + override fun getCategories(): List = listOf("crafting", "survival") + + override fun onEnable() { + val helmetRecipe = ShapedRecipe(helmetRecipeKey, ItemStack(Material.CHAINMAIL_HELMET)) + helmetRecipe.shape("CCC", "C C") + helmetRecipe.setIngredient('C', Material.CHAIN) + + val chestplateRecipe = ShapedRecipe(chestplateRecipeKey, ItemStack(Material.CHAINMAIL_CHESTPLATE)) + chestplateRecipe.shape("C C", "CCC", "CCC") + chestplateRecipe.setIngredient('C', Material.CHAIN) + + val leggingsRecipe = ShapedRecipe(leggingsRecipeKey, ItemStack(Material.CHAINMAIL_LEGGINGS)) + leggingsRecipe.shape("CCC", "C C", "C C") + leggingsRecipe.setIngredient('C', Material.CHAIN) + + val bootsRecipe = ShapedRecipe(bootsRecipeKey, ItemStack(Material.CHAINMAIL_BOOTS)) + bootsRecipe.shape("C C", "C C") + bootsRecipe.setIngredient('C', Material.CHAIN) + + plugin.server.addRecipe(helmetRecipe) + plugin.server.addRecipe(chestplateRecipe) + plugin.server.addRecipe(leggingsRecipe) + plugin.server.addRecipe(bootsRecipe) + } + + override fun onDisable() { + plugin.server.removeRecipe(helmetRecipeKey) + plugin.server.removeRecipe(chestplateRecipeKey) + plugin.server.removeRecipe(leggingsRecipeKey) + plugin.server.removeRecipe(bootsRecipeKey) + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/craftingtweaks/MoreSandstone.kt b/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/craftingtweaks/MoreSandstone.kt new file mode 100644 index 0000000..968d778 --- /dev/null +++ b/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/craftingtweaks/MoreSandstone.kt @@ -0,0 +1,41 @@ +package nl.kallestruik.dtweaks.tweaks.craftingtweaks + +import nl.kallestruik.dtweaks.tweaks.ITweak +import org.bukkit.Bukkit +import org.bukkit.Material +import org.bukkit.NamespacedKey +import org.bukkit.Tag +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.RecipeChoice +import org.bukkit.inventory.ShapedRecipe +import org.bukkit.inventory.ShapelessRecipe +import org.bukkit.plugin.java.JavaPlugin + +class MoreSandstone( + private val plugin: JavaPlugin +): ITweak { + private var normalRecipeKey = NamespacedKey(plugin, "sandstone") + private var redRecipeKey = NamespacedKey(plugin, "red_sandstone") + + override fun getIdentifier(): String = "MoreSandstone" + override fun getCategories(): List = listOf("crafting", "survival") + + override fun onEnable() { + val normalRecipe = ShapedRecipe(normalRecipeKey, ItemStack(Material.SANDSTONE, 4)) + normalRecipe.shape("ss", "ss") + normalRecipe.setIngredient('s', Material.SAND) + + val redRecipe = ShapedRecipe(redRecipeKey, ItemStack(Material.RED_SANDSTONE, 4)) + redRecipe.shape("ss", "ss") + redRecipe.setIngredient('s', Material.RED_SAND) + + plugin.server.addRecipe(normalRecipe) + plugin.server.addRecipe(redRecipe) + + } + + override fun onDisable() { + plugin.server.removeRecipe(normalRecipeKey) + plugin.server.removeRecipe(redRecipeKey) + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/craftingtweaks/RedyeTerracotta.kt b/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/craftingtweaks/RedyeTerracotta.kt index 3509455..b2ffff4 100644 --- a/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/craftingtweaks/RedyeTerracotta.kt +++ b/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/craftingtweaks/RedyeTerracotta.kt @@ -77,7 +77,7 @@ class RedyeTerracotta( } private fun addRecipe(key: NamespacedKey, output: Material, dye: Material) { - val recipe = ShapedRecipe(key, ItemStack(output)) + val recipe = ShapedRecipe(key, ItemStack(output, 8)) recipe.shape("TTT", "TDT", "TTT") recipe.setIngredient('T', allTerracotta) recipe.setIngredient('D', dye) diff --git a/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/miscellaneoustweaks/BetterBundles.kt b/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/miscellaneoustweaks/BetterBundles.kt new file mode 100644 index 0000000..d63e544 --- /dev/null +++ b/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/miscellaneoustweaks/BetterBundles.kt @@ -0,0 +1,345 @@ +package nl.kallestruik.dtweaks.tweaks.miscellaneoustweaks + +import com.google.common.base.Preconditions +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.NamedTextColor +import net.kyori.adventure.text.format.TextDecoration +import nl.kallestruik.dtweaks.BundleColor +import nl.kallestruik.dtweaks.BundleVariant +import nl.kallestruik.dtweaks.DTweaks +import nl.kallestruik.dtweaks.tweaks.ITweak +import org.bukkit.Bukkit +import org.bukkit.Keyed +import org.bukkit.Material +import org.bukkit.NamespacedKey +import org.bukkit.event.EventHandler +import org.bukkit.event.HandlerList +import org.bukkit.event.Listener +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.event.inventory.PrepareItemCraftEvent +import org.bukkit.inventory.* +import org.bukkit.inventory.meta.BundleMeta +import org.bukkit.persistence.PersistentDataType + + +class BetterBundles( + private val plugin: DTweaks +): ITweak, Listener { + override fun getIdentifier(): String = "BetterBundles" + override fun getCategories(): List = listOf("miscellaneous", "survival") + + private val recipeKeys = mutableListOf() + + override fun onEnable() { + plugin.server.pluginManager.registerEvents(this, plugin) + + for (color in BundleColor.values()) { + val result = ItemStack(Material.BUNDLE) + val resultMeta = result.itemMeta + resultMeta.setCustomModelData(BundleVariant.NORMAL.modelDataStart + color.modelData) + resultMeta.persistentDataContainer.set(COLOR_KEY, PersistentDataType.STRING, color.toString()) + result.itemMeta = resultMeta + + val key = NamespacedKey(plugin, "${color.name.lowercase()}_color_bundle") + recipeKeys.add(key) + + addDyeRecipe(key, color.dye, result) + } + + for (variant in BundleVariant.values()) { + if (variant.material == null) + continue + + val result = ItemStack(Material.BUNDLE) + val resultMeta = result.itemMeta + resultMeta.setCustomModelData(variant.modelDataStart + BundleColor.BROWN.modelData) + resultMeta.persistentDataContainer.set(VARIANT_KEY, PersistentDataType.STRING, variant.toString()) + + result.itemMeta = resultMeta + + val key = NamespacedKey(plugin, "${variant.name.lowercase()}_variant_bundle") + recipeKeys.add(key) + + addUpgradeRecipe(key, variant.material, result) + } + } + + override fun onDisable() { + HandlerList.unregisterAll(this) + for (key in recipeKeys) { + plugin.server.removeRecipe(key) + } + + recipeKeys.clear() + } + + val VARIANT_KEY = NamespacedKey(plugin, "variant") + val COLOR_KEY = NamespacedKey(plugin, "color") + + private fun addDyeRecipe(key: NamespacedKey, dye: Material, result: ItemStack) { + val recipe = ShapelessRecipe(key, result) + recipe.addIngredient(dye) + recipe.addIngredient(Material.BUNDLE) + + plugin.server.addRecipe(recipe) + } + + private fun addUpgradeRecipe(key: NamespacedKey, material: Material, result: ItemStack) { + val recipe = ShapedRecipe(key, result) + recipe.shape("MMM", "MBM", "MMM") + recipe.setIngredient('M', material) + recipe.setIngredient('B', Material.BUNDLE) + + plugin.server.addRecipe(recipe) + } + + private fun isBundleRecipe(recipe: Recipe): Boolean { + return isDyeRecipe(recipe) || isVariantRecipe(recipe) + } + + private fun isDyeRecipe(recipe: Recipe): Boolean { + if (recipe !is Keyed) + return false + + val key = recipe.key + if (key.namespace != plugin.getNamespace()) + return false + + if (!key.key.endsWith("_color_bundle")) + return false + + return true + } + + private fun isVariantRecipe(recipe: Recipe): Boolean { + if (recipe !is Keyed) + return false + + val key = recipe.key + if (key.namespace != plugin.getNamespace()) + return false + + if (!key.key.endsWith("_variant_bundle")) + return false + + return true + } + + @EventHandler + fun onCraft(event: PrepareItemCraftEvent) { + val recipe = event.recipe ?: return + + if (!isBundleRecipe(recipe)) + return + + + val inventory = event.inventory + Bukkit.getScheduler().runTask( + plugin, + Runnable { + inventory.result = + if (isDyeRecipe(recipe)) getDyeResult(recipe, inventory) + else if (isVariantRecipe(recipe)) getVariantResult(recipe, inventory) + else null + }) + } + + private fun getDyeResult(recipe: Recipe, inventory: CraftingInventory): ItemStack? { + val bundle = inventory.matrix?.filter { it?.type == Material.BUNDLE }?.get(0) ?: return null + val recipeResult = recipe.result + + val result = bundle.clone() + val resultMeta = result.itemMeta + resultMeta.persistentDataContainer.set( + COLOR_KEY, + PersistentDataType.STRING, + recipeResult.itemMeta.persistentDataContainer.get( + COLOR_KEY, + PersistentDataType.STRING + ) ?: BundleColor.BROWN.name + ) + + result.itemMeta = resultMeta + updateBundle(result) + + return result + } + + private fun getVariantResult(recipe: Recipe, inventory: CraftingInventory): ItemStack? { + val bundle = inventory.matrix?.filter { it?.type == Material.BUNDLE }?.get(0) ?: return null + val recipeResult = recipe.result + + val result = bundle.clone() + val resultMeta = result.itemMeta + resultMeta.persistentDataContainer.set( + VARIANT_KEY, + PersistentDataType.STRING, + recipeResult.itemMeta.persistentDataContainer.get( + VARIANT_KEY, + PersistentDataType.STRING + ) ?: BundleVariant.NORMAL.name + ) + + result.itemMeta = resultMeta + updateBundle(result) + + return result + } + + + @EventHandler + fun onInventoryClick(event: InventoryClickEvent) { + if (!event.isRightClick) + return + + val cursor = event.cursor ?: return + val clicked = event.currentItem ?: return + + val bundle: ItemStack + val item: ItemStack + var itemIsCursor = true + + if (cursor.type == Material.BUNDLE) { + bundle = cursor + item = clicked + itemIsCursor = false + } else if (clicked.type == Material.BUNDLE) { + bundle = clicked + item = cursor + } else + return + + if (item.type.isAir || item.amount == 0) { + val itemStack = removeItemFromBundle(bundle) ?: return + + if (itemIsCursor) + event.cursor = itemStack + else + event.currentItem = itemStack + } else + addItemToBundle(bundle, item) + + event.isCancelled = true + } + + private fun removeItemFromBundle(bundle: ItemStack): ItemStack? { + val meta = bundle.itemMeta as BundleMeta + if (!meta.hasItems()) + return null + + val itemsInBundle = mutableListOf() + itemsInBundle.addAll(meta.items) + val toReturn = itemsInBundle.removeFirst() + + meta.setItems(itemsInBundle) + bundle.itemMeta = meta + + updateBundle(bundle) + + return toReturn + } + + private fun addItemToBundle(bundle: ItemStack, item: ItemStack) { + var meta = bundle.itemMeta as BundleMeta + val persistentContainer = bundle.itemMeta.persistentDataContainer + val variant = BundleVariant.valueOf(persistentContainer.get(VARIANT_KEY, PersistentDataType.STRING) ?: "NORMAL") + + val currentWeight = computeWeight(meta.items) + val availableWeight = variant.maxWeight - currentWeight + + val weightPerItem = getWeightPerItem(item) + val totalStackWeight = weightPerItem * item.amount + if (totalStackWeight > availableWeight) { + val toStoreAmount = availableWeight / weightPerItem + if (toStoreAmount <= 0) + return + + meta = addItemToBundleProper(meta, item.asQuantity(toStoreAmount)) + item.amount -= toStoreAmount + } else { + meta = addItemToBundleProper(meta, item.clone()) + item.amount = 0 + } + + bundle.itemMeta = meta + + updateBundle(bundle) + } + + fun updateBundle(bundle: ItemStack) { + val meta = bundle.itemMeta as BundleMeta + val persistentContainer = meta.persistentDataContainer + + val variant = BundleVariant.valueOf(persistentContainer.get(VARIANT_KEY, PersistentDataType.STRING) ?: "NORMAL") + val color = BundleColor.valueOf(persistentContainer.get(COLOR_KEY, PersistentDataType.STRING) ?: "BROWN") + + val customModelData = variant.modelDataStart + color.modelData + + meta.setCustomModelData(customModelData) + meta.addItemFlags(ItemFlag.HIDE_POTION_EFFECTS) + meta.lore(listOf(Component + .text("${computeWeight(meta.items)}/${variant.maxWeight}") + .color(NamedTextColor.GRAY) + .decoration(TextDecoration.ITALIC, false))) + + bundle.itemMeta = meta + } + + private fun computeWeight(items: List): Int { + return items.sumOf { getWeight(it) } + } + + private fun getWeight(item: ItemStack): Int { + return item.amount * getWeightPerItem(item) + } + + private fun getWeightPerItem(item: ItemStack): Int { + // Special case for bundle nesting. + if (item.type == Material.BUNDLE) { + val meta = item.itemMeta as BundleMeta + return if (meta.hasItems()) + 64 + else + 4 + } + + // Calculate the right weight. + return (64 / item.maxStackSize) + } + + private fun addItemToBundleProper(itemMeta: BundleMeta, itemStack: ItemStack): BundleMeta { + Preconditions.checkArgument(!itemStack.type.isAir, "item is null or air") + + var leftOver = true + val items = mutableListOf() + items.addAll(itemMeta.items) + + for (item in itemMeta.items) { + if (item.isSimilar(itemStack)) { + if (item.amount >= item.maxStackSize) + continue + + items.remove(item) + val spaceLeft = item.maxStackSize - item.amount + if (spaceLeft >= itemStack.amount) { + item.amount += itemStack.amount + itemStack.amount = 0 + items.add(0, item) + leftOver = false + break + } else { + item.amount = item.maxStackSize + itemStack.amount -= spaceLeft + items.add(0, item) + } + } + } + + if (leftOver) + items.add(0, itemStack) + + itemMeta.setItems(items) + + return itemMeta + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/miscellaneoustweaks/DyeableNetherite.kt b/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/miscellaneoustweaks/DyeableNetherite.kt new file mode 100644 index 0000000..eb12583 --- /dev/null +++ b/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/miscellaneoustweaks/DyeableNetherite.kt @@ -0,0 +1,109 @@ +package nl.kallestruik.dtweaks.tweaks.miscellaneoustweaks + +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.NamedTextColor +import net.kyori.adventure.text.format.TextDecoration +import nl.kallestruik.dtweaks.DTweaks +import nl.kallestruik.dtweaks.tweaks.ITweak +import org.bukkit.Keyed +import org.bukkit.Material +import org.bukkit.NamespacedKey +import org.bukkit.attribute.Attribute +import org.bukkit.attribute.AttributeModifier +import org.bukkit.event.EventHandler +import org.bukkit.event.HandlerList +import org.bukkit.event.Listener +import org.bukkit.event.inventory.PrepareSmithingEvent +import org.bukkit.inventory.* +import org.bukkit.plugin.java.JavaPlugin +import java.util.* + +class DyeableNetherite( + private val plugin: DTweaks +): ITweak, Listener { + override fun getIdentifier(): String = "DyeableNetherite" + override fun getCategories(): List = listOf("miscellaneous", "survival") + + private val helmetRecipeKey = NamespacedKey(plugin, "dyed_netherite_helmet") + private val chestplateRecipeKey = NamespacedKey(plugin, "dyed_netherite_chestplate") + private val leggingsRecipeKey = NamespacedKey(plugin, "dyed_netherite_leggings") + private val bootsRecipeKey = NamespacedKey(plugin, "dyed_netherite_boots") + + private val helmetStats = ArmorStats(2.0, 3.0, 0.1, EquipmentSlot.HEAD) + private val chestplateStats = ArmorStats(5.0, 3.0, 0.1, EquipmentSlot.CHEST) + private val leggingsStats = ArmorStats(4.0, 3.0, 0.1, EquipmentSlot.LEGS) + private val bootsStats = ArmorStats(2.0, 3.0, 0.1, EquipmentSlot.FEET) + + override fun onEnable() { + plugin.server.pluginManager.registerEvents(this, plugin) + + addRecipe(helmetRecipeKey, Material.NETHERITE_HELMET, "Netherite Helmet", Material.LEATHER_HELMET) + addRecipe(chestplateRecipeKey, Material.NETHERITE_CHESTPLATE, "Netherite Chestplate", Material.LEATHER_CHESTPLATE) + addRecipe(leggingsRecipeKey, Material.NETHERITE_LEGGINGS, "Netherite Leggings", Material.LEATHER_LEGGINGS) + addRecipe(bootsRecipeKey, Material.NETHERITE_BOOTS, "Netherite Boots", Material.LEATHER_BOOTS) + } + + private fun addRecipe(key: NamespacedKey, sourceMaterial: Material, name: String, resultMaterial: Material) { + val resultStack = ItemStack(resultMaterial) + resultStack.editMeta { it.displayName(Component.text(name).color(NamedTextColor.WHITE).decoration(TextDecoration.ITALIC, false))} + val recipe = SmithingRecipe(key, resultStack, RecipeChoice.MaterialChoice(sourceMaterial), RecipeChoice.MaterialChoice(Material.LEATHER)) + + plugin.server.addRecipe(recipe) + } + + override fun onDisable() { + HandlerList.unregisterAll(this) + plugin.server.removeRecipe(helmetRecipeKey) + plugin.server.removeRecipe(chestplateRecipeKey) + plugin.server.removeRecipe(leggingsRecipeKey) + plugin.server.removeRecipe(bootsRecipeKey) + } + + @EventHandler + fun onSmith(event: PrepareSmithingEvent) { + val recipe = event.inventory.recipe ?: return + if (!isDyeableNetheriteRecipe(recipe)) + return + + val result = event.inventory.result ?: return + + when (result.type) { + Material.LEATHER_HELMET -> helmetStats.addToItem(result) + Material.LEATHER_CHESTPLATE -> chestplateStats.addToItem(result) + Material.LEATHER_LEGGINGS -> leggingsStats.addToItem(result) + Material.LEATHER_BOOTS -> bootsStats.addToItem(result) + else -> return + } + + event.inventory.result = result + } + + private fun isDyeableNetheriteRecipe(recipe: Recipe): Boolean { + if (recipe !is Keyed) + return false + + if (recipe.key.namespace != plugin.getNamespace()) + return false + + if (!recipe.key.key.startsWith("dyed_netherite_")) + return false + + return true + } + + data class ArmorStats( + val armor: Double, + val toughness: Double, + val knockbackResistance: Double, + val slot: EquipmentSlot, + ) { + fun addToItem(item: ItemStack) { + item.type = Material.DIAMOND + item.editMeta { + it.addAttributeModifier(Attribute.GENERIC_ARMOR, AttributeModifier(UUID.randomUUID(), "Armor", armor, AttributeModifier.Operation.ADD_NUMBER, slot)) + it.addAttributeModifier(Attribute.GENERIC_ARMOR_TOUGHNESS, AttributeModifier(UUID.randomUUID(), "Armor Toughness", toughness, AttributeModifier.Operation.ADD_NUMBER, slot)) + it.addAttributeModifier(Attribute.GENERIC_KNOCKBACK_RESISTANCE, AttributeModifier(UUID.randomUUID(), "Knockback Resistance", knockbackResistance, AttributeModifier.Operation.ADD_NUMBER, slot)) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/miscellaneoustweaks/FakePlayers.kt b/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/miscellaneoustweaks/FakePlayers.kt index 4c168bc..9023024 100644 --- a/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/miscellaneoustweaks/FakePlayers.kt +++ b/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/miscellaneoustweaks/FakePlayers.kt @@ -1,15 +1,48 @@ -//package nl.kallestruik.dtweaks.tweaks.miscellaneoustweaks -// -//import nl.kallestruik.dtweaks.managers.FakePlayerManager -//import nl.kallestruik.dtweaks.tweaks.ITweak -// -//class FakePlayers( -// private val fakePlayerManager: FakePlayerManager -//): ITweak { -// override fun getIdentifier(): String = "FakePlayers" -// override fun getCategories(): List = listOf("players") -// -// override fun onDisable() { -// fakePlayerManager.killAllFakePlayers() -// } -//} \ No newline at end of file +package nl.kallestruik.dtweaks.tweaks.miscellaneoustweaks + +import nl.kallestruik.dtweaks.DTweaks +import nl.kallestruik.dtweaks.fakeplayer.FakePlayer +import nl.kallestruik.dtweaks.managers.FakePlayerManager +import nl.kallestruik.dtweaks.tweaks.ITweak +import org.bukkit.Material +import org.bukkit.craftbukkit.v1_18_R1.entity.CraftPlayer +import org.bukkit.craftbukkit.v1_18_R1.inventory.CraftItemStack +import org.bukkit.entity.Player +import org.bukkit.event.EventHandler +import org.bukkit.event.HandlerList +import org.bukkit.event.Listener +import org.bukkit.event.player.PlayerInteractEntityEvent +import org.bukkit.inventory.EquipmentSlot + +class FakePlayers( + private val fakePlayerManager: FakePlayerManager, + private val plugin: DTweaks +): ITweak, Listener { + override fun getIdentifier(): String = "FakePlayers" + override fun getCategories(): List = listOf("players") + + override fun onEnable() { + plugin.server.pluginManager.registerEvents(this, plugin) + } + + override fun onDisable() { + HandlerList.unregisterAll(this) + fakePlayerManager.killAllFakePlayers() + } + + @EventHandler + fun onRightClickEntity(event: PlayerInteractEntityEvent) { + if (event.hand != EquipmentSlot.HAND) return + val fakePlayer = event.rightClicked as? Player ?: return + + if ((fakePlayer as CraftPlayer).handle !is FakePlayer) return + + val playerItem = event.player.inventory.itemInMainHand.clone() + val fakePlayerItem = fakePlayer.inventory.itemInMainHand.clone() + + event.player.inventory.setItemInMainHand(fakePlayerItem) + fakePlayer.inventory.setItemInMainHand(playerItem) + + fakePlayer.handle.tick() + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/miscellaneoustweaks/Hammers.kt b/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/miscellaneoustweaks/Hammers.kt new file mode 100644 index 0000000..46b1ef3 --- /dev/null +++ b/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/miscellaneoustweaks/Hammers.kt @@ -0,0 +1,134 @@ +package nl.kallestruik.dtweaks.tweaks.miscellaneoustweaks + +import nl.kallestruik.dtweaks.DTweaks +import nl.kallestruik.dtweaks.tweaks.ITweak +import org.bukkit.Material +import org.bukkit.NamespacedKey +import org.bukkit.block.Block +import org.bukkit.block.BlockFace +import org.bukkit.event.EventHandler +import org.bukkit.event.HandlerList +import org.bukkit.event.Listener +import org.bukkit.event.block.BlockBreakEvent +import org.bukkit.inventory.* +import org.bukkit.inventory.meta.Damageable +import org.bukkit.persistence.PersistentDataType + + +class Hammers( + private val plugin: DTweaks +): ITweak, Listener { + override fun getIdentifier(): String = "Hammers" + override fun getCategories(): List = listOf("miscellaneous", "survival") + + val HAMMER_KEY = NamespacedKey(plugin, "hammer") + val diamondHammerRecipeKey = NamespacedKey(plugin, "diamond_hammer") + + override fun onEnable() { + plugin.server.pluginManager.registerEvents(this, plugin) + + val diamondHammer = ItemStack(Material.DIAMOND_PICKAXE) + val diamondHammerMeta = diamondHammer.itemMeta + diamondHammerMeta.persistentDataContainer.set(HAMMER_KEY, PersistentDataType.SHORT, 1) + diamondHammer.itemMeta = diamondHammerMeta + updateHammer(diamondHammer) + + val recipe = ShapedRecipe(diamondHammerRecipeKey, diamondHammer) + recipe.shape( + " BD", + " SB", + "S ") + recipe.setIngredient('B', Material.DIAMOND_BLOCK) + recipe.setIngredient('D', Material.DIAMOND) + recipe.setIngredient('S', Material.STICK) + + plugin.server.addRecipe(recipe) + } + + override fun onDisable() { + HandlerList.unregisterAll(this) + plugin.server.removeRecipe(diamondHammerRecipeKey) + } + + fun updateHammer(hammer: ItemStack) { + if (!isHammer(hammer)) + return + val meta = hammer.itemMeta + + meta.setCustomModelData(1) + + hammer.itemMeta = meta + } + + private fun isHammer(itemStack: ItemStack): Boolean { + val meta = itemStack.itemMeta ?: return false + + val persistentContainer = meta.persistentDataContainer + + return persistentContainer.get(HAMMER_KEY, PersistentDataType.SHORT) == 1.toShort() + } + + @EventHandler + fun onBlockBreak(event: BlockBreakEvent) { + val hammer = event.player.inventory.itemInMainHand + if (!isHammer(hammer)) + return + + val player = event.player + + val lastTwoTargetBlocks: List = player.getLastTwoTargetBlocks(null, 100) + if (lastTwoTargetBlocks.size != 2 || !lastTwoTargetBlocks[1].type.isOccluding) return + + val targetBlock: Block = lastTwoTargetBlocks[1] + val adjacentBlock: Block = lastTwoTargetBlocks[0] + + val targetFace = targetBlock.getFace(adjacentBlock) + + val toBreak: MutableList = mutableListOf() + + when (targetFace) { + BlockFace.NORTH, BlockFace.SOUTH -> { + toBreak.add(targetBlock.getRelative(1, 0, 0)) + toBreak.add(targetBlock.getRelative(-1, 0, 0)) + toBreak.add(targetBlock.getRelative(0, 1, 0)) + toBreak.add(targetBlock.getRelative(0, -1, 0)) + toBreak.add(targetBlock.getRelative(1, 1, 0)) + toBreak.add(targetBlock.getRelative(-1, 1, 0)) + toBreak.add(targetBlock.getRelative(1, -1, 0)) + toBreak.add(targetBlock.getRelative(-1, -1, 0)) + } + + BlockFace.EAST, BlockFace.WEST -> { + toBreak.add(targetBlock.getRelative(0, 1, 0)) + toBreak.add(targetBlock.getRelative(0, -1, 0)) + toBreak.add(targetBlock.getRelative(0, 0, 1)) + toBreak.add(targetBlock.getRelative(0, 0, -1)) + toBreak.add(targetBlock.getRelative(0, 1, 1)) + toBreak.add(targetBlock.getRelative(0, -1, 1)) + toBreak.add(targetBlock.getRelative(0, 1, -1)) + toBreak.add(targetBlock.getRelative(0, -1, -1)) + } + + BlockFace.UP, BlockFace.DOWN -> { + toBreak.add(targetBlock.getRelative(1, 0, 0)) + toBreak.add(targetBlock.getRelative(-1, 0, 0)) + toBreak.add(targetBlock.getRelative(0, 0, 1)) + toBreak.add(targetBlock.getRelative(0, 0, -1)) + toBreak.add(targetBlock.getRelative(1, 0, 1)) + toBreak.add(targetBlock.getRelative(-1, 0, 1)) + toBreak.add(targetBlock.getRelative(1, 0, -1)) + toBreak.add(targetBlock.getRelative(-1, 0, -1)) + } + else -> return + } + + val maxHardness = targetBlock.type.hardness + + for (block in toBreak) { + if (block.type.hardness > maxHardness) + continue + + block.breakNaturally(hammer) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/miscellaneoustweaks/NoMelting.kt b/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/miscellaneoustweaks/NoMelting.kt new file mode 100644 index 0000000..b668bed --- /dev/null +++ b/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/miscellaneoustweaks/NoMelting.kt @@ -0,0 +1,36 @@ +package nl.kallestruik.dtweaks.tweaks.miscellaneoustweaks + +import nl.kallestruik.dtweaks.tweaks.ITweak +import org.bukkit.Material +import org.bukkit.entity.ArmorStand +import org.bukkit.event.EventHandler +import org.bukkit.event.HandlerList +import org.bukkit.event.Listener +import org.bukkit.event.block.BlockFadeEvent +import org.bukkit.event.block.BlockFromToEvent +import org.bukkit.event.player.PlayerInteractAtEntityEvent +import org.bukkit.inventory.EquipmentSlot +import org.bukkit.plugin.java.JavaPlugin + +class NoMelting( + private val plugin: JavaPlugin +): ITweak, Listener { + override fun getIdentifier(): String = "NoMelting" + override fun getCategories(): List = listOf("miscellaneous", "survival") + + override fun onEnable() { + plugin.server.pluginManager.registerEvents(this, plugin) + } + + override fun onDisable() { + HandlerList.unregisterAll(this) + } + + @EventHandler + fun onClickEntity(event: BlockFadeEvent) { + if (event.block.type != Material.SNOW && event.block.type != Material.ICE) + return + + event.isCancelled = true + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/mobtweaks/NoSpawnZones.kt b/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/mobtweaks/NoSpawnZones.kt new file mode 100644 index 0000000..e4a6ee6 --- /dev/null +++ b/src/main/kotlin/nl/kallestruik/dtweaks/tweaks/mobtweaks/NoSpawnZones.kt @@ -0,0 +1,44 @@ +package nl.kallestruik.dtweaks.tweaks.mobtweaks + +import net.minecraft.core.GlobalPos +import net.minecraft.world.entity.ai.memory.MemoryModuleType +import net.minecraft.world.entity.schedule.Activity +import nl.kallestruik.dtweaks.managers.NoSpawnZoneManager +import nl.kallestruik.dtweaks.tweaks.ITweak +import org.bukkit.craftbukkit.v1_18_R1.entity.CraftVillager +import org.bukkit.entity.Villager +import org.bukkit.event.EventHandler +import org.bukkit.event.HandlerList +import org.bukkit.event.Listener +import org.bukkit.event.entity.CreatureSpawnEvent +import org.bukkit.event.entity.EntitySpawnEvent +import org.bukkit.event.player.PlayerInteractEntityEvent +import org.bukkit.plugin.java.JavaPlugin +import java.util.concurrent.atomic.AtomicReference + +class NoSpawnZones( + private val plugin: JavaPlugin, + private val noSpawnZoneManager: NoSpawnZoneManager +): ITweak, Listener { + override fun getIdentifier(): String = "NoSpawnZones" + override fun getCategories(): List = listOf("mobs") + + override fun onEnable() { + plugin.server.pluginManager.registerEvents(this, plugin) + } + + override fun onDisable() { + HandlerList.unregisterAll(this) + } + + @EventHandler + fun onMobSpawn(event: CreatureSpawnEvent) { + if (event.spawnReason != CreatureSpawnEvent.SpawnReason.NATURAL) + return + + if (!noSpawnZoneManager.isChunkClaimed(event.location.chunk.x, event.location.chunk.z, event.location.world.key)) + return + + event.isCancelled = true + } +} \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 3070c90..64129f7 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -11,7 +11,12 @@ commands: player: description: "Interact with fake players." permission: op + bundle: + description: "Admin command for managing custom bundles." + permission: op mobcaps: description: "Display mobcap information from your current dimension." pocketdim: description: "Interact with pocket dimensions. Testing command." + spawnzones: + description: "Manage no spawn zones" diff --git a/src/main/resources/tweak_states.yml b/src/main/resources/tweak_states.yml index 7010d68..6090e45 100644 --- a/src/main/resources/tweak_states.yml +++ b/src/main/resources/tweak_states.yml @@ -17,8 +17,15 @@ HoesHarvestArea: true FakePlayers: true CarpetBlockPlacingProtocol: true NoDoorBreaking: true -NoCreeperGrief: true +NoCreeperGrief: false NoEndermanGrief: true SugarcaneBonemealing: true SpaceTimePockets: true -VillagerInfo: true \ No newline at end of file +VillagerInfo: true +BetterBundles: true +RedyeTerracotta: true +DyeableNetherite: true +CraftableChainmail: true +NoMelting: true +Hammers: true +NoSpawnZones: true diff --git a/textures/make_bundles.py b/textures/make_bundles.py new file mode 100644 index 0000000..13675aa --- /dev/null +++ b/textures/make_bundles.py @@ -0,0 +1,106 @@ +from PIL import Image +import os +import json + +variants = [ + {"name": "normal", "data": 0}, + {"name": "iron", "data": 100}, + {"name": "gold", "data": 200}, + {"name": "diamond", "data": 300}, + {"name": "netherite", "data": 400} +] + +colors = [ + {"name": "black", "data": 0}, + {"name": "blue", "data": 1}, + {"name": "brown", "data": 2}, + {"name": "cyan", "data": 3}, + {"name": "gray", "data": 4}, + {"name": "green", "data": 5}, + {"name": "light_blue", "data": 6}, + {"name": "light_gray", "data": 7}, + {"name": "lime", "data": 8}, + {"name": "magenta", "data": 9}, + {"name": "orange", "data": 10}, + {"name": "pink", "data": 11}, + {"name": "purple", "data": 12}, + {"name": "red", "data": 13}, + {"name": "white", "data": 14}, + {"name": "yellow", "data": 15} +] + +COLOR_SRC_DIR = "src/bundles/colors" +VARIANT_SRC_DIR = "src/bundles/variants" + +MODEL_OUTPUT_DIR = "out/bundles/models" +TEXTURE_OUTPUT_DIR = "out/bundles/textures" + +bundle_model = { + "parent": "item/generated", + "textures": { + "layer0": "item/bundle" + }, + "overrides": [ + { + "predicate": { + "filled": 0.0000001 + }, + "model": "item/bundle_filled" + }, + ] +} + +for variant in variants: + variant_name = variant["name"] + variant_data = variant["data"] + + for color in colors: + color_name = color["name"] + color_data = color["data"] + + color_image = Image.open(f"{COLOR_SRC_DIR}/{color_name}_bundle.png") + color_image_filled = Image.open(f"{COLOR_SRC_DIR}/{color_name}_bundle_filled.png") + + variant_image = Image.open(f"{VARIANT_SRC_DIR}/cord_{variant_name}.png") + variant_image_filled = Image.open(f"{VARIANT_SRC_DIR}/cord_{variant_name}_filled.png") + + color_image.paste(variant_image, None, variant_image) + color_image_filled.paste(variant_image_filled, None, variant_image_filled) + + color_image.save(f"{TEXTURE_OUTPUT_DIR}/{color_name}_{variant_name}_bundle.png") + color_image_filled.save(f"{TEXTURE_OUTPUT_DIR}/{color_name}_{variant_name}_bundle_filled.png") + + bundle_model["overrides"].append({ + "predicate": { + "custom_model_data": (variant_data + color_data) + }, + "model": f"item/{color_name}_{variant_name}_bundle" + }) + + bundle_model["overrides"].append({ + "predicate": { + "filled": 0.0000001, + "custom_model_data": variant_data + color_data + }, + "model": f"item/{color_name}_{variant_name}_bundle_filled" + }) + + with open(f"{MODEL_OUTPUT_DIR}/{color_name}_{variant_name}_bundle.json", "w") as output_file: + json.dump({ + "parent": "item/bundle", + "textures": { + "layer0": f"item/{color_name}_{variant_name}_bundle" + } + }, output_file, indent=4) + + with open(f"{MODEL_OUTPUT_DIR}/{color_name}_{variant_name}_bundle_filled.json", "w") as output_file: + json.dump({ + "parent": "item/bundle_filled", + "textures": { + "layer0": f"item/{color_name}_{variant_name}_bundle_filled" + } + }, output_file, indent=4) + + +with open(f"{MODEL_OUTPUT_DIR}/bundle.json", "w") as output_file: + json.dump(bundle_model, output_file, indent=4) \ No newline at end of file diff --git a/textures/src/bundles/colors/black_bundle.png b/textures/src/bundles/colors/black_bundle.png new file mode 100644 index 0000000000000000000000000000000000000000..fe9ec097e7f6da57d341edb2e7a430d7fe74f2ec GIT binary patch literal 412 zcmV;N0b~A&P)3>PEH*<7iW^!`_*me*)>(k|dYfF=rQaYz47z~@VF0=h_BQG2DQAH1 z`;;UJm1SwUffLX3WS_MffbRVrWkHIfU^h}>7;-+yCYxrg*8mA`e6ml2^Ly4rQN&Mx zP$Gug(akgOIZCM-wotH-x;Wb9=ketu&E94_GYa9J9&Xe4;Z<_@0w7@t5c5k7ds`YL z;Qr{&0%C+Zj$*6CY!Q`#Wm!z2 zu;E<1%k0l=liMsaJLkLS{?zE7QFH0r*X;!VPRA2JF~*d>Exm-e)ojsjzoYHXmH|pB z0pXkKDgnOl)8=cFLbTR&_&vy(wUs5n1t4J*G*tZusCK zP)a5Bi#m@DkbEG!jMKRXfIui9frv1_M9xhVknQ5!xPr#H%7q#n7k?Aem=V_AZVvze N002ovPDHLkV1je?r5OMK literal 0 HcmV?d00001 diff --git a/textures/src/bundles/colors/blue_bundle.png b/textures/src/bundles/colors/blue_bundle.png new file mode 100644 index 0000000000000000000000000000000000000000..6437b807274e397f5563af9fc24dc167c1f28208 GIT binary patch literal 428 zcmV;d0aN~oP)L?Xd+@US+Sh5!b%iVvXqp+!q(ie^;b;E&PJh>6;@NTbFt7YhHCd`jJ^cW9{qshk$?eX+4V(iIcjo8l`M%B! z@BPdg{0n9QB91RN=~KtfWUYQcUtSNdwIolZ?~bj`0VM4s)ZLq>-ylL%ZG#Sk0q9{I?Uw4V>tXab6b}7J%;M!l484b;NF@g5hP(2icg9Ycel2WdRc2 z{^}#mZsb@K3>Enc5K6>w8y+OQru@DM16wHAM{TWC`MXpZqu2T#&x}I2;fzP+qXAyS zKLe1k1c>>iwihcFB;aEB-~g5CDj1WSwjyzn2A!@~={$-kEk;x1zyP^t z`7Eqm@EHr>AW{$+MJyo?gqnVp*mG_{AR>Yz>0)PVyh3@UxI)&Y4`e~vJ?`8P%HB6! Wvcy|Lz?Xmk00002-3{2=*95y{x*jH-=BaDU}NV3)4zUxWBC8! z99$n>1Aek=G5omE12^Qqh${or*Xv+D%n-a@0J#EW2>Z`#VB0}10s(>sG=`b{H*?l! z@NRg`@Q+&)Y%4502zx=L92|I!Mtd1RhG=MMG2FOzjp5YZi(o_QSDis|HL4d>%KrAVG0+cS0!vSspEbd{>M^0ccgWkWokKqjz13)2;90o8$kPX0@4%o$Q{{LnY0!JIj z5Oe^t9TuH9(*ej3kVcq6FhgJ((GwABAR<`_GYHugpnwA#fRqn#8i3(`JOTi0n7k2c S6~IXV0000abLYdinuaTP(!_-Pmva+rYR0@swke{~R)-{T^w9 zf59|B#Jk7m6ozlcWVu`~4cj*FJp=*mT)b+X0m%1Q={l?xgq;QH2q0aQx`70*?xTBBX0K$X=RH5&~|i_sK0FhJfJIdG?p3ygyKkBAR>Yz>GB;&@(bmc;tBZ`(igIz>>1V~l(R4H%)Rm8 S9w7Jt0000o*u)euiwFZm?3z*0CEM$kO%L+gKY=72m}Zk&=_X&-^^K` zVe^W^41C<2U|V6~LC^~oKKkIm+?qSYHPGB&D9=`dE;SCf6Kp~DC1~5aA4ZxWW{Iv!CON)ttqYY#T zIsn-Yi%y*B0AvVABg`O}Aux^Ti3lYSk!^$-ge(RMI1oV22RIGDa6cXa0Ks~{5zf~< Qt^fc407*qoM6N<$f+gjdRR910 literal 0 HcmV?d00001 diff --git a/textures/src/bundles/colors/cyan_bundle.png b/textures/src/bundles/colors/cyan_bundle.png new file mode 100644 index 0000000000000000000000000000000000000000..d32643936260458e3cb8105ea2bf7cb79c7adef4 GIT binary patch literal 435 zcmV;k0ZjghP)7Z`8zDj!KdE-6qxrm|fa&kVN-+A8W97PS5YEVx<0Fi|mU!b*vlYcKT4nW+LoS?Sc z6*Hb(R~!5brU4?h4o^u{zJ(z8Jr33Ec7{c~XYx{KT4w+{9EV;KL-ZR&th^1z3p&lMAEoKNP&C}YNpn2L>*7KKaQmuXFd(hBWp*R=F+a`uAe)w(B0c8v zN&_Uko!MSmIlg1f{alhqfKVca+s(`auPJ+K(uXY+?4$Pjt2{21qEt_xbI*LZ;?mk! z7j0B+Uc;OLNLT_y|5E$)yaow)are>ys?Nl)Z;g*?k+=wgu174o$Yv=h#-+%C0rJks z0Tp(ER?t84S>Dd@84bWB2$5FA67oQ(!C#3z;}rxVA~=#;bZ=k3P<|<%kb?~#2!q0V dn2S*MJ^`$%zS!lNQMUj9002ovPDHLkV1h|FyP*I8 literal 0 HcmV?d00001 diff --git a/textures/src/bundles/colors/cyan_bundle_filled.png b/textures/src/bundles/colors/cyan_bundle_filled.png new file mode 100644 index 0000000000000000000000000000000000000000..dc4f0b8bc68d920c85177423ddee2c0e9c9518f4 GIT binary patch literal 371 zcmV-(0gV2MP)2-3`CX~gjF_&EmlN6)|p{1*}c(~R#vFnqp# z3$72Z0Y9Y08Q9irha1A8r^WE~!v`=QW(Zy{fLsAGgyZQ;up(< z_^-Rmz%3vEwiOm01ic^_9smx!g8xq#K!#{&X))Zmc8%fG-iu&E>Q|jXaWx7Dr1`t1 z64==wAh_o^!!jpfu;vMjG7P_;Ji{G`=$c{CyZzrSumP5;RS4S$q(pTLcA*MM-0!y3a{sJp&8#}S~7uaYKln8<$#fk(U zEURgvB%P6S@s63OSnO?vd7N*~Ju?OkS;mlh`T^*6yOltrDEjvT?Eu7c^BsD87%(Hx zb7k-^m;#9S>hgxlveY^=O_MCk;*9_nHE7(H8K5BD$jK|~iH;Qk2+%pQ{ZUy6X+WX)&%o%`$ zB|!8qwKDUnKmy)7yw-q*>$;WaeT&3J9kjjHq9l%~F2<$EfdTT)$N|-KfmYBz^4U4K z;4=zxrez3W$zPP3%=bO S106#E0000H8*YfAq9VhmPoKbim?3z*0CEM$kf+b?gKY=72m}Zk&=_X&-^^K` zp?cYU6vn?mAWG{WY_T{9;-=tMEe?GFpTI$I$lxOGK7o^qba4pi?CuM65HVXJI*H&Q zN(w3#4Fn?Jxtt_7)v0H>+&|}czVn^yV8U=F%*Pm@v5icj!<+H@YC#LYu)A&PgMo+-c3!EIF<^?-G{Odq+KD#EB zAciJW?MXkBGiPoC8D%jjpV8~=%X)ZwiTC?djFYLvz;U;Yv*%FGxEBB&Rs-3;#?_3U zgA}}X5m-QHY6;oGRxvhJQ3eeNq5)1Td*@h1mxQ-)|O zs)>Hk)bg+7oizo8Xc84^m7+QGf%KPhgffNlgDR+c$Gs-%-7kASps^~8n9KkG002ov JPDHLkV1gy(w?O~^ literal 0 HcmV?d00001 diff --git a/textures/src/bundles/colors/green_bundle_filled.png b/textures/src/bundles/colors/green_bundle_filled.png new file mode 100644 index 0000000000000000000000000000000000000000..6202c1827fa25f0bce91df34c5dc955a5d32028f GIT binary patch literal 360 zcmV-u0hj)XP)@)nXL{X%^6AVfb+U55w075ChmnAoR~S3=BV>BMif9 zz)P(^4D4$e;D!j9F);jm1>wUC!RrN(D?o;bUt02!j8rNwaL+BJq#doO|wsb6&l#nmVrkY-L{h_gX}?=S7$7ev%pfkt%Ye-gn2E9~0Vsxm4FK7mqT0l; z`{zWk0Y=)a4BPj=W4LndA&P52F457FhloR60XG0-yDNJ>iXrE&{sy}S2Amyqz~KW5 zo5%N$Ffe@RVuTw23%qKtc?_-FlE8XA9fiO&$RH4aX$HBrewPzij8Q;!*?*9;Kmg=? z5J)kZ2&U(p$!9oq<^kBxAn@Vi4~9mEX$*62*FnPJ^#zClpb&-yAjtnPV8q!2Hwb2M z+6;e$fgl4I1j_!~*!#mZ!CZlCP?YgDh8x$eF(7LOSqcgVumJ+12H2-ZDJCe*|s_UN3-L0W##=)!$&-K`sITf(A5(nfy0% z)@MkY;m;s1Ck(a~79Iq>VB>!j9C)tm`3xXKG_ zOai+A6c(U#fgBES17L9vb3SqcgBkSl)n^QEpcnuOapW+78G>v8&U7H4y6itM4-Yun zK!%_LknOPO#F-92hJZA}41yT~(}0F>gf U5fnp>D*ylh07*qoM6N<$f^&A1mjD0& literal 0 HcmV?d00001 diff --git a/textures/src/bundles/colors/light_gray_bundle.png b/textures/src/bundles/colors/light_gray_bundle.png new file mode 100644 index 0000000000000000000000000000000000000000..e5b9adb127421217b87138bd91e99ba6b8fbc7f4 GIT binary patch literal 439 zcmV;o0Z9IdP)0-~#>;3cm&U2pEpbpFEP)|Pqn_J~rpd0Jue;a5IAkGil z6h#p;qUTT<{0pW4B0fDkrfQ|8O*S!Er2I&R&l-1a+S%S$It^gk1++F>GK;!`&NtdJu|Bv8M5c4l2 hAEZI)yQEO=z5wbi!mSCN1y%q6002ovPDHLkV1lvO!tDS6 literal 0 HcmV?d00001 diff --git a/textures/src/bundles/colors/light_gray_bundle_filled.png b/textures/src/bundles/colors/light_gray_bundle_filled.png new file mode 100644 index 0000000000000000000000000000000000000000..d7a5f6d39dc6c71477d8cd1c20d2399cd572e630 GIT binary patch literal 374 zcmV-+0g3*JP)dmDK>@@2PmdW6A2<&;1g{rBt^gVG`0-<~?I0I{06_y9!%Y60 zIqNfA{U*yG$R`W76&4-@y)bv~TyWq$**Tp7WQc~A7Q>Be*BDOiy$Cj>e$^QiSEF!1 zngs;~!OjMOOP4M&Nc}km)-2mt{E1+Z_dpC8^CSpgW-RW0Ne%G41fjR z+?qSYHPGB&D-oJm3;SCf6Kp~DC1~5aA4ZxWWva-|uE2&6< zqYY#TIsn-Yi%y*B0AvVABg`O}Aux^Ti3lYSk!^$-ge(RMI1oV22RIGDa6cXa00uU_ U5txk_O#lD@07*qoM6N<$f_bx#%m4rY literal 0 HcmV?d00001 diff --git a/textures/src/bundles/colors/lime_bundle.png b/textures/src/bundles/colors/lime_bundle.png new file mode 100644 index 0000000000000000000000000000000000000000..bdee73044d1f82b89abd16eeb8d92d3626236699 GIT binary patch literal 431 zcmV;g0Z{&lP)Y5Qb+78W7V60TV+cSa{6|ig2YUD5TQPuODEi|G>hZU}s_H2X?U%EEMey0)hu& zEhHFBA_Q#CCQ3MY2WP!K6^l%>?Cj1n@4UN;`V7@)J^cV|?WQAv9_)4hZD3tM@wAtx zmv)C4?Z$@@_!rCor1<=#PW2#TO;&P4Do&(1*LdvG#qqat3y>=g&}c48zd?jJxAJa5 z7=Z41?NYR)8IT?8QBZv)|N6~R10~-4Fy}>u0ie6ObSy}xnPN2(%Nr&32j1LN21(zC z$^gVXueVBm>WpjL*%4j=LP#;xp6UnAsq!X8kcEJJ#Q5_9uTO<5y7OB+GXmzWJCoFM z0?uL205~iLVt$Dsf6;&hTxlLzK;=wjqP1=FcZ`ZSXeQhtU8_;N7){{=1LU3I10tyc zqhNmcvwwTYcMJfVI7CK~G2jPGjsHrVS+5`vDS{&DVhs8Eh4M@Bg!~He3rSG&40|b* Zvrl(ty!#`BdIkUh002ovPDHLkV1lh@xXJ(k literal 0 HcmV?d00001 diff --git a/textures/src/bundles/colors/lime_bundle_filled.png b/textures/src/bundles/colors/lime_bundle_filled.png new file mode 100644 index 0000000000000000000000000000000000000000..8dbc7d68c0778c830d417065cc894fba511283fd GIT binary patch literal 371 zcmV-(0gV2MP)2-0k5F2!*E8FsCE2{$An??1!yN58>*m?3z*0CEM$kk?O{!M1~31OfyNXbdy?Z|1De zu;J+v21z+qu&uE0An1jLIXvLNW89^|05U{FON-&gwQCHg_Fe=VQorg9imOpLAk7kz zqF`r(z~wXV7|j2MgEjwmyTNdB{}6mTGboDXmsfZ={T0syeUv=LFX R--G}F002ovPDHLkV1foNm!SXv literal 0 HcmV?d00001 diff --git a/textures/src/bundles/colors/magenta_bundle.png b/textures/src/bundles/colors/magenta_bundle.png new file mode 100644 index 0000000000000000000000000000000000000000..6cbf808919c56e7167e1a6b4654d72668b6dbcbc GIT binary patch literal 437 zcmV;m0ZRUfP)MXCwAm~@cX>}(RV;gN{hKuW~eG>xHXEK)T! zf^HVEG}K&}88@`~Xp*mjVMK!BhDjbSGL&7Aca z*5?T^Nd1)u+X@R0f?jA~nF00w&yT)*8??td7^{dXHxEh56(yaK$ z1ng`OILWe+!F=j@u;%~k5*YUX*@`<5(KW-PcV+|FfTtTBG5im9DNG?Y17LyoAS?&$ z0#I0h(gkujzzu-KJvA-as(`6ynHX05b&H0G#Q-p273KI=c=y+CYY& z1CZ^o=){>0K!$)c!VH2L0@H||h)@C%*+!T_$YP*?0~>&p4{#cQ;eI><03MmF5wTD3 Qp8x;=07*qoM6N<$fN?g#KJ&T~dh+I&&F-mKWSV6^<~MKNtV2D9(_=pU0Gw=iu|Su0qJIlm50D)6M|AO) zXGSpass;W9(*Q|6sQJ`TEvvJ!z^8@rFFvb2_0d+PrSmL+dLO3JV2yr*2yu1ZvmgvW z^~>fcjbu6un9dUI*7NkB+Ljt9nG7;~E-Ewt)nD4K1-We!tC1+U9(9I3*_-{dhz@Va z(Eu?oD3(cGpL5Q7uEr}s2$+jxsJ*LS@tL!^?8lIWfP93SSWBkxxJT+E=@|iYr*bni zmwo0l>=^)u#X$5gaq4zK8XO*3Kxd^u@nR{NyQ#tiHDBf&V?v}AQ3O9=s`)E%XH7vMk_1IkrRzxY3+0z$gggcFK^m04!(I~Q Z?i*L*yQ+STgq{EZ002ovPDHLkV1oX!xw-%V literal 0 HcmV?d00001 diff --git a/textures/src/bundles/colors/orange_bundle_filled.png b/textures/src/bundles/colors/orange_bundle_filled.png new file mode 100644 index 0000000000000000000000000000000000000000..3229d5c2df8cde1586527142ee9a58c5117b3efe GIT binary patch literal 369 zcmV-%0gnEOP)$|6L}qlCCW;v8u%?2+}OW_L||zFHwg12Va8?P>>M^(+{4!WLR~9 z0j>|P0XmWl3_Bho3~{y4WRPKh12z<92wpFMTmdrV%5#WEK`sITf(A5(nfy0%)@NY2 zwu#}%PZ6-Ku<#)0h1nUJ;J|zKj++5wh=!IH!;Nd#7*6fI2sWgC)fp65qi{f)EqJek zoectWZfP>e7+QffO9_2txc%fE?m$G>42#|!+{s`A%p@OT_#Y$ycL6p7V1c*pp$XUp zps)a?3*>Ns8vu)YnDdbn7|fs(pEWVOfnoqC#F4`QW(cwYIMYFykNAH_<$vI40~vx2 zK(@o86K6UA83NJ>GYDn~Oe1E{4B&dYgC_+FX{&W@+h!#GFjg6v>oe$sxh)5unAgBpw5fBAo zF;Rmg0hOTQPm{o+temTN*olfoPBZM?Ip3T)V^EVan)K5T!1~0vC(xzr@xKRX8(?0H zkI`CopBX_TpaT8{Qvfl~Z>6a6@~Yj;YHy>_t`N^o?yHo(Dk`4_n6Y|j(C($*AVQpr z3}_Gr;B>d6PKjWc0j+i)IhjLR$(?FxkYqJB!gEQX060BeD{7GU(ha8(?JOjzP%6`I z<(AH#o+;&A7z%*$@bfMfr0!geJ<-W2UIBu{jI>Un&NCy?ANCLnLVV~T^uX(MwUbSV z458fFxJBk;fO}{$%`X5NmI85q(Ox~LKmtDBzGgGTfMhh{Ee{VVlcey2%Jv&w=JVt? z<52X#fDhN@cSa9TV+y$n`q|t#;ynt04?je%B9YJsN=5U2X|Es<*?>fHir*x~VTLE< kSJ1HEX&Pt#Z!ya36CMh~XoD9&ApigX07*qoM6N<$f;d9JdjJ3c literal 0 HcmV?d00001 diff --git a/textures/src/bundles/colors/pink_bundle_filled.png b/textures/src/bundles/colors/pink_bundle_filled.png new file mode 100644 index 0000000000000000000000000000000000000000..aad1be231e1b195aba1929ccc5a5876bdd745944 GIT binary patch literal 373 zcmV-*0gC>KP)e&mhlJz%lV^xb)5Tv=p!G@vdz!8SkS8sp~kQWpH)68sa3~Mf5 zh3ms>0E?nBL&yAua6=Y&xiD;f_=Mr?n|E+S@OlB{3Xmax?%xI54ssC)5Hz4M%;dkB zvp&Ox6}K3?LO!oq`~7cM14f&*{;x7!RLLo~Fs7;ap<#&BxyMX(|DtInXf8ifPW ztf{B~b~Xsq&6~ks6{!!_9LS~0P=EXk?m$G>42#|!-;aY0V0+Dp;eU_-+y&SSfCXOL z)!kqhfWiWlE|9|kZU8LqVa`WRU@(KO-+zGN4HN@FA&wjdFhh_Hz?lvL)m8tSNlAdC z4P*#90ND&=_w;AT=d~@!ZQPg6o7WMQ4upBFd0^MzH{d<9R0OEnZ zLHg`zFr!|2)dv59X@H22EBo}}yjz{cgLX>ATwbd*J@Tlebrv9*>7=&j1pNjP;;716 z5C-7(;rT0idzofHzuQZWXrd-N|u!Vws)Wmw3$IHz$)gJG;XB5I+ zS#Zcd9OpI68GwW(K=d!woj=kb0dE)L7Eld2>2NkXr$yqz1m)Ihbm#kIig77&V1T?c zazI60pcV9wd^T3Ad`1H>F(J~5SVA5M)%=y%vtB_UB7!5iMfdjo3+0#M3HcSw15r?P d4|5U9-WPH!yUys{v#0<7002ovPDHLkV1nN%#4rE= literal 0 HcmV?d00001 diff --git a/textures/src/bundles/colors/purple_bundle_filled.png b/textures/src/bundles/colors/purple_bundle_filled.png new file mode 100644 index 0000000000000000000000000000000000000000..b31bd2a09fad9216ee569e5fbac01dd757bc78c8 GIT binary patch literal 375 zcmV--0f_#IP)Q|jXaWx7Dq?z-H z2-w*maOKTe2G_8sV9kGCF)(aCz==B$(KW-OSJG({*nn#rEHV5K5`en^n*p%EW7A*6 z@bRq|I4nTv0y!Mu2EgJT=6vJ?1~cfp%o~P%d-vlu02JcLVE{7(*#MmBz<|N_zdD-% zINCslpaYQYu;|2@4nT&0G{Ovm83NOYo`_Ha5!ptVLC9jDfCB;Ke1Ovc4EN&^008ZP Vw-NYHh+Y5y002ovPDHLkV1jCYq&xrs literal 0 HcmV?d00001 diff --git a/textures/src/bundles/colors/red_bundle.png b/textures/src/bundles/colors/red_bundle.png new file mode 100644 index 0000000000000000000000000000000000000000..fd421a0f192148b7745c78940f53eb6c9352b443 GIT binary patch literal 421 zcmV;W0b2fvP)e!seznd zj1z8nY(DZZ{kAnfS#cbL)1_ID`rVosw3K=B~L4VrKbGc&y z++-m-ijF})#MI2Ml35H4Sk+<`1Zj5X=4ROW^((^}r~$H2n;tVTFl;A1Apz#Y48iLKkSjojJmlsE+YfRP2oN-&G0fz@nX^8_u_d<{7+$;p z+X@R0f?lXqRRsqgiy=P)$Pf)JEruJ{t}&e2dl76%{i-u4u14X2G`lM+gPjcy`4=x3 ztV-;_n*W^t$nf&^Z3dh!K-Ub5-YYR`5CfibV)!2<0Cxd417Lx-m+uYO1)#71r3++N zAPj)UJT03yD;exZK9uT5rI7CDZ?Yu#>_eBW0(4Pcrk4c_kPH;52D&(k0b zK=-p&oi_H57*JYVrzAwCEFlksntzqp({4c^B7!67qGtPkh4M;qg?tP714U4A d5C0;Ry)T|e%h*37TXp~d002ovPDHLkV1noVx>f)H literal 0 HcmV?d00001 diff --git a/textures/src/bundles/colors/white_bundle_filled.png b/textures/src/bundles/colors/white_bundle_filled.png new file mode 100644 index 0000000000000000000000000000000000000000..aabfb2b3157123528b56abbe1dc2b1ceeaefcd5e GIT binary patch literal 371 zcmV-(0gV2MP)}K&}88a_;0>up(< z@aZHmh>D7WZH0vgK`-38bqgGL%WI+-K!#{&X))Zmc8%fG-iu&E>Q|jXaWx7Dr1|r^ zOJHY%!0p?&8Sd`y1Z$3LS;nw?_io&Qh^`qHy*p;5fDLfTpNQdqkO15T*bIOL-j==& zunRz80ZJFh;Q%)P7WXjcBPTGJLA*kG7~ViJ02JcLVE{7(*#MmBVCvMV{|yWbz|jUW z1Ra2EheapObO160q!DHi%n+DH^hAUbh{!g=3_=zI1sn(<=L4JuV7MQT007%TS8 RRwV!c002ovPDHLkV1g$Rn3Dhi literal 0 HcmV?d00001 diff --git a/textures/src/bundles/colors/yellow_bundle.png b/textures/src/bundles/colors/yellow_bundle.png new file mode 100644 index 0000000000000000000000000000000000000000..dd3d3d923c552dbbcc165c67ac6433733e50c1e7 GIT binary patch literal 434 zcmV;j0ZsmiP)fkq!4x*#8n}b*g3W9_92LwlJB?X6~ z8Kfvki`9MsF>)^6M_yGNdY6}Xlk8lF^;OXZ(eHP@g;fNcp!eUGEVK)1I8|4Ey z2B6_}!YmSQO*B(pAS*#E71M2qTj@C)>*2oY#C3P(?u&Tu>2-2*k{FdR?U15eLE8c(&P*;-x(>HIuU|70} z0j>|P0ZI!08FuUyfg6&WX~gjU{Ub0RW(Zy{fLsAG`5VJ830ob?$P zL^m=B3d(_Pg@p$}FPzwK1P;8N)0r4RhG=MMG2FOzjp5YZi(o_QSDis|H3|o$Sybpe z*x4X(@|+RF`&))!&F-217j<%;0D0r9_D=H1O_wc^*cihZ=e_e3UTBxfEj{p0M2yKR3q`<)aVa5+CYY& z1CZ^o=){>0K!$)c!VH2L0@H||h)@C%*+!T_$YP*?0|DfGfYSgB_u~-&0MX905rPw1 QSpWb407*qoM6N<$f;;q>#Q*>R literal 0 HcmV?d00001 diff --git a/textures/src/bundles/variants/cord_diamond.png b/textures/src/bundles/variants/cord_diamond.png new file mode 100644 index 0000000000000000000000000000000000000000..b1b070e94706386fdaa9ab06e74d55227cb351c2 GIT binary patch literal 432 zcmV;h0Z;ykP)In?h%S)js0L?G+kcBR13;Q}<;55tzWEI1fBO2J zpka*Yns=PO!tnX?M-)Z8y!;H(Vq#$S(^sz;HeUSzmt%%8OcjM0e*F2zV4^C^@c7Lq zhF`ya!WDtoZ{EKHV!MIDTb;wt`w{QuzHcLsYkF$ND)9k3=C2Jt}va0ut>P|EU@T8_8Z6$5DhZ~nXeGy z%lH&%DF}dKiwQ))G{fQ_*&q2BR0px`A> z7sn8b)5!@71PmTc5|}Q)e!0HxKjY>6`}>Zr>f@ZAW*BI=!%aX;OpGmg5l;i7X{hNA zZ-=%IA3q-aX>g#)_wi|Q{Xm|^ozJem4nM-$b7j@j|9kJ<`{N(@;tl>dIp{BQ2(C#st&PA3jWY6Qt(EwT*N2;w7SAR;0M}S2Hp)@;KYo{9stNzvkzm z4=*pPPjP8|#593vMp8^e>r~ciD+bk@`3@c24Ltw9{gwW=hvBpQ2{AoCy&V>t+@83! k85<{hf8Jpve3XHqf+xyy*7BRJK;JQVy85}Sb4q9e0Fyv-bpQYW literal 0 HcmV?d00001 diff --git a/textures/src/bundles/variants/cord_gold.png b/textures/src/bundles/variants/cord_gold.png new file mode 100644 index 0000000000000000000000000000000000000000..c86d5386b358536ee9e894acc120954f1a49d6a9 GIT binary patch literal 433 zcmV;i0Z#sjP)In?h%S)js0L?G+kcBR13;Q}<;55tzWEI1fBO2J zpka*Yns=PO!tnX?M-)Z8y!;H(Vq#$S(^sz;HeUSzmt%%8OcjM0e*F2zV4^C^@c7Lq zhF`ya!WDtoZ{EKHVBVT?Ar-q7fDj@IbtN{~_2FATOW;kVZyk1xA?H z(B)b7J^Bv}(T@yIiAeV#Ltsh$#@*WtAieOwK#mqz{KK3H10d(aQYlC?*35(wzgPfF bBMbllzoDPP;O~Zs00000NkvXXu0mjfDn7j! literal 0 HcmV?d00001 diff --git a/textures/src/bundles/variants/cord_gold_filled.png b/textures/src/bundles/variants/cord_gold_filled.png new file mode 100644 index 0000000000000000000000000000000000000000..f1fc1de1e669c1cb45b4c28e150fa026343c02b5 GIT binary patch literal 288 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GGLLkg|>2BR0px`A> z7sn8b)5!@71PmTc5|}Q)e!0HxKjY>6`}>Zr>f@ZAW*BI=!%aX;OpGmg5l;i7X{hNA zZ-=%IA3q-aX>g#)_wi|Q{Xm|^ozJem4nM+r=5+6(|NrZH|N95NIK=bw|98e2Y#$vZ z)~mU?y7Cy#NQ5akrdO=I+eBBib3^ezC#Ch1JD0&f2IHJVfZY6LQKz3Z->PuwR=>pLZAuA7x;8p%#-AyZXlupzjzwUHx3vIVCg!0Agr#4gdfE literal 0 HcmV?d00001 diff --git a/textures/src/bundles/variants/cord_iron.png b/textures/src/bundles/variants/cord_iron.png new file mode 100644 index 0000000000000000000000000000000000000000..d5c509d9c0d24f8f27e36b4e7a00f906eb764c70 GIT binary patch literal 428 zcmV;d0aN~oP)In?h%S)js0L?G+kcBR13;Q}<;55tzWEI1fBO2J zpka*Yns=PO!tnX?M-)Z8y!;H(Vq#$S(^sz;HeUSzmt%%8OcjM0e*F2zV4^C^@c7Lq zhF`ya!WDtoZ{EKHV@1MY@*=zp8X6jmAVY8&1k&8s*9W(r1r}Y%eghc-qG5(0^CxBc zF@h`w0Z?o)fe4spSo|X!1j7G;0Hhhj21O$*9N>X?{r*F+D?naA2Oy1%KmhX^x;)Fi zNB@B#`jO!&5$PUe2rQ}JxO2BR0px_x# z7sn8b(@TRday1+9gcR}%3oi8e{_60O``6ch6<)SDz~xSJQ#6-mnDp}(o*Zj;8gHAf z@WI7nug3eO=RTXf)>&F|%~fRQ4$(G_gA$huy%+-Rvo3RXq(r7&>$u^XGk>jW)7_Hw z@9rGrP@J|bQ*8ZZS3!o92xpl|t5)55t2pzKOLoz$W%cD!f3+UWiH^G&m%3x`Z=3Vw zN?w|C+Y>o{?c45?TFSs0;iI_kNS=H{>hbr^j7N@tudw0%QU77;lUXzVnil3770rm5 ex4iA7wEUha(b>B!3tNHyV(@hJb6Mw<&;$Ul(saE5 literal 0 HcmV?d00001 diff --git a/textures/src/bundles/variants/cord_netherite.png b/textures/src/bundles/variants/cord_netherite.png new file mode 100644 index 0000000000000000000000000000000000000000..44e421335ca50a324ce0b89640d967941a628653 GIT binary patch literal 455 zcmV;&0XY7NP)In?h%S)js0L?G+kcBR13;Q}<;55tzWEI1fBO2J zpka*Yns=PO!tnX?M-)Z8y!;H(Vq#$S(^sz;HeUSzmt%%8OcjM0e*F2zV4^C^@c7Lq zhF`ya!WDtoZ{EKHV~ z&i{@DOBt~R*v6|zz_fv;=Kl+i{)5#p`fCd?Xe!FX4ar<^gdr+C8tk2AD=`fMYrcLR zF3tjrE@Z#KG@=^>;tRYw%_z?#09FTzEfx^L2Xz4~{?QF8DJ=T`?Ac2&ALL?KINSgk z0ODM~{}3zyN(blwWNS}M#3=^o?)SW>@n_cjAaFWfX( xw7}vU=1gP&N`2BR0px`4< z7sn8b)5!@71PmTc5|}Q)e!0HxKjY>6`}>Zr>f@ZAW*BI=!%aX;OpGmg5l;i7X{hNA zZ-=%IA3q-aX>g#)_wi|Q{Xm|^ozJem4nM-$)41%}{}mrz{1;(WIw7EItIVdUS=g3- zKF%m+cL|?P-NRp{bE+gnL`4mcIOs7-wt4dlZ%UJslbdvzQCN}nbeNuNp;1+c(W2vf z8yXne6y@z|7%s=}t4aLv<)yIKBBw$|4@MtDy#t3%HQCBGOnPIkaD>&Nt^W5v-g=Wm zf7my*c2DNk>supeIq{(n`;ALB>(00sGB8Z~9$;U7^T2U%OjyaO<8!E1pWBjLu3i{X6pHT)1=ziKGWNPShz9zh z{_zEoF)!SeD$r=W8A%wI9?LXFy*?Oreu7@C)1Hk_9~yK^Caf<-VI~}=ldYAwosPCO z8wa)5cj4&ime_x`T!UCpfp~P1NIY zr<5a>C;DJKpi+W5;G(a8E=28Q4|Q>tE0A#rG&eRoEgy;%fonLo^L24=eoynO=LHKj z2MO^!xqTp55Mf6Z_fG|U|5l;++KSXN88DO42d&8^6EAObRE5!k$K-U#CsSKH0)0uW z&dZJu2_5Cff~~L+myKlet3+rw*RYqDt(VBx!uQQ(n!&~)klgWSG7EJA6B*xI{j}W) SJq_sq0000Q4E2(=GE(0#Dzg)n8|-LXMKi*`YnuH zoLmgwzJG&<2YOgw7@(-A2&NNEg~5hsXlXIrxOR=<)ZU9=@%mL~7;zc^(#*rd!yq6a z0Omh=@`Pbph0}kK<^`wUGJg8>3Bv_UxHN-+jEoGN4e|yHD+_6{@ZrM;hO#NAz^(*^ z1qh%=B@?m@=m6vz5MFcrCnLypFhg*<;LDdUU|aD4bZ@|UpeXvr@C~O%km1-|i4qGi t|AA~lb}q6;WG4NlLsyQFGEYE;S)~T)s4U zmHXd3-Ne{vxqBc44bPtUJ^+ooP%L=f_p#>{PoVAzE;CjCy5uzRDSDx*^&dj}7Mx#I zHDeDhwxRpc)*Zbh)yecH67Y5gZ87M$g_h-VR+V*5A7Fk{p6CYl4`5LCx3ByF|FdWc zHo-hk00009a7bBm000XT000XT0n*)m`~Uy}0!c(cR5(v#V4yYNvp4$>!erS4(rEnp zG#KMDgb|m?APuT(rZRjvex89vR0yo>aYYwH6#ol`8Jix#^)X>H09i9g{?F@oU}dr$ zWem$cN}y=LY5?br?~G4$lHfLfzi}7E5M=LQHQ?pT%OG1A-vl&*ZR6BXWSVYf$lihZ1E&F=W}^Qc422j3+3$f3fdwRwr6I%ZkZgue2T$RQ z00xle3_rR58gkt5_!k$~#T%XQkj17MBndPC?r)rl3Z%I+T^+?{LMagL50GYg2~LIs y7v3`5c=Q#w|FD@zj?F0MfC39Szz8{;s`3EHu5snOR%5IH0000