diff --git a/src/main/kotlin/org/GrapplingHook/grapplingHook/GrapplingHook.kt b/src/main/kotlin/org/GrapplingHook/grapplingHook/GrapplingHook.kt index 820d820..e7e80dc 100644 --- a/src/main/kotlin/org/GrapplingHook/grapplingHook/GrapplingHook.kt +++ b/src/main/kotlin/org/GrapplingHook/grapplingHook/GrapplingHook.kt @@ -26,6 +26,7 @@ class GrapplingHook : JavaPlugin(), Listener { private val grapplingHookKey = NamespacedKey(this, "grappling_hook") private val cooldowns = HashMap() private val recentlyGrappled = HashSet() + private val activeHooks = HashMap() private var cooldownSeconds = 2 private var pullStrength = 1.5 @@ -33,6 +34,8 @@ class GrapplingHook : JavaPlugin(), Listener { private var maxDistance = 30.0 private var showParticles = true private var hookSpeed = 2.5 + private var airGrappleStrength = 1.2 + private var airGrappleEnabled = true override fun onEnable() { server.pluginManager.registerEvents(this, this) @@ -58,6 +61,8 @@ class GrapplingHook : JavaPlugin(), Listener { writer.println("max-distance: 30.0") writer.println("show-particles: true") writer.println("hook-speed: 2.5") + writer.println("air-grapple-enabled: true") + writer.println("air-grapple-strength: 1.2") writer.close() } catch (e: Exception) { logger.severe("Could not create config.yml: ${e.message}") @@ -77,6 +82,8 @@ class GrapplingHook : JavaPlugin(), Listener { maxDistance = config.getDouble("max-distance", 30.0) showParticles = config.getBoolean("show-particles", true) hookSpeed = config.getDouble("hook-speed", 2.5) + airGrappleEnabled = config.getBoolean("air-grapple-enabled", true) + airGrappleStrength = config.getDouble("air-grapple-strength", 1.2) } override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array): Boolean { @@ -139,6 +146,7 @@ class GrapplingHook : JavaPlugin(), Listener { val lore = ArrayList() lore.add("${ChatColor.GRAY}Right-click to launch the hook") lore.add("${ChatColor.GRAY}Pull yourself to the hook location") + lore.add("${ChatColor.YELLOW}Right-click while hook is in air for a quick boost!") meta?.lore = lore meta?.isUnbreakable = true @@ -167,7 +175,7 @@ class GrapplingHook : JavaPlugin(), Listener { val player = event.player val item = player.inventory.itemInMainHand - if (!isGrapplingHook(item)) return + if (!isGrapplingHook(item) && !isGrapplingHook(player.inventory.itemInOffHand)) return when (event.state) { PlayerFishEvent.State.FISHING -> { @@ -185,6 +193,9 @@ class GrapplingHook : JavaPlugin(), Listener { 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) + + // Store the active hook for air grapples + activeHooks[uuid] = event.hook } PlayerFishEvent.State.IN_GROUND, PlayerFishEvent.State.CAUGHT_ENTITY -> { @@ -196,29 +207,47 @@ class GrapplingHook : JavaPlugin(), Listener { 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) - } + // Standard grapple when the hook hits something + pullPlayerTowards(player, hook.location.toVector(), pullStrength, distance) - direction.normalize().multiply(adjustedPullStrength) - direction.y = Math.max(0.4, direction.y) - player.velocity = direction + // Clean up + activeHooks.remove(player.uniqueId) + } - if (preventFallDamage) { - recentlyGrappled.add(player.uniqueId) - object : BukkitRunnable() { - override fun run() { - recentlyGrappled.remove(player.uniqueId) + PlayerFishEvent.State.REEL_IN -> { + // This is triggered when player right-clicks while the hook is in the air + if (airGrappleEnabled && activeHooks.containsKey(player.uniqueId)) { + val hook = activeHooks[player.uniqueId] + if (hook != null && !hook.isDead) { + // Air grapple logic - boost in the direction the player is looking + val lookDirection = player.location.direction + val boost = lookDirection.normalize().multiply(airGrappleStrength) + + // Add a slight upward boost to prevent players from smashing into the ground + if (boost.y < 0.2) { + boost.y = 0.2 } - }.runTaskLater(this, 100L) - } - player.world.playSound(player.location, Sound.ENTITY_ZOMBIE_INFECT, 1.0f, 2.0f) + player.velocity = boost - if (showParticles) { - showGrapplingParticles(player, hook) + 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_BAT_TAKEOFF, 1.0f, 1.5f) + + if (showParticles) { + showAirGrappleParticles(player) + } + } + + // Clean up + activeHooks.remove(player.uniqueId) } } @@ -226,6 +255,34 @@ class GrapplingHook : JavaPlugin(), Listener { } } + private fun pullPlayerTowards(player: Player, targetLocation: Vector, strength: Double, distance: Double) { + val direction = targetLocation.subtract(player.location.toVector()) + var adjustedPullStrength = strength + + 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, targetLocation.toLocation(player.world)) + } + } + @EventHandler fun onEntityDamage(event: EntityDamageEvent) { if (event.entity !is Player) return @@ -241,25 +298,25 @@ class GrapplingHook : JavaPlugin(), Listener { } } - private fun isGrapplingHook(item: ItemStack): Boolean { - if (item.type != Material.FISHING_ROD) return false + private fun isGrapplingHook(item: ItemStack?): Boolean { + if (item == null || 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) { + private fun showGrapplingParticles(player: Player, targetLocation: org.bukkit.Location) { object : BukkitRunnable() { - var distance = player.location.distance(hook.location) + var distance = player.location.distance(targetLocation) var traveled = 0.0 val particleSpacing = 0.3 override fun run() { - if (traveled >= distance || !player.isOnline || hook.isDead) { + if (traveled >= distance || !player.isOnline) { cancel() return } - val direction = hook.location.toVector().subtract(player.location.toVector()).normalize() + val direction = targetLocation.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) @@ -271,4 +328,33 @@ class GrapplingHook : JavaPlugin(), Listener { } }.runTaskTimer(this, 0L, 1L) } -} + + private fun showAirGrappleParticles(player: Player) { + // Create a swirl of particles around the player + object : BukkitRunnable() { + var counter = 0 + + override fun run() { + if (counter >= 10 || !player.isOnline) { + cancel() + return + } + + // Create a circular pattern + for (i in 0 until 8) { + val angle = i * Math.PI / 4 + val x = Math.cos(angle) * 0.7 + val z = Math.sin(angle) * 0.7 + val particleLocation = player.location.clone().add(x, counter * 0.1, z) + + player.world.spawnParticle(Particle.CLOUD, particleLocation, 1, 0.05, 0.05, 0.05, 0.01) + if (i % 2 == 0) { + player.world.spawnParticle(Particle.CRIT, particleLocation, 1, 0.05, 0.05, 0.05, 0.01) + } + } + + counter++ + } + }.runTaskTimer(this, 0L, 2L) + } +} \ No newline at end of file