This commit is contained in:
rattatwinko
2025-05-01 21:55:55 +02:00
commit 8f6af4a237
5 changed files with 605 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.CrossBowShoot</groupId>
<artifactId>crossbowshoot</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>crossbowshoot</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,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<String, Boolean>()
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
}
}

View File

@@ -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!."

View File

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