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 0000000..fe9ec09 Binary files /dev/null and b/textures/src/bundles/colors/black_bundle.png differ diff --git a/textures/src/bundles/colors/black_bundle_filled.png b/textures/src/bundles/colors/black_bundle_filled.png new file mode 100644 index 0000000..4eb9dcf Binary files /dev/null and b/textures/src/bundles/colors/black_bundle_filled.png differ diff --git a/textures/src/bundles/colors/blue_bundle.png b/textures/src/bundles/colors/blue_bundle.png new file mode 100644 index 0000000..6437b80 Binary files /dev/null and b/textures/src/bundles/colors/blue_bundle.png differ diff --git a/textures/src/bundles/colors/blue_bundle_filled.png b/textures/src/bundles/colors/blue_bundle_filled.png new file mode 100644 index 0000000..c99f343 Binary files /dev/null and b/textures/src/bundles/colors/blue_bundle_filled.png differ diff --git a/textures/src/bundles/colors/brown_bundle.png b/textures/src/bundles/colors/brown_bundle.png new file mode 100644 index 0000000..2a6033e Binary files /dev/null and b/textures/src/bundles/colors/brown_bundle.png differ diff --git a/textures/src/bundles/colors/brown_bundle_filled.png b/textures/src/bundles/colors/brown_bundle_filled.png new file mode 100644 index 0000000..3781af4 Binary files /dev/null and b/textures/src/bundles/colors/brown_bundle_filled.png differ diff --git a/textures/src/bundles/colors/cyan_bundle.png b/textures/src/bundles/colors/cyan_bundle.png new file mode 100644 index 0000000..d326439 Binary files /dev/null and b/textures/src/bundles/colors/cyan_bundle.png differ 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 0000000..dc4f0b8 Binary files /dev/null and b/textures/src/bundles/colors/cyan_bundle_filled.png differ diff --git a/textures/src/bundles/colors/gray_bundle.png b/textures/src/bundles/colors/gray_bundle.png new file mode 100644 index 0000000..36ba7af Binary files /dev/null and b/textures/src/bundles/colors/gray_bundle.png differ diff --git a/textures/src/bundles/colors/gray_bundle_filled.png b/textures/src/bundles/colors/gray_bundle_filled.png new file mode 100644 index 0000000..cbee671 Binary files /dev/null and b/textures/src/bundles/colors/gray_bundle_filled.png differ diff --git a/textures/src/bundles/colors/green_bundle.png b/textures/src/bundles/colors/green_bundle.png new file mode 100644 index 0000000..381a511 Binary files /dev/null and b/textures/src/bundles/colors/green_bundle.png differ 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 0000000..6202c18 Binary files /dev/null and b/textures/src/bundles/colors/green_bundle_filled.png differ diff --git a/textures/src/bundles/colors/light_blue_bundle.png b/textures/src/bundles/colors/light_blue_bundle.png new file mode 100644 index 0000000..0a0fa17 Binary files /dev/null and b/textures/src/bundles/colors/light_blue_bundle.png differ diff --git a/textures/src/bundles/colors/light_blue_bundle_filled.png b/textures/src/bundles/colors/light_blue_bundle_filled.png new file mode 100644 index 0000000..abad3fd Binary files /dev/null and b/textures/src/bundles/colors/light_blue_bundle_filled.png differ 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 0000000..e5b9adb Binary files /dev/null and b/textures/src/bundles/colors/light_gray_bundle.png differ 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 0000000..d7a5f6d Binary files /dev/null and b/textures/src/bundles/colors/light_gray_bundle_filled.png differ diff --git a/textures/src/bundles/colors/lime_bundle.png b/textures/src/bundles/colors/lime_bundle.png new file mode 100644 index 0000000..bdee730 Binary files /dev/null and b/textures/src/bundles/colors/lime_bundle.png differ 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 0000000..8dbc7d6 Binary files /dev/null and b/textures/src/bundles/colors/lime_bundle_filled.png differ diff --git a/textures/src/bundles/colors/magenta_bundle.png b/textures/src/bundles/colors/magenta_bundle.png new file mode 100644 index 0000000..6cbf808 Binary files /dev/null and b/textures/src/bundles/colors/magenta_bundle.png differ diff --git a/textures/src/bundles/colors/magenta_bundle_filled.png b/textures/src/bundles/colors/magenta_bundle_filled.png new file mode 100644 index 0000000..9fe2953 Binary files /dev/null and b/textures/src/bundles/colors/magenta_bundle_filled.png differ diff --git a/textures/src/bundles/colors/orange_bundle.png b/textures/src/bundles/colors/orange_bundle.png new file mode 100644 index 0000000..71a85c8 Binary files /dev/null and b/textures/src/bundles/colors/orange_bundle.png differ 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 0000000..3229d5c Binary files /dev/null and b/textures/src/bundles/colors/orange_bundle_filled.png differ diff --git a/textures/src/bundles/colors/pink_bundle.png b/textures/src/bundles/colors/pink_bundle.png new file mode 100644 index 0000000..5348bcc Binary files /dev/null and b/textures/src/bundles/colors/pink_bundle.png differ 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 0000000..aad1be2 Binary files /dev/null and b/textures/src/bundles/colors/pink_bundle_filled.png differ diff --git a/textures/src/bundles/colors/purple_bundle.png b/textures/src/bundles/colors/purple_bundle.png new file mode 100644 index 0000000..bf203e9 Binary files /dev/null and b/textures/src/bundles/colors/purple_bundle.png differ 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 0000000..b31bd2a Binary files /dev/null and b/textures/src/bundles/colors/purple_bundle_filled.png differ diff --git a/textures/src/bundles/colors/red_bundle.png b/textures/src/bundles/colors/red_bundle.png new file mode 100644 index 0000000..fd421a0 Binary files /dev/null and b/textures/src/bundles/colors/red_bundle.png differ diff --git a/textures/src/bundles/colors/red_bundle_filled.png b/textures/src/bundles/colors/red_bundle_filled.png new file mode 100644 index 0000000..6f8b957 Binary files /dev/null and b/textures/src/bundles/colors/red_bundle_filled.png differ diff --git a/textures/src/bundles/colors/white_bundle.png b/textures/src/bundles/colors/white_bundle.png new file mode 100644 index 0000000..e865c06 Binary files /dev/null and b/textures/src/bundles/colors/white_bundle.png differ 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 0000000..aabfb2b Binary files /dev/null and b/textures/src/bundles/colors/white_bundle_filled.png differ diff --git a/textures/src/bundles/colors/yellow_bundle.png b/textures/src/bundles/colors/yellow_bundle.png new file mode 100644 index 0000000..dd3d3d9 Binary files /dev/null and b/textures/src/bundles/colors/yellow_bundle.png differ diff --git a/textures/src/bundles/colors/yellow_bundle_filled.png b/textures/src/bundles/colors/yellow_bundle_filled.png new file mode 100644 index 0000000..38a9fa7 Binary files /dev/null and b/textures/src/bundles/colors/yellow_bundle_filled.png differ diff --git a/textures/src/bundles/variants/cord_diamond.png b/textures/src/bundles/variants/cord_diamond.png new file mode 100644 index 0000000..b1b070e Binary files /dev/null and b/textures/src/bundles/variants/cord_diamond.png differ diff --git a/textures/src/bundles/variants/cord_diamond_filled.png b/textures/src/bundles/variants/cord_diamond_filled.png new file mode 100644 index 0000000..5f0b664 Binary files /dev/null and b/textures/src/bundles/variants/cord_diamond_filled.png differ diff --git a/textures/src/bundles/variants/cord_gold.png b/textures/src/bundles/variants/cord_gold.png new file mode 100644 index 0000000..c86d538 Binary files /dev/null and b/textures/src/bundles/variants/cord_gold.png differ 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 0000000..f1fc1de Binary files /dev/null and b/textures/src/bundles/variants/cord_gold_filled.png differ diff --git a/textures/src/bundles/variants/cord_iron.png b/textures/src/bundles/variants/cord_iron.png new file mode 100644 index 0000000..d5c509d Binary files /dev/null and b/textures/src/bundles/variants/cord_iron.png differ diff --git a/textures/src/bundles/variants/cord_iron_filled.png b/textures/src/bundles/variants/cord_iron_filled.png new file mode 100644 index 0000000..a0d9b34 Binary files /dev/null and b/textures/src/bundles/variants/cord_iron_filled.png differ diff --git a/textures/src/bundles/variants/cord_netherite.png b/textures/src/bundles/variants/cord_netherite.png new file mode 100644 index 0000000..44e4213 Binary files /dev/null and b/textures/src/bundles/variants/cord_netherite.png differ diff --git a/textures/src/bundles/variants/cord_netherite_filled.png b/textures/src/bundles/variants/cord_netherite_filled.png new file mode 100644 index 0000000..3e4fdae Binary files /dev/null and b/textures/src/bundles/variants/cord_netherite_filled.png differ diff --git a/textures/src/bundles/variants/cord_normal.png b/textures/src/bundles/variants/cord_normal.png new file mode 100644 index 0000000..9c436d3 Binary files /dev/null and b/textures/src/bundles/variants/cord_normal.png differ diff --git a/textures/src/bundles/variants/cord_normal_filled.png b/textures/src/bundles/variants/cord_normal_filled.png new file mode 100644 index 0000000..169c282 Binary files /dev/null and b/textures/src/bundles/variants/cord_normal_filled.png differ diff --git a/textures/src/hammers/diamond_hammer.json b/textures/src/hammers/diamond_hammer.json new file mode 100644 index 0000000..00426c4 --- /dev/null +++ b/textures/src/hammers/diamond_hammer.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "item/diamond_hammer" + } +} diff --git a/textures/src/hammers/diamond_hammer.png b/textures/src/hammers/diamond_hammer.png new file mode 100644 index 0000000..a59f2c2 Binary files /dev/null and b/textures/src/hammers/diamond_hammer.png differ diff --git a/textures/src/hammers/diamond_pickaxe.json b/textures/src/hammers/diamond_pickaxe.json new file mode 100644 index 0000000..41fb1e2 --- /dev/null +++ b/textures/src/hammers/diamond_pickaxe.json @@ -0,0 +1,14 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "item/diamond_pickaxe" + }, + "overrides": [ + { + "predicate": { + "custom_model_data": 1 + }, + "model": "item/diamond_hammer" + }, + ] +}