Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add timers for the game phase and TNT #13

Merged
merged 4 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/main/kotlin/live/adamlearns/animalroyale/AnimalRoyale.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import org.bukkit.plugin.java.JavaPlugin
class AnimalRoyale : JavaPlugin() {
private var eventListener: EventListener? = null
private var twitchChat: TwitchChat? = null
private var hud: Hud? = null
private val gameContext = GameContext(this)

private fun setupWorld() {
Expand Down Expand Up @@ -35,13 +36,18 @@ class AnimalRoyale : JavaPlugin() {
this.setupWorld()
this.setupEventListener()
this.setupTwitchChat()
this.setupHud()
}

private fun setupTwitchChat() {
twitchChat = TwitchChat(gameContext)
gameContext.registerTwitchChat(twitchChat)
}

private fun setupHud() {
hud = Hud(gameContext)
}

override fun onDisable() {
twitchChat?.destroy()

Expand Down
16 changes: 13 additions & 3 deletions src/main/kotlin/live/adamlearns/animalroyale/Arena.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Arena(private val gameContext: GameContext) {
/**
* This is in ticks.
*/
private var timeBetweenTurns = 20 * Ticks.TICKS_PER_SECOND
private var timeUntilNextRound = 20 * Ticks.TICKS_PER_SECOND

// This represents the top-center of the arena (since we're looking south).
private var location: Location? = null
Expand All @@ -54,6 +54,12 @@ class Arena(private val gameContext: GameContext) {

var startingNumSheep: Int = 0

var currentRoundStartTick: Float = Float.MIN_VALUE
private set

var nextRoundStartTick: Float = Float.MAX_VALUE
private set

private val length: Int
get() = depth * 2

Expand Down Expand Up @@ -534,11 +540,15 @@ class Arena(private val gameContext: GameContext) {
}

startNewMatchTask = Bukkit.getScheduler().runTaskLater(gameContext.javaPlugin, Runnable {
val currentTick = gameContext.javaPlugin.server.currentTick.toFloat()
currentRoundStartTick = currentTick
nextRoundStartTick = currentTick + timeUntilNextRound

gameContext.arena?.startRound()
startRoundIn(timeBetweenTurns.toLong())
startRoundIn(timeUntilNextRound.toLong())

// The minimum possible turn time is 1 second
timeBetweenTurns = max(20.0, floor(timeBetweenTurns * 0.9).toInt().toDouble()).toInt()
timeUntilNextRound = max(Ticks.TICKS_PER_SECOND, floor(timeUntilNextRound * 0.9).toInt())
}, delay)
}

Expand Down
145 changes: 145 additions & 0 deletions src/main/kotlin/live/adamlearns/animalroyale/Hud.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package live.adamlearns.animalroyale

import live.adamlearns.animalroyale.ComponentUtils.join
import live.adamlearns.animalroyale.extensions.cancelIfNeeded
import live.adamlearns.animalroyale.extensions.clamp
import live.adamlearns.animalroyale.extensions.format
import net.kyori.adventure.bossbar.BossBar
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
import net.kyori.adventure.util.Ticks
import org.bukkit.Bukkit
import org.bukkit.scheduler.BukkitTask

class Hud(private val gameContext: GameContext) {
private val gamePhaseBossBar = BossBar.bossBar(Component.text(), 1f, BossBar.Color.WHITE, BossBar.Overlay.PROGRESS)
private val tntBossBar = BossBar.bossBar(Component.text(), 1f, BossBar.Color.RED, BossBar.Overlay.PROGRESS)

private var updateTntTask: BukkitTask? = null

private var gamePhase: GamePhase? = null
private var gamePhaseStartTick = 0f
private var gamePhaseEndTick = 1f

init {
Bukkit.getScheduler()
.runTaskTimer(gameContext.javaPlugin, this::updateGamePhaseBar, 0L, PHASE_BAR_UPDATE_PERIOD)
}

private fun updateGamePhaseBar() {
showBarToPlayerIfNeeded(gamePhaseBossBar)

// When we change game phase, we need to set the bar title and reset the progress
if (gamePhase != gameContext.gamePhase) {
gamePhase = gameContext.gamePhase

val currentTick = gameContext.javaPlugin.server.currentTick
gamePhaseStartTick = currentTick.toFloat()
gamePhaseEndTick = (currentTick + getPhaseDuration(gameContext.gamePhase)).toFloat()

gamePhaseBossBar.name(GamePhaseBarTitle.getTitle(gameContext.gamePhase))
gamePhaseBossBar.progress(1f)

if (gamePhase == GamePhase.GAMEPLAY) {
updateTntTask = Bukkit.getScheduler()
.runTaskTimer(gameContext.javaPlugin, this::updateTntBar, 0L, TNT_BAR_UPDATE_PERIOD)
showBarToPlayerIfNeeded(tntBossBar)
} else {
updateTntTask?.cancelIfNeeded()
hideBarFromPlayerIfNeeded(tntBossBar)
}
}

// Update the progress
if (PHASES_THAT_HAVE_PROGRESS.contains(gamePhase)) {
val currentTick = gameContext.javaPlugin.server.currentTick
val progress: Float =
((currentTick.toFloat() - gamePhaseStartTick) / (gamePhaseEndTick - gamePhaseStartTick)).clamp(0f, 1f)
gamePhaseBossBar.progress(1f - progress) // We want the bar to go 'down' to get the timer effect
}
}

private fun updateTntBar() {
val arena = gameContext.arena ?: return

val currentTick = gameContext.javaPlugin.server.currentTick
val progress: Float =
((currentTick.toFloat() - arena.currentRoundStartTick) / (arena.nextRoundStartTick - arena.currentRoundStartTick)).clamp(
0f, 1f
)
tntBossBar.progress(1f - progress) // We want the bar to go 'down' to get the timer effect

val remainingSeconds = (arena.nextRoundStartTick - currentTick) / Ticks.TICKS_PER_SECOND.toFloat()
tntBossBar.name(TntBarTitle.getTitle(remainingSeconds))
}

private fun showBarToPlayerIfNeeded(bar: BossBar) {
val player = gameContext.firstPlayer ?: return

if (!player.activeBossBars().contains(bar)) {
player.showBossBar(bar)
}
}

private fun hideBarFromPlayerIfNeeded(bar: BossBar) {
val player = gameContext.firstPlayer ?: return

if (player.activeBossBars().contains(bar)) {
player.hideBossBar(bar)
}
}

companion object {
private const val PHASE_BAR_UPDATE_PERIOD: Long = 1L * Ticks.TICKS_PER_SECOND
private const val TNT_BAR_UPDATE_PERIOD: Long = Ticks.TICKS_PER_SECOND / 10L

private val PHASES_THAT_HAVE_PROGRESS = listOf(GamePhase.LOBBY, GamePhase.GAMEPLAY)

private fun getPhaseDuration(phase: GamePhase): Int = when (phase) {
GamePhase.LOBBY -> Arena.NUM_SECONDS_BEFORE_STARTING_MATCH * Ticks.TICKS_PER_SECOND
GamePhase.GAMEPLAY -> Arena.NUM_SECONDS_BEFORE_SUDDEN_DEATH * Ticks.TICKS_PER_SECOND
// For other phases, return an arbitrary long timeframe since we do not update the bar during them
else -> 3600 * Ticks.TICKS_PER_SECOND
}
}

private object GamePhaseBarTitle {
private val CREATING_ARENA_TITLE: Component = Component.text("Waiting to start...")

private val LOBBY_TITLE: Component
get() = join(
" ",
Component.text("Game will start soon, type"),
Component.text("!join", NamedTextColor.BLUE),
Component.text("in chat!"),
)

private val PRE_GAMEPLAY_TITLE: Component = Component.text("Starting soon...")

private val GAMEPLAY: Component
get() = join(
" ",
Component.text("Game is ongoing! Waiting for someone to"),
Component.text("win", NamedTextColor.GREEN),
Component.text("or for"),
Component.text("Sudden Death", NamedTextColor.RED),
Component.text("to start")
)

fun getTitle(phase: GamePhase): Component = when (phase) {
GamePhase.CREATING_ARENA -> CREATING_ARENA_TITLE
GamePhase.PRE_GAMEPLAY -> PRE_GAMEPLAY_TITLE
GamePhase.LOBBY -> LOBBY_TITLE
GamePhase.GAMEPLAY -> GAMEPLAY
}
}

private object TntBarTitle {
fun getTitle(remainingSeconds: Float): Component = join(
" ",
Component.text("Launching TNT in"),
Component.text(remainingSeconds.format(1), NamedTextColor.RED),
Component.text("seconds!"),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package live.adamlearns.animalroyale.extensions

fun Float.format(digits: Int) = "%.${digits}f".format(this)