Custom Block Tracking
Custom Block Tracking
Learn how zItems tracks custom blocks to ensure they drop the correct custom item when broken, and how the system handles persistence and performance.
Overview
When a player places a custom item as a block, zItems needs to remember which custom item was placed. This allows effects like Hammer and Vein Mining to drop the correct custom item instead of vanilla drops.
Example problem without tracking:
Player places a custom "Ruby Ore" block (custom item ID:
ruby_ore)Player breaks it with Hammer
Without tracking: Drops vanilla
STONE(wrong!)With tracking: Drops custom
ruby_oreitem (correct!)
How It Works
Architecture
zItems uses a two-layer system:
Memory Cache (Guava Table) - Fast lookups during gameplay
Chunk PDC (PersistentDataContainer) - Persistent storage across restarts
Memory Cache
Data Structure: Table<WorldChunkKey, Integer, String>
Row:
WorldChunkKey- Identifies chunk across all worlds (world UUID + chunk key)Column:
Integer- Packed block position (x, y, z encoded in 32 bits)Value:
String- Custom item ID
Why this structure?
Fast chunk-based lookups (O(1) for most operations)
Efficient memory usage (only loaded chunks are cached)
Automatically grouped by chunk for easy save/load
Persistent Storage
Storage: Chunk PersistentDataContainer (PDC)
Each chunk stores a List<TrackedBlock> where:
When saved:
On
ChunkUnloadEvent- Chunk is unloadingMemory cache → PDC
When loaded:
On
ChunkLoadEvent- Chunk is loadingPDC → Memory cache
Position Packing
To save memory, block positions are packed into a single 32-bit integer.
Bit Layout
Why No Collisions?
Each coordinate occupies separate, non-overlapping bits:
X: Bits 13-16 (mask
0xF000when shifted)Z: Bits 9-12 (mask
0x0F00when shifted)Y: Bits 0-8 (mask
0x01FF)
Total: 4 + 4 + 9 = 17 bits used out of 32 available
Packing Algorithm
Example
Block at chunk-relative position (5, 100, 10):
Why this works:
X shifted left 13 bits:
5 << 13 = 0x2000Z shifted left 9 bits:
10 << 9 = 0x1400Y stays in place:
164 = 0xA4OR them together:
0x2000 | 0x1400 | 0xA4 = 0x34A4
WorldChunkKey
Chunks are identified using a composite key:
Why include world UUID?
Without it, chunks at the same (X, Z) in different worlds would collide:
Overworld chunk (0, 0)
Nether chunk (0, 0)
With world UUID, they are distinct:
WorldChunkKey(overworld_uuid, chunkKey(0, 0))WorldChunkKey(nether_uuid, chunkKey(0, 0))
Block Lifecycle
Tracking a Block
When: Player places a custom item as a block
Implementation: BlockTracker.trackBlock()
Breaking a Block
When: Effect handler (Hammer, VeinMiner) breaks a tracked block
Implementation: BlockTracker.getCustomBlockDrop()
Untracking a Block
When: Block is successfully broken
Important: Always call untrackBlock() after breaking a tracked block to prevent memory leaks!
Chunk Load/Unload
Loading a Chunk
When: ChunkLoadEvent fires
Implementation:
Unloading a Chunk
When: ChunkUnloadEvent fires
Implementation:
Key points:
Only chunks with tracked blocks save PDC data
Empty chunks have their PDC data removed (cleanup)
Cache is cleared after successful save
Integration with Effects
Hammer Effect
Order of checks:
BlockTracker- zItems custom blocksCustomBlockProviderRegistry- Third-party custom blocks (ItemsAdder, etc.)Vanilla drops - Fallback
Performance Considerations
Memory Usage
Per tracked block:
WorldChunkKey: 16 bytes (UUID) + 8 bytes (long) = 24 bytes
Packed position: 4 bytes (int)
Item ID: ~20 bytes (String)
Total: ~50 bytes per block
Example server:
1000 tracked blocks = ~50 KB
10,000 tracked blocks = ~500 KB
100,000 tracked blocks = ~5 MB
Very efficient even for large servers!
Lookup Speed
All operations are O(1) average case:
trackBlock()- Guava Table putgetTrackedItemId()- Guava Table getuntrackBlock()- Guava Table remove
Chunk Load/Unload
Load: O(n) where n = tracked blocks in chunk
Typically < 100 blocks per chunk
Happens asynchronously during chunk load
Unload: O(n) where n = tracked blocks in chunk
Happens during chunk unload
No gameplay impact (player isn't in chunk)
Debugging
Enable Debug Logging
Debug Messages
When tracking:
When breaking:
When loading chunk:
When unloading chunk:
Checking Cache
Clear cache (for testing):
This clears memory but does not affect persistent data in chunk PDC.
Common Issues
Custom blocks drop vanilla items
Symptoms:
Place custom "Ruby Ore" block
Break it
Drops vanilla
STONEinstead ofruby_ore
Causes:
Block wasn't tracked when placed
BlockTracker was cleared (server restart without chunk unload)
Item ID in registry doesn't match tracked ID
Debug:
Check logs for:
If missing, the block placement event isn't being handled.
Tracked blocks not persisting
Symptoms:
Place custom blocks
Restart server
Blocks drop vanilla items
Causes:
Chunk didn't unload before shutdown (stayed loaded)
PDC data was cleared
Solution:
Force chunk unload before shutdown (happens automatically with graceful stop)
Avoid killing server process (use
/stopor/restart)
Memory leak
Symptoms:
Memory usage increases over time
Thousands of tracked blocks in cache
Causes:
Not calling
untrackBlock()after breaking blocksBlocks broken by non-player causes (explosions, etc.) not handled
Solution:
Always call
untrackBlock()in effect handlersListen to
BlockBreakEventandBlockExplodeEventto clean up
API Usage
Tracking a Block
Getting Tracked Item
Checking if Tracked
Manual Cleanup
Best Practices
Always untrack after breaking - Prevents memory leaks
Use getCustomBlockDrop() in effect handlers - Ensures correct drops
Don't track vanilla items - Only track custom items
Check BlockTracker first - Before checking CustomBlockProviderRegistry
Enable debug logging during testing - Helps catch tracking issues
Use graceful server shutdown - Ensures chunks save properly
Implementation Files
BlockTracker.java- Main tracking logic (src/main/java/fr/traqueur/items/blocks/)TrackedBlock.java- Record for persistence (api)ZTrackedBlock.java- Implementation (src)ZTrackedBlockDataType.java- PDC serialization (src/main/java/fr/traqueur/items/serialization/)Keys.java- PDC key constants (src/main/java/fr/traqueur/items/serialization/)
Related Documentation
Effect Handlers Reference - How effects use BlockTracker
Hooks System - Custom block provider integrations
Need help? Join our Discord or check GitHub Issues!
Last updated