From 1c724873409e491484753869f1a7252c6b6cbb13 Mon Sep 17 00:00:00 2001 From: Cizetux Date: Sat, 30 Mar 2024 15:55:45 +0100 Subject: [PATCH 01/23] feat(commands): Add commands to easily manage games --- .../rushyverse/rtf/commands/RTFCommand.kt | 52 +++++++++++++++---- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/rtf/commands/RTFCommand.kt b/src/main/kotlin/com/github/rushyverse/rtf/commands/RTFCommand.kt index 300ff0b..73d788c 100644 --- a/src/main/kotlin/com/github/rushyverse/rtf/commands/RTFCommand.kt +++ b/src/main/kotlin/com/github/rushyverse/rtf/commands/RTFCommand.kt @@ -30,18 +30,18 @@ class RTFCommand( suspend fun register() { commandAPICommand("rtf") { - subcommand("spectate") { - - withArguments(IntegerArgument("game")) - + subcommand("create") { + withArguments(IntegerArgument("gameID")) playerExecutor { player, args -> val gameIndex = args[0] as Int var game = games.getGame(gameIndex) plugin.launch { - if (game == null && gameIndex == 1) { + if (game == null) { game = games.createAndSave(gameIndex) + } else { + player.sendMessage("game.already.exists") } game?.clientSpectate(clients.getClient(player) as ClientRTF) @@ -49,6 +49,35 @@ class RTFCommand( } } + subcommand("list") { + playerExecutor { player, _ -> + val length = games.games.size + player.sendMessage("List of games ($length):") + for (game in games.games) { + player.sendMessage("#${game.id} - ${game.players.size}/${game.config.game.maxGames} - ${game.state()}") + } + } + } + + subcommand("spectate") { + withArguments(IntegerArgument("gameID")) + playerExecutor { player, args -> + val gameIndex = args[0] as Int + var game = games.getGame(gameIndex) + + if (game == null) { + player.sendMessage("game.not.exists") + } else { + plugin.launch { + + game = games.createAndSave(gameIndex) + + game?.clientSpectate(clients.getClient(player) as ClientRTF) + } + } + } + } + subcommand("join") { playerExecutor { player, _ -> val game = games.getByWorld(player.world) ?: return@playerExecutor @@ -105,10 +134,15 @@ class RTFCommand( subcommand("end") { withPermission("rtf.end") - playerExecutor { player, _ -> - val game = games.getByWorld(player.world) ?: return@playerExecutor - - plugin.launch { game.end(null) } + withArguments(IntegerArgument("gameID")) + playerExecutor { player, args -> + val gameId = args[0] as Int + player.sendMessage("game.ask.delete") + val game = games.getGame(gameId) + plugin.launch { + game?.end(null) + player.sendMessage("game.deleted") + } } } } From 4a4371236b28bbcddd9321f7b8601bc5f4ca15cd Mon Sep 17 00:00:00 2001 From: Cizetux Date: Fri, 12 Apr 2024 15:02:26 +0200 Subject: [PATCH 02/23] feat(commands): add permission checks to subcommands --- .../github/rushyverse/rtf/commands/RTFCommand.kt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/rtf/commands/RTFCommand.kt b/src/main/kotlin/com/github/rushyverse/rtf/commands/RTFCommand.kt index 73d788c..5b5eb0f 100644 --- a/src/main/kotlin/com/github/rushyverse/rtf/commands/RTFCommand.kt +++ b/src/main/kotlin/com/github/rushyverse/rtf/commands/RTFCommand.kt @@ -32,6 +32,7 @@ class RTFCommand( subcommand("create") { withArguments(IntegerArgument("gameID")) + withPermission("rtf.command.create") playerExecutor { player, args -> val gameIndex = args[0] as Int var game = games.getGame(gameIndex) @@ -42,6 +43,7 @@ class RTFCommand( game = games.createAndSave(gameIndex) } else { player.sendMessage("game.already.exists") + return@launch } game?.clientSpectate(clients.getClient(player) as ClientRTF) @@ -50,6 +52,7 @@ class RTFCommand( } subcommand("list") { + withPermission("rtf.command.list") playerExecutor { player, _ -> val length = games.games.size player.sendMessage("List of games ($length):") @@ -61,24 +64,19 @@ class RTFCommand( subcommand("spectate") { withArguments(IntegerArgument("gameID")) + withPermission("rtf.command.spectate") playerExecutor { player, args -> val gameIndex = args[0] as Int - var game = games.getGame(gameIndex) + val game = games.getGame(gameIndex) if (game == null) { player.sendMessage("game.not.exists") - } else { - plugin.launch { - - game = games.createAndSave(gameIndex) - - game?.clientSpectate(clients.getClient(player) as ClientRTF) - } } } } subcommand("join") { + withPermission("rtf.command.join") playerExecutor { player, _ -> val game = games.getByWorld(player.world) ?: return@playerExecutor From 1a769a41a22393374b05cea450b4abf9f0274042 Mon Sep 17 00:00:00 2001 From: Cizetux Date: Wed, 29 May 2024 15:53:47 +0200 Subject: [PATCH 03/23] feat: add new unwanted events --- .../rtf/listener/UndesirableEventListener.kt | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/rtf/listener/UndesirableEventListener.kt b/src/main/kotlin/com/github/rushyverse/rtf/listener/UndesirableEventListener.kt index e6eb6ff..d02c6bd 100644 --- a/src/main/kotlin/com/github/rushyverse/rtf/listener/UndesirableEventListener.kt +++ b/src/main/kotlin/com/github/rushyverse/rtf/listener/UndesirableEventListener.kt @@ -2,14 +2,18 @@ package com.github.rushyverse.rtf.listener import com.github.rushyverse.api.extension.event.cancelIf import org.bukkit.event.EventHandler +import org.bukkit.event.entity.EntityDamageEvent import org.bukkit.event.entity.FoodLevelChangeEvent +import org.bukkit.event.entity.PlayerDeathEvent import org.bukkit.event.player.PlayerDropItemEvent import org.bukkit.event.weather.WeatherChangeEvent class UndesirableEventListener : ListenerRTF() { @EventHandler - fun onFoodLevelChange(event: FoodLevelChangeEvent) = event.cancelIf { isRTFWorld(event.entity.world) } + fun onFoodLevelChange(event: FoodLevelChangeEvent) = event.cancelIf { + isRTFWorld(event.entity.world) + } @EventHandler fun onWeatherChange(event: WeatherChangeEvent) = event.cancelIf { @@ -17,5 +21,15 @@ class UndesirableEventListener : ListenerRTF() { } @EventHandler - fun onPlayerDropItem(event: PlayerDropItemEvent) = event.cancelIf { isRTFWorld(event.player.world) } + fun onPlayerDropItem(event: PlayerDropItemEvent) = event.cancelIf { + isRTFWorld(event.player.world) + } + + @EventHandler + fun onPlayerDeath(event: PlayerDeathEvent) { + val player = event.entity + if (isRTFWorld(player.world) && player.lastDamageCause?.cause == EntityDamageEvent.DamageCause.FALL) { + event.drops.clear() + } + } } From 1cb6aeb84252fc32d1fcbda2eccef0d8e9cc0d98 Mon Sep 17 00:00:00 2001 From: Cizetux Date: Wed, 29 May 2024 15:56:04 +0200 Subject: [PATCH 04/23] feat: add kit feature system for custom items --- .../github/rushyverse/rtf/kit/AutoBridge.kt | 76 +++++++++++++ .../github/rushyverse/rtf/kit/KitFeature.kt | 24 ++++ .../github/rushyverse/rtf/kit/LadderFly.kt | 60 ++++++++++ .../rushyverse/rtf/kit/SnowballSwitch.kt | 43 +++++++ .../com/github/rushyverse/rtf/kit/TntFly.kt | 105 ++++++++++++++++++ 5 files changed, 308 insertions(+) create mode 100644 src/main/kotlin/com/github/rushyverse/rtf/kit/AutoBridge.kt create mode 100644 src/main/kotlin/com/github/rushyverse/rtf/kit/KitFeature.kt create mode 100644 src/main/kotlin/com/github/rushyverse/rtf/kit/LadderFly.kt create mode 100644 src/main/kotlin/com/github/rushyverse/rtf/kit/SnowballSwitch.kt create mode 100644 src/main/kotlin/com/github/rushyverse/rtf/kit/TntFly.kt diff --git a/src/main/kotlin/com/github/rushyverse/rtf/kit/AutoBridge.kt b/src/main/kotlin/com/github/rushyverse/rtf/kit/AutoBridge.kt new file mode 100644 index 0000000..7a27fa1 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/rtf/kit/AutoBridge.kt @@ -0,0 +1,76 @@ +package com.github.rushyverse.rtf.kit + +import com.github.rushyverse.api.extension.ItemStack +import com.github.rushyverse.api.game.GameState +import com.github.rushyverse.rtf.client.ClientRTF +import org.bukkit.Bukkit +import org.bukkit.Material +import org.bukkit.block.Block +import org.bukkit.block.BlockFace +import org.bukkit.event.EventHandler +import org.bukkit.event.block.BlockPlaceEvent +import org.bukkit.util.Vector +import org.koin.core.qualifier.named + +class AutoBridge : KitFeature("autobridge") { + override fun item() = ItemStack { + type = Material.REPEATER + named("Autobridge") + } + + override fun onGiveKit(client: ClientRTF) { + client.player?.inventory?.addItem(item()) + } + + @EventHandler + fun onBlockPlace(event: BlockPlaceEvent) { + val player = event.player + val block = event.block + if (event.isCancelled) return + if (!isRTFWorld(block.world)) return + if (games.getGame(block.world)?.state() == GameState.STARTED) { + if (block.type == Material.REPEATER) { + createBridge(block, Material.SMOOTH_SANDSTONE, 6, player.location.direction) + } + } + } + + private fun createBridge(startBlock: Block, material: Material, length: Int, direction: Vector) { + val horizontalDirections = listOf(BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST) + val delay = 3L // Delay in ticks (20 ticks = 1 second) + + // Check if the direction is horizontal + val blockFaceDirection = direction.toHorizontalBlockFace() + if (blockFaceDirection !in horizontalDirections) { + return // Exit function if direction is not horizontal + } + + // Loop through each block in the bridge + for (i in 1..length) { + // Schedule the block placement with a delay + Bukkit.getScheduler().runTaskLater(plugin, Runnable { + val nextBlock = startBlock.getRelative(blockFaceDirection, i).getRelative(BlockFace.DOWN) + if (nextBlock.type == Material.AIR) { + nextBlock.type = material + } + }, delay * i) + } + } + + // Extension function to convert Vector to horizontal BlockFace + private fun Vector.toHorizontalBlockFace(): BlockFace { + val directions = listOf( + BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST + ) + var closestDirection = BlockFace.NORTH + var closestDot = Double.NEGATIVE_INFINITY + for (direction in directions) { + val dot = this.dot(direction.direction) + if (dot > closestDot) { + closestDot = dot + closestDirection = direction + } + } + return closestDirection + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/rtf/kit/KitFeature.kt b/src/main/kotlin/com/github/rushyverse/rtf/kit/KitFeature.kt new file mode 100644 index 0000000..e19e333 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/rtf/kit/KitFeature.kt @@ -0,0 +1,24 @@ +package com.github.rushyverse.rtf.kit + +import com.github.rushyverse.rtf.client.ClientRTF +import com.github.rushyverse.rtf.listener.ListenerRTF +import org.bukkit.inventory.ItemStack + +abstract class KitFeature(val name: String) : ListenerRTF() { + + companion object { + /** + * Static map of the registered KitFeatures. + * Each element is mapped automatically during the object initialization. + */ + val featureMap = mutableMapOf() + } + + init { + featureMap[name] = this + } + + abstract fun item() : ItemStack + + abstract fun onGiveKit(client: ClientRTF) +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/rtf/kit/LadderFly.kt b/src/main/kotlin/com/github/rushyverse/rtf/kit/LadderFly.kt new file mode 100644 index 0000000..6e5e46c --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/rtf/kit/LadderFly.kt @@ -0,0 +1,60 @@ +package com.github.rushyverse.rtf.kit + +import com.github.rushyverse.api.extension.ItemStack +import com.github.rushyverse.api.game.GameState +import com.github.rushyverse.rtf.client.ClientRTF +import org.bukkit.Material +import org.bukkit.block.BlockFace +import org.bukkit.entity.Player +import org.bukkit.event.EventHandler +import org.bukkit.event.player.PlayerInteractEvent +import org.bukkit.util.Vector +import org.koin.core.qualifier.named + +class LadderFly : KitFeature("ladderfly") { + + override fun item() = ItemStack { + type = Material.LADDER + named("Ladderfly") + } + + override fun onGiveKit(client: ClientRTF) { + client.player?.inventory?.addItem(item()) + } + + private val ladderflyCooldown = mutableMapOf() + + @EventHandler + fun onPlayerInteract(event: PlayerInteractEvent) { + val player = event.player + val clickedBlock = event.clickedBlock ?: return + if (event.material != Material.LADDER) return + if (event.isCancelled) return + if (!isRTFWorld(player.world)) return + + if (games.getGame(player.world)?.state() == GameState.STARTED) + + if (isLadderOnSide(event.blockFace)) { + applyLadderfly(player) + } + } + + private fun isLadderOnSide(face: BlockFace): Boolean { + // Check if the ladder is placed on the side of a block + return face != BlockFace.UP && face != BlockFace.DOWN + } + + private fun applyLadderfly(player: Player) { + if (ladderflyCooldown.containsKey(player)) return // Check if cooldown is active + ladderflyCooldown[player] = System.currentTimeMillis() // Set cooldown start time + + val velocity = Vector(0.0, 1.0, 0.0) // Upward velocity + player.velocity = velocity + + // Schedule cooldown expiration + plugin.server.scheduler.runTaskLater(plugin, Runnable { + ladderflyCooldown.remove(player) + }, 20L) // 20 ticks cooldown (1 second) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/rtf/kit/SnowballSwitch.kt b/src/main/kotlin/com/github/rushyverse/rtf/kit/SnowballSwitch.kt new file mode 100644 index 0000000..ccf7827 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/rtf/kit/SnowballSwitch.kt @@ -0,0 +1,43 @@ +package com.github.rushyverse.rtf.kit + +import com.github.rushyverse.api.extension.ItemStack +import com.github.rushyverse.rtf.client.ClientRTF +import org.bukkit.Material +import org.bukkit.entity.EntityType +import org.bukkit.entity.Player +import org.bukkit.entity.Snowball +import org.bukkit.event.EventHandler +import org.bukkit.event.entity.EntityDamageByEntityEvent +import org.koin.core.qualifier.named + +class SnowballSwitch : KitFeature("snowballswitch") { + override fun item() = ItemStack { + type = Material.SNOWBALL + named("Snowballswitch") + } + + override fun onGiveKit(client: ClientRTF) { + client.player?.inventory?.addItem(item()) + } + + @EventHandler + fun onEntityDamageByEntity(event: EntityDamageByEntityEvent) { + if (event.damager.type == EntityType.SNOWBALL && event.entity is Player) { + val snowball = event.damager as Snowball + val hitPlayer = event.entity as Player + val shooter = snowball.shooter + + if (event.isCancelled) return + if (!isRTFWorld(snowball.world)) return + + if (shooter is Player) { + val shooterLocation = shooter.location + val hitPlayerLocation = hitPlayer.location + + // Swap the positions + shooter.teleport(hitPlayerLocation) + hitPlayer.teleport(shooterLocation) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/rtf/kit/TntFly.kt b/src/main/kotlin/com/github/rushyverse/rtf/kit/TntFly.kt new file mode 100644 index 0000000..c1bc282 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/rtf/kit/TntFly.kt @@ -0,0 +1,105 @@ +package com.github.rushyverse.rtf.kit + +import com.github.rushyverse.api.extension.ItemStack +import com.github.rushyverse.api.game.GameState +import com.github.rushyverse.rtf.client.ClientRTF +import net.kyori.adventure.text.Component.text +import org.bukkit.Bukkit +import org.bukkit.ChatColor +import org.bukkit.Location +import org.bukkit.Material +import org.bukkit.entity.ArmorStand +import org.bukkit.entity.EntityType +import org.bukkit.entity.Player +import org.bukkit.entity.TNTPrimed +import org.bukkit.event.EventHandler +import org.bukkit.event.block.BlockPlaceEvent +import org.bukkit.event.entity.EntityExplodeEvent +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.meta.ItemMeta +import org.bukkit.potion.PotionEffect +import org.bukkit.potion.PotionEffectType +import org.koin.core.qualifier.named + +class TntFly : KitFeature("tntfly") { + + override fun item() = ItemStack { + type = Material.TNT + named("Tntfly") + } + + override fun onGiveKit(client: ClientRTF) { + client.player?.inventory?.addItem(item()) + } + + fun createTNTFlyItem(): ItemStack { + val item = ItemStack(Material.TNT) + val meta: ItemMeta = item.itemMeta!! + meta.setDisplayName("${ChatColor.RED}TNT Fly") + item.itemMeta = meta + return item + } + + @EventHandler + fun onBlockPlace(event: BlockPlaceEvent) { + val player = event.player + val block = event.block + if (event.isCancelled) return + if (!isRTFWorld(block.world)) return + if (games.getGame(block.world)?.state() != GameState.STARTED) + return + + if (block.type == Material.TNT) { + event.isCancelled = true // Prevent default block placement + + // Spawn a primed TNT with a delay of 60 ticks (3 seconds) + val tnt = block.world.spawnEntity(block.location.add(0.5, 0.0, 0.5), EntityType.PRIMED_TNT) as TNTPrimed + tnt.fuseTicks = 60 + + // Apply damage resistance to the player + player.addPotionEffect(PotionEffect(PotionEffectType.DAMAGE_RESISTANCE, 70, 4, true, false, false)) + + // Create an armor stand for the hologram + val hologram = createHologram(block.location.add(0.5, 1.5, 0.5), "3") + + // Schedule the countdown and player launch + for (i in 1..3) { + val countdown = 3 - i + Bukkit.getScheduler().runTaskLater(plugin, Runnable { + if (tnt.isValid) { + hologram.customName = countdown.toString() + if (countdown == 0) { + hologram.remove() + val explosionLocation = tnt.location + val players = explosionLocation.world.getNearbyEntities(explosionLocation, 5.0, 5.0, 5.0) + .filterIsInstance() + for (p in players) { + val direction = p.location.toVector().subtract(explosionLocation.toVector()).normalize() + val launchVector = direction.multiply(1.5).setY(1.5) + p.velocity = launchVector + } + } + } + }, (20L * i)) + } + } + } + + @EventHandler + fun onEntityExplode(event: EntityExplodeEvent) { + if (event.entity.type == EntityType.PRIMED_TNT) { + event.blockList().clear() // Prevent block damage + } + } + + private fun createHologram(location: Location, text: String): ArmorStand { + val hologram = location.world.spawnEntity(location, EntityType.ARMOR_STAND) as ArmorStand + hologram.isVisible = false + hologram.isCustomNameVisible = true + hologram.customName(text(text)) + hologram.isMarker = true + hologram.isSmall = true + hologram.setGravity(false) + return hologram + } +} \ No newline at end of file From e06140e2ae7618893795de1c36309e6863749155 Mon Sep 17 00:00:00 2001 From: Cizetux Date: Sun, 2 Jun 2024 21:17:37 +0200 Subject: [PATCH 05/23] chore: update commandAPIVersion --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 56eb849..3c15fcb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,7 +18,7 @@ repositories { dependencies { val paperVersion = "1.20-R0.1-SNAPSHOT" val rushyApiVersion = "2.1.0" - val commandApiVersion = "9.0.3" + val commandApiVersion = "9.4.1" compileOnly(kotlin("stdlib")) From a3ea84e3ec619299c9f705175e74ddb9f22f7540 Mon Sep 17 00:00:00 2001 From: Cizetux Date: Mon, 3 Jun 2024 10:08:15 +0200 Subject: [PATCH 06/23] feat: update map config with minPlayers and mapCuboid settings --- src/main/resources/maps.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/resources/maps.yml b/src/main/resources/maps.yml index 1634f8d..500d2ac 100644 --- a/src/main/resources/maps.yml +++ b/src/main/resources/maps.yml @@ -1,6 +1,15 @@ - ! worldTemplateName: mapClassic.yml - limitY: 40 + minPlayers: 4 + mapCuboid: + location1: + x: 42 + y: 40 + z: 83 + location2: + x: -46 + y: 110 + z: -85 allowedBlocks: - SMOOTH_SANDSTONE teams: From d256c0e235c8fed8562f3b004a9b0fac7ef367fe Mon Sep 17 00:00:00 2001 From: Cizetux Date: Mon, 3 Jun 2024 17:11:06 +0200 Subject: [PATCH 07/23] refactor: use getGame instead of getByWorld --- .../github/rushyverse/rtf/listener/AuthenticationListener.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/github/rushyverse/rtf/listener/AuthenticationListener.kt b/src/main/kotlin/com/github/rushyverse/rtf/listener/AuthenticationListener.kt index d290a16..bf1ea94 100644 --- a/src/main/kotlin/com/github/rushyverse/rtf/listener/AuthenticationListener.kt +++ b/src/main/kotlin/com/github/rushyverse/rtf/listener/AuthenticationListener.kt @@ -10,7 +10,7 @@ class AuthenticationListener : ListenerRTF() { suspend fun onQuit(event: PlayerQuitEvent) { val player = event.player val world = player.world - val game = games.getByWorld(world) + val game = games.getGame(world) game?.apply { clientLeave(clients.getClient(player) as ClientRTF) From 5473c1b95cf0d4602e11f19e1ac3d290edf6e36c Mon Sep 17 00:00:00 2001 From: Cizetux Date: Mon, 3 Jun 2024 17:12:36 +0200 Subject: [PATCH 08/23] feat: add minPlayers and mapCuboid --- src/main/kotlin/com/github/rushyverse/rtf/config/MapConfig.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/github/rushyverse/rtf/config/MapConfig.kt b/src/main/kotlin/com/github/rushyverse/rtf/config/MapConfig.kt index 000362f..9357153 100644 --- a/src/main/kotlin/com/github/rushyverse/rtf/config/MapConfig.kt +++ b/src/main/kotlin/com/github/rushyverse/rtf/config/MapConfig.kt @@ -11,7 +11,8 @@ import org.bukkit.Material @SerialName("map") data class MapConfig( val worldTemplateName: String, - val limitY: Int, + val minPlayers: Int, + val mapCuboid: CubeArea, val allowedBlocks: Set, val teams: List ) From f33ccc750394587c7a04ff4a7104f848760cd567 Mon Sep 17 00:00:00 2001 From: Cizetux Date: Mon, 3 Jun 2024 17:15:01 +0200 Subject: [PATCH 09/23] refactor: improve world object copying --- .../com/github/rushyverse/rtf/game/TeamRTF.kt | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/rtf/game/TeamRTF.kt b/src/main/kotlin/com/github/rushyverse/rtf/game/TeamRTF.kt index 543ceee..552aecc 100644 --- a/src/main/kotlin/com/github/rushyverse/rtf/game/TeamRTF.kt +++ b/src/main/kotlin/com/github/rushyverse/rtf/game/TeamRTF.kt @@ -1,5 +1,7 @@ package com.github.rushyverse.rtf.game +import com.github.rushyverse.api.extension.copy +import com.github.rushyverse.api.world.CubeArea import com.github.rushyverse.rtf.client.ClientRTF import com.github.rushyverse.rtf.config.TeamRTFConfig import org.bukkit.World @@ -10,25 +12,25 @@ class TeamRTF( ) { val type get() = config.type - val spawnPoint = config.spawnPoint.also { it.world = world } - val spawnCuboid = config.spawnCuboid.also { - it.min.world = world - it.max.world = world - } - val flagPoint = config.flagPoint.also { it.world = world } - val flagCuboid = config.flagCuboid.also { - it.min.world = world - it.max.world = world - } + val spawnPoint = config.spawnPoint.copy(world = world) + val spawnCuboid = CubeArea( + config.spawnCuboid.min.copy(world = world), + config.spawnCuboid.max.copy(world = world) + ) + val flagPoint = config.flagPoint.copy(world = world) + val flagCuboid = CubeArea( + config.flagCuboid.min.copy(world = world), + config.flagCuboid.max.copy(world = world) + ) - val flagMaterial = config.flagMaterial +val flagMaterial = config.flagMaterial - val members = mutableListOf() - var flagStolenState = false - set(value) { - field = value - if (!value) { - flagPoint.block.type = flagMaterial - } +val members = mutableListOf() +var flagStolenState = false + set(value) { + field = value + if (!value) { + flagPoint.block.type = flagMaterial } + } } \ No newline at end of file From e64d99caea3eeafe3a48a87df2aa2a3a3d2e22ae Mon Sep 17 00:00:00 2001 From: Cizetux Date: Mon, 3 Jun 2024 17:17:02 +0200 Subject: [PATCH 10/23] refactor: update kit sending method --- src/main/kotlin/com/github/rushyverse/rtf/gui/KitsGUI.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/rtf/gui/KitsGUI.kt b/src/main/kotlin/com/github/rushyverse/rtf/gui/KitsGUI.kt index 7f517b3..021fb80 100644 --- a/src/main/kotlin/com/github/rushyverse/rtf/gui/KitsGUI.kt +++ b/src/main/kotlin/com/github/rushyverse/rtf/gui/KitsGUI.kt @@ -3,6 +3,7 @@ package com.github.rushyverse.rtf.gui import com.github.rushyverse.api.extension.withoutItalic import com.github.rushyverse.api.player.Client import com.github.rushyverse.api.translation.getComponent +import com.github.rushyverse.rtf.client.ClientRTF import com.github.rushyverse.rtf.config.Kit import com.github.rushyverse.rtf.config.KitsConfig import com.github.rushyverse.rtf.gui.commons.GUI @@ -31,7 +32,7 @@ class KitsGUI( .withoutItalic() ) ) - meta.addItemFlags(*ItemFlag.entries.toTypedArray()) + meta.addItemFlags(*ItemFlag.entries.toTypedArray()) } } @@ -49,7 +50,7 @@ class KitsGUI( client.requirePlayer().inventory.apply { clear() - selectedKit.sendItems(this) + selectedKit.giveKit(client as ClientRTF) } } } From 292d84dc83479a0b2ddb53ed6a678584072f4327 Mon Sep 17 00:00:00 2001 From: Cizetux Date: Wed, 3 Jul 2024 12:53:43 +0200 Subject: [PATCH 11/23] feat: add respawnState and initial coins --- src/main/kotlin/com/github/rushyverse/rtf/client/ClientRTF.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/kotlin/com/github/rushyverse/rtf/client/ClientRTF.kt b/src/main/kotlin/com/github/rushyverse/rtf/client/ClientRTF.kt index 3960fff..76d691d 100644 --- a/src/main/kotlin/com/github/rushyverse/rtf/client/ClientRTF.kt +++ b/src/main/kotlin/com/github/rushyverse/rtf/client/ClientRTF.kt @@ -9,9 +9,12 @@ import java.util.* class ClientRTF( val stats: RTFStats = RTFStats(), uuid: UUID, + val coins: Int = 0, scope: CoroutineScope ) : Client(uuid, scope) { + var respawnState: Boolean = false + /** * Represents the current kit of the player. * Null by default. From 10ee0daee96d9b985bbd7aecc482a87a287df1a3 Mon Sep 17 00:00:00 2001 From: Cizetux Date: Wed, 3 Jul 2024 13:02:28 +0200 Subject: [PATCH 12/23] chore: add some translations --- src/main/resources/rtf_translate.properties | 35 ++++++++++++++++--- .../resources/rtf_translate_en_GB.properties | 6 +++- .../resources/rtf_translate_es_ES.properties | 2 +- .../resources/rtf_translate_fr_FR.properties | 6 +++- 4 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/main/resources/rtf_translate.properties b/src/main/resources/rtf_translate.properties index 91c1f65..30c09a4 100644 --- a/src/main/resources/rtf_translate.properties +++ b/src/main/resources/rtf_translate.properties @@ -1,4 +1,5 @@ -game.message.starting=The game will start in {0}s +# rtfcommands +game.message.starting=The game will start in {0}s game.message.started=Game starts, good luck! game.message.client.leave.starting=There are not enough players to start the game. join.already.in.team=You are already in a team. @@ -7,7 +8,7 @@ player.pickup.flag={0} picked the {1} flag! player.pickup.flag.info=You are in possession of the enemy flag. Your goal is to return home to place it below your flag. player.place.flag={0} placed the {1} flag! player.pickup.own.flag=You cant pick your own team flag! -scoreboard.waiting=Waiting for players +scoreboard.waiting=Waiting for players {0}/{1} scoreboard.starting=Game starts in {0} scoreboard.started=Game time: {0} scoreboard.ending=WINNER! {0} @@ -22,15 +23,41 @@ scoreboard.flag.placed={0}: In place game.end.win=The {0} team has won the game! game.end.other=The game must have ended suddenly. menu.kits.title=Choose a kit + +# rtfcommands +game.already.exists=A rtf instance already exists! + +# kits kit.rusher.name=Rusher -kit.rusher.description=The most basic kit in the Rushyverse! +kit.rusher.description=The most basic kit! +kit.tank.name=Tank +kit.tank.description=The final boss is coming! +kit.archer.name=Archer +kit.archer.description=Do not spam too much arrows! +kit.engineer.name=Engineer +kit.engineer.description=Be smart, and use your utilities! +kit.switcher.name=Switcher +kit.switcher.description=Outsmart your opponents by switching places! +kit.kamikaze.name=Kamikaze +kit.kamikaze.description=Go out with a bang and take your enemies with you! +kit.speedster.name=Speedster +kit.speedster.description=Zoom past your enemies with incredible speed! +kit.nokit.name=NoKit +kit.nokit.description=Enter the battle with nothing but your wits! + +# Win game.end.time=The elapsed time is {0}. game.end.top=Top 3 best players: game.end.top.1=1st - {0} game.end.top.2=2nd - {0} game.end.top.3=3rd - {0} + game.player.spectate=You are currently a spectator of the game. To join the game, use {0}. game.player.spectate.hover=Click to join! -player.death.killed={0} was killed by {1} +player.death.killed={0} has been killed by {1} player.death.void={0} fell into the void +player.death.fall={0} broke his legs player.death.with.flag={0} with the {1} flag! +respawn.time.remaining=Respawn in {0}... +respawn.end.go=GO ! +game.not.exists=This game not exists. diff --git a/src/main/resources/rtf_translate_en_GB.properties b/src/main/resources/rtf_translate_en_GB.properties index 5c4b22e..3899095 100644 --- a/src/main/resources/rtf_translate_en_GB.properties +++ b/src/main/resources/rtf_translate_en_GB.properties @@ -6,7 +6,7 @@ player.join.team={0} joined {1} team. player.pickup.flag={0} picked the {1} flag! player.place.flag={0} placed the {1} flag! cant.break.your.team.flag=You cant pick your own team flag! -scoreboard.waiting=Waiting for players +scoreboard.waiting=Waiting for players {0}/{1} scoreboard.starting=Game starts in {0} scoreboard.started=Game time: {0} scoreboard.ending=WINNER ! {0} @@ -28,3 +28,7 @@ game.end.top=Top 3 best players: game.end.top.1=1st - {0} game.end.top.2=2nd - {0} game.end.top.3=3rd - {0} +player.death.fall={0} broke his legs +respawn.time.remaining=Respawn in {0}... +respawn.end.go=GO ! +game.not.exists=This game not exists. diff --git a/src/main/resources/rtf_translate_es_ES.properties b/src/main/resources/rtf_translate_es_ES.properties index d3ec928..2edbaed 100644 --- a/src/main/resources/rtf_translate_es_ES.properties +++ b/src/main/resources/rtf_translate_es_ES.properties @@ -6,7 +6,7 @@ player.join.team={0} se uni player.pickup.flag={0} recogió la bandera {1}! player.place.flag={0} colocó la bandera {1}! cant.break.your.team.flag=¡No puedes recoger la bandera de tu propio equipo! -scoreboard.waiting=Esperando jugadores +scoreboard.waiting=Esperando jugadores {0}/{1} scoreboard.starting=El juego comienza en {0} scoreboard.started=Tiempo de juego: {0} scoreboard.ending=¡GANADOR! {0} diff --git a/src/main/resources/rtf_translate_fr_FR.properties b/src/main/resources/rtf_translate_fr_FR.properties index 5f186de..6e8cae1 100644 --- a/src/main/resources/rtf_translate_fr_FR.properties +++ b/src/main/resources/rtf_translate_fr_FR.properties @@ -6,7 +6,7 @@ player.join.team={0} a rejoint l' player.pickup.flag={0} a pris le drapeau {1} ! player.place.flag={0} a placé le drapeau {1} ! cant.break.your.team.flag=Vous ne pouvez pas prendre le drapeau de votre propre équipe ! -scoreboard.waiting=En attente de joueurs +scoreboard.waiting=En attente de joueurs {0}/{1} scoreboard.starting=Le jeu commence dans {0} scoreboard.started=Temps de jeu : {0} scoreboard.ending=GAGNANT ! {0} @@ -28,3 +28,7 @@ game.end.top=Top 3 des meilleurs joueurs : game.end.top.1=1er - {0} game.end.top.2=2ème - {0} game.end.top.3=3ème - {0} +player.death.fall={0} s'est cassé les jambes +respawn.time.remaining=Réapparition dans {0}... +respawn.end.go=Allez ! +game.not.exists=Cette partie n'existe pas. From cd7278cfc06af47c0a332c8e773d823a909d7a30 Mon Sep 17 00:00:00 2001 From: Cizetux Date: Wed, 3 Jul 2024 13:02:38 +0200 Subject: [PATCH 13/23] feat: initial commit --- .../rushyverse/rtf/runnable/RespawnState.kt | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 src/main/kotlin/com/github/rushyverse/rtf/runnable/RespawnState.kt diff --git a/src/main/kotlin/com/github/rushyverse/rtf/runnable/RespawnState.kt b/src/main/kotlin/com/github/rushyverse/rtf/runnable/RespawnState.kt new file mode 100644 index 0000000..e4a9fb1 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/rtf/runnable/RespawnState.kt @@ -0,0 +1,60 @@ +package com.github.rushyverse.rtf.runnable + +import com.github.rushyverse.api.extension.withBold +import com.github.rushyverse.api.koin.inject +import com.github.rushyverse.api.translation.Translator +import com.github.rushyverse.api.translation.getComponent +import com.github.rushyverse.rtf.RTFPlugin +import com.github.rushyverse.rtf.client.ClientRTF +import com.github.rushyverse.rtf.game.Game +import kotlinx.coroutines.runBlocking +import net.kyori.adventure.text.Component.text +import net.kyori.adventure.text.format.NamedTextColor +import net.kyori.adventure.title.Title +import org.bukkit.scheduler.BukkitRunnable + +class RespawnState( + val client: ClientRTF, + val game: Game, + time: Int, +) : BukkitRunnable() { + + val translator: Translator by inject(RTFPlugin.ID) + var remainingTime = time; + val savedWalkSpeed = client.requirePlayer().walkSpeed + + init { + client.requirePlayer().walkSpeed = 0F + } + + override fun run() { + val player = client.player + if (player == null || !player.isOnline || !player.world.name.contains("rtf")) { + cancel() + return + } + + runBlocking { + if (remainingTime == 0) { + val title = Title.title( + translator.getComponent("respawn.end.go", client.lang().locale) + .color(NamedTextColor.RED).withBold(), + text("") + ) + client.requirePlayer().showTitle(title) + client.respawnState = false + client.requirePlayer().walkSpeed = savedWalkSpeed + cancel() + } else { + val title = Title.title( + translator.getComponent("respawn.time.remaining", client.lang().locale, arrayOf("$remainingTime")) + .color(NamedTextColor.GOLD).withBold(), + text("") + ) + client.requirePlayer().showTitle(title) + } + } + + remainingTime-- + } +} \ No newline at end of file From 73b0a412c51c16cfc66bcf2160961ac298439556 Mon Sep 17 00:00:00 2001 From: Cizetux Date: Wed, 3 Jul 2024 13:02:52 +0200 Subject: [PATCH 14/23] feat: add feedback messages --- .../kotlin/com/github/rushyverse/rtf/kit/SnowballSwitch.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/kotlin/com/github/rushyverse/rtf/kit/SnowballSwitch.kt b/src/main/kotlin/com/github/rushyverse/rtf/kit/SnowballSwitch.kt index ccf7827..6f3da97 100644 --- a/src/main/kotlin/com/github/rushyverse/rtf/kit/SnowballSwitch.kt +++ b/src/main/kotlin/com/github/rushyverse/rtf/kit/SnowballSwitch.kt @@ -37,6 +37,9 @@ class SnowballSwitch : KitFeature("snowballswitch") { // Swap the positions shooter.teleport(hitPlayerLocation) hitPlayer.teleport(shooterLocation) + + hitPlayer.sendMessage("${shooter.name} has switched places with you!") + shooter.sendMessage("You have switched places with ${hitPlayer.name}!") } } } From 20c22b6e36a548eac25ad96f699566e055a84e97 Mon Sep 17 00:00:00 2001 From: Cizetux Date: Wed, 3 Jul 2024 13:05:40 +0200 Subject: [PATCH 15/23] chore: add some kits --- src/main/resources/kits.yml | 270 +++++++++++++++++++++++++++++++++++- 1 file changed, 267 insertions(+), 3 deletions(-) diff --git a/src/main/resources/kits.yml b/src/main/resources/kits.yml index 95eef01..90021fd 100644 --- a/src/main/resources/kits.yml +++ b/src/main/resources/kits.yml @@ -3,13 +3,89 @@ kits: name: kit.rusher.name description: kit.rusher.description icon: - material: IRON_CHESTPLATE + material: CHAINMAIL_CHESTPLATE armor: helmet: material: IRON_HELMET unbreakable: true enchantments: protection: 1 + chestplate: + material: CHAINMAIL_CHESTPLATE + unbreakable: true + enchantments: + protection: 1 + leggings: + material: CHAINMAIL_LEGGINGS + unbreakable: true + enchantments: + protection: 2 + boots: + material: IRON_BOOTS + unbreakable: true + enchantments: + protection: 1 + items: + - ! + material: GOLDEN_SWORD + unbreakable: true + - ! + material: IRON_PICKAXE + unbreakable: true + enchantments: + efficiency: 1 + - ! + material: SMOOTH_SANDSTONE + amount: 64 + - ! + name: kit.tank.name + description: kit.tank.description + icon: + material: DIAMOND_CHESTPLATE + armor: + helmet: + material: GOLDEN_HELMET + unbreakable: true + enchantments: + protection: 1 + chestplate: + material: DIAMOND_CHESTPLATE + unbreakable: true + enchantments: + protection: 2 + leggings: + material: CHAINMAIL_LEGGINGS + unbreakable: true + enchantments: + protection: 2 + boots: + material: LEATHER_BOOTS + unbreakable: true + enchantments: + protection: 1 + items: + - ! + material: WOODEN_SWORD + unbreakable: true + - ! + material: IRON_PICKAXE + unbreakable: true + enchantments: + efficiency: 1 + - ! + material: SMOOTH_SANDSTONE + amount: 64 + - ! + name: kit.archer.name + description: kit.archer.description + icon: + material: BOW + armor: + helmet: + material: DIAMOND_HELMET + unbreakable: true + enchantments: + protection: 1 chestplate: material: IRON_CHESTPLATE unbreakable: true @@ -21,19 +97,207 @@ kits: enchantments: protection: 2 boots: - material: IRON_BOOTS + material: DIAMOND_BOOTS unbreakable: true enchantments: protection: 1 items: - ! - material: IRON_SWORD + material: STONE_SWORD unbreakable: true - ! material: IRON_PICKAXE unbreakable: true enchantments: efficiency: 1 + - ! + material: BOW + unbreakable: true + enchantments: + power: 3 + - ! + material: SMOOTH_SANDSTONE + amount: 64 + - ! + material: ARROW + amount: 12 + - ! + name: kit.engineer.name + description: kit.engineer.description + icon: + material: REDSTONE_TORCH + features: + - autobridge + - ladderfly + armor: + helmet: + material: GOLDEN_HELMET + unbreakable: true + enchantments: + protection: 1 + chestplate: + material: IRON_CHESTPLATE + unbreakable: true + enchantments: + protection: 1 + leggings: + material: IRON_LEGGINGS + unbreakable: true + enchantments: + protection: 2 + boots: + material: GOLDEN_BOOTS + unbreakable: true + enchantments: + protection: 1 + items: + - ! + material: STONE_SWORD + unbreakable: true + - ! + material: GOLDEN_PICKAXE + unbreakable: true + enchantments: + efficiency: 2 + - ! + material: SMOOTH_SANDSTONE + amount: 64 + - ! + name: kit.switcher.name + description: kit.switcher.description + icon: + material: SNOWBALL + features: + - snowballswitch + armor: + helmet: + material: LEATHER_HELMET + unbreakable: true + enchantments: + protection: 1 + chestplate: + material: IRON_CHESTPLATE + unbreakable: true + enchantments: + protection: 1 + leggings: + material: IRON_LEGGINGS + unbreakable: true + enchantments: + protection: 2 + boots: + material: LEATHER_BOOTS + unbreakable: true + enchantments: + protection: 1 + items: + - ! + material: IRON_SWORD + unbreakable: true + - ! + material: GOLDEN_PICKAXE + unbreakable: true + enchantments: + efficiency: 2 + - ! + material: SMOOTH_SANDSTONE + amount: 64 + - ! + name: kit.kamikaze.name + description: kit.kamikaze.description + icon: + material: TNT + features: + - tntfly + armor: + helmet: + material: CHAINMAIL_HELMET + unbreakable: true + enchantments: + protection: 1 + chestplate: + material: IRON_CHESTPLATE + unbreakable: true + enchantments: + protection: 1 + leggings: + material: CHAINMAIL_LEGGINGS + unbreakable: true + enchantments: + protection: 2 + boots: + material: DIAMOND_BOOTS + unbreakable: true + enchantments: + protection: 1 + feather_falling: 3 + items: + - ! + material: GOLDEN_SWORD + unbreakable: true + - ! + material: GOLDEN_PICKAXE + unbreakable: true + enchantments: + efficiency: 2 - ! material: SMOOTH_SANDSTONE amount: 64 + - ! + name: kit.speedster.name + description: kit.speedster.description + icon: + material: POTION + armor: + helmet: + material: CHAINMAIL_HELMET + unbreakable: true + enchantments: + protection: 1 + chestplate: + material: IRON_CHESTPLATE + unbreakable: true + enchantments: + protection: 1 + leggings: + material: CHAINMAIL_LEGGINGS + unbreakable: true + enchantments: + protection: 2 + boots: + material: DIAMOND_BOOTS + unbreakable: true + enchantments: + protection: 1 + items: + - ! + material: GOLDEN_SWORD + unbreakable: true + - ! + material: GOLDEN_PICKAXE + unbreakable: true + enchantments: + efficiency: 2 + - ! + material: SMOOTH_SANDSTONE + amount: 64 + - ! + name: kit.nokit.name + description: kit.nokit.description + icon: + material: GLASS + items: + - ! + material: DIAMOND_SWORD + unbreakable: true + enchantments: + sharpness: 5 + fire_aspect: 2 + - ! + material: DIAMOND_PICKAXE + unbreakable: true + enchantments: + efficiency: 5 + - ! + material: SMOOTH_SANDSTONE + amount: 64 \ No newline at end of file From ee9725e12ee0ba5f252566fb4a72a2c47d98f9af Mon Sep 17 00:00:00 2001 From: Cizetux Date: Wed, 3 Jul 2024 13:06:20 +0200 Subject: [PATCH 16/23] chore: change world border location --- src/main/resources/maps.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/maps.yml b/src/main/resources/maps.yml index 500d2ac..4435c13 100644 --- a/src/main/resources/maps.yml +++ b/src/main/resources/maps.yml @@ -8,7 +8,7 @@ z: 83 location2: x: -46 - y: 110 + y: 80 z: -85 allowedBlocks: - SMOOTH_SANDSTONE From 0f21f7797d1f97c3d4b9edeff0120054573b7943 Mon Sep 17 00:00:00 2001 From: Cizetux Date: Wed, 3 Jul 2024 13:08:12 +0200 Subject: [PATCH 17/23] chore: update commandAPI version --- build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 3c15fcb..c609895 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,7 +18,7 @@ repositories { dependencies { val paperVersion = "1.20-R0.1-SNAPSHOT" val rushyApiVersion = "2.1.0" - val commandApiVersion = "9.4.1" + val commandApiVersion = "9.5.0" compileOnly(kotlin("stdlib")) @@ -72,4 +72,4 @@ tasks { shadowJar { archiveClassifier.set("") } -} +} \ No newline at end of file From fc885ecc9440854513a7c71a0e1b457d7dcf3449 Mon Sep 17 00:00:00 2001 From: Cizetux Date: Wed, 3 Jul 2024 13:13:41 +0200 Subject: [PATCH 18/23] feat: add register kit feature --- .../kotlin/com/github/rushyverse/rtf/RTFPlugin.kt | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/rtf/RTFPlugin.kt b/src/main/kotlin/com/github/rushyverse/rtf/RTFPlugin.kt index c02d2e6..8cc06dd 100644 --- a/src/main/kotlin/com/github/rushyverse/rtf/RTFPlugin.kt +++ b/src/main/kotlin/com/github/rushyverse/rtf/RTFPlugin.kt @@ -12,6 +12,7 @@ import com.github.rushyverse.rtf.commands.RTFCommand import com.github.rushyverse.rtf.config.* import com.github.rushyverse.rtf.game.GameManager import com.github.rushyverse.rtf.gui.KitsGUI +import com.github.rushyverse.rtf.kit.* import com.github.rushyverse.rtf.listener.* import com.github.shynixn.mccoroutine.bukkit.scope import kotlinx.coroutines.SupervisorJob @@ -28,15 +29,11 @@ class RTFPlugin : Plugin(ID, BUNDLE_RTF) { const val ID = "RTF" } - lateinit var config: RTFConfig private set lateinit var configMaps: List private set lateinit var configKits: KitsConfig private set - lateinit var mapsDir: File private set - lateinit var tempDir: File private set - lateinit var kitsGui: KitsGUI private set override suspend fun onEnableAsync() { @@ -56,6 +53,7 @@ class RTFPlugin : Plugin(ID, BUNDLE_RTF) { RTFCommand(this).register() + registerKitFeatures() registerListeners() } @@ -80,6 +78,13 @@ class RTFPlugin : Plugin(ID, BUNDLE_RTF) { } } + private fun registerKitFeatures() { + registerListener { AutoBridge() } + registerListener { LadderFly() } + registerListener { TntFly() } + registerListener { SnowballSwitch() } + } + private fun registerListeners() { registerListener { GUIListener(setOf(kitsGui)) } registerListener { AuthenticationListener() } From b73014d649bb2888a71ce37b6220c2878dd28923 Mon Sep 17 00:00:00 2001 From: Cizetux Date: Tue, 9 Jul 2024 23:30:43 +0200 Subject: [PATCH 19/23] feat: add support for kit features --- .../rushyverse/rtf/config/KitsConfig.kt | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/rtf/config/KitsConfig.kt b/src/main/kotlin/com/github/rushyverse/rtf/config/KitsConfig.kt index b15e6f6..8a6b8db 100644 --- a/src/main/kotlin/com/github/rushyverse/rtf/config/KitsConfig.kt +++ b/src/main/kotlin/com/github/rushyverse/rtf/config/KitsConfig.kt @@ -1,10 +1,11 @@ package com.github.rushyverse.rtf.config import com.github.rushyverse.api.serializer.ItemStackSerializer +import com.github.rushyverse.rtf.client.ClientRTF +import com.github.rushyverse.rtf.kit.KitFeature.Companion.featureMap import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import org.bukkit.inventory.ItemStack -import org.bukkit.inventory.PlayerInventory typealias ItemStackSerializable = @Contextual ItemStack @@ -20,19 +21,36 @@ data class Kit( val name: String, val description: String, val icon: ItemStackSerializable, - val armor: ArmorConfig, - val items: Set<@Serializable(with = ItemStackSerializer::class) ItemStack> + val features: Set = emptySet(), + val armor: ArmorConfig? = null, + val items: Set<@Serializable(with = ItemStackSerializer::class) ItemStack>, ) { - fun sendItems(inventory: PlayerInventory) { + fun giveKit(client: ClientRTF) { + val inventory = client.player!!.inventory armor.let { - inventory.helmet = it.helmet - inventory.chestplate = it.chestplate - inventory.leggings = it.leggings - inventory.boots = it.boots + if (it != null) { + inventory.helmet = it.helmet + } + if (it != null) { + inventory.chestplate = it.chestplate + } + if (it != null) { + inventory.leggings = it.leggings + } + if (it != null) { + inventory.boots = it.boots + } } inventory.addItem(*items.toTypedArray()) + + // Give items of features attributed to this kit + features?.forEach { featureName -> + val feature = featureMap[featureName]!! + feature.item()?.apply { inventory.addItem(this) } + // feature.onGiveKit(client) (not working properly) + } } } @@ -43,4 +61,4 @@ data class ArmorConfig( val chestplate: ItemStackSerializable, val leggings: ItemStackSerializable, val boots: ItemStackSerializable -) +) \ No newline at end of file From 0f1c4b8321cacae462f0c30f87901b3f99db86ed Mon Sep 17 00:00:00 2001 From: Cizetux Date: Tue, 9 Jul 2024 23:35:06 +0200 Subject: [PATCH 20/23] refactor: change game state name --- .../kotlin/com/github/rushyverse/rtf/game/GameScoreboard.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/rtf/game/GameScoreboard.kt b/src/main/kotlin/com/github/rushyverse/rtf/game/GameScoreboard.kt index 5f40e95..1779bb2 100644 --- a/src/main/kotlin/com/github/rushyverse/rtf/game/GameScoreboard.kt +++ b/src/main/kotlin/com/github/rushyverse/rtf/game/GameScoreboard.kt @@ -115,7 +115,8 @@ object GameScoreboard { } private fun translateStateLine(timeFormatted: String, game: Game, locale: Locale) = when (game.state()) { - GameState.WAITING -> translateLine("waiting", locale) + GameState.NOT_STARTED -> translateLine("notStarted", locale) + GameState.WAITING -> translateLine("waiting", locale, arrayOf(game.playersInTeams(), game.mapConfig.minPlayers)) GameState.STARTING -> translateLine("starting", locale, arrayOf(timeFormatted)) GameState.STARTED -> translateLine( "started", @@ -123,8 +124,7 @@ object GameScoreboard { arrayOf("$timeFormatted"), NamedTextColor.LIGHT_PURPLE ) - - GameState.ENDING -> translateLine( + GameState.ENDED -> translateLine( "ending", locale, arrayOf( "<${game.teamWon.type.name.lowercase()}>${game.teamWon.type.name(translator, locale)}" ), From 3234d4d080e73b23e30642d7d46bfa7cb287b107 Mon Sep 17 00:00:00 2001 From: Cizetux Date: Tue, 9 Jul 2024 23:37:40 +0200 Subject: [PATCH 21/23] feat: add new subcommands and permissions --- .../rushyverse/rtf/commands/RTFCommand.kt | 135 +++++++++++------- 1 file changed, 87 insertions(+), 48 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/rtf/commands/RTFCommand.kt b/src/main/kotlin/com/github/rushyverse/rtf/commands/RTFCommand.kt index 5b5eb0f..0138a1d 100644 --- a/src/main/kotlin/com/github/rushyverse/rtf/commands/RTFCommand.kt +++ b/src/main/kotlin/com/github/rushyverse/rtf/commands/RTFCommand.kt @@ -10,9 +10,11 @@ import com.github.rushyverse.rtf.client.ClientRTF import com.github.rushyverse.rtf.game.GameManager import com.github.shynixn.mccoroutine.bukkit.launch import dev.jorel.commandapi.arguments.IntegerArgument +import dev.jorel.commandapi.arguments.StringArgument import dev.jorel.commandapi.kotlindsl.* import net.kyori.adventure.text.Component.text import net.kyori.adventure.text.format.NamedTextColor +import org.bukkit.entity.Player class RTFCommand( private val plugin: RTFPlugin @@ -30,29 +32,33 @@ class RTFCommand( suspend fun register() { commandAPICommand("rtf") { + // OP-ONLY subcommand("create") { withArguments(IntegerArgument("gameID")) withPermission("rtf.command.create") - playerExecutor { player, args -> + + anyExecutor { commandSender, args -> val gameIndex = args[0] as Int var game = games.getGame(gameIndex) plugin.launch { - if (game == null) { game = games.createAndSave(gameIndex) } else { - player.sendMessage("game.already.exists") + commandSender.sendMessage("game.already.exists") return@launch } - - game?.clientSpectate(clients.getClient(player) as ClientRTF) + if (commandSender is Player) { + game?.clientSpectate(clients.getClient(commandSender) as ClientRTF) + } } } } + // OP-ONLY subcommand("list") { withPermission("rtf.command.list") + playerExecutor { player, _ -> val length = games.games.size player.sendMessage("List of games ($length):") @@ -62,23 +68,89 @@ class RTFCommand( } } + // OP-ONLY + subcommand("start") { + withPermission("rtf.command.start") + withOptionalArguments(StringArgument("force")) + + playerExecutor { player, args -> + val game = games.getGame(player.world) ?: return@playerExecutor + val force = args[0] as String? + + if (game.state() == GameState.WAITING) { + val forceStart= force == "force" + plugin.launch { + game.start(forceStart) + } + } else { + player.sendMessage("The game has already started.") + } + } + } + + // OP-ONLY + subcommand("end") { + withPermission("rtf.end") + withArguments(IntegerArgument("gameID")) + + anyExecutor { player, args -> + val gameId = args[0] as Int + player.sendMessage("game.ask.delete") + val game = games.getGame(gameId) + plugin.launch { + game?.end(null) + player.sendMessage("game.deleted") + } + } + } + + // OP-ONLY + subcommand("win") { + withPermission("rtf.win") + stringArgument("team") + + playerExecutor { player, arg -> + val teamName = arg[0].toString() + val type = TeamType.valueOf(teamName.uppercase()) + val game = games.getGame(player.world) ?: return@playerExecutor + + if (game.state() != GameState.STARTED) { + player.sendMessage("La game n'a pas commencé.") + return@playerExecutor + } + + val team = game.teams.firstOrNull { it.type == type } + + if (team == null) { + player.sendMessage("The team '$teamName' does not exist.") + return@playerExecutor + } + + plugin.launch { game.end(team) } + } + } + subcommand("spectate") { withArguments(IntegerArgument("gameID")) - withPermission("rtf.command.spectate") + // withPermission("rtf.command.spectate") + playerExecutor { player, args -> val gameIndex = args[0] as Int val game = games.getGame(gameIndex) if (game == null) { player.sendMessage("game.not.exists") + } else { + plugin.launch { game.clientSpectate(clients.getClient(player) as ClientRTF) } } } } subcommand("join") { - withPermission("rtf.command.join") + // withPermission("rtf.command.join") + playerExecutor { player, _ -> - val game = games.getByWorld(player.world) ?: return@playerExecutor + val game = games.getGame(player.world) ?: return@playerExecutor plugin.launch { val client = clients.getClient(player) as ClientRTF @@ -98,48 +170,15 @@ class RTFCommand( } } - subcommand("start") { - withPermission("rtf.command.start") - playerExecutor { player, _ -> - val game = games.getByWorld(player.world) ?: return@playerExecutor - - if (game.state() != GameState.STARTED) { - plugin.launch { game.start(true) } - } else { - player.sendMessage("The game is already started.") - } - } - } - - // DEV - subcommand("win") { - withPermission("rtf.win") - stringArgument("team") - playerExecutor { player, arg -> - val teamName = arg[0].toString() - val type = TeamType.valueOf(teamName.uppercase()) - val game = games.getByWorld(player.world) ?: return@playerExecutor - val team = game.teams.firstOrNull { it.type == type } - - if (team == null) { - player.sendMessage("The team '$teamName' does not exist.") - return@playerExecutor - } + subcommand("kits") { + // withPermission("rtf.kits") - plugin.launch { game.end(team) } - } - } - - subcommand("end") { - withPermission("rtf.end") - withArguments(IntegerArgument("gameID")) playerExecutor { player, args -> - val gameId = args[0] as Int - player.sendMessage("game.ask.delete") - val game = games.getGame(gameId) - plugin.launch { - game?.end(null) - player.sendMessage("game.deleted") + val game = games.getGame(player.world) + if (game != null) { + plugin.launch { + plugin.kitsGui.open(clients.getClient(player)) + } } } } From 35e0a5400a192073a0c4061e8175e41eaa3fd291 Mon Sep 17 00:00:00 2001 From: Cizetux Date: Tue, 9 Jul 2024 23:43:50 +0200 Subject: [PATCH 22/23] refactor: enhance with state handling and data updates --- .../github/rushyverse/rtf/game/GameManager.kt | 40 +++++++-- .../rushyverse/rtf/listener/GameListener.kt | 87 +++++++++++-------- 2 files changed, 85 insertions(+), 42 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/rtf/game/GameManager.kt b/src/main/kotlin/com/github/rushyverse/rtf/game/GameManager.kt index 3c63c41..e702aa2 100644 --- a/src/main/kotlin/com/github/rushyverse/rtf/game/GameManager.kt +++ b/src/main/kotlin/com/github/rushyverse/rtf/game/GameManager.kt @@ -1,5 +1,7 @@ package com.github.rushyverse.rtf.game +import com.github.rushyverse.api.game.GameData +import com.github.rushyverse.api.game.GameState import com.github.rushyverse.api.game.SharedGameData import com.github.rushyverse.api.koin.inject import com.github.rushyverse.rtf.RTFPlugin @@ -19,9 +21,11 @@ class GameManager( val sharedGameData: SharedGameData by inject() val games: MutableList = mutableListOf() - fun getByWorld(world: World): Game? { + fun getGame(world: World): Game? { games.forEach { - if (it.world == world) return it + if (it.world == world) { + return it + } } return null @@ -36,10 +40,27 @@ class GameManager( var world = plugin.server.getWorld(worldName) if (world == null) { world = createWorldFromTemplate(worldName) - return Game(plugin, gameIndex, world, plugin.config, plugin.configMaps[0]).also { - games.add(it) + val mapConfig = plugin.configMaps[0].copy().apply { + mapCuboid.apply { + min.world = world + max.world = world + } + } + return Game(plugin, gameIndex, world, plugin.config, mapConfig).also { game -> + games.add(game) + + sharedGameData.apply { + + val gameData = games.find { it.id == gameIndex } - sharedGameData.saveUpdate(it.data) + if (gameData == null) { + saveUpdate(game.data) + } else { + gameData.state = GameState.WAITING + game.data = gameData + callOnChange() + } + } } } else { throw IllegalStateException("A game already exists for this world $worldName") @@ -91,13 +112,18 @@ class GameManager( } sharedGameData.apply { - games.removeIf { it.id == game.id } + val data = game.data + + if (data.permanent){ + data.state = GameState.NOT_STARTED + } + + games.removeIf { it.id == game.id && !game.data.permanent } callOnChange() } games.remove(game) } - fun getGame(gameIndex: Int) = games.firstOrNull { it.id == gameIndex } } \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/rtf/listener/GameListener.kt b/src/main/kotlin/com/github/rushyverse/rtf/listener/GameListener.kt index 3856d7a..151142d 100644 --- a/src/main/kotlin/com/github/rushyverse/rtf/listener/GameListener.kt +++ b/src/main/kotlin/com/github/rushyverse/rtf/listener/GameListener.kt @@ -1,6 +1,5 @@ package com.github.rushyverse.rtf.listener -import com.github.rushyverse.api.extension.event.cancel import com.github.rushyverse.api.extension.event.cancelIf import com.github.rushyverse.api.extension.isWool import com.github.rushyverse.api.game.GameState @@ -9,6 +8,7 @@ import com.github.rushyverse.api.translation.getComponent import com.github.rushyverse.rtf.client.ClientRTF import com.github.rushyverse.rtf.game.Game import com.github.rushyverse.rtf.game.TeamRTF +import com.github.rushyverse.rtf.runnable.RespawnState import net.kyori.adventure.text.format.NamedTextColor import org.bukkit.GameMode import org.bukkit.entity.EntityType @@ -27,7 +27,7 @@ class GameListener : ListenerRTF() { suspend fun onChangeWorld(event: PlayerChangedWorldEvent) { val from = event.from if (isRTFWorld(from)) { - val game = games.getByWorld(from) ?: return + val game = games.getGame(from) ?: return val player = event.player game.clientLeave(clients.getTypedClient(player)) } @@ -44,13 +44,36 @@ class GameListener : ListenerRTF() { } } + @EventHandler + suspend fun onEntityDamage(event: EntityDamageEvent) { + val entity = event.entity + val world = entity.world + val game = games.getGame(world) ?: return + + if (entity.type != EntityType.PLAYER) { + event.isCancelled = true + } else { + val client = clients.getTypedClient(entity.name) + + if (game.state() != GameState.STARTED) { + event.isCancelled = true + } else { + val team = game.getClientTeam(client) ?: return + + if (team.spawnCuboid.isInArea(entity.location)) { + event.isCancelled = true + } + } + } + } + @EventHandler suspend fun onPlayerDeath(event: PlayerDeathEvent) { val player = event.player - if (!isRTFWorld(player.world)) { return } + if (!isRTFWorld(player.world)) return - val game = games.getByWorld(player.world) ?: return + val game = games.getGame(player.world) ?: return val client = clients.getTypedClient(player) val team = game.getClientTeam(client) ?: return val killer = player.killer @@ -72,7 +95,13 @@ class GameListener : ListenerRTF() { args.add("<$playerColor>${player.name}") if (killer == null) { - deathTypeKey = "player.death.void" + val standBlock = player.location.add(0.0, -1.0, 0.0).block.type + deathTypeKey = + if (standBlock.isAir) + "player.death.void" + else if (standBlock.isBlock) + "player.death.fall" + else "" } else { val clientKiller = clients.getTypedClient(killer) val killerTeam = game.getClientTeam(clientKiller) @@ -113,34 +142,11 @@ class GameListener : ListenerRTF() { } - @EventHandler - suspend fun onEntityDamage(event: EntityDamageEvent) { - val entity = event.entity - val world = entity.world - val game = games.getByWorld(world) ?: return - - if (entity.type != EntityType.PLAYER) { - event.isCancelled = true - } else { - val client = clients.getTypedClient(entity.name) - - if (game.state() != GameState.STARTED) { - event.isCancelled = true - } else { - val team = game.getClientTeam(client) ?: return - - if (team.spawnCuboid.isInArea(entity.location)) { - event.isCancelled = true - } - } - } - } - @EventHandler suspend fun onPlayerRespawn(event: PlayerRespawnEvent) { val player = event.player val world = player.world - val game = games.getByWorld(world) ?: return + val game = games.getGame(world) ?: return val client = clients.getTypedClient(player) val team = game.getClientTeam(client) @@ -148,6 +154,8 @@ class GameListener : ListenerRTF() { event.respawnLocation = world.spawnLocation } else { event.respawnLocation = team.spawnPoint + client.respawnState = true + RespawnState(client, game, 3).runTaskTimer(plugin, 0, 20) } } @@ -155,9 +163,15 @@ class GameListener : ListenerRTF() { suspend fun onMove(event: PlayerMoveEvent) { val player = event.player val world = player.world - val game = games.getByWorld(world) ?: return + val game = games.getGame(world) ?: return + val client = clients.getTypedClient(player) + + if (client.respawnState) { + //event.to = event.from + return + } - if (event.to.y <= game.mapConfig.limitY) { + if (event.to.y <= game.mapConfig.mapCuboid.min.y) { val gameMode = player.gameMode if (gameMode == GameMode.SPECTATOR) { player.teleport(game.world.spawnLocation) @@ -167,11 +181,14 @@ class GameListener : ListenerRTF() { } } + /** + * + * @param event BlockPlaceEvent + */ @EventHandler suspend fun onBlockPlace(event: BlockPlaceEvent) { val player = event.player - val game = games.getByWorld(player.world) ?: return - + val game = games.getGame(player.world) ?: return if (game.state() != GameState.STARTED) { event.isCancelled = true } else { @@ -182,6 +199,7 @@ class GameListener : ListenerRTF() { if (game.isProtectedLocation(blockLoc)) { event.isCancelled = true + // Flag place check if (block.type.name.contains("WOOL")) { val clientTeam = game.getClientTeam(client) ?: return @@ -202,7 +220,7 @@ class GameListener : ListenerRTF() { @EventHandler suspend fun onBlockBreak(event: BlockBreakEvent) { val player = event.player - val game = games.getByWorld(player.world) ?: return + val game = games.getGame(player.world) ?: return if (game.state() != GameState.STARTED) { event.isCancelled = true @@ -233,7 +251,6 @@ class GameListener : ListenerRTF() { !game.isBlockAllowed(type) -> event.isCancelled = true else -> event.isDropItems = false } - } } } From cec627cbbd5e55ba627f1d6dec35ad9809cce75b Mon Sep 17 00:00:00 2001 From: Cizetux Date: Tue, 9 Jul 2024 23:47:41 +0200 Subject: [PATCH 23/23] feat: start game automatically when minimum players are reached --- .../com/github/rushyverse/rtf/game/Game.kt | 52 ++++++++++++++----- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/rtf/game/Game.kt b/src/main/kotlin/com/github/rushyverse/rtf/game/Game.kt index 96ad4b9..0baf710 100644 --- a/src/main/kotlin/com/github/rushyverse/rtf/game/Game.kt +++ b/src/main/kotlin/com/github/rushyverse/rtf/game/Game.kt @@ -42,7 +42,7 @@ class Game( val config: RTFConfig, val mapConfig: MapConfig ) { - val data = GameData("rtf", id) + var data = GameData("rtf", id) val players: Collection get() = world.players @@ -66,7 +66,7 @@ class Game( if (force) { data.state = GameState.STARTED - broadcast("game.message.started", NamedTextColor.GREEN) + broadcast("game.message.started") teams.forEach { team -> team.members.forEach { member -> @@ -82,9 +82,7 @@ class Game( gameTask.run() } else { val time = AtomicInteger(5) - data.state = GameState.STARTING - gameTask.add { startingTask(this, time) } gameTask.run() } @@ -92,8 +90,17 @@ class Game( manager.sharedGameData.saveUpdate(data) } - private suspend fun startingTask(task: SchedulerTask.Task, atomicTime: AtomicInteger) { + fun playersInTeams() : Int { + var players = 0 + teams.forEach { + players+=it.members.size + } + return players + } + + suspend fun startingTask(task: SchedulerTask.Task, atomicTime: AtomicInteger) { val time = atomicTime.get() + if (time == 0) { task.remove() // end the repeating task start(true) @@ -101,8 +108,7 @@ class Game( } broadcast( "game.message.starting", - NamedTextColor.GREEN, - argumentBuilder = { arrayOf("$time") } + argumentBuilder = { arrayOf("$time") }, ) atomicTime.set(time - 1) } @@ -186,7 +192,7 @@ class Game( val colorName = joinedTeam.type.name.lowercase() client.kit = plugin.configKits.kits.firstOrNull()?.apply { - sendItems(player.inventory) + giveKit(client) } broadcast( @@ -203,6 +209,16 @@ class Game( if (startedTime == 0L) GameScoreboard.update(client, this) + + val minPlayers = mapConfig.minPlayers / teams.size + var teamReadyCount = 0 + for (team in teams){ + if (team.members.size >= minPlayers) { + teamReadyCount++ + } + } + if (teamReadyCount == teams.size) + start() } suspend fun clientLeave(client: ClientRTF) { @@ -237,7 +253,6 @@ class Game( else -> {} } - } manager.sharedGameData.saveUpdate(data) @@ -280,7 +295,7 @@ class Game( client.requirePlayer().apply { inventory.clear() removePotionEffect(PotionEffectType.SPEED) - client.kit?.sendItems(inventory) + client.kit?.giveKit(client) } flagTeam.flagStolenState = false @@ -320,7 +335,7 @@ class Game( * The game state is set to ENDING while players are teleported and the world is destroyed. */ suspend fun end(winTeam: TeamRTF?) { - data.state = GameState.ENDING + data.state = GameState.ENDED gameTask.cancelAndJoin() manager.sharedGameData.saveUpdate(data) @@ -419,12 +434,21 @@ class Game( */ suspend fun broadcast( key: String, - color: NamedTextColor = NamedTextColor.WHITE, + color: NamedTextColor? = null, argumentBuilder: Translator.(Locale) -> Array = { emptyArray() } - ) = plugin.broadcast(world.players, key, argumentBuilder = argumentBuilder, messageModifier = { it.color(color) }) + ) = plugin.broadcast(world.players, key, argumentBuilder = argumentBuilder) + /** + * Method to know if a location is protected. + * That's include for teams spawn area, flag area and also the game area. + * + * @param location Location you want to check the protection status. + * @return Boolean true if the location provided is in a protected area, the method return true, false otherwise. + */ fun isProtectedLocation(location: Location): Boolean { - return teams.any { it.spawnCuboid.isInArea(location) || it.flagCuboid.isInArea(location) } + return teams.any { it.spawnCuboid.isInArea(location) + || it.flagCuboid.isInArea(location) } + || !mapConfig.mapCuboid.isInArea(location) } fun isBlockAllowed(blockType: Material): Boolean {