Skip to content

Commit 0e16b87

Browse files
author
54895y
committed
Add shared hologram compatibility bridge
1 parent 2cd96c6 commit 0e16b87

8 files changed

Lines changed: 938 additions & 1 deletion

File tree

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
group=com.y54895.matrixlib
2-
version=1.1.0
2+
version=1.2.0
33
kotlin.incremental=true
44
kotlin.incremental.java=true
55
kotlin.caching.enabled=true
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.y54895.matrixlib.api.hologram
2+
3+
import org.bukkit.Location
4+
5+
enum class MatrixHologramAnchor {
6+
EXACT,
7+
BLOCK_CENTER
8+
}
9+
10+
data class MatrixHologramRequest(
11+
val namespace: String,
12+
val id: String,
13+
val baseLocation: Location,
14+
val lines: List<String>,
15+
val anchor: MatrixHologramAnchor = MatrixHologramAnchor.BLOCK_CENTER,
16+
val heightOverride: Double? = null
17+
) {
18+
19+
fun qualifiedId(): String {
20+
val resolvedNamespace = namespace.trim().ifBlank { "matrix" }.lowercase()
21+
return "${resolvedNamespace}_${id.trim()}"
22+
}
23+
}
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
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

Comments
 (0)