Repository navigation
Provenance
Where a block, a creature or an item came from: a player placed it, a spawner made it, a player dropped it - or
nothing recorded it, and it is NATURAL. A job that pays only for natural ore, a quest that counts only wild mobs, a
pickup reward that must not pay for one's own drop all ask the same question. The package is
br.com.finalcraft.evernifecore.action.provenance.
ProvenanceTracker origins = ProvenanceTracker.open(getEcPluginData(), storage); // the plugin's own ECStorage
ProvenanceHold recording = origins.record(); // while something needs origins
origins.getOrigin(blockPos).thenAccept(origin -> { // async: the chunk may not be cached
if (origin.getKind() == OriginKind.NATURAL) { ... }
if (origin.isOwn(player)) { ... } // placed or dropped by that player
});
Provenance mob = origins.getOrigin(entity.getUniqueId()); // sync: creatures and items live in memory
recording.close(); // nothing needs origins any moreProvenance carries getKind(), getPlayer() (who placed or dropped it; null for creatures), getAt() and
getKey() (what was placed, spawned or dropped - a block whose key changed since, a sapling grown into a tree, is
the plugin's to judge). isOwn(player) is true for a PLAYER_PLACED block or a PLAYER_DROP item of that player.
OriginKind |
Recorded from | Kept |
|---|---|---|
PLAYER_PLACED |
PlaceActionEvent.Post with a player behind the actor |
per chunk in the storage, until broken or past retention |
SPAWNER |
SpawnActionEvent.Post, reason SPAWNER
|
in memory, until killed |
EGG |
reason EGG, SPAWNER_EGG, DISPENSE_EGG
|
in memory |
BREEDING |
reason BREEDING
|
in memory |
COMMAND |
reason COMMAND, CUSTOM
|
in memory |
PLAYER_DROP |
DropActionEvent.Post with the item entity |
in memory, until picked up |
NATURAL |
no record - never stored | - |
BreakActionEvent.Post drops the block's record, KillActionEvent.Post the creature's and PickupActionEvent.Post
the item's, each on the bus's last priority, so every other subscriber of those events still reads the origin.
On Hytale every creature is natural: the server tells no spawn reason but AddReason.SPAWN, and a drop does not
carry its item entity.
A tracker records only while record() has a ProvenanceHold open (closing it twice is closing it once). The
first hold subscribes to the six action events above and the last one closed unsubscribes - and those
subscriptions are what switch the platform's listeners on (Events). A server where no
plugin needs origins pays nothing. The flip side: a block placed while nobody recorded reads NATURAL.
-
Separate (the default):
open(owner, storage)gives each plugin its own tracker on its own storage - the same object for the same storage. Blocks go to the collectionprovenance_blocks, write-back per chunk throughSVWorldDataManager(ISOLATED, so it never collides with the plugin's own block manager): a placed block is a change to the cached chunk, written on the next flush (10 min) or byflush(). -
Shared (
Provenance.shared: true): the core opens one tracker on a storage of its own over the default backend of itsstorage.yml, andopen(owner, storage)returns it to every plugin, ignoringstorage(the log says so once per plugin). One source of truth for a network of plugins, at the cost of one more connection to that backend. Applied on the next restart.
ofIfPresent(owner) returns the tracker without opening one. The plugin's tracker closes - and writes its blocks -
in IECPluginBootstrap.onECPluginShutdownPre, before its regions and before the plugin closes its storage.
In the core's config.yml:
Provenance:
shared: false # true: one tracker in EverNifeCore's own storage answers every plugin
always: false # true: record from boot; false: only while some plugin holds a recording
retention:
player-placed: 30d # how long a placed block remembers who placed itA placed block past retention.player-placed reads NATURAL, and its record is dropped when read.
-
getOrigin(UUID)andpeekOrigin(block)answer on any thread;getOrigin(block)completes on the storage's thread.peekOriginreads only cached chunks and returns null when it has no record in memory. - Recording runs on the action event's thread.
- Creatures and items live in memory only, at most 50 000 (the least recently read leave first): a mob that
outlives a restart reads
NATURAL. A native tag on the entity is the upgrade if a product ever needs it. - A block moved by a piston keeps nothing: pistons post no action event. Nor does growth, nor a replant done by
another plugin: a crop it replants by setting the block raises no place event, so that block reads
NATURAL; a grown block keeps the record only while its key is the one that was placed (see the model).
ActionWorld in common-tests opens a plugin's tracker on an in-memory storage and posts the actions as a native
listener would - see Testing Toolkit.
- Events - the action events the tracker subscribes to.
- SVWorldDataManager - the per-chunk store the blocks live in.
- Configuration - where the core's settings are.
EverNifeCore · Home · made by Petrus Pradella
Getting Started
Commands & Text
Player Data & Storage
- PlayerData & PDSections
- Accounts
- Storage Backends
- Inline Backends for Plugins
- Block Data (SVWorldDataManager)
- Legacy Data Migration
- Cooldowns
Config & Minecraft Systems
- Configuration
- Logging & Debug Modules
- Events
- Regions
- Groups
- Notifications
- NPCs
- Logic
- Quests
- Provenance
- Scheduler & Threading
- Items & NBT
- Screens
- Dialogues
- Menus
- Integrations
- Economy
- Boosters
- Deliveries
- Audit Log
- Dynamic Values
- Player Ignore
- Version Compatibility
Architecture & Reference