From 8f6af4a2375386628a8aadee9b9cc028b94374a1 Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Thu, 1 May 2025 21:55:55 +0200 Subject: [PATCH] initial --- .gitignore | 113 ++++++ pom.xml | 87 ++++ .../crossBowShoot/CrossBowShoot.kt | 371 ++++++++++++++++++ src/main/resources/config.yml | 18 + src/main/resources/plugin.yml | 16 + 5 files changed, 605 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/kotlin/org/CrossBowShoot/crossBowShoot/CrossBowShoot.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..f218528 --- /dev/null +++ b/pom.xml @@ -0,0 +1,87 @@ + + + 4.0.0 + + org.CrossBowShoot + crossbowshoot + 1.0-SNAPSHOT + jar + + crossbowshoot + + + 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/CrossBowShoot/crossBowShoot/CrossBowShoot.kt b/src/main/kotlin/org/CrossBowShoot/crossBowShoot/CrossBowShoot.kt new file mode 100644 index 0000000..ccd6948 --- /dev/null +++ b/src/main/kotlin/org/CrossBowShoot/crossBowShoot/CrossBowShoot.kt @@ -0,0 +1,371 @@ +package org.CrossBowShoot.crossBowShoot + +import org.bukkit.ChatColor +import org.bukkit.Material +import org.bukkit.Sound +import org.bukkit.entity.Player +import org.bukkit.entity.Projectile +import org.bukkit.event.EventHandler +import org.bukkit.event.Listener +import org.bukkit.event.entity.EntityDamageEvent +import org.bukkit.event.entity.ProjectileHitEvent +import org.bukkit.event.entity.ProjectileLaunchEvent +import org.bukkit.event.player.PlayerJoinEvent +import org.bukkit.inventory.ItemStack +import org.bukkit.metadata.FixedMetadataValue +import org.bukkit.plugin.java.JavaPlugin +import org.bukkit.util.Vector +import kotlin.math.max +import kotlin.math.min + +class CrossBowShoot : JavaPlugin(), Listener { + + private val rocketJumpEnabled = mutableMapOf() + private val configFilename = "config.yml" + + override fun onEnable() { + // Register event listener + server.pluginManager.registerEvents(this, this) + + // Set up config + setupConfig() + + // Register commands + registerCommands() + + // Register a repeating task for particle trails + server.scheduler.scheduleSyncRepeatingTask(this, Runnable { + handlePlayerTrails() + }, 1L, 1L) // Run every tick + + logger.info("CrossBowShoot plugin has been enabled!") + } + + override fun onDisable() { + logger.info("CrossBowShoot plugin has been disabled!") + } + + private fun setupConfig() { + // Create default config if it doesn't exist + saveDefaultConfig() + + // Load config settings + val config = config + if (!config.contains("settings.default-enabled")) { + config.set("settings.default-enabled", true) + config.set("settings.knockback-base", 1.5) + config.set("settings.knockback-max", 3.2) + config.set("settings.immunity-duration", 10) + config.set("settings.messages.enabled", "${ChatColor.GREEN}Rocket jump enabled!") + config.set("settings.messages.disabled", "${ChatColor.RED}Rocket jump disabled!") + config.set("settings.messages.welcome", "${ChatColor.GOLD}Use /rocketjump to toggle CrossbBow Jump.") + saveConfig() + } + } + + private fun registerCommands() { + // Register the rocketjump command + getCommand("rocketjump")?.setExecutor { sender, _, _, args -> + if (sender is Player) { + val uuid = sender.uniqueId.toString() + val isEnabled = rocketJumpEnabled.getOrDefault(uuid, config.getBoolean("settings.default-enabled")) + + if (args.isEmpty()) { + // Toggle rocket jump + rocketJumpEnabled[uuid] = !isEnabled + + if (!isEnabled) { + sender.sendMessage(config.getString("settings.messages.enabled") ?: "${ChatColor.GREEN}Rocket jump enabled!") + sender.playSound(sender.location, Sound.BLOCK_NOTE_BLOCK_PLING, 0.5f, 2.0f) + } else { + sender.sendMessage(config.getString("settings.messages.disabled") ?: "${ChatColor.RED}Rocket jump disabled!") + sender.playSound(sender.location, Sound.BLOCK_NOTE_BLOCK_BASS, 0.5f, 0.8f) + } + } else if (args[0].equals("status", ignoreCase = true)) { + // Check status + val status = if (isEnabled) + "${ChatColor.GREEN}ENABLED" + else + "${ChatColor.RED}DISABLED" + sender.sendMessage("${ChatColor.GOLD}Your rocket jump is currently: $status") + } + + return@setExecutor true + } + false + } + } + + @EventHandler + fun onPlayerJoin(event: PlayerJoinEvent) { + val player = event.player + + // Send welcome message if it's their first time with the plugin + if (!player.hasPlayedBefore() || !player.hasMetadata("crossbow_welcomed")) { + // Send welcome message after a short delay + server.scheduler.runTaskLater(this, Runnable { + player.sendMessage(config.getString("settings.messages.welcome") + ?: "${ChatColor.GOLD} Use /rocketjump to toggle Crossbow Jump.") + + // Mark as welcomed + player.setMetadata("crossbow_welcomed", FixedMetadataValue(this, true)) + }, 40L) // 2 second delay + } + } + + private fun handlePlayerTrails() { + // Find players who have recently been launched + for (player in server.onlinePlayers) { + // Check if player is moving fast enough (probably from knockback) + val velocity = player.velocity + val speed = velocity.length() + + if (speed > 0.8 && player.hasMetadata("crossbow_launched")) { + // How long ago they were launched + val launchTime = player.getMetadata("crossbow_launched")[0].asLong() + val currentTime = System.currentTimeMillis() + val timeSinceLaunch = currentTime - launchTime + + // Only create trails for 1.5 seconds after launch + if (timeSinceLaunch < 1500) { + // Add particles behind the player based on their movement direction + val oppositeDir = velocity.clone().multiply(-1).normalize() + val particleLocation = player.location.clone().add( + oppositeDir.x * 0.5, + 0.0, + oppositeDir.z * 0.5 + ) + + // Diminishing particle count over time + val particleCount = max(1, (10 * (1500 - timeSinceLaunch) / 1500)).toInt() + + // Smoke trail + player.world.spawnParticle( + org.bukkit.Particle.SMOKE, + particleLocation, + particleCount, 0.1, 0.1, 0.1, 0.01 + ) + } else { + // Clear metadata after effect duration ends + player.removeMetadata("crossbow_launched", this) + } + } + } + } + + @EventHandler + fun onEntityDamage(event: EntityDamageEvent) { + // Check if the entity is a player + if (event.entity is Player) { + val player = event.entity as Player + + // Check if damage is from fall damage + if (event.cause == EntityDamageEvent.DamageCause.FALL) { + // Check if player has fall immunity metadata + if (player.hasMetadata("fall_immune")) { + val immuneUntil = player.getMetadata("fall_immune")[0].asLong() + + // If current time is before immunity expiration + if (System.currentTimeMillis() < immuneUntil) { + // Cancel the damage + event.isCancelled = true + + // Create a small dust effect to indicate fall damage prevention + player.world.spawnParticle( + org.bukkit.Particle.CLOUD, + player.location.clone().add(0.0, 0.1, 0.0), + 8, 0.3, 0.0, 0.3, 0.05 + ) + } + } + } + } + } + + @EventHandler + fun onProjectileLaunch(event: ProjectileLaunchEvent) { + val projectile = event.entity + val shooter = projectile.shooter + + // Check if shooter is a player + if (shooter is Player) { + val player = shooter + val uuid = player.uniqueId.toString() + + // Check if rocket jumps are enabled for this player + val isEnabled = rocketJumpEnabled.getOrDefault(uuid, config.getBoolean("settings.default-enabled")) + if (!isEnabled) return + + // Check if player is using a crossbow with firework rockets + val mainHand = player.inventory.itemInMainHand + val offHand = player.inventory.itemInOffHand + + if (isHoldingCrossbowWithRockets(mainHand, offHand)) { + // Store the launch location and direction for later use + projectile.setMetadata("crossbowshoot", FixedMetadataValue(this, true)) + projectile.setMetadata("shooterLocation", FixedMetadataValue(this, player.location.clone())) + projectile.setMetadata("shooterDirection", FixedMetadataValue(this, player.location.direction.clone())) + + // Visual indicator that rocket jump is active + player.world.spawnParticle( + org.bukkit.Particle.FLAME, + player.location.clone().add(0.0, 1.8, 0.0), + 5, 0.1, 0.1, 0.1, 0.02 + ) + } + } + } + + @EventHandler + fun onProjectileHit(event: ProjectileHitEvent) { + val projectile = event.entity + + // Check if the projectile was fired by our plugin + if (projectile.hasMetadata("crossbowshoot") && !projectile.hasMetadata("processed")) { + val shooter = projectile.shooter + + // Make sure shooter is still a player + if (shooter is Player) { + val player = shooter + + // Only apply knockback if the projectile hit something + if (event.hitBlock != null || event.hitEntity != null) { + // Mark as processed to prevent multiple triggers + projectile.setMetadata("processed", FixedMetadataValue(this, true)) + applyKnockback(player, projectile) + } + } + } + } + + private fun applyKnockback(player: Player, projectile: Projectile) { + // Get config values + val baseKnockback = config.getDouble("settings.knockback-base", 1.5) + val maxKnockback = config.getDouble("settings.knockback-max", 3.2) + val immunityDuration = config.getInt("settings.immunity-duration", 10) * 1000L + + // Get the original shooting location and direction + val shooterLocation = projectile.getMetadata("shooterLocation")[0].value() as org.bukkit.Location + val shooterDirection = projectile.getMetadata("shooterDirection")[0].value() as Vector + + // Calculate distance between shot location and hit location + val distance = shooterLocation.distance(projectile.location) + + // Calculate the knockback strength (higher when shooting close to self) + // The closer the shot (smaller distance), the stronger the knockback + val knockbackStrength = max(baseKnockback, maxKnockback - min(distance / 10.0, maxKnockback - baseKnockback)) + + // Calculate the knockback vector (opposite to shooting direction) + val knockbackVector = shooterDirection.clone().multiply(-knockbackStrength) + + // Apply smoother upward force for a more fluid motion + // Scale the upward boost based on horizontal motion for natural arc + val horizontalSpeed = Math.sqrt(knockbackVector.x * knockbackVector.x + knockbackVector.z * knockbackVector.z) + knockbackVector.y = max(0.3, knockbackVector.y + (horizontalSpeed * 0.15)) + + // Apply the knockback to the player with a more fluid approach + // We'll use a scheduler to apply it after a tiny delay for better feel + server.scheduler.runTaskLater(this, Runnable { + // Apply velocity + player.velocity = knockbackVector + + // Set metadata for player to track trail effect and fall immunity + player.setMetadata("crossbow_launched", FixedMetadataValue(this, System.currentTimeMillis())) + player.setMetadata("fall_immune", FixedMetadataValue(this, System.currentTimeMillis() + immunityDuration)) + + // Create visual explosion effect at player's feet + player.world.spawnParticle( + org.bukkit.Particle.EXPLOSION, + player.location.clone().add(0.0, 0.5, 0.0), + 1, 0.0, 0.0, 0.0, 0.0 + ) + + // Add flame particles for rocket effect + player.world.spawnParticle( + org.bukkit.Particle.FLAME, + player.location.clone().add(0.0, 1.0, 0.0), + 15, 0.2, 0.2, 0.2, 0.05 + ) + + // Add smoke trail + player.world.spawnParticle( + org.bukkit.Particle.SMOKE, + player.location.clone().add(0.0, 1.0, 0.0), + 10, 0.2, 0.2, 0.2, 0.03 + ) + + // Play sound effect (only once) + player.world.playSound(player.location, org.bukkit.Sound.ENTITY_GENERIC_EXPLODE, 0.7f, 1.3f) + + // Reset fall distance immediately and periodically + player.fallDistance = 0f + + // Create a colored particle ring showing the effect is active + createParticleRing(player.location.clone().add(0.0, 0.1, 0.0), 1.0, 16) + + // Schedule periodic fall distance resets + var taskId = -1 + taskId = server.scheduler.scheduleSyncRepeatingTask(this, Runnable { + if (player.isOnline && player.hasMetadata("fall_immune")) { + val endTime = player.getMetadata("fall_immune")[0].asLong() + if (System.currentTimeMillis() < endTime) { + // Reset fall distance every tick while immunity is active + player.fallDistance = 0f + } else { + // Immunity expired, remove metadata and cancel task + player.removeMetadata("fall_immune", this) + if (taskId != -1) { + server.scheduler.cancelTask(taskId) + } + } + } else if (taskId != -1) { + // Player offline or metadata removed, cancel task + server.scheduler.cancelTask(taskId) + } + }, 1L, 1L) + }, 1L) // 1 tick delay for more natural feel + } + + private fun createParticleRing(location: org.bukkit.Location, radius: Double, count: Int) { + for (i in 0 until count) { + val angle = 2 * Math.PI * i / count + val x = radius * Math.cos(angle) + val z = radius * Math.sin(angle) + location.world.spawnParticle( + org.bukkit.Particle.FLAME, + location.clone().add(x, 0.0, z), + 1, 0.0, 0.0, 0.0, 0.0, + org.bukkit.Particle.DustOptions(org.bukkit.Color.fromRGB(255, 100, 0), 1.0f) + ) + } + } + + private fun isHoldingCrossbowWithRockets(mainHand: ItemStack, offHand: ItemStack): Boolean { + // Check if player is holding a crossbow + val holdingCrossbow = mainHand.type == Material.CROSSBOW || offHand.type == Material.CROSSBOW + + // Check if player has firework rockets in inventory + val hasRockets = mainHand.type == Material.FIREWORK_ROCKET || + offHand.type == Material.FIREWORK_ROCKET || + isCrossbowLoadedWithRocket(mainHand) || + isCrossbowLoadedWithRocket(offHand) + + return holdingCrossbow && hasRockets + } + + private fun isCrossbowLoadedWithRocket(item: ItemStack): Boolean { + if (item.type != Material.CROSSBOW) { + return false + } + + // Check crossbow metadata for loaded projectiles + val meta = item.itemMeta + if (meta is org.bukkit.inventory.meta.CrossbowMeta) { + // Check if crossbow is loaded (has charged projectiles) + return meta.hasChargedProjectiles() && + meta.chargedProjectiles.any { it.type == Material.FIREWORK_ROCKET } + } + + return false + } +} \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..d9643a2 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,18 @@ +# CrossBowShoot Configuration + +settings: + # Whether rocket jumps are enabled by default for new players + default-enabled: true + + # Knockback values + knockback-base: 1.5 # Minimum knockback strength + knockback-max: 3.2 # Maximum knockback strength + + # Fall immunity duration in seconds + immunity-duration: 10 + + # Messages + messages: + enabled: "&aRocket jump enabled!" + disabled: "&cRocket jump disabled!" + welcome: "&6Use /rocketjump to toggle Crossbow Jumping!." \ 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..92c2623 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,16 @@ +name: CrossBowShoot +version: 1.0-SNAPSHOT +main: org.CrossBowShoot.crossBowShoot.CrossBowShoot +api-version: 1.21.5 +description: A plugin that makes players get knocked back when shooting a crossbow with rockets +author: YourName +commands: + rocketjump: + description: Toggle the rocket jump functionality + usage: /rocketjump [status] + aliases: [rj, rocket] + permission: crossbowshoot.use +permissions: + crossbowshoot.use: + description: Allows players to use the rocket jump feature + default: true \ No newline at end of file