ArchieDataAttachment¶
interface ArchieDataAttachment<T> : ReadWriteProperty<Any?, T>
A DataManager-backed attachment. Unlike NBTHolder, which owns its own per-instance field storage, a single ArchieDataAttachment instance is stateless and reusable as the delegate for a var Holder.property by ... extension property on any number of holder instances - the holder passed to each method (or, via getValue/setValue, the property's receiver) is where the data actually lives. Obtain instances via AttachmentRegistry.attachment.
Usage¶
object MyAttachments : AttachmentRegistry(MyMod.MOD_ID) {
val mana by intAttachment(sync = true, default = { 0 })
}
var Entity.mana by MyAttachments.mana
// in mod init, after MyAttachments' properties above have already run:
MyAttachments.init()
Supported holder types¶
What's supported depends on the platform and on whether the attachment was declared with itemComponent = true:
-
Entity / BlockEntity: a NeoForge attachment / Fabric
AttachmentTarget, on both platforms. -
ServerLevel: NeoForge only. Fabric's
updateTargetdispatch has noLevel/ServerLevelcase at all, so even where the underlyingget/set/hascalls happen to succeed there,sync = truewill silently never push an update. Don't rely on world-level attachments if you need Fabric parity. -
ItemStack: only if declared with
itemComponent = true(backed by a vanilla net.minecraft.core.component.DataComponentType instead of an attachment). Calling any method here against anItemStackfor an attachment that wasn't declared withitemComponent = truethrowsNullPointerException(its backingDataComponentTypeis null) rather thanIllegalArgumentException-ItemStackstill passes CSL's holder-kind check either way, it just has nowhere to actually read/write.
Any other object type throws IllegalArgumentException from every method here except getValue/setValue, which forward straight into get/set.
sync vs. itemComponent¶
These are two genuinely different mechanisms, not two flavors of one thing:
-
Entity/BlockEntity/ServerLevel sync (
sync = trueonAttachmentRegistry.attachment) is a reactive push straight out ofset/removeto tracking players, driven by CSL's ownDataManagerImpl/packets. -
itemComponentattachments are not covered by that push at all -seton anItemStacknever sends anything itself. They ride vanilla's normal item/component replication instead (the same mechanism as vanilla's ownBundleContents), which isn't reactive the same way.
Declaring itemComponent = true without sync = true still needs a network codec under the hood (vanilla's DataComponentType always carries one) - AttachmentRegistry.attachment handles that for you regardless of what you pass for sync.
has() after get() on Entity/BlockEntity/ServerLevel holders¶
Confirmed on real Fabric/NeoForge attachment internals, not documented by Common Storage Lib itself: get on these holder kinds silently creates and persists the default value on first read (Fabric's AttachmentTarget.getAttachedOrCreate, NeoForge's AttachmentHolder.getData - both write-through on a miss, they don't just compute-and-discard). That means has can only tell "never touched" apart from "read once" if you call it before the first get - calling get first, then has, will report true even though nothing was ever explicitly set. ItemStack/itemComponent holders don't have this quirk - DataComponentHolder.getOrDefault genuinely doesn't persist on read.
Functions¶
get¶
Reads holder's current value, falling back to the attachment's default if unset. Never throws for an unset value - only for an unsupported holder.
On Entity/BlockEntity/ServerLevel holders, an unset read silently creates and persists the default - see the class-level "has() after get()" note before relying on has afterward.
getOrCreate¶
abstract fun getOrCreate(holder: Any, default: T): T
Reads holder's current value if has is true, otherwise sets it to default first. Returns the (possibly just-written) current value either way.
getOrThrow¶
abstract fun getOrThrow(holder: Any): T
Reads holder's current value, throwing if it's never been explicitly set. The exact exception type (NullPointerException vs. RuntimeException) differs by platform for ItemStack holders - don't match on a specific type for that case.
getValue¶
has¶
modify¶
remove¶
Removes holder's explicitly-set value, if any, reverting subsequent get calls to the default. The return value mirrors the removed data 1:1 from the underlying Java API and can be a JVM-level null if nothing was set - prefer checking has first if you actually need it.