diff --git a/build.gradle.kts b/build.gradle.kts index 7c01a6d..6aa3d62 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,6 +17,7 @@ repositories { maven("https://oss.sonatype.org/content/repositories/snapshots/") maven("https://libraries.minecraft.net/") maven("https://repo.codemc.io/repository/maven-public/") + maven("https://repo.lunarclient.dev") maven { url = uri("https://maven.pkg.github.com/McScrims-Network/Main-CoreSystem") @@ -37,6 +38,11 @@ dependencies { compileOnly("net.luckperms:api:5.4") + compileOnly("com.lunarclient:apollo-api:1.2.0") + compileOnly("com.lunarclient:apollo-extra-adventure4:1.2.0") + + compileOnly("org.popcraft:chunky-common:1.3.38") + implementation("club.mcscrims:core:1.4.2") implementation("club.mcscrims:spigot:1.4.2") diff --git a/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt b/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt index 216ae65..4c64425 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt @@ -9,6 +9,9 @@ import club.mcscrims.speedhg.config.MessageConfig import club.mcscrims.speedhg.config.PluginConfig import club.mcscrims.speedhg.database.StatsRepository import club.mcscrims.speedhg.game.GameManager +import club.mcscrims.speedhg.listener.GameStateListener +import club.mcscrims.speedhg.listener.LunarClientListener +import club.mcscrims.speedhg.world.WorldManager import club.mcscrims.spigot.chat.ChatFormatter import club.mcscrims.spigot.chat.ChatManager import club.mcscrims.spigot.network.SpigotNetworkManager @@ -39,10 +42,12 @@ class SpeedHG : JavaPlugin() { internal lateinit var schedulerManager: SchedulerManager internal lateinit var gameManager: GameManager + internal lateinit var worldManager: WorldManager internal lateinit var luckPerms: LuckPerms + internal var isReady: Boolean = false - override fun onEnable() + override fun onLoad() { instance = this @@ -50,6 +55,14 @@ class SpeedHG : JavaPlugin() { setupDatabase() networkManager = SpigotNetworkManager.getInstance()!! + worldManager = WorldManager( this ) + worldManager.deleteWorld() + } + + override fun onEnable() + { + worldManager.setupWorld() + chatFormatter = ChatFormatter.create( plugin = this, configClass = MessageConfig::class, @@ -65,6 +78,7 @@ class SpeedHG : JavaPlugin() { gameManager.initialize() setupLuckPerms() + registerListener() } override fun onDisable() @@ -73,6 +87,12 @@ class SpeedHG : JavaPlugin() { networkManager.shutdown() } + private fun registerListener() + { + server.pluginManager.registerEvents(GameStateListener( this, gameManager ), this ) + LunarClientListener( this ) + } + /* * LUCKPERMS >> */ diff --git a/src/main/kotlin/club/mcscrims/speedhg/game/GameManager.kt b/src/main/kotlin/club/mcscrims/speedhg/game/GameManager.kt index c568108..72b0d17 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/game/GameManager.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/game/GameManager.kt @@ -1,13 +1,28 @@ package club.mcscrims.speedhg.game import club.mcscrims.speedhg.SpeedHG +import club.mcscrims.speedhg.game.impl.BattleState +import club.mcscrims.speedhg.game.impl.FeastState +import club.mcscrims.speedhg.game.impl.ImmunityState +import club.mcscrims.speedhg.game.impl.PreStartState import club.mcscrims.speedhg.game.impl.WaitingState +import org.bukkit.Location +import org.bukkit.entity.Player +import org.bukkit.util.BoundingBox +import java.util.concurrent.ConcurrentHashMap class GameManager( private val plugin: SpeedHG ) { private var currentState: GameState? = null + private val gameStateTypes = ConcurrentHashMap() + + private val winners = ArrayList() + + internal lateinit var feastLocation: Location + internal lateinit var feastBox: BoundingBox + internal var feastHeight: Int = 1 fun initialize() { @@ -24,12 +39,21 @@ class GameManager( plugin.logger.severe("Error during onEnter for state ${currentState?.name}: ${e.message}") e.printStackTrace() } + + gameStateTypes[ GameStateTypes.WAITING ] = currentState!! + gameStateTypes[ GameStateTypes.PRE_START ] = PreStartState( this, plugin, plugin.schedulerManager, plugin.pluginConfig.data.getDuration( "pre_start" ).seconds ) + gameStateTypes[ GameStateTypes.IMMUNITY ] = ImmunityState( this, plugin, plugin.schedulerManager, plugin.pluginConfig.data.getDuration( "immunity" ).seconds ) + gameStateTypes[ GameStateTypes.BATTLE ] = BattleState( this, plugin, plugin.schedulerManager, plugin.pluginConfig.data.getDuration( "battle" ).seconds ) + gameStateTypes[ GameStateTypes.FEAST ] = FeastState( this, plugin, plugin.schedulerManager, plugin.pluginConfig.data.getDuration( "feast" ).seconds ) + gameStateTypes[ GameStateTypes.DEATHMATCH ] = TODO() + gameStateTypes[ GameStateTypes.END ] = TODO() } fun transitionTo( - nextState: GameState + stateType: GameStateTypes ) { val previousState = currentState + val nextState = gameStateTypes[ stateType ]!! try { currentState?.onExit( nextState ) @@ -38,6 +62,13 @@ class GameManager( e.printStackTrace() } + if ( nextState is FeastState ) + { + feastLocation = nextState.feastLocation + feastBox = nextState.feastBox + feastHeight = nextState.feastHeight + } + currentState = nextState try { @@ -48,8 +79,33 @@ class GameManager( } } + fun addWinners( + vararg winners: Player + ) { + winners.forEach { this.winners.add( it ) } + } + fun getCurrentState(): GameState? = currentState + fun getCurrentStateType(): GameStateTypes? = gameStateTypes.filter { it.value.name == currentState?.name }.keys.firstOrNull() + + fun isRunning(): Boolean + { + return getCurrentStateType() == GameStateTypes.IMMUNITY || + getCurrentStateType() == GameStateTypes.BATTLE || + getCurrentStateType() == GameStateTypes.FEAST || + getCurrentStateType() == GameStateTypes.DEATHMATCH + } + + fun isBeforeFeast(): Boolean + { + return getCurrentStateType() == GameStateTypes.WAITING || + getCurrentStateType() == GameStateTypes.PRE_START || + getCurrentStateType() == GameStateTypes.IMMUNITY || + (getCurrentStateType() == GameStateTypes.BATTLE && + !( currentState as BattleState ).afterFeast ) + } + fun shutdown() { currentState?.onExit( null ) @@ -57,3 +113,7 @@ class GameManager( } } + +enum class GameStateTypes { + WAITING, PRE_START, IMMUNITY, BATTLE, FEAST, DEATHMATCH, END +} diff --git a/src/main/kotlin/club/mcscrims/speedhg/game/GameState.kt b/src/main/kotlin/club/mcscrims/speedhg/game/GameState.kt index bf6d5de..9a40089 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/game/GameState.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/game/GameState.kt @@ -1,5 +1,6 @@ package club.mcscrims.speedhg.game +import club.mcscrims.core.config.DurationType import club.mcscrims.speedhg.SpeedHG import club.mcscrims.spigot.scheduler.SchedulerManager import club.mcscrims.spigot.scheduler.TaskRegistration @@ -18,7 +19,7 @@ abstract class GameState( private val listeners = mutableListOf() private var tickTask: TaskRegistration? = null - private var remainingSeconds: Int = durationSeconds ?: 0 + var remainingSeconds: Int = durationSeconds ?: 0 private var isActive: Boolean = false open fun onEnter( @@ -62,6 +63,12 @@ abstract class GameState( if ( durationSeconds != null && remainingSeconds > 0 ) { + if (plugin.pluginConfig.data.getDuration( name ).type == DurationType.INCREASING) + { + remainingSeconds++ + return@runRepeating + } + remainingSeconds-- if ( remainingSeconds == 0) diff --git a/src/main/kotlin/club/mcscrims/speedhg/game/impl/BattleState.kt b/src/main/kotlin/club/mcscrims/speedhg/game/impl/BattleState.kt new file mode 100644 index 0000000..cc8adfe --- /dev/null +++ b/src/main/kotlin/club/mcscrims/speedhg/game/impl/BattleState.kt @@ -0,0 +1,64 @@ +package club.mcscrims.speedhg.game.impl + +import club.mcscrims.speedhg.SpeedHG +import club.mcscrims.speedhg.game.GameManager +import club.mcscrims.speedhg.game.GameState +import club.mcscrims.speedhg.game.GameStateTypes +import club.mcscrims.spigot.scheduler.SchedulerManager +import org.bukkit.Bukkit +import org.bukkit.entity.Player + +class BattleState( + gameManager: GameManager, + plugin: SpeedHG, + schedulerManager: SchedulerManager, + durationSeconds: Int? = null +) : GameState( "battle", gameManager, plugin, schedulerManager, durationSeconds ) { + + var afterFeast: Boolean = false + + override fun onEnter( + previous: GameState? + ) { + super.onEnter( previous ) + Bukkit.getOnlinePlayers().forEach( Player::clearActivePotionEffects ) + } + + override fun onTick() + { + val win = checkForWinners() + + when( remainingSeconds ) + { + 300 -> + { + gameManager.transitionTo( GameStateTypes.FEAST ) + afterFeast = true + } + + 1800 -> + { + if ( !win ) + { + gameManager.transitionTo( GameStateTypes.DEATHMATCH ) + return + } + } + } + } + + override fun onEndOfDuration() {} + + private fun checkForWinners(): Boolean + { + val players = Bukkit.getOnlinePlayers() + + if ( players.size > 1 ) + return false + + gameManager.addWinners( players.first() ) + gameManager.transitionTo( GameStateTypes.END ) + return true + } + +} \ No newline at end of file diff --git a/src/main/kotlin/club/mcscrims/speedhg/game/impl/FeastState.kt b/src/main/kotlin/club/mcscrims/speedhg/game/impl/FeastState.kt new file mode 100644 index 0000000..5376fa0 --- /dev/null +++ b/src/main/kotlin/club/mcscrims/speedhg/game/impl/FeastState.kt @@ -0,0 +1,35 @@ +package club.mcscrims.speedhg.game.impl + +import club.mcscrims.speedhg.SpeedHG +import club.mcscrims.speedhg.game.GameManager +import club.mcscrims.speedhg.game.GameState +import club.mcscrims.spigot.scheduler.SchedulerManager +import org.bukkit.Location +import org.bukkit.util.BoundingBox +import java.util.Random + +class FeastState( + gameManager: GameManager, + plugin: SpeedHG, + schedulerManager: SchedulerManager, + durationSeconds: Int? = null +) : GameState( "feast", gameManager, plugin, schedulerManager, durationSeconds ) { + + private val world = plugin.worldManager.getWorld() + private val random = Random() + + internal lateinit var feastLocation: Location + internal lateinit var feastBox: BoundingBox + internal var feastHeight: Int = 1 + + override fun onTick() + { + TODO("Not yet implemented") + } + + override fun onEndOfDuration() + { + TODO("Not yet implemented") + } + +} \ No newline at end of file diff --git a/src/main/kotlin/club/mcscrims/speedhg/game/impl/ImmunityState.kt b/src/main/kotlin/club/mcscrims/speedhg/game/impl/ImmunityState.kt index 8165400..925a69f 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/game/impl/ImmunityState.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/game/impl/ImmunityState.kt @@ -3,23 +3,11 @@ package club.mcscrims.speedhg.game.impl import club.mcscrims.speedhg.SpeedHG import club.mcscrims.speedhg.game.GameManager import club.mcscrims.speedhg.game.GameState +import club.mcscrims.speedhg.game.GameStateTypes import club.mcscrims.speedhg.game.impl.PreStartState.AnnouncementType -import club.mcscrims.speedhg.util.DirectionUtil import club.mcscrims.spigot.scheduler.SchedulerManager import org.bukkit.Bukkit import org.bukkit.Material -import org.bukkit.Sound -import org.bukkit.entity.Player -import org.bukkit.event.Event -import org.bukkit.event.EventHandler -import org.bukkit.event.Listener -import org.bukkit.event.block.Action -import org.bukkit.event.block.BlockBreakEvent -import org.bukkit.event.entity.EntityDamageEvent -import org.bukkit.event.entity.FoodLevelChangeEvent -import org.bukkit.event.inventory.CraftItemEvent -import org.bukkit.event.player.PlayerDropItemEvent -import org.bukkit.event.player.PlayerInteractEvent import org.bukkit.inventory.ItemStack import org.bukkit.potion.PotionEffect import org.bukkit.potion.PotionEffectType @@ -29,13 +17,12 @@ class ImmunityState( plugin: SpeedHG, schedulerManager: SchedulerManager, durationSeconds: Int? = null -) : GameState( "immunity", gameManager, plugin, schedulerManager, durationSeconds ), Listener { +) : GameState( "immunity", gameManager, plugin, schedulerManager, durationSeconds ) { override fun onEnter( previous: GameState? ) { super.onEnter( previous ) - registerListener( this ) val effects = listOf( PotionEffect( PotionEffectType.HASTE, durationSeconds?.times( 20 ) ?: 0, 0 ), @@ -62,7 +49,7 @@ class ImmunityState( override fun onTick() { - when( durationSeconds ) + when( remainingSeconds ) { 180 -> announce( AnnouncementType.MINUTES, 3 ) 120 -> announce( AnnouncementType.MINUTES, 2 ) @@ -78,7 +65,7 @@ class ImmunityState( 0 -> { broadcast( "gameStates.immunity.ended" ) - gameManager.transitionTo( TODO() ) + gameManager.transitionTo( GameStateTypes.BATTLE ) } } } @@ -93,160 +80,4 @@ class ImmunityState( broadcast( "gameStates.immunity.ending$arg", "{time}" to time.toString() ) } - @EventHandler - fun onInteractCompass( - event: PlayerInteractEvent - ) { - val player = event.player - val action = event.action - - if ( action != Action.RIGHT_CLICK_BLOCK && - action != Action.RIGHT_CLICK_AIR ) - return - - val item = event.item - ?: return - - if ( item.type != Material.COMPASS ) - return - - val nearestPlayer = Bukkit.getOnlinePlayers().stream() - .filter { other -> other != player } - .map { other -> other.location.distance( player.location ) to other } - .filter { pair -> pair.first > 5.0 } - .toList() - .firstOrNull() - ?.second - - if ( nearestPlayer == null ) - { - plugin.chatManager.sendMessage( player, "compass.no_nearby_players" ) - return - } - - player.compassTarget = nearestPlayer.location - - val direction = DirectionUtil.getDirectionToPlayer( player, nearestPlayer ) - val actionBar = plugin.chatFormatter.format( "compass.actionBar" ) - actionBar.replaceText { it.match( "" ).replacement( direction ).once() } - plugin.chatManager.sendInteractiveMessage( player, actionBar ) - } - - @EventHandler - fun onDropItem( - event: PlayerDropItemEvent - ) { - val player = event.player - - TODO( "Add kit & perk check" ) - } - - @EventHandler - fun onCraftItem( - event: CraftItemEvent - ) { - val player = event.whoClicked - - if ( player !is Player ) - return - - val item = event.recipe.result - - if ( item.type == Material.SHIELD ) - { - if ( event.isShiftClick ) - { - plugin.chatManager.sendMessage( player, "craft.no_siftclick" ) - player.playSound( player, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f ) - return - } - - event.result = Event.Result.DENY - - plugin.chatManager.sendMessage( player, "craft.no_shield" ) - player.playSound( player, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f ) - return - } - - if (!item.type.name.contains( "iron", true )) - return - - if ( event.isShiftClick ) - { - plugin.chatManager.sendMessage( player, "craft.no_siftclick" ) - player.playSound( player, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f ) - return - } - - event.result = Event.Result.DENY - - plugin.chatManager.sendMessage( player, "craft.no_iron_before_feast" ) - player.playSound( player, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f ) - } - - @EventHandler - fun onBlockBreak( - event: BlockBreakEvent - ) { - val player = event.player - val block = event.block - - if ( block.type == Material.DIAMOND_ORE ) - { - event.isCancelled = true - block.type = Material.AIR - block.tick() - - plugin.chatManager.sendMessage( player, "build.no_diamonds" ) - player.playSound( player, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f ) - return - } - - if ( block.type == Material.IRON_ORE ) - { - event.isCancelled = true - - plugin.chatManager.sendMessage( player, "build.no_iron_before_feast" ) - player.playSound( player, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f ) - return - } - } - - @EventHandler - fun onInteract( - event: PlayerInteractEvent - ) { - val player = event.player - val action = event.action - - if ( action != Action.RIGHT_CLICK_BLOCK && - action != Action.RIGHT_CLICK_AIR ) - return - - val item = event.item - ?: return - - TODO( "Check for kit items" ) - } - - @EventHandler - fun onEntityDamage( - event: EntityDamageEvent - ) { - if ( gameManager.getCurrentState() != this ) - return - - event.isCancelled = true - } - - @EventHandler - fun onFoodLevelChange( - event: FoodLevelChangeEvent - ) { - if ( gameManager.getCurrentState() != this ) - return - - event.isCancelled = true - } - } \ No newline at end of file diff --git a/src/main/kotlin/club/mcscrims/speedhg/game/impl/PreStartState.kt b/src/main/kotlin/club/mcscrims/speedhg/game/impl/PreStartState.kt index 45798aa..dc79a73 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/game/impl/PreStartState.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/game/impl/PreStartState.kt @@ -3,26 +3,17 @@ package club.mcscrims.speedhg.game.impl import club.mcscrims.speedhg.SpeedHG import club.mcscrims.speedhg.game.GameManager import club.mcscrims.speedhg.game.GameState +import club.mcscrims.speedhg.game.GameStateTypes import club.mcscrims.spigot.scheduler.SchedulerManager import org.bukkit.Bukkit import org.bukkit.Location -import org.bukkit.Sound -import org.bukkit.event.EventHandler -import org.bukkit.event.Listener -import org.bukkit.event.block.BlockBreakEvent -import org.bukkit.event.block.BlockPlaceEvent -import org.bukkit.event.block.LeavesDecayEvent -import org.bukkit.event.entity.EntityDamageEvent -import org.bukkit.event.entity.FoodLevelChangeEvent -import org.bukkit.event.player.PlayerAttemptPickupItemEvent -import org.bukkit.event.player.PlayerDropItemEvent class PreStartState( gameManager: GameManager, plugin: SpeedHG, schedulerManager: SchedulerManager, durationSeconds: Int? = null -) : GameState( "pre_start", gameManager, plugin, schedulerManager, durationSeconds ), Listener { +) : GameState( "pre_start", gameManager, plugin, schedulerManager, durationSeconds ) { private lateinit var previous: GameState @@ -30,7 +21,6 @@ class PreStartState( previous: GameState? ) { super.onEnter( previous ) - registerListener( this ) if ( previous != null ) this.previous = previous @@ -45,17 +35,17 @@ class PreStartState( if ( playerSize < plugin.pluginConfig.data.game.minimumPlayers ) { isStarting = false - transitionToPrevious() + gameManager.transitionTo( GameStateTypes.WAITING ) } else isStarting = true if ( !isStarting ) return - if ( durationSeconds == 15 ) + if ( remainingSeconds == 15 ) teleport() - when( durationSeconds ) + when( remainingSeconds ) { 300 -> announce( AnnouncementType.MINUTES, 5 ) 240 -> announce( AnnouncementType.MINUTES, 4 ) @@ -74,7 +64,7 @@ class PreStartState( { broadcast( "gameStates.preStart.started" ) isStarting = false - gameManager.transitionTo(ImmunityState( gameManager, plugin, schedulerManager, plugin.pluginConfig.data.getDuration( "immunity" ).seconds )) + gameManager.transitionTo( GameStateTypes.IMMUNITY ) } } } @@ -106,87 +96,6 @@ class PreStartState( } } - private fun transitionToPrevious() - { - if ( ::previous.isInitialized ) - { - gameManager.transitionTo( previous ) - return - } - - gameManager.transitionTo(WaitingState( gameManager, plugin, schedulerManager, plugin.pluginConfig.data.getDuration( "waiting" ).seconds )) - } - - @EventHandler - fun onLeavesDecay( - event: LeavesDecayEvent - ) { - event.isCancelled = true - } - - @EventHandler - fun onBlockBreak( - event: BlockBreakEvent - ) { - if ( gameManager.getCurrentState() != this ) - return - - event.isCancelled = true - event.player.playSound( event.player, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f ) - } - - @EventHandler - fun onBlockPlace( - event: BlockPlaceEvent - ) { - if ( gameManager.getCurrentState() != this ) - return - - event.isCancelled = true - event.player.playSound( event.player, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f ) - } - - @EventHandler - fun onPlayerPickupItem( - event: PlayerAttemptPickupItemEvent - ) { - if ( gameManager.getCurrentState() != this ) - return - - event.isCancelled = true - } - - @EventHandler - fun onPlayerDropItem( - event: PlayerDropItemEvent - ) { - if ( gameManager.getCurrentState() != this ) - return - - event.isCancelled = true - event.player.playSound( event.player, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f ) - } - - @EventHandler - fun onEntityDamage( - event: EntityDamageEvent - ) { - if ( gameManager.getCurrentState() != this ) - return - - event.isCancelled = true - } - - @EventHandler - fun onFoodLevelChange( - event: FoodLevelChangeEvent - ) { - if ( gameManager.getCurrentState() != this ) - return - - event.isCancelled = true - } - internal enum class AnnouncementType { MINUTES, SECONDS } diff --git a/src/main/kotlin/club/mcscrims/speedhg/game/impl/WaitingState.kt b/src/main/kotlin/club/mcscrims/speedhg/game/impl/WaitingState.kt index fdefbc1..4963479 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/game/impl/WaitingState.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/game/impl/WaitingState.kt @@ -3,32 +3,16 @@ package club.mcscrims.speedhg.game.impl import club.mcscrims.speedhg.SpeedHG import club.mcscrims.speedhg.game.GameManager import club.mcscrims.speedhg.game.GameState +import club.mcscrims.speedhg.game.GameStateTypes import club.mcscrims.spigot.scheduler.SchedulerManager import org.bukkit.Bukkit -import org.bukkit.Sound -import org.bukkit.event.EventHandler -import org.bukkit.event.Listener -import org.bukkit.event.block.BlockBreakEvent -import org.bukkit.event.block.BlockPlaceEvent -import org.bukkit.event.block.LeavesDecayEvent -import org.bukkit.event.entity.EntityDamageEvent -import org.bukkit.event.entity.FoodLevelChangeEvent -import org.bukkit.event.player.PlayerAttemptPickupItemEvent -import org.bukkit.event.player.PlayerDropItemEvent class WaitingState( gameManager: GameManager, plugin: SpeedHG, schedulerManager: SchedulerManager, durationSeconds: Int? = null -) : GameState( "waiting", gameManager, plugin, schedulerManager, durationSeconds ), Listener { - - override fun onEnter( - previous: GameState? - ) { - super.onEnter( previous ) - registerListener( this ) - } +) : GameState( "waiting", gameManager, plugin, schedulerManager, durationSeconds ) { private var secondsCounter: Int = 0 @@ -43,79 +27,9 @@ class WaitingState( secondsCounter = 0 } else secondsCounter++ - else gameManager.transitionTo(PreStartState( gameManager, plugin, schedulerManager, plugin.pluginConfig.data.getDuration( "pre_start" ).seconds )) + else gameManager.transitionTo( GameStateTypes.PRE_START ) } override fun onEndOfDuration() {} - @EventHandler - fun onLeavesDecay( - event: LeavesDecayEvent - ) { - event.isCancelled = true - } - - @EventHandler - fun onBlockBreak( - event: BlockBreakEvent - ) { - if ( gameManager.getCurrentState() != this ) - return - - event.isCancelled = true - event.player.playSound( event.player, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f ) - } - - @EventHandler - fun onBlockPlace( - event: BlockPlaceEvent - ) { - if ( gameManager.getCurrentState() != this ) - return - - event.isCancelled = true - event.player.playSound( event.player, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f ) - } - - @EventHandler - fun onPlayerPickupItem( - event: PlayerAttemptPickupItemEvent - ) { - if ( gameManager.getCurrentState() != this ) - return - - event.isCancelled = true - } - - @EventHandler - fun onPlayerDropItem( - event: PlayerDropItemEvent - ) { - if ( gameManager.getCurrentState() != this ) - return - - event.isCancelled = true - event.player.playSound( event.player, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f ) - } - - @EventHandler - fun onEntityDamage( - event: EntityDamageEvent - ) { - if ( gameManager.getCurrentState() != this ) - return - - event.isCancelled = true - } - - @EventHandler - fun onFoodLevelChange( - event: FoodLevelChangeEvent - ) { - if ( gameManager.getCurrentState() != this ) - return - - event.isCancelled = true - } - } \ No newline at end of file diff --git a/src/main/kotlin/club/mcscrims/speedhg/listener/GameStateListener.kt b/src/main/kotlin/club/mcscrims/speedhg/listener/GameStateListener.kt new file mode 100644 index 0000000..5b4fb2d --- /dev/null +++ b/src/main/kotlin/club/mcscrims/speedhg/listener/GameStateListener.kt @@ -0,0 +1,545 @@ +package club.mcscrims.speedhg.listener + +import club.mcscrims.speedhg.SpeedHG +import club.mcscrims.speedhg.game.GameManager +import club.mcscrims.speedhg.game.GameStateTypes +import club.mcscrims.speedhg.util.DirectionUtil +import kotlinx.coroutines.runBlocking +import org.bukkit.Bukkit +import org.bukkit.Material +import org.bukkit.Sound +import org.bukkit.attribute.Attribute +import org.bukkit.entity.Player +import org.bukkit.event.Event +import org.bukkit.event.EventHandler +import org.bukkit.event.Listener +import org.bukkit.event.block.Action +import org.bukkit.event.block.BlockBreakEvent +import org.bukkit.event.block.BlockPlaceEvent +import org.bukkit.event.block.BlockSpreadEvent +import org.bukkit.event.block.LeavesDecayEvent +import org.bukkit.event.enchantment.EnchantItemEvent +import org.bukkit.event.entity.EntityDamageByEntityEvent +import org.bukkit.event.entity.EntityDamageEvent +import org.bukkit.event.entity.FoodLevelChangeEvent +import org.bukkit.event.inventory.ClickType +import org.bukkit.event.inventory.CraftItemEvent +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.event.inventory.InventoryCloseEvent +import org.bukkit.event.inventory.InventoryOpenEvent +import org.bukkit.event.inventory.InventoryType +import org.bukkit.event.player.PlayerAttemptPickupItemEvent +import org.bukkit.event.player.PlayerBucketEmptyEvent +import org.bukkit.event.player.PlayerDropItemEvent +import org.bukkit.event.player.PlayerInteractEvent +import org.bukkit.event.player.PlayerItemDamageEvent +import org.bukkit.event.player.PlayerJoinEvent +import org.bukkit.event.player.PlayerQuitEvent +import org.bukkit.inventory.EnchantingInventory +import org.bukkit.inventory.EquipmentSlot +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.meta.Damageable +import java.util.Random +import kotlin.math.min + +class GameStateListener( + private val plugin: SpeedHG, + private val gameManager: GameManager +) : Listener { + + @EventHandler + fun onLeavesDecay( + event: LeavesDecayEvent + ) { + if ( gameManager.isRunning() ) + return + + event.isCancelled = true + } + + @EventHandler + fun onBlockPlace( + event: BlockPlaceEvent + ) { + val player = event.player + + if ( gameManager.getCurrentStateType() == GameStateTypes.FEAST ) + { + val block = event.block + + if (!gameManager.feastBox.contains( block.location.toVector() )) + return + + event.isCancelled = true + player.playSound( player, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f ) + return + } + + if ( gameManager.isRunning() ) + return + + event.isCancelled = true + player.playSound( player, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f ) + } + + private val beforeInvisMaterials = setOf( + Material.OAK_LOG, + Material.DARK_OAK_LOG, + Material.BIRCH_LOG, + Material.ACACIA_LOG, + Material.JUNGLE_LOG, + Material.SPRUCE_LOG, + Material.STONE + ) + + private val alwaysMaterials = setOf( + Material.RED_MUSHROOM, + Material.BROWN_MUSHROOM, + Material.COCOA_BEANS, + Material.CACTUS + ) + + @EventHandler + fun onBlockBreak( + event: BlockBreakEvent + ) { + val player = event.player + + if ( !gameManager.isRunning() ) + { + event.isCancelled = true + player.playSound( player, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f ) + return + } + + val block = event.block + + if ( gameManager.getCurrentStateType() == GameStateTypes.FEAST ) + { + if (!gameManager.feastBox.contains( block.location.toVector() )) + return + + event.isCancelled = true + player.playSound( player, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f ) + return + } + + if ( gameManager.getCurrentStateType() == GameStateTypes.IMMUNITY && + beforeInvisMaterials.contains( block.type )) + { + pickup( event, player ) + return + } + + if (alwaysMaterials.contains( block.type )) + { + pickup( event, player ) + return + } + + if ( block.type == Material.DIAMOND_ORE ) + { + event.isCancelled = true + event.block.type = Material.AIR + event.block.tick() + + plugin.chatManager.sendMessage( player, "build.no_diamonds" ) + player.playSound( player, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f ) + return + } + + if ( block.type == Material.IRON_ORE && gameManager.isBeforeFeast() ) + { + event.isCancelled = true + plugin.chatManager.sendMessage( player, "build.no_iron_before_feast" ) + player.playSound( player, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f ) + } + else if ( block.type == Material.IRON_ORE && !gameManager.isBeforeFeast() ) + { + runBlocking { plugin.statsRepository.addIronFarmed( player.uniqueId, 0.1 ) } + } + } + + private fun pickup( + event: BlockBreakEvent, + player: Player + ) { + event.isCancelled = true + + if (!hasInventorySpace( player )) + { + event.block.drops.forEach { player.world.dropItem( event.block.location, it ) } + event.block.type = Material.AIR + return + } + + event.block.drops.forEach { player.inventory.addItem( it ) } + event.block.type = Material.AIR + } + + private fun hasInventorySpace( + player: Player + ): Boolean + { + return player.inventory.any { it == null || it.type == Material.AIR } + } + + @EventHandler + fun onDropItem( + event: PlayerDropItemEvent + ) { + val player = event.player + + if ( !gameManager.isRunning() ) + { + event.isCancelled = true + player.playSound( player, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f ) + return + } + + TODO( "Kit & perk items" ) + } + + @EventHandler + fun onPickupItem( + event: PlayerAttemptPickupItemEvent + ) { + if ( gameManager.isRunning() ) + return + + event.isCancelled = true + } + + @EventHandler + fun onInteractCompass( + event: PlayerInteractEvent + ) { + if ( !gameManager.isRunning() ) + return + + val player = event.player + val action = event.action + + if ( action != Action.RIGHT_CLICK_AIR && + action != Action.RIGHT_CLICK_BLOCK ) + return + + val item = event.item + ?: return + + if ( item.type != Material.COMPASS ) + return + + val nearestPlayer = Bukkit.getOnlinePlayers().stream() + .filter { other -> other != player } + .map { other -> other.location.distance( player.location ) to other } + .filter { pair -> pair.first > 5.0 } + .toList() + .firstOrNull() + ?.second + + if ( nearestPlayer == null ) + { + plugin.chatManager.sendMessage( player, "compass.no_nearby_players" ) + return + } + + player.compassTarget = nearestPlayer.location + + val direction = DirectionUtil.getDirectionToPlayer( player, nearestPlayer ) + var actionBar = plugin.chatFormatter.format( "compass.actionBar" ) + actionBar = actionBar.replaceText { it.match( "" ).replacement( direction ).once() } + plugin.chatManager.getAudience( player ).sendActionBar( actionBar ) + } + + private val swordNerf = 0.5 + private val otherNerf = 0.2 + + private val nerfedItems = listOf( + "_AXE", "_SHOVEL", "_PICKAXE" + ) + + @EventHandler + fun onDamageEntity( + event: EntityDamageByEntityEvent + ) { + val damager = event.damager + + if ( damager !is Player ) + return + + val itemName = damager.inventory.itemInMainHand.type.name + + if (itemName.endsWith( "_SWORD" )) + { + event.damage *= swordNerf + return + } + + for ( nerfedItem in nerfedItems ) + if (itemName.endsWith( nerfedItem )) + event.damage *= otherNerf + } + + private val random = Random() + + @EventHandler + fun onPlayerItemDamage( + event: PlayerItemDamageEvent + ) { + val item = event.item + + if (!item.type.name.endsWith( "_SWORD" ) && + !item.type.name.endsWith( "_AXE" )) + return + + if ( random.nextBoolean() ) + event.isCancelled = true + } + + @EventHandler + fun onJoin( + event: PlayerJoinEvent + ) { + disableCooldown( event.player ) + } + + @EventHandler + fun onQuit( + event: PlayerQuitEvent + ) { + disableCooldown( event.player ) + } + + private fun disableCooldown( + player: Player + ) { + val attackSpeed = player.getAttribute( Attribute.GENERIC_ATTACK_SPEED ) + + if ( attackSpeed != null ) + attackSpeed.baseValue = 40.0 + } + + private val lapisLazuli = Material.LAPIS_LAZULI + + @EventHandler + fun onEnchant( + event: EnchantItemEvent + ) { + val enchInv = event.inventory as EnchantingInventory + enchInv.secondary = ItemStack( lapisLazuli, 64 ) + } + + @EventHandler + fun onInventoryClick( + event: InventoryClickEvent + ) { + if ( event.inventory.type != InventoryType.ENCHANTING ) + return + + val item = event.currentItem + ?: return + + // prevent taking it out + if ( item.type == lapisLazuli && event.rawSlot == 1 ) + event.isCancelled = true + else if ( event.cursor.type == lapisLazuli && event.click == ClickType.DOUBLE_CLICK) + event.isCancelled = true + } + + @EventHandler + fun onInventoryClose( + event: InventoryCloseEvent + ) { + val inventory = event.inventory + + if ( inventory.type != InventoryType.ENCHANTING ) + return + + ( inventory as EnchantingInventory ).secondary = null + } + + @EventHandler + fun onInventoryOpen( + event: InventoryOpenEvent + ) { + val inventory = event.inventory + + if ( inventory.type != InventoryType.ENCHANTING ) + return + + ( inventory as EnchantingInventory ).secondary = ItemStack( lapisLazuli, 64 ) + } + + @EventHandler + fun onInteractSoup( + event: PlayerInteractEvent + ) { + val player = event.player + val action = event.action + + if ( action != Action.RIGHT_CLICK_AIR && + action != Action.RIGHT_CLICK_BLOCK ) + return + + if ( !event.hasItem() || + event.material != Material.MUSHROOM_STEW ) + return + + if ( event.hand == EquipmentSlot.OFF_HAND ) + return + + if ( player.health < requireNotNull(player.getAttribute( Attribute.GENERIC_MAX_HEALTH )!!.defaultValue)) + { + player.health = min( player.health + 7, requireNotNull(player.getAttribute( Attribute.GENERIC_MAX_HEALTH )!!.defaultValue)) + player.inventory.setItemInMainHand(ItemStack( Material.BOWL )) + } + else if ( player.foodLevel < 20 ) + { + player.foodLevel += 6 + player.saturation += 7 + player.inventory.setItemInMainHand(ItemStack( Material.BOWL )) + } + } + + @EventHandler + fun onEntityDamage( + event: EntityDamageEvent + ) { + if ( gameManager.isRunning() ) + return + + event.isCancelled = true + } + + @EventHandler + fun onFoodLevelChange( + event: FoodLevelChangeEvent + ) { + if ( gameManager.isRunning() ) + return + + event.foodLevel = 20 + event.isCancelled = true + } + + @EventHandler + fun onCraftItem( + event: CraftItemEvent + ) { + if ( !gameManager.isRunning() ) + return + + val player = event.whoClicked + + if ( player !is Player ) + return + + val item = event.recipe.result + + if ( item.type == Material.SHIELD ) + { + if ( event.isShiftClick ) + { + plugin.chatManager.sendMessage( player, "craft.no_shift_click" ) + player.playSound( player, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f ) + return + } + + event.result = Event.Result.DENY + plugin.chatManager.sendMessage( player, "craft.no_shield" ) + player.playSound( player, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f ) + return + } + + if (!item.type.name.contains( "iron", true )) + return + + if ( gameManager.isBeforeFeast() ) + { + event.result = Event.Result.DENY + plugin.chatManager.sendMessage( player, "craft.no_iron_before_feast" ) + player.playSound( player, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f ) + return + } + + if ( item.type.maxDurability > 0 ) + { + item.editMeta { meta -> ( meta as Damageable ).damage /= 2 } + plugin.chatManager.sendMessage( player, "craft.iron_nerf" ) + } + + runBlocking { plugin.statsRepository.addIronFarmed( player.uniqueId, 0.1 ) } + } + + @EventHandler + fun onInteractCleanse( + event: PlayerInteractEvent + ) { + if ( gameManager.isBeforeFeast() ) + return + + val player = event.player + val action = event.action + + val item = event.item ?: return + val block = event.clickedBlock + + if ( block != null && gameManager.feastBox.contains( block.location.toVector() )) + { + event.isCancelled = true + player.playSound( player, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f ) + return + } + + if ( action != Action.RIGHT_CLICK_AIR && + action != Action.RIGHT_CLICK_BLOCK ) + return + + if ( item.type != Material.SUGAR || + !item.itemMeta.isUnbreakable ) + return + + event.isCancelled = true + + player.inventory.removeItemAnySlot( item ) + player.clearActivePotionEffects() + + plugin.chatManager.sendMessage( player, "feast.cleanser.cleaned" ) + } + + @EventHandler + fun onBucketEmpty( + event: PlayerBucketEmptyEvent + ) { + if ( gameManager.getCurrentStateType() != GameStateTypes.FEAST ) + return + + val location = event.blockClicked.location.toVector() + + if (!gameManager.feastBox.contains( location )) + return + + event.isCancelled = true + event.player.playSound( event.player, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f ) + } + + @EventHandler + fun onLiquidSpread( + event: BlockSpreadEvent + ) { + if ( gameManager.getCurrentStateType() != GameStateTypes.FEAST ) + return + + val block = event.block + + if ( !block.isLiquid ) + return + + if (!gameManager.feastBox.contains( block.location.toVector() )) + return + + event.isCancelled = true + } + +} \ No newline at end of file diff --git a/src/main/kotlin/club/mcscrims/speedhg/listener/LunarClientListener.kt b/src/main/kotlin/club/mcscrims/speedhg/listener/LunarClientListener.kt new file mode 100644 index 0000000..e3bd4c9 --- /dev/null +++ b/src/main/kotlin/club/mcscrims/speedhg/listener/LunarClientListener.kt @@ -0,0 +1,105 @@ +package club.mcscrims.speedhg.listener + +import club.mcscrims.speedhg.SpeedHG +import club.mcscrims.speedhg.game.GameStateTypes +import club.mcscrims.speedhg.util.LuckPermsUtils +import club.mcscrims.speedhg.util.TimeUtils +import com.lunarclient.apollo.Apollo +import com.lunarclient.apollo.event.ApolloListener +import com.lunarclient.apollo.event.Listen +import com.lunarclient.apollo.event.player.ApolloRegisterPlayerEvent +import com.lunarclient.apollo.mods.impl.ModFreelook +import com.lunarclient.apollo.mods.impl.ModMinimap +import com.lunarclient.apollo.mods.impl.ModSnaplook +import com.lunarclient.apollo.mods.impl.ModTeamView +import com.lunarclient.apollo.mods.impl.ModWaypoints +import com.lunarclient.apollo.module.modsetting.ModSettingModule +import com.lunarclient.apollo.module.richpresence.RichPresenceModule +import com.lunarclient.apollo.module.richpresence.ServerRichPresence +import com.lunarclient.apollo.module.serverrule.ServerRuleModule +import com.lunarclient.apollo.module.staffmod.StaffMod +import com.lunarclient.apollo.module.staffmod.StaffModModule +import com.lunarclient.apollo.player.ApolloPlayer + +class LunarClientListener( + private val plugin: SpeedHG +) : ApolloListener { + + private val modSettingModule = Apollo.getModuleManager().getModule( ModSettingModule::class.java ) + private val staffModModule = Apollo.getModuleManager().getModule( StaffModModule::class.java ) + + private val richPresenceModule = Apollo.getModuleManager().getModule( RichPresenceModule::class.java ) + + private val serverRuleModule = Apollo.getModuleManager().getModule( ServerRuleModule::class.java ) + + init { + this.handle( ApolloRegisterPlayerEvent::class.java, this::onApolloRegister ) + } + + @Listen + fun onApolloRegister( + event: ApolloRegisterPlayerEvent + ) { + val player = event.player + + setModSettings( player ) + setRichPresence( player ) + setServerRules( player ) + } + + private fun setServerRules( + player: ApolloPlayer + ) { + serverRuleModule.options.set( player, ServerRuleModule.COMPETITIVE_GAME, plugin.pluginConfig.data.game.competitiveGame ) + serverRuleModule.options.set( player, ServerRuleModule.COMPETITIVE_COMMANDS, plugin.pluginConfig.data.game.competitiveCommands ) + serverRuleModule.options.set( player, ServerRuleModule.ANTI_PORTAL_TRAPS, true ) + } + + private fun setRichPresence( + player: ApolloPlayer + ) { + val teamMaxSize = plugin.pluginConfig.data.game.teams["maximum_players"] as? Int ?: 2 + + val playerState = when( plugin.gameManager.getCurrentStateType() ) + { + GameStateTypes.WAITING -> plugin.pluginConfig.data.game.playerStates[ "waiting" ]?.scoreboard ?: "N/A" + GameStateTypes.PRE_START -> plugin.pluginConfig.data.game.playerStates[ "pre_start" ]?.scoreboard ?: "N/A" + GameStateTypes.IMMUNITY -> plugin.pluginConfig.data.game.playerStates[ "immunity" ]?.scoreboard ?: "N/A" + GameStateTypes.BATTLE -> plugin.pluginConfig.data.game.playerStates[ "battle" ]?.scoreboard ?: "N/A" + GameStateTypes.FEAST -> plugin.pluginConfig.data.game.playerStates[ "feast" ]?.scoreboard ?: "N/A" + GameStateTypes.DEATHMATCH -> plugin.pluginConfig.data.game.playerStates[ "deathmatch" ]?.scoreboard ?: "N/A" + GameStateTypes.END -> plugin.pluginConfig.data.game.playerStates[ "end" ]?.scoreboard ?: "N/A" + else -> throw IllegalStateException("Current game state is null!") + } + + val presence = ServerRichPresence.builder() + .gameName( plugin.pluginConfig.data.game.name ) + .gameState( plugin.gameManager.getCurrentStateType()!!.name ) + .gameVariantName( plugin.pluginConfig.data.game.variantName ) + .playerState(playerState.replace( "%time%", TimeUtils.scoreboardTimeFromState() )) + .teamCurrentSize( 0 ) // TODO: Add team manager + .teamMaxSize( teamMaxSize ) + .build() + + richPresenceModule.overrideServerRichPresence( player, presence ) + } + + private fun setModSettings( + player: ApolloPlayer + ) { + if (LuckPermsUtils.hasPermission( player.uniqueId, "mcscrims.staff" )) + staffModModule.enableStaffMods( player, listOf( StaffMod.XRAY )) + else + staffModModule.disableAllStaffMods( player ) + + if (LuckPermsUtils.hasPermission( player.uniqueId, "speedhg.bypass.modSettings" )) + return + + modSettingModule.options.set( player, ModMinimap.ENABLED, false ) + modSettingModule.options.set( player, ModFreelook.ENABLED, false ) + modSettingModule.options.set( player, ModSnaplook.ENABLED, false ) + modSettingModule.options.set( player, ModWaypoints.ENABLED, false ) + modSettingModule.options.set( player, ModTeamView.ENABLED, true ) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/club/mcscrims/speedhg/util/LuckPermsUtils.kt b/src/main/kotlin/club/mcscrims/speedhg/util/LuckPermsUtils.kt new file mode 100644 index 0000000..592eb69 --- /dev/null +++ b/src/main/kotlin/club/mcscrims/speedhg/util/LuckPermsUtils.kt @@ -0,0 +1,61 @@ +package club.mcscrims.speedhg.util + +import club.mcscrims.speedhg.SpeedHG +import net.luckperms.api.cacheddata.CachedDataManager +import net.luckperms.api.model.group.Group +import net.luckperms.api.model.user.User +import org.bukkit.entity.Player +import java.util.UUID + +object LuckPermsUtils { + + private val plugin = SpeedHG.instance + private val luckPerms = plugin.luckPerms + + fun getUser( + player: Player + ): User? + { + return luckPerms.userManager.getUser( player.uniqueId ) + } + + fun editUser( + player: Player, + action: (User) -> Unit + ) { + luckPerms.userManager.loadUser( player.uniqueId ).thenAcceptAsync( action ) + } + + fun getGroup( + groupName: String + ): Group? + { + return luckPerms.groupManager.getGroup( groupName ) + } + + fun getCachedData( + player: Player + ): CachedDataManager? + { + return getUser( player )?.cachedData + } + + fun hasPermission( + uniqueId: UUID, + permission: String + ): Boolean + { + val cachedData = luckPerms.userManager.loadUser( uniqueId ).get().cachedData + return cachedData.permissionData.checkPermission( permission ).asBoolean() + } + + fun hasPermission( + player: Player, + permission: String + ): Boolean + { + return getCachedData( player )?.permissionData?.checkPermission( permission )?.asBoolean() + ?: player.hasPermission( permission ) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/club/mcscrims/speedhg/util/TimeUtils.kt b/src/main/kotlin/club/mcscrims/speedhg/util/TimeUtils.kt new file mode 100644 index 0000000..eea6724 --- /dev/null +++ b/src/main/kotlin/club/mcscrims/speedhg/util/TimeUtils.kt @@ -0,0 +1,29 @@ +package club.mcscrims.speedhg.util + +import club.mcscrims.speedhg.SpeedHG + +object TimeUtils { + + private val plugin = SpeedHG.instance + + fun scoreboardTimeFromState(): String + { + val currentTime = plugin.gameManager.getCurrentState()?.remainingSeconds + ?: throw IllegalArgumentException("Remaining seconds for state is null!") + return scoreboardTime( currentTime ) + } + + fun scoreboardTime( + totalSeconds: Int + ): String + { + val hours = totalSeconds / 3600 + val minutes = (totalSeconds % 3600) / 60 + val seconds = totalSeconds % 60 + + if ( totalSeconds > 3600 ) + return String.format( "%02d:%02d:%02d", hours, minutes, seconds ) + return String.format( "%02d:%02d", minutes, seconds ) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/club/mcscrims/speedhg/world/WorldManager.kt b/src/main/kotlin/club/mcscrims/speedhg/world/WorldManager.kt new file mode 100644 index 0000000..edf9e20 --- /dev/null +++ b/src/main/kotlin/club/mcscrims/speedhg/world/WorldManager.kt @@ -0,0 +1,157 @@ +package club.mcscrims.speedhg.world + +import club.mcscrims.speedhg.SpeedHG +import org.bukkit.Bukkit +import org.bukkit.GameRule +import org.bukkit.Location +import org.bukkit.World +import org.popcraft.chunky.api.ChunkyAPI +import java.io.File + +class WorldManager( + private val plugin: SpeedHG +) { + + private lateinit var worldName: String + private lateinit var world: World + + fun highestLocationWithRadius( + center: Location, + radius: Int + ): Location + { + getWorld() + val minX = center.blockX - radius + val minZ = center.blockZ - radius + val maxX = center.blockX + radius + val maxZ = center.blockZ + radius + + var highestY = center.blockY + var highestX = minX + var highestZ = minZ + + for ( x in minX..maxX ) + for ( z in minZ..maxZ ) + { + val y = world.getHighestBlockYAt( x, z ) + + if ( y > highestY ) + { + highestY = y + highestX = x + highestZ = z + } + } + + val highest = Location( world, highestX.toDouble(), highestY.toDouble(), highestZ.toDouble() ) + return highest + } + + /* + * DELETION >> + */ + + fun deleteWorld() + { + getWorld() + Bukkit.unloadWorld( worldName, false ) + val folder = File( worldName ) + deleteFolder( folder ) + } + + private fun deleteFolder( + folder: File + ) { + val files = folder.listFiles() + + if ( files != null ) + for ( f in files ) + { + if ( f.isDirectory ) + deleteFolder( f ) + else + f.delete() + } + + folder.delete() + } + + /* + * WORLD >> + */ + + lateinit var spawnLocation: Location + var borderDecrease: Double = 100.0 + + fun setupWorld() + { + val world = getWorld() ?: return + plugin.logger.info("Setting up world...") + + // BORDER >> + + plugin.logger.info("Setting up world... [STAGE [1]: WORLDBORDER]") + + spawnLocation = Location( world, 0.0, world.getHighestBlockYAt( 0, 0).toDouble(), 0.0 ) + world.worldBorder.center = spawnLocation + + world.worldBorder.size = plugin.pluginConfig.data.world.border["size"]!! + world.worldBorder.warningDistance = plugin.pluginConfig.data.world.border["warning_distance"]!!.toInt() + world.worldBorder.damageAmount = plugin.pluginConfig.data.world.border["damage"]!! + + borderDecrease = plugin.pluginConfig.data.world.border["decrease"]!! + + // GAMERULES >> + + plugin.logger.info("Setting up world... [Stage [2]: GAMERULES]") + + world.setGameRule( GameRule.ANNOUNCE_ADVANCEMENTS, false ) + world.setGameRule( GameRule.DO_INSOMNIA, false ) + world.setGameRule( GameRule.DISABLE_RAIDS, true ) + world.setGameRule( GameRule.DO_PATROL_SPAWNING, false ) + world.setGameRule( GameRule.DO_TRADER_SPAWNING, false ) + + // CHUNKY >> + + plugin.logger.info("Setting up world... [Stage [3]: CHUNKY]") + val chunky = Bukkit.getServicesManager().load( ChunkyAPI::class.java ) + + if ( chunky == null || chunky.version() != 0 ) + { + plugin.isReady = true + return + } + + val radius = world.worldBorder.size / 2 + + chunky.startTask( worldName, "square", 0.0, 0.0, radius, radius, "concentric" ) + chunky.onGenerationComplete { plugin.isReady = true } + + plugin.server.dispatchCommand( Bukkit.getConsoleSender(), "chunky silent" ) + + // FINISH >> + + plugin.logger.info("World has been set up!") + } + + private fun setWorld( + worldName: String + ): World? + { + this.worldName = worldName + val world = Bukkit.getWorld( worldName ) + + if ( world != null ) + this.world = world + + return world + } + + fun getWorld(): World? + { + return if ( !::world.isInitialized ) + setWorld( plugin.pluginConfig.data.world.name ) + else this.world + } + +} \ No newline at end of file