From d683bc1a9e4a14593173dfecdcded9c7595280af Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Fri, 2 May 2025 14:56:18 +0200 Subject: [PATCH] initial --- .gitignore | 113 ++++++++ pom.xml | 87 ++++++ .../grapplingHook/GrapplingHook.kt | 274 ++++++++++++++++++ src/main/resources/config.yml | 16 + src/main/resources/plugin.yml | 22 ++ 5 files changed, 512 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/kotlin/org/GrapplingHook/grapplingHook/GrapplingHook.kt create mode 100644 src/main/resources/config.yml create mode 100644 src/main/resources/plugin.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4788b4b --- /dev/null +++ b/.gitignore @@ -0,0 +1,113 @@ +# User-specific stuff +.idea/ + +*.iml +*.ipr +*.iws + +# IntelliJ +out/ + +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +target/ + +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next + +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar +.flattened-pom.xml + +# Common working directory +run/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..147dbc9 --- /dev/null +++ b/pom.xml @@ -0,0 +1,87 @@ + + + 4.0.0 + + org.GrapplingHook + GrapplingHook + 1.0-SNAPSHOT + jar + + GrapplingHook + + + 21 + 2.2.0-Beta2 + UTF-8 + + + + clean package + ${project.basedir}/src/main/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + + ${java.version} + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.3 + + + package + + shade + + + + + + + + src/main/resources + true + + + + + + + papermc-repo + https://repo.papermc.io/repository/maven-public/ + + + sonatype + https://oss.sonatype.org/content/groups/public/ + + + + + + io.papermc.paper + paper-api + 1.21.5-R0.1-SNAPSHOT + provided + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + ${kotlin.version} + + + diff --git a/src/main/kotlin/org/GrapplingHook/grapplingHook/GrapplingHook.kt b/src/main/kotlin/org/GrapplingHook/grapplingHook/GrapplingHook.kt new file mode 100644 index 0000000..820d820 --- /dev/null +++ b/src/main/kotlin/org/GrapplingHook/grapplingHook/GrapplingHook.kt @@ -0,0 +1,274 @@ +package org.GrapplingHook.grapplingHook + +import org.bukkit.ChatColor +import org.bukkit.Material +import org.bukkit.NamespacedKey +import org.bukkit.Particle +import org.bukkit.Sound +import org.bukkit.entity.FishHook +import org.bukkit.entity.Player +import org.bukkit.event.EventHandler +import org.bukkit.event.Listener +import org.bukkit.event.player.PlayerFishEvent +import org.bukkit.event.player.PlayerJoinEvent +import org.bukkit.inventory.ItemFlag +import org.bukkit.inventory.ItemStack +import org.bukkit.persistence.PersistentDataType +import org.bukkit.plugin.java.JavaPlugin +import org.bukkit.command.Command +import org.bukkit.command.CommandSender +import org.bukkit.event.entity.EntityDamageEvent +import org.bukkit.scheduler.BukkitRunnable +import org.bukkit.util.Vector +import java.util.* + +class GrapplingHook : JavaPlugin(), Listener { + private val grapplingHookKey = NamespacedKey(this, "grappling_hook") + private val cooldowns = HashMap() + private val recentlyGrappled = HashSet() + + private var cooldownSeconds = 2 + private var pullStrength = 1.5 + private var preventFallDamage = true + private var maxDistance = 30.0 + private var showParticles = true + private var hookSpeed = 2.5 + + override fun onEnable() { + server.pluginManager.registerEvents(this, this) + createDefaultConfig() + loadConfig() + logger.info("${description.name} v${description.version} has been enabled!") + } + + private fun createDefaultConfig() { + if (!dataFolder.exists()) { + dataFolder.mkdirs() + } + + val configFile = java.io.File(dataFolder, "config.yml") + if (!configFile.exists()) { + try { + configFile.createNewFile() + val writer = java.io.PrintWriter(configFile) + writer.println("# GrapplingHook Config") + writer.println("cooldown-seconds: 2") + writer.println("pull-strength: 1.5") + writer.println("prevent-fall-damage: true") + writer.println("max-distance: 30.0") + writer.println("show-particles: true") + writer.println("hook-speed: 2.5") + writer.close() + } catch (e: Exception) { + logger.severe("Could not create config.yml: ${e.message}") + } + } + } + + override fun onDisable() { + logger.info("${description.name} has been disabled!") + } + + private fun loadConfig() { + reloadConfig() + cooldownSeconds = config.getInt("cooldown-seconds", 2) + pullStrength = config.getDouble("pull-strength", 1.5) + preventFallDamage = config.getBoolean("prevent-fall-damage", true) + maxDistance = config.getDouble("max-distance", 30.0) + showParticles = config.getBoolean("show-particles", true) + hookSpeed = config.getDouble("hook-speed", 2.5) + } + + override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array): Boolean { + if (command.name.equals("grapplinghook", ignoreCase = true)) { + if (sender !is Player) { + sender.sendMessage("${ChatColor.RED}This command can only be used by players.") + return true + } + + val player = sender as Player + + if (args.isEmpty()) { + giveGrapplingHook(player) + player.sendMessage("${ChatColor.GREEN}You have received a Grappling Hook!") + return true + } + + when (args[0].lowercase()) { + "reload" -> { + if (player.hasPermission("grapplinghook.reload")) { + reloadConfig() + loadConfig() + player.sendMessage("${ChatColor.GREEN}GrapplingHook config reloaded!") + } else { + player.sendMessage("${ChatColor.RED}You don't have permission to do that.") + } + return true + } + "give" -> { + if (args.size > 1) { + val targetPlayer = server.getPlayer(args[1]) + if (targetPlayer != null && targetPlayer.isOnline) { + giveGrapplingHook(targetPlayer) + player.sendMessage("${ChatColor.GREEN}Gave a Grappling Hook to ${targetPlayer.name}!") + targetPlayer.sendMessage("${ChatColor.GREEN}You received a Grappling Hook from ${player.name}!") + } else { + player.sendMessage("${ChatColor.RED}Player not found or not online.") + } + } else { + giveGrapplingHook(player) + player.sendMessage("${ChatColor.GREEN}You have received a Grappling Hook!") + } + return true + } + else -> { + player.sendMessage("${ChatColor.RED}Unknown subcommand. Use /grapplinghook, /grapplinghook reload, or /grapplinghook give [player]") + return true + } + } + } + return false + } + + private fun giveGrapplingHook(player: Player) { + val grapplingHook = ItemStack(Material.FISHING_ROD) + val meta = grapplingHook.itemMeta + + meta?.setDisplayName("${ChatColor.GOLD}${ChatColor.BOLD}Grappling Hook") + + val lore = ArrayList() + lore.add("${ChatColor.GRAY}Right-click to launch the hook") + lore.add("${ChatColor.GRAY}Pull yourself to the hook location") + meta?.lore = lore + + meta?.isUnbreakable = true + meta?.addItemFlags(ItemFlag.HIDE_UNBREAKABLE, ItemFlag.HIDE_ATTRIBUTES) + meta?.addEnchant(org.bukkit.enchantments.Enchantment.LUCK_OF_THE_SEA, 1, true) + meta?.addItemFlags(ItemFlag.HIDE_ENCHANTS) + meta?.persistentDataContainer?.set(grapplingHookKey, PersistentDataType.BYTE, 1) + + grapplingHook.itemMeta = meta + + if (player.inventory.firstEmpty() == -1) { + player.world.dropItem(player.location, grapplingHook) + player.sendMessage("${ChatColor.YELLOW}Your inventory was full! The grappling hook was dropped at your feet.") + } else { + player.inventory.addItem(grapplingHook) + } + } + + @EventHandler + fun onPlayerJoin(event: PlayerJoinEvent) { + // Optional: giveGrapplingHook(event.player) + } + + @EventHandler + fun onPlayerFish(event: PlayerFishEvent) { + val player = event.player + val item = player.inventory.itemInMainHand + + if (!isGrapplingHook(item)) return + + when (event.state) { + PlayerFishEvent.State.FISHING -> { + val uuid = player.uniqueId + + if (cooldowns.containsKey(uuid)) { + val secondsLeft = (cooldowns[uuid]!! + cooldownSeconds * 1000 - System.currentTimeMillis()) / 1000 + if (secondsLeft > 0) { + player.sendMessage("${ChatColor.RED}Grappling Hook is on cooldown for $secondsLeft seconds!") + event.isCancelled = true + return + } + } + + cooldowns[uuid] = System.currentTimeMillis() + event.hook.velocity = event.hook.velocity.multiply(hookSpeed) + player.world.playSound(player.location, Sound.ENTITY_ARROW_SHOOT, 1.0f, 1.0f) + } + + PlayerFishEvent.State.IN_GROUND, PlayerFishEvent.State.CAUGHT_ENTITY -> { + val hook = event.hook + val distance = player.location.distance(hook.location) + + if (distance > maxDistance) { + player.sendMessage("${ChatColor.RED}Hook is too far away!") + return + } + + val direction = hook.location.toVector().subtract(player.location.toVector()) + var adjustedPullStrength = pullStrength + if (distance > 15.0) { + adjustedPullStrength *= 1.0 + ((distance - 15.0) / 15.0) + } + + direction.normalize().multiply(adjustedPullStrength) + direction.y = Math.max(0.4, direction.y) + player.velocity = direction + + if (preventFallDamage) { + recentlyGrappled.add(player.uniqueId) + object : BukkitRunnable() { + override fun run() { + recentlyGrappled.remove(player.uniqueId) + } + }.runTaskLater(this, 100L) + } + + player.world.playSound(player.location, Sound.ENTITY_ZOMBIE_INFECT, 1.0f, 2.0f) + + if (showParticles) { + showGrapplingParticles(player, hook) + } + } + + else -> {} + } + } + + @EventHandler + fun onEntityDamage(event: EntityDamageEvent) { + if (event.entity !is Player) return + + val player = event.entity as Player + + if (preventFallDamage && + event.cause == EntityDamageEvent.DamageCause.FALL && + recentlyGrappled.contains(player.uniqueId)) { + event.isCancelled = true + player.world.spawnParticle(Particle.CLOUD, player.location, 10, 0.3, 0.1, 0.3, 0.05) + player.world.playSound(player.location, Sound.ENTITY_ARMOR_STAND_FALL, 0.5f, 1.2f) + } + } + + private fun isGrapplingHook(item: ItemStack): Boolean { + if (item.type != Material.FISHING_ROD) return false + val meta = item.itemMeta ?: return false + return meta.persistentDataContainer.has(grapplingHookKey, PersistentDataType.BYTE) + } + + private fun showGrapplingParticles(player: Player, hook: FishHook) { + object : BukkitRunnable() { + var distance = player.location.distance(hook.location) + var traveled = 0.0 + val particleSpacing = 0.3 + + override fun run() { + if (traveled >= distance || !player.isOnline || hook.isDead) { + cancel() + return + } + + val direction = hook.location.toVector().subtract(player.location.toVector()).normalize() + val particleLocation = player.location.clone().add(direction.multiply(traveled)) + + player.world.spawnParticle(Particle.CRIT, particleLocation, 2, 0.05, 0.05, 0.05, 0.01) + if (Math.random() < 0.3) { + player.world.spawnParticle(Particle.FLAME, particleLocation, 1, 0.05, 0.05, 0.05, 0.01) + } + + traveled += particleSpacing + } + }.runTaskTimer(this, 0L, 1L) + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..206ad99 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,16 @@ +# GrapplingHook Config + +# Cooldown in seconds between uses +cooldown-seconds: 1 + +# Pull strength (higher values make you go faster) +pull-strength: 2.5 + +# Prevent fall damage after using the grappling hook +prevent-fall-damage: true + +# Maximum distance the hook can travel +max-distance: 60.0 + +# Show particle effects +show-particles: true \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..e047ef5 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,22 @@ +name: GrapplingHook +version: 1.0-SNAPSHOT +main: org.GrapplingHook.grapplingHook.GrapplingHook +api-version: 1.21 +authors: [YourName] +description: A plugin that adds a grappling hook to the game +commands: + grapplinghook: + description: Main command for the GrapplingHook plugin + usage: / [reload|give [player]] + aliases: [ghook, grapple] + permission: grapplinghook.use +permissions: + grapplinghook.use: + description: Allows use of the grappling hook command + default: true + grapplinghook.reload: + description: Allows reloading of the plugin configuration + default: op + grapplinghook.give: + description: Allows giving grappling hooks to other players + default: op \ No newline at end of file