This commit is contained in:
rattatwinko
2025-05-02 14:56:18 +02:00
commit d683bc1a9e
5 changed files with 512 additions and 0 deletions

113
.gitignore vendored Normal file
View File

@@ -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/

87
pom.xml Normal file
View File

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.GrapplingHook</groupId>
<artifactId>GrapplingHook</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>GrapplingHook</name>
<properties>
<java.version>21</java.version>
<kotlin.version>2.2.0-Beta2</kotlin.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<defaultGoal>clean package</defaultGoal>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
<configuration>
<jvmTarget>${java.version}</jvmTarget>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
<repositories>
<repository>
<id>papermc-repo</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
<repository>
<id>sonatype</id>
<url>https://oss.sonatype.org/content/groups/public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.21.5-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -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<UUID, Long>()
private val recentlyGrappled = HashSet<UUID>()
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<out String>): 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<String>()
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)
}
}

View File

@@ -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

View File

@@ -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: /<command> [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