Skip to content

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:

  1. PdcService โ€” read/write namespaced data on any PersistentDataContainer (items, entities, etc.).
  2. DatabaseManager โ€” SQLite / MySQL database facade, including connection retrieval and serialization utilities.
  3. 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 โ€‹

kotlin
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 namespaces

1.2 Key rules โ€‹

The NamespacedKey produced by createKey(key) looks like:

qinhcorelib:namespace_keyname

For 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.

kotlin
val key: NamespacedKey = pdc.createKey("level")   // โ†’ qinhcorelib:mymodule_level

1.3 Typed read/write โ€‹

PdcService's read/write methods operate on any PersistentDataContainer (item meta, entities, etc.):

TypeWriteRead
StringsetString(container, key, value)getString(...)
IntsetInt(...)getInt(...) / getIntOrDefault(..., default)
LongsetLong(...)getLong(...)
DoublesetDouble(...)getDouble(...)
BooleansetBoolean(...)getBoolean(...) / getBooleanOrDefault(..., default)
Genericset(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 by PdcService, so what you see is a Boolean.

1.4 Storing data on an item โ€‹

kotlin
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 โ€‹

kotlin
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 โ€‹

kotlin
PdcServiceManager.diagnoseAll()   // troubleshoot namespace / key status

2. DatabaseManager โ€” Database Facade โ€‹

Package: com.qinhuai.corelib.database

2.1 Initialization and retrieval โ€‹

kotlin
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()      // diagnose

The DatabaseType enum: SQLITE / MYSQL.

2.2 The database section of the config โ€‹

The database type is determined by the database section in the config:

yaml
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 into global.db (global) and one {uuid}.db per player.

2.3 QclDatabase โ€” connection and serialization utilities โ€‹

kotlin
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):

UtilityPurpose
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
kotlin
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 โ€‹

kotlin
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: apply only 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:

kotlin
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 โ€” params is the part after identifier_; returning null means "I don't recognize this, hand it back to PAPI".
kotlin
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 โ€‹