Update game states & world management

This commit is contained in:
Laurin
2025-12-06 05:41:42 +01:00
parent 2c10e3e7fd
commit 590318772f
14 changed files with 1105 additions and 362 deletions

View File

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

View File

@@ -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<GameStateTypes, GameState>()
private val winners = ArrayList<Player>()
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
}

View File

@@ -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<Listener>()
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)

View File

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

View File

@@ -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")
}
}

View File

@@ -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( "<direction>" ).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
}
}

View File

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

View File

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

View File

@@ -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( "<direction>" ).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
}
}

View File

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

View File

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

View File

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

View File

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