Related: APIๆฆ่ง.md ยท ๅทฅๅ ท้.md ยท GUI็ผ็จAPI.md ยท ../05-ๅ่/ๆฏ่ฏญ่กจ.md
๐พ Data Storage & Placeholders (PdcService / DatabaseManager / PapiBridge) โ
This page covers the three persistence / data-fetching capabilities QCL provides to module developers:
- PdcService โ read/write namespaced data on any
PersistentDataContainer(items, entities, etc.). - DatabaseManager โ SQLite / MySQL database facade, including connection retrieval and serialization utilities.
- PapiBridge + QinhPlaceholderProvider โ PlaceholderAPI bridge: register custom
%qcl_xxx%, safe parsing, graceful degradation when not installed.
1. PdcService โ Persistent Data Container Service โ
Package: com.qinhuai.corelib.pdc
PDC (PersistentDataContainer) is Bukkit's standard container for attaching custom data to items / entities, etc. On top of it, PdcService adds namespace isolation and typed read/write, so modules don't step on each other's keys.
1.1 Obtaining a service for a namespace โ
val pdc: PdcService = PdcServiceManager.get("mymodule") // get the service for a namespace
PdcServiceManager.allNamespaces() // list all registered namespaces
PdcServiceManager.registerStatus("mymodule")
PdcServiceManager.diagnoseAll() // diagnose all namespaces1.2 Key rules โ
The NamespacedKey produced by createKey(key) looks like:
qinhcorelib:namespace_keynameFor example, with namespace mymodule and key level, the final key is qinhcorelib:mymodule_level. The uniform prefix qinhcorelib guarantees no collision with other plugins, and the namespace segment provides per-module isolation.
val key: NamespacedKey = pdc.createKey("level") // โ qinhcorelib:mymodule_level1.3 Typed read/write โ
PdcService's read/write methods operate on any PersistentDataContainer (item meta, entities, etc.):
| Type | Write | Read |
|---|---|---|
| String | setString(container, key, value) | getString(...) |
| Int | setInt(...) | getInt(...) / getIntOrDefault(..., default) |
| Long | setLong(...) | getLong(...) |
| Double | setDouble(...) | getDouble(...) |
| Boolean | setBoolean(...) | getBoolean(...) / getBooleanOrDefault(..., default) |
| Generic | set(container, key, type, value) | get(container, key, type) |
| โ | has(container, key) / remove(container, key) | โ |
๐งท Boolean is stored as an int underneath (
1/0); both read and write are converted automatically byPdcService, so what you see is aBoolean.
1.4 Storing data on an item โ
val pdc = PdcServiceManager.get("mymodule")
val item = ItemStack(Material.DIAMOND_SWORD)
val meta = item.itemMeta!!
val container = meta.persistentDataContainer
pdc.setInt(container, "level", 5)
pdc.setString(container, "owner", player.uniqueId.toString())
pdc.setBoolean(container, "bound", true)
item.itemMeta = meta // don't forget to write meta back
// read
val level = pdc.getIntOrDefault(item.itemMeta!!.persistentDataContainer, "level", 0)
val bound = pdc.getBooleanOrDefault(item.itemMeta!!.persistentDataContainer, "bound", false)1.5 Storing data on an entity โ
val pdc = PdcServiceManager.get("mymodule")
val container = entity.persistentDataContainer
pdc.setLong(container, "spawnedAt", System.currentTimeMillis())
if (pdc.has(container, "spawnedAt")) {
val t = pdc.getLong(container, "spawnedAt")
}
pdc.remove(container, "spawnedAt")1.6 Diagnostics โ
PdcServiceManager.diagnoseAll() // troubleshoot namespace / key status2. DatabaseManager โ Database Facade โ
Package: com.qinhuai.corelib.database
2.1 Initialization and retrieval โ
DatabaseManager.init(databaseConfig) // initialize with a DatabaseConfig
val db: QclDatabase = DatabaseManager.get() // get the database instance
DatabaseManager.getType() // DatabaseType.SQLITE or .MYSQL
DatabaseManager.isMySQL() // Boolean
DatabaseManager.isSQLite() // Boolean
DatabaseManager.bridgeStatus() // bridge status
DatabaseManager.diagnose() // diagnoseThe DatabaseType enum: SQLITE / MYSQL.
2.2 The database section of the config โ
The database type is determined by the database section in the config:
database:
type: sqlite # sqlite or mysql
sqlite:
data-folder: data # data directory relative to plugins/QinhCoreLib/
mysql:
host: localhost
port: 3306
database: qcl
user: root
password: ""- With SQLite, data lives in
plugins/QinhCoreLib/data/, split intoglobal.db(global) and one{uuid}.dbper player.
2.3 QclDatabase โ connection and serialization utilities โ
val db = DatabaseManager.get()
// get a connection: pass the player UUID for owner โ the corresponding {uuid}.db; pass null โ the global database global.db
val conn: Connection = db.getConnection(player.uniqueId)
val globalConn = db.getConnection(null)
// ... operate via JDBC ...
db.close()Serialization utilities (commonly used when storing into the database):
| Utility | Purpose |
|---|---|
serializeLocation(location) | Location โ string |
deserializeLocation(world, x, y, z, yaw, pitch) | Deserialize a location |
serializeLocations(...) | Multiple locations (semicolon-separated) |
serializeInventory(inventory) | Inventory โ Base64 string |
deserializeInventory(base64) | Base64 โ inventory |
val db = DatabaseManager.get()
// store an inventory
val base64 = db.serializeInventory(player.inventory)
// ... write to a database field ...
// retrieve it
val inv = db.deserializeInventory(base64)
// store a location
val locStr = db.serializeLocation(player.location)3. PapiBridge + Custom Placeholders โ
Package: com.qinhuai.corelib.placeholder
PapiBridge wraps PlaceholderAPI (PAPI): when PAPI is not installed it degrades safely โ no errors, no crashes.
3.1 Parsing text โ
PapiBridge.isEnabled() // whether PAPI is available
val s1 = PapiBridge.apply(player, "Hello %player_name%!") // online player
val s2 = PapiBridge.apply(offlinePlayer, "Points: %qcl_points%") // offline player๐ Degradation and short-circuit:
applyonly actually parses when the text contains%; when PAPI is not installed it returns the text unchanged, degrading safely.
3.2 Registering a custom placeholder %qcl_xxx% โ
Implement the QinhPlaceholderProvider interface and register it, and %identifier_params% will reach your code:
interface QinhPlaceholderProvider {
val identifier: String // e.g. "qcl" โ %qcl_xxx%
fun onRequest(player: OfflinePlayer?, params: String): String? // returning null = don't parse
}identifierโ the placeholder prefix;"qcl"corresponds to%qcl_...%.onRequestโparamsis the part afteridentifier_; returningnullmeans "I don't recognize this, hand it back to PAPI".
class MyProvider : QinhPlaceholderProvider {
override val identifier = "qcl"
override fun onRequest(player: OfflinePlayer?, params: String): String? {
return when (params) {
"points" -> getPoints(player).toString() // %qcl_points%
"level" -> getLevel(player).toString() // %qcl_level%
else -> null // hand unrecognized ones back to PAPI
}
}
}
// register / unregister
val ok: Boolean = PapiBridge.register(myPlugin, MyProvider())
PapiBridge.unregister(myPlugin)
PapiBridge.bridgeStatus()
PapiBridge.diagnose()Once registered, %qcl_points%, %qcl_level%, etc. in any config / GUI / message will be parsed.
๐ Continue Reading โ
- GUI็ผ็จAPI.md โ how to use placeholders and data sources in GUIs
- ๅทฅๅ ท้.md โ the reflection bridge and other low-level tools
- APIๆฆ่ง.md โ the full picture of QCL's public API
- ../05-ๅ่/ๆฏ่ฏญ่กจ.md โ terminology definitions