|
| 1 | +package com.y54895.matrixlib.api.hologram |
| 2 | + |
| 3 | +import com.y54895.matrixlib.api.hologram.internal.CMIHologramsAdapter |
| 4 | +import com.y54895.matrixlib.api.hologram.internal.DecentHologramsAdapter |
| 5 | +import com.y54895.matrixlib.api.hologram.internal.FancyHologramsAdapter |
| 6 | +import com.y54895.matrixlib.api.hologram.internal.MatrixHologramAdapter |
| 7 | +import com.y54895.matrixlib.api.hologram.internal.MatrixRenderedHologram |
| 8 | +import com.y54895.matrixlib.api.resource.MatrixResourceFiles |
| 9 | +import com.y54895.matrixlib.api.text.MatrixText |
| 10 | +import org.bukkit.Bukkit |
| 11 | +import org.bukkit.configuration.file.YamlConfiguration |
| 12 | +import org.bukkit.event.server.PluginDisableEvent |
| 13 | +import org.bukkit.event.server.PluginEnableEvent |
| 14 | +import taboolib.common.LifeCycle |
| 15 | +import taboolib.common.platform.Awake |
| 16 | +import taboolib.common.platform.event.SubscribeEvent |
| 17 | +import taboolib.common.platform.function.info |
| 18 | +import taboolib.common.platform.function.warning |
| 19 | +import taboolib.platform.BukkitPlugin |
| 20 | +import java.io.File |
| 21 | +import java.util.concurrent.ConcurrentHashMap |
| 22 | + |
| 23 | +object MatrixHolograms { |
| 24 | + |
| 25 | + private const val resourcePath = "Hologram/config.yml" |
| 26 | + |
| 27 | + private val providerSpecs = listOf( |
| 28 | + ProviderSpec( |
| 29 | + pluginName = "DecentHolograms", |
| 30 | + aliases = setOf("decentholograms", "decent"), |
| 31 | + factory = { DecentHologramsAdapter.createOrNull() } |
| 32 | + ), |
| 33 | + ProviderSpec( |
| 34 | + pluginName = "FancyHolograms", |
| 35 | + aliases = setOf("fancyholograms", "fancy"), |
| 36 | + factory = { FancyHologramsAdapter.createOrNull() } |
| 37 | + ), |
| 38 | + ProviderSpec( |
| 39 | + pluginName = "CMI", |
| 40 | + aliases = setOf("cmi"), |
| 41 | + factory = { CMIHologramsAdapter.createOrNull() } |
| 42 | + ) |
| 43 | + ) |
| 44 | + |
| 45 | + private val requests = ConcurrentHashMap<String, MatrixHologramRequest>() |
| 46 | + |
| 47 | + @Volatile |
| 48 | + private var settings = HologramSettings( |
| 49 | + providerOrder = providerSpecs.map(ProviderSpec::pluginName) |
| 50 | + ) |
| 51 | + |
| 52 | + @Volatile |
| 53 | + private var adapter: MatrixHologramAdapter? = null |
| 54 | + |
| 55 | + @Volatile |
| 56 | + private var adapterName: String? = null |
| 57 | + |
| 58 | + @Awake(LifeCycle.DISABLE) |
| 59 | + fun shutdown() { |
| 60 | + adapter?.cleanup() |
| 61 | + adapter = null |
| 62 | + adapterName = null |
| 63 | + requests.clear() |
| 64 | + } |
| 65 | + |
| 66 | + fun reload() { |
| 67 | + settings = loadSettings() |
| 68 | + refreshAdapter(rebuildCached = true, force = true) |
| 69 | + } |
| 70 | + |
| 71 | + fun configFile(): File { |
| 72 | + val plugin = BukkitPlugin.getInstance() |
| 73 | + MatrixResourceFiles.saveResourceIfAbsent(plugin, resourcePath) |
| 74 | + return MatrixResourceFiles.dataFile(plugin, resourcePath) |
| 75 | + } |
| 76 | + |
| 77 | + fun createOrUpdate(request: MatrixHologramRequest) { |
| 78 | + val qualifiedId = request.qualifiedId() |
| 79 | + if (request.lines.isEmpty()) { |
| 80 | + remove(request.namespace, request.id) |
| 81 | + return |
| 82 | + } |
| 83 | + requests[qualifiedId] = request |
| 84 | + ensureAdapter() |
| 85 | + adapter?.createOrUpdate(render(request)) |
| 86 | + } |
| 87 | + |
| 88 | + fun remove(namespace: String, id: String) { |
| 89 | + val qualifiedId = qualifiedId(namespace, id) |
| 90 | + requests.remove(qualifiedId) |
| 91 | + adapter?.remove(qualifiedId) |
| 92 | + } |
| 93 | + |
| 94 | + fun clearNamespace(namespace: String) { |
| 95 | + val prefix = "${namespace.trim().ifBlank { "matrix" }.lowercase()}_" |
| 96 | + val keys = requests.keys.filter { it.startsWith(prefix) } |
| 97 | + keys.forEach { qualifiedId -> |
| 98 | + requests.remove(qualifiedId) |
| 99 | + adapter?.remove(qualifiedId) |
| 100 | + } |
| 101 | + } |
| 102 | + |
| 103 | + fun defaultHeight(): Double { |
| 104 | + return settings.defaultHeight |
| 105 | + } |
| 106 | + |
| 107 | + fun providerSummary(): String { |
| 108 | + return if (!settings.enabled) { |
| 109 | + "disabled" |
| 110 | + } else { |
| 111 | + adapterName ?: "none" |
| 112 | + } |
| 113 | + } |
| 114 | + |
| 115 | + fun isEnabled(): Boolean { |
| 116 | + return settings.enabled |
| 117 | + } |
| 118 | + |
| 119 | + @SubscribeEvent |
| 120 | + fun onPluginEnable(event: PluginEnableEvent) { |
| 121 | + if (providerSpecs.any { it.pluginName.equals(event.plugin.name, ignoreCase = true) }) { |
| 122 | + refreshAdapter(rebuildCached = true) |
| 123 | + } |
| 124 | + } |
| 125 | + |
| 126 | + @SubscribeEvent |
| 127 | + fun onPluginDisable(event: PluginDisableEvent) { |
| 128 | + if (providerSpecs.any { it.pluginName.equals(event.plugin.name, ignoreCase = true) }) { |
| 129 | + refreshAdapter(rebuildCached = true, force = true) |
| 130 | + } |
| 131 | + } |
| 132 | + |
| 133 | + private fun ensureAdapter() { |
| 134 | + if (adapter == null && settings.enabled) { |
| 135 | + refreshAdapter(rebuildCached = false) |
| 136 | + } |
| 137 | + } |
| 138 | + |
| 139 | + private fun refreshAdapter(rebuildCached: Boolean, force: Boolean = false) { |
| 140 | + val previousName = adapterName |
| 141 | + |
| 142 | + if (!settings.enabled) { |
| 143 | + adapter?.cleanup() |
| 144 | + adapter = null |
| 145 | + adapterName = null |
| 146 | + if (previousName != null || force) { |
| 147 | + info("MatrixLib holograms are disabled in ${configFile().name}.") |
| 148 | + } |
| 149 | + return |
| 150 | + } |
| 151 | + |
| 152 | + val nextAdapter = selectAdapter() |
| 153 | + val nextName = nextAdapter?.name |
| 154 | + |
| 155 | + if (!force && previousName == nextName && adapter != null) { |
| 156 | + if (rebuildCached) { |
| 157 | + rebuildCachedRequests(adapter!!) |
| 158 | + } |
| 159 | + return |
| 160 | + } |
| 161 | + |
| 162 | + adapter?.cleanup() |
| 163 | + adapter = nextAdapter |
| 164 | + adapterName = nextName |
| 165 | + |
| 166 | + if (previousName != nextName || force) { |
| 167 | + when (nextName) { |
| 168 | + "DecentHolograms" -> info("MatrixLib hologram bridge: DecentHolograms") |
| 169 | + "FancyHolograms" -> info("MatrixLib hologram bridge: FancyHolograms") |
| 170 | + "CMI" -> info("MatrixLib hologram bridge: CMI") |
| 171 | + null -> info("MatrixLib hologram bridge: unavailable") |
| 172 | + } |
| 173 | + } |
| 174 | + |
| 175 | + if (rebuildCached && nextAdapter != null) { |
| 176 | + rebuildCachedRequests(nextAdapter) |
| 177 | + } |
| 178 | + } |
| 179 | + |
| 180 | + private fun rebuildCachedRequests(adapter: MatrixHologramAdapter) { |
| 181 | + requests.values.forEach { request -> |
| 182 | + adapter.createOrUpdate(render(request)) |
| 183 | + } |
| 184 | + } |
| 185 | + |
| 186 | + private fun selectAdapter(): MatrixHologramAdapter? { |
| 187 | + val forced = settings.forceProvider |
| 188 | + if (forced != null) { |
| 189 | + val spec = findProviderSpec(forced) |
| 190 | + if (spec == null) { |
| 191 | + warning("MatrixLib hologram force-provider '$forced' is unknown.") |
| 192 | + return null |
| 193 | + } |
| 194 | + if (!isPluginEnabled(spec.pluginName)) { |
| 195 | + warning("MatrixLib hologram force-provider '${spec.pluginName}' is not enabled.") |
| 196 | + return null |
| 197 | + } |
| 198 | + return spec.factory() |
| 199 | + } |
| 200 | + |
| 201 | + orderedProviderSpecs().forEach { spec -> |
| 202 | + if (!isPluginEnabled(spec.pluginName)) { |
| 203 | + return@forEach |
| 204 | + } |
| 205 | + val selected = spec.factory() |
| 206 | + if (selected != null) { |
| 207 | + return selected |
| 208 | + } |
| 209 | + } |
| 210 | + return null |
| 211 | + } |
| 212 | + |
| 213 | + private fun orderedProviderSpecs(): List<ProviderSpec> { |
| 214 | + if (settings.providerOrder.isEmpty()) { |
| 215 | + return providerSpecs |
| 216 | + } |
| 217 | + |
| 218 | + val ordered = mutableListOf<ProviderSpec>() |
| 219 | + settings.providerOrder.forEach { configured -> |
| 220 | + findProviderSpec(configured)?.let { spec -> |
| 221 | + if (ordered.none { it.pluginName == spec.pluginName }) { |
| 222 | + ordered += spec |
| 223 | + } |
| 224 | + } |
| 225 | + } |
| 226 | + providerSpecs.forEach { spec -> |
| 227 | + if (ordered.none { it.pluginName == spec.pluginName }) { |
| 228 | + ordered += spec |
| 229 | + } |
| 230 | + } |
| 231 | + return ordered |
| 232 | + } |
| 233 | + |
| 234 | + private fun render(request: MatrixHologramRequest): MatrixRenderedHologram { |
| 235 | + val height = request.heightOverride ?: settings.defaultHeight |
| 236 | + val location = request.baseLocation.clone().add( |
| 237 | + if (request.anchor == MatrixHologramAnchor.BLOCK_CENTER) 0.5 else 0.0, |
| 238 | + height, |
| 239 | + if (request.anchor == MatrixHologramAnchor.BLOCK_CENTER) 0.5 else 0.0 |
| 240 | + ) |
| 241 | + return MatrixRenderedHologram( |
| 242 | + qualifiedId = request.qualifiedId(), |
| 243 | + location = location, |
| 244 | + lines = request.lines.map(MatrixText::color) |
| 245 | + ) |
| 246 | + } |
| 247 | + |
| 248 | + private fun loadSettings(): HologramSettings { |
| 249 | + val yaml = YamlConfiguration.loadConfiguration(configFile()) |
| 250 | + val providerOrder = yaml.getStringList("provider-order") |
| 251 | + .mapNotNull { line -> line?.trim()?.takeIf(String::isNotBlank) } |
| 252 | + |
| 253 | + return HologramSettings( |
| 254 | + enabled = yaml.getBoolean("enabled", true), |
| 255 | + defaultHeight = yaml.getDouble("default-height", 1.5), |
| 256 | + forceProvider = yaml.getString("force-provider")?.trim()?.takeIf(String::isNotBlank), |
| 257 | + providerOrder = if (providerOrder.isEmpty()) providerSpecs.map(ProviderSpec::pluginName) else providerOrder, |
| 258 | + debug = yaml.getBoolean("debug", false) |
| 259 | + ) |
| 260 | + } |
| 261 | + |
| 262 | + private fun findProviderSpec(name: String): ProviderSpec? { |
| 263 | + val normalized = name.trim().lowercase() |
| 264 | + return providerSpecs.firstOrNull { spec -> |
| 265 | + spec.pluginName.equals(name, ignoreCase = true) || spec.aliases.contains(normalized) |
| 266 | + } |
| 267 | + } |
| 268 | + |
| 269 | + private fun isPluginEnabled(name: String): Boolean { |
| 270 | + return Bukkit.getPluginManager().getPlugin(name)?.isEnabled == true |
| 271 | + } |
| 272 | + |
| 273 | + private fun qualifiedId(namespace: String, id: String): String { |
| 274 | + val resolvedNamespace = namespace.trim().ifBlank { "matrix" }.lowercase() |
| 275 | + return "${resolvedNamespace}_${id.trim()}" |
| 276 | + } |
| 277 | + |
| 278 | + private data class ProviderSpec( |
| 279 | + val pluginName: String, |
| 280 | + val aliases: Set<String>, |
| 281 | + val factory: () -> MatrixHologramAdapter? |
| 282 | + ) |
| 283 | + |
| 284 | + private data class HologramSettings( |
| 285 | + val enabled: Boolean = true, |
| 286 | + val defaultHeight: Double = 1.5, |
| 287 | + val forceProvider: String? = null, |
| 288 | + val providerOrder: List<String> = emptyList(), |
| 289 | + val debug: Boolean = false |
| 290 | + ) |
| 291 | +} |
0 commit comments