Data Storage
In: Developer · Same group: QinhRuinsAPI · Provider & Bridges Related: Core Concepts · How data is stored · Full config.yml
QR's data falls into two kinds: instances / runtime state bound to world coordinates all go to local YAML (pointless across servers), and only the player codex discoveries can optionally go to a database for cross-server sharing. This page covers what each store holds, where, and when it goes to a database.
1. Storage responsibilities in one line
Recipes (templates / blueprints / archives / loot tables / affixes) = disk YAML + structure files, take effect on /qr reload
Instances and runtime state (anchors / claims / slots / mechanisms / purge vessels) = local YAML, bound to world coordinates → should be local
Player codex discoveries = optionally to a database (cross-server shared), the rest still localThe one-line boundary: "Does this data still mean anything off this server?" Anchors / chest claims / slot records are bound to this server's world coordinates and are useless across servers → local; "which ruin templates a player has discovered" is a portable, cross-server collection book → can go to a database.
2. Storage facade: RuinStorage
RuinStorage is the only storage facade that follows the QCL database, and it only handles the codex. The other stores are all standalone local YAML files that don't go through it.
RuinStorage.init(plugin, config) // Reads storage.type, decides whether the codex goes YAML or database
RuinStorage.isDatabase(): Boolean // Whether the codex currently goes to a databaseThe storage.type switch
storage.type | Codex destination |
|---|---|
yaml (default) | Codex discoveries written to local discoveries.yml |
database | Codex discoveries written to the QCL database table qr_codex |
In database mode, if the QCL database is unavailable / table creation fails, it logs a warning and automatically falls back to local YAML, never crashing the server over it:
[QinhRuins] storage.type=database, but the QinhCoreLib database is unavailable; the codex falls back to local YAMLThe database backend follows QCL: MySQL = true cross-server sharing; SQLite = local single-instance.
Tables and prefix
| Table | Use | Structure |
|---|---|---|
qr_codex | Player codex: discovered templates | (player VARCHAR(36), template VARCHAR(64)), primary key (player, template) |
QR itself only creates the one
qr_codextable (prefixqr_). In the Qinhuai ecosystem, other table prefixes (e.g. QI'sqi_) belong to their respective plugins, and QR doesn't touch them.
3. The stores at a glance
| Store | File | What it holds | Key | To database? |
|---|---|---|---|---|
AnchorStore | anchors.yml | Anchor instances (template / coords / orientation / dimensions / state / clear time) | anchor id | ❌ local only |
DiscoveryStore | discoveries.yml | Player-discovered anchors + discovered templates (codex) | player UUID | Anchor discoveries local only; template codex can go to a database |
LootClaimStore | loot_claims.yml | Reward chests a player has claimed | `UUID → anchorId | chestId` |
MechFiredStore | mech_fired.yml | One-time mechanisms already triggered | anchorId → mechId | ❌ local only |
VesselStore | vessels.yml | Content snapshot of each player's individual purge vessel | `... | anchorId |
SharedVesselStore | shared_vessels.yml | Whether the server-shared vessel has been filled | `anchorId | ...` |
SpinStore | spins.yml | A player's available purge-slot spin grants | `UUID | anchorId` |
SnapshotStore | snapshots/<anchorId>.nbt | Pre-generation terrain snapshot (restored on fade / remove) | anchor id | ❌ local only (binary NBT) |
All are init-ed at onEnable by file path, all under plugins/QinhRuins/.
4. Why these "should be local"
- Anchors (AnchorStore): records "there's a ruin at (123, 64, -45) in world X". That coordinate doesn't exist at all on another server, so storing it in a database is meaningless.
- Claims / slots / mechanisms (LootClaimStore / SpinStore / MechFiredStore): all recorded by concrete anchor id, and anchors themselves are local, so the records naturally follow local.
- Vessel contents (VesselStore / SharedVesselStore): bound to a specific container slot of a specific ruin.
- Snapshots (SnapshotStore): the raw backup of world blocks, pure local binary, with an extra volume cap (
cleanup.max-snapshot-volume, default 500000); over the limit it skips the snapshot to avoid stalling the main thread.
5. The codex: the only cross-server data
DiscoveryStore manages two things at once:
- Anchor discoveries (
discoveries.<UUID>): which concrete anchors a player has walked up to — always local (anchors themselves are local). - Template discoveries / codex (
template-discoveries.<UUID>or theqr_codextable): which ruin kinds a player has seen — this is a collection achievement, can be cross-server.
The codex's YAML / database switch is decided by RuinStorage.isDatabase():
- YAML mode: the template codex is written into the
template-discoveriessection ofdiscoveries.yml. - Database mode: the template codex is written to the
qr_codextable; local YAML no longer writestemplate-discoveries. Writes go async (RuinStorage.runAsync) to avoid blocking the main thread.
Cross-server sync: CodexSyncListener
In database mode, CodexSyncListener handles the loading / unloading of the cross-server codex:
| When | Action |
|---|---|
| Player joins | Async-read that player's codex from qr_codex → switch back to the main thread, DiscoveryStore.mergeTemplates merges it into memory |
| Player quits | DiscoveryStore.unloadTemplates unloads it from memory (to avoid cache bloat) |
This way the ruin kinds a player discovered on server A are visible when they log into server B (same database). In YAML mode this listener idles (returns immediately when
isDatabase()is false).
6. When data is written to disk
| Store | When it writes |
|---|---|
AnchorStore | Unified saveAll by the anchor manager (on generation / state change / recycle) |
DiscoveryStore / LootClaimStore / MechFiredStore / SpinStore | Save immediately on each new record (in database mode the codex switches to async DB writes) |
SharedVesselStore | Marked dirty + batch save every 600 ticks (about 30 seconds), plus save on shutdown |
VesselStore | Marked dirty, save on anchor cleanup (clearAnchor) and on shutdown (no timer) |
SnapshotStore | Capture on generation, delete the snapshot file after a successful restore |
On shutdown (
onDisable),RuinStorage.close()+VesselStore.save()+SharedVesselStore.save()provide a final fallback flush to disk.
7. Related config.yml keys
storage:
type: yaml # yaml | database (database follows the QCL database, cross-server shared codex)
cleanup:
snapshot-restore: true # Whether to restore terrain on remove / fade
max-snapshot-volume: 500000 # Snapshot volume cap, skipped over the limit (prevents large structures stalling the server)For the full config see Full config.yml.
Next
- Provider & Bridges — keystone item source / growth / party
- Guides & Codex — the codex gameplay layer
- Core Concepts — the recipe vs. instance vs. endgame layering