diff --git a/bukkit/src/main/kotlin/com/r4g3baby/simplescore/bukkit/scoreboard/VarReplacer.kt b/bukkit/src/main/kotlin/com/r4g3baby/simplescore/bukkit/scoreboard/VarReplacer.kt index 575a17e..ad40c9f 100644 --- a/bukkit/src/main/kotlin/com/r4g3baby/simplescore/bukkit/scoreboard/VarReplacer.kt +++ b/bukkit/src/main/kotlin/com/r4g3baby/simplescore/bukkit/scoreboard/VarReplacer.kt @@ -3,9 +3,11 @@ package com.r4g3baby.simplescore.bukkit.scoreboard import com.r4g3baby.simplescore.BukkitPlugin import com.r4g3baby.simplescore.api.scoreboard.VarReplacer import com.r4g3baby.simplescore.bukkit.util.Adventure +import com.r4g3baby.simplescore.bukkit.util.getPlayerPing import com.r4g3baby.simplescore.bukkit.util.lazyReplace import com.r4g3baby.simplescore.core.util.translateColorCodes import me.clip.placeholderapi.PlaceholderAPI +import org.bukkit.Statistic import org.bukkit.entity.Player import kotlin.math.max import kotlin.math.min @@ -17,17 +19,18 @@ class VarReplacer(plugin: BukkitPlugin) : VarReplacer { override fun replace(text: String, viewer: Player): String { var result = if (usePlaceholderAPI) PlaceholderAPI.setPlaceholders(viewer, text) else text - result = result.lazyReplace("%player_name%") { viewer.name } + result = result + .lazyReplace("%player_name%") { viewer.name } .lazyReplace("%player_displayname%") { viewer.displayName } .lazyReplace("%player_uuid%") { viewer.uniqueId.toString() } .lazyReplace("%player_level%") { viewer.level.toString() } - .lazyReplace("%player_gamemode%") { viewer.gameMode.name.lowercase().replaceFirstChar { it.titlecase() } } + .lazyReplace("%player_gamemode%") { getGameMode(viewer) } .lazyReplace("%player_health%") { viewer.health.roundToInt().toString() } .lazyReplace("%player_maxhealth%") { viewer.maxHealth.roundToInt().toString() } - .lazyReplace("%player_hearts%") { - val hearts = min(10, max(0, ((viewer.health / viewer.maxHealth) * 10).roundToInt())) - "&c${"❤".repeat(hearts)}&0${"❤".repeat(10 - hearts)}" - } + .lazyReplace("%player_hearts%") { getHearts(viewer) } + .lazyReplace("%player_ping%") { getPing(viewer) } + .lazyReplace("%player_kills%") { viewer.getStatistic(Statistic.PLAYER_KILLS).toString() } + .lazyReplace("%player_deaths%") { viewer.getStatistic(Statistic.DEATHS).toString() } .lazyReplace("%player_world%") { viewer.world.name } .lazyReplace("%player_world_online%") { viewer.world.players.size.toString() } .lazyReplace("%server_online%") { viewer.server.onlinePlayers.size.toString() } @@ -36,4 +39,18 @@ class VarReplacer(plugin: BukkitPlugin) : VarReplacer { result = Adventure.parseToString(result) return translateColorCodes(result) } -} \ No newline at end of file + + private fun getGameMode(player: Player): String { + return player.gameMode.name.lowercase().replaceFirstChar { it.titlecase() } + } + + private val fullHearts = Array(11) { "❤".repeat(it) } + private fun getHearts(player: Player): String { + val hearts = min(10, max(0, ((player.health / player.maxHealth) * 10).roundToInt())) + return "&c${fullHearts[hearts]}&0${fullHearts[10 - hearts]}" + } + + private fun getPing(player: Player): String { + return getPlayerPing.apply(player).toString() + } +} diff --git a/bukkit/src/main/kotlin/com/r4g3baby/simplescore/bukkit/util/Utils.kt b/bukkit/src/main/kotlin/com/r4g3baby/simplescore/bukkit/util/Utils.kt index c09010e..49e8a79 100644 --- a/bukkit/src/main/kotlin/com/r4g3baby/simplescore/bukkit/util/Utils.kt +++ b/bukkit/src/main/kotlin/com/r4g3baby/simplescore/bukkit/util/Utils.kt @@ -1,32 +1,76 @@ package com.r4g3baby.simplescore.bukkit.util import com.r4g3baby.simplescore.api.scoreboard.data.Provider +import com.r4g3baby.simplescore.bukkit.protocol.util.Utils +import com.r4g3baby.simplescore.core.util.Reflection +import org.bukkit.entity.Player import org.bukkit.plugin.Plugin +import java.util.function.Function fun String.lazyReplace(oldValue: String, newValueFunc: () -> String): String { - run { - var occurrenceIndex: Int = indexOf(oldValue, 0, true) - if (occurrenceIndex < 0) return this - - val newValue = newValueFunc() - - val oldValueLength = oldValue.length - val searchStep = oldValueLength.coerceAtLeast(1) - val newLengthHint = length - oldValueLength + newValue.length - if (newLengthHint < 0) throw OutOfMemoryError() - val stringBuilder = StringBuilder(newLengthHint) - - var i = 0 - do { - stringBuilder.append(this, i, occurrenceIndex).append(newValue) - i = occurrenceIndex + oldValueLength - if (occurrenceIndex >= length) break - occurrenceIndex = indexOf(oldValue, occurrenceIndex + searchStep, true) - } while (occurrenceIndex > 0) - return stringBuilder.append(this, i, length).toString() + var occurrenceIndex = this.indexOf(oldValue, ignoreCase = true) + if (occurrenceIndex < 0) return this + + val newValue = newValueFunc() + val oldValueLength = oldValue.length + + val newLengthHint = this.length - oldValueLength + newValue.length + if (newLengthHint < 0) throw OutOfMemoryError() + + var cursor = 0 + val stringBuilder = StringBuilder(newLengthHint) + while (occurrenceIndex >= 0) { + stringBuilder.append(this, cursor, occurrenceIndex).append(newValue) + + cursor = occurrenceIndex + oldValueLength + occurrenceIndex = this.indexOf(oldValue, cursor, ignoreCase = true) } + + if (cursor < this.length) { + stringBuilder.append(this, cursor, this.length) + } + + return stringBuilder.toString() } fun bukkitProvider(plugin: Plugin): Provider { return Provider(plugin.name) -} \ No newline at end of file +} + +@JvmField +val getPlayerPing = object : Function { + private val getPingMethod: Reflection.MethodInvoker? = try { + Reflection.getMethodByName(Player::class.java, "getPing") + } catch (_: Exception) { null } + + private val getPlayerHandle: Reflection.MethodInvoker? + private val pingField: Reflection.FieldAccessor? + + init { + if (getPingMethod == null) { + getPlayerHandle = try { + val craftPlayer = Reflection.getClass("${Utils.OBC}.entity.CraftPlayer") + Reflection.getMethodByName(craftPlayer, "getHandle") + } catch (_: Exception) { null } + + pingField = try { + val entityPlayer = Reflection.findClass( + "net.minecraft.server.level.ServerPlayer", + "net.minecraft.server.level.EntityPlayer", "${Utils.NMS}.EntityPlayer" + ) + Reflection.getField(entityPlayer, Int::class.java, filter = { field -> field.name == "ping" }) + } catch (_: Exception) { null } + } else { + getPlayerHandle = null + pingField = null + } + } + + override fun apply(player: Player): Int { + return when { + getPingMethod != null -> getPingMethod.invoke(player) as? Int ?: -1 + getPlayerHandle != null && pingField != null -> pingField.get(getPlayerHandle.invoke(player)) as? Int ?: -1 + else -> -1 + } + } +}