High-level architecture overview for maintainers and contributors.
- System Overview
- Module Structure
- Core Subsystems
- Data Flow
- Plugin Lifecycle
- Design Decisions
- Performance Considerations
- Security Architecture
zCrates is a modular Minecraft crate plugin built on Paper 1.21+ with Java 21. The architecture emphasizes:
- Separation of Concerns - API, implementation, and extensions are distinct
- Extensibility - Hook system for seamless plugin integration
- Security - Sandboxed JavaScript execution with restricted API surface
- Performance - Async database operations, in-memory caching, efficient registries
- Type Safety - Polymorphic YAML deserialization, strong typing throughout
┌─────────────────────────────────────────────────────────────────┐
│ USER INTERFACE │
│ (Commands, GUIs, Chat Messages, Placed Crates) │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ PRESENTATION LAYER │
│ (Commands, Listeners, zMenu Integration) │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ BUSINESS LOGIC LAYER │
│ (CratesManager, UsersManager, AnimationExecutor) │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ DOMAIN MODEL LAYER │
│ (Crate, Reward, Key, Animation, Algorithm, User) │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ INFRASTRUCTURE LAYER │
│ (Registries, Script Engine, Storage, Serialization) │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ EXTERNAL SERVICES │
│ (Database, zMenu, Bukkit API, Custom Item Plugins) │
└─────────────────────────────────────────────────────────────────┘
Purpose: Public contract for external plugins
Responsibilities:
- Define interfaces for all core models
- Expose event system
- Provide registry access
- Define extension points (Hook, ItemsProvider, etc.)
Key Characteristics:
- No implementation details
- Minimal dependencies (only Bukkit API, annotations)
- Semantic versioning
- Backward compatibility guarantees
Exported Packages:
fr.traqueur.crates.api- Core interfacesfr.traqueur.crates.api.events- Event systemfr.traqueur.crates.api.models- Model interfacesfr.traqueur.crates.api.registries- Registry systemfr.traqueur.crates.api.managers- Manager interfacesfr.traqueur.crates.api.hooks- Hook systemfr.traqueur.crates.api.providers- Provider interfaces
Purpose: Shared implementations used across main plugin and hooks
Responsibilities:
- Block/Entity display implementations
- Shared wrapper classes
- Common utilities
Dependencies:
- API module
- Bukkit API
Purpose: Core plugin implementation
Responsibilities:
- Implement all API interfaces
- Manage plugin lifecycle
- Orchestrate business logic
- Persist data
- Execute JavaScript
Dependencies:
- API module
- Common module
- Bukkit/Paper API
- Rhino (JavaScript engine)
- Structura (YAML serialization)
- CommandsAPI (command framework)
- Sarah (ORM)
- zMenu (GUI system)
- Reflections (classpath scanning)
Purpose: Optional plugin integrations
Structure:
hooks/
├── ItemsAdder/ - ItemsAdder custom items
├── MythicMobs/ - MythicMobs entity displays
├── Nexo/ - Nexo custom items
├── Oraxen/ - Oraxen custom items
├── PlaceholderAPI/ - Placeholder support
└── zItems/ - zItems integration
Characteristics:
- Each hook is a separate Gradle subproject
- Isolated dependencies (only required hook targets)
- Auto-discovered via
@AutoHookannotation - Gracefully disabled if target plugin missing
Architecture:
┌────────────────────────────────────────┐
│ Static Registry Accessor │
│ ClassToInstanceMap<Registry> │
└────────────────────────────────────────┘
↓
┌────────────────────────────────────────┐
│ Registry<ID, T> Interface │
│ - register(ID, T) │
│ - getById(ID): T │
│ - getAll(): Collection<T> │
└────────────────────────────────────────┘
↓
┌────────────────────────────────────────┐
│ FileBasedRegistry<ID, T> Abstract │
│ - loadFromFolder() │
│ - reload() │
│ - folder hierarchy support │
└────────────────────────────────────────┘
↓
┌────────────────────────────────────────┐
│ Concrete Implementations │
│ - ZAnimationRegistry (.js) │
│ - ZRandomAlgorithmRegistry (.js) │
│ - ZCratesRegistry (.yml) │
└────────────────────────────────────────┘
Design Decisions:
-
Static Access via ClassToInstanceMap
- Avoids dependency injection complexity
- Type-safe registry retrieval
- Single source of truth for all registries
-
FileBasedRegistry Pattern
- Automatic resource copying from JAR
- Folder hierarchy with metadata
- Hot-reload support
- Consistent loading behavior
-
Generic ID Type
- String IDs for file-based registries
- Enum IDs for runtime registries (DisplayType)
- Type safety at compile time
Architecture:
┌────────────────────────────────────────┐
│ Bukkit ServicesManager │
│ (Plugin service registration) │
└────────────────────────────────────────┘
↓
┌────────────────────────────────────────┐
│ Manager Interface │
│ - init() │
│ - reload() │
└────────────────────────────────────────┘
↓
┌────────────────┬───────────────────────┐
│ CratesManager │ UsersManager │
│ - Opening │ - User cache │
│ - Animation │ - Key management │
│ - Reroll │ - Persistence │
│ - Preview │ - History │
│ - Placed │ │
└────────────────┴───────────────────────┘
Design Decisions:
-
ServicesManager Integration
- Standard Bukkit pattern
- Automatic lifecycle management
- Accessible from any plugin
-
Single Responsibility
- CratesManager: crate operations
- UsersManager: user data
- Clear separation of concerns
-
In-Memory State
- Active openings in CratesManager
- User cache in UsersManager
- Placed crates loaded per chunk
Architecture:
┌────────────────────────────────────────┐
│ ZScriptEngine (Singleton) │
│ - Rhino Context │
│ - Custom WrapFactory │
│ - Security constraints │
└────────────────────────────────────────┘
↓
┌────────────────────────────────────────┐
│ ScriptableObject Scopes │
│ (Isolated per execution) │
└────────────────────────────────────────┘
↓
┌────────────────┬───────────────────────┐
│ animations │ algorithms │
│ global │ global │
│ object │ object │
└────────────────┴───────────────────────┘
↓
┌────────────────────────────────────────┐
│ Context Objects (Wrappers) │
│ - PlayerWrapper │
│ - InventoryWrapper │
│ - CrateWrapper │
│ - RewardsWrapper │
│ - HistoryWrapper │
└────────────────────────────────────────┘
Design Decisions:
-
Single Engine Instance
- Shared Rhino Context across all scripts
- Reduced memory footprint
- Consistent security settings
-
Isolated Scopes
- Each script execution uses new scope
- Null parent (no prototype chain access)
- Prevents cross-script interference
-
Wrapper Pattern
- Controlled API surface
- Type-safe method exposure
- No direct Java object access
-
Security First
- Interpreter mode (no bytecode)
- Method blacklist via WrapFactory
- No Java package access
- No reflection
Architecture:
┌────────────────────────────────────────┐
│ AnimationsRegistry │
│ - Load .js files │
│ - Register via AnimationsRegistrar │
└────────────────────────────────────────┘
↓
┌────────────────────────────────────────┐
│ Animation Model │
│ - List<AnimationPhase> │
│ - onComplete callback │
│ - onCancel callback │
└────────────────────────────────────────┘
↓
┌────────────────────────────────────────┐
│ AnimationExecutor │
│ - Phase timing (BukkitRunnable) │
│ - SpeedCurve application │
│ - Context passing │
└────────────────────────────────────────┘
↓
┌────────────────────────────────────────┐
│ JavaScript Callbacks │
│ - onStart(context) │
│ - onTick(context, tickData) │
│ - onComplete(context) │
└────────────────────────────────────────┘
Design Decisions:
-
Phase-Based Execution
- Sequential phase execution
- Independent timing per phase
- Clean state transitions
-
SpeedCurve Integration
- Mathematical easing functions
- Applied to progress value
- Smooth visual transitions
-
BukkitRunnable Scheduling
- Sync with server tick rate
- Cancel-safe execution
- Automatic cleanup
Architecture:
┌────────────────────────────────────────┐
│ DatabaseConnection │
│ - SQLite / MySQL / MariaDB │
│ - Connection pooling (HikariCP) │
└────────────────────────────────────────┘
↓
┌────────────────────────────────────────┐
│ Sarah ORM Layer │
│ - RequestHelper │
│ - Repository pattern │
│ - Migration system │
└────────────────────────────────────────┘
↓
┌────────────────────────────────────────┐
│ UserRepository │
│ - CRUD operations │
│ - CompletableFuture async │
└────────────────────────────────────────┘
↓
┌────────────────────────────────────────┐
│ DTO ↔ Domain Mapping │
│ - UserDTO → ZUser │
│ - Lightweight transfer objects │
└────────────────────────────────────────┘
Design Decisions:
-
Sarah ORM
- Lightweight abstraction
- Multiple database support
- Migration management
- Connection pooling
-
Repository Pattern
- Separation of persistence logic
- Testable data access
- Async by default
-
DTO Pattern
- Database schema independence
- Clear domain/persistence boundary
- Easy schema evolution
-
Async Operations
- Non-blocking database access
- CompletableFuture API
- Main thread safety
Architecture:
┌────────────────────────────────────────┐
│ CrateDisplayFactoriesRegistry │
│ Map<DisplayType, Factory> │
└────────────────────────────────────────┘
↓
┌────────────────────────────────────────┐
│ CrateDisplayFactory<T> │
│ - create(Location, value, yaw) │
│ - isValidValue(value) │
│ - getSuggestions() │
└────────────────────────────────────────┘
↓
┌────────────────────────────────────────┐
│ CrateDisplay<T> │
│ - spawn() │
│ - remove() │
│ - matches(T) │
└────────────────────────────────────────┘
↓
┌────────────────────────────────────────┐
│ PlacedCrate (Immutable Record) │
│ - Stored in Chunk PDC │
│ - Load/unload with chunks │
└────────────────────────────────────────┘
Design Decisions:
-
Generic Type Parameter
- Type-safe value matching
- Compile-time validation
- Flexible display types
-
Factory Pattern
- Encapsulates creation logic
- Validation before creation
- Tab completion support
-
Chunk PDC Storage
- Persistent across restarts
- Automatic chunk association
- No separate database table
-
Lazy Loading
- Displays created on chunk load
- Removed on chunk unload
- Memory efficient
1. Player interacts with placed crate
↓
2. CratesListener handles interaction
↓
3. CratesManager.tryOpenCrate(player, crate)
├─ Check if player has key
├─ Check all OpenConditions
├─ Fire CratePreOpenEvent (cancellable)
└─ If all pass, continue
↓
4. Fire CrateOpenEvent
↓
5. Consume key (virtual or physical)
↓
6. CratesManager.openCrate(player, crate, animation)
├─ Generate reward via algorithm
├─ Fire RewardGeneratedEvent
├─ Store OpenedCrate state
└─ Start animation
↓
7. AnimationExecutor.start(player, animation, context)
├─ Execute phases sequentially
├─ Call JavaScript callbacks
└─ Update inventory
↓
8. Animation completes
↓
9. If rerolls available, show reroll button
OR
Give reward and close inventory
↓
10. Fire RewardGivenEvent
↓
11. Persist CrateOpening to database (async)
↓
12. Cleanup OpenedCrate state
1. Player joins server
↓
2. PlayerJoinEvent listener
↓
3. UsersManager.loadUser(uuid) (async)
├─ Query database via UserRepository
├─ Convert UserDTO → ZUser
└─ Store in cache
↓
4. Player operations (synchronous via cache)
├─ getKeyAmount()
├─ addKey()
├─ removeKey()
└─ All modifications in-memory
↓
5. Player quits server
↓
6. PlayerQuitEvent listener
↓
7. UsersManager.saveUser(user) (async)
├─ Convert ZUser → UserDTO
├─ Update database via UserRepository
└─ Remove from cache
1. Plugin enable or /zcrates reload
↓
2. For each FileBasedRegistry:
├─ Clear existing entries
├─ Scan folder recursively
├─ Load each file via loadFile(Path)
│ ├─ For .js: evaluate via ZScriptEngine
│ └─ For .yml: deserialize via Structura
├─ Capture registrations
└─ Store in internal map
↓
3. Validate cross-references
├─ Crate → Animation exists
├─ Crate → Algorithm exists
└─ Reward display items valid
↓
4. Log statistics
onEnable()
├─1. Load config files (config.yml, messages.yml)
│
├─2. Register polymorphic type adapters
│ ├─ Reward types (ITEM, ITEMS, COMMAND, COMMANDS)
│ ├─ Key types (VIRTUAL, PHYSIC)
│ ├─ DatabaseSettings types (SQLITE, MYSQL, MARIADB)
│ └─ OpenCondition types (PERMISSION, COOLDOWN)
│
├─3. Initialize core services
│ ├─ Logger (debug mode)
│ ├─ Keys (PDC key registry)
│ ├─ MessagesService (MiniMessage parsing)
│ └─ PlacedCrateDataType (PDC serialization)
│
├─4. Create ZScriptEngine instance
│
├─5. Integrate with zMenu
│ ├─ Get InventoryManager
│ ├─ Get ButtonManager
│ └─ Register custom buttons (ZCRATES_ANIMATION, etc.)
│
├─6. Create and register all registries
│ ├─ AnimationsRegistry
│ ├─ RandomAlgorithmsRegistry
│ ├─ CratesRegistry
│ ├─ ItemsProvidersRegistry
│ ├─ HooksRegistry
│ └─ CrateDisplayFactoriesRegistry
│
├─7. Discover and enable hooks
│ ├─ Scan classpath for @AutoHook
│ ├─ Check if target plugin loaded
│ └─ Call hook.onEnable()
│
├─8. Register built-in display factories
│ ├─ BLOCK (BlockCrateDisplayFactory)
│ └─ ENTITY (EntityCrateDisplayFactory)
│
├─9. Load configurations
│ ├─ loadFromFolder(animations/)
│ ├─ loadFromFolder(algorithms/)
│ └─ loadFromFolder(crates/)
│
├─10. Initialize database
│ ├─ Create DatabaseConnection
│ ├─ Validate connection
│ ├─ Create UserRepository
│ └─ Execute migrations
│
├─11. Create and register managers
│ ├─ UsersManager (with repository)
│ └─ CratesManager
│
├─12. Initialize managers
│ ├─ usersManager.init()
│ │ ├─ Register listeners
│ │ └─ Load online players
│ └─ cratesManager.init()
│ └─ Verify menu files exist
│
└─13. Register commands
└─ ZCratesCommand with subcommands
onDisable()
├─1. Stop all active animations
│ └─ cratesManager.stopAllOpening()
│
├─2. Unload all placed crate displays
│ └─ cratesManager.unloadAllPlacedCrates()
│
├─3. Save all cached users
│ └─ For each cached user: saveUser(user)
│
├─4. Close database connection
│ └─ databaseConnection.disconnect()
│
├─5. Close script engine
│ └─ scriptEngine.close()
│
└─6. Shutdown services
└─ MessagesService.close()
/zcrates reload
├─1. Stop all active animations
│
├─2. Clear all registries
│ ├─ animationsRegistry.clear()
│ ├─ algorithmsRegistry.clear()
│ └─ cratesRegistry.clear()
│
├─3. Reload configuration files
│ ├─ config.yml
│ └─ messages.yml
│
├─4. Reload file-based registries
│ ├─ loadFromFolder(animations/)
│ ├─ loadFromFolder(algorithms/)
│ └─ loadFromFolder(crates/)
│
└─5. Notify managers
└─ cratesManager.reload()
Decision: Use Rhino JavaScript engine
Rationale:
- Security: Fine-grained control over Java access
- Compatibility: Works on all Java versions (11+)
- Sandbox: Built-in sandboxing with
initSafeStandardObjects() - Performance: Interpreter mode sufficient for animation/algorithm scripts
Trade-offs:
- Not ES2015+ compliant (limited ES6 support)
- Slower than GraalVM for compute-intensive tasks
- No native Promise support
Decision: Use ClassToInstanceMap for static registry access
Rationale:
- Simplicity: No dependency injection framework needed
- Type Safety: Compile-time verification of registry types
- Accessibility: Accessible from any plugin code
- Singleton: Single source of truth
Trade-offs:
- Global state (harder to test in isolation)
- Cannot swap implementations at runtime
- Requires manual registration
Decision: Store placed crates in Chunk PDC
Rationale:
- Automatic Association: Chunks own their crates
- Persistence: Survives server restarts
- Performance: No database queries for crate lookup
- Simplicity: No separate placed_crates table
Trade-offs:
- Limited to chunk-sized queries (can't query all crates in world easily)
- PDC size limits (unlikely to hit in practice)
- Chunk load required for data access
Decision: Register managers via Bukkit ServicesManager
Rationale:
- Standard Pattern: Follows Bukkit conventions
- Discovery: Other plugins can find managers
- Lifecycle: Bukkit manages service lifecycle
- Type Safety: Retrieval by class
Trade-offs:
- Couples to Bukkit API
- Limited lifecycle control
- No priority/ordering guarantees
Decision: Use Structura for YAML deserialization
Rationale:
- Type Safety: Compile-time validation of YAML structure
- Polymorphism:
@Polymorphicannotation for type field - Validation: Built-in validation (@Options, @DefaultInt, etc.)
- Maintainability: YAML changes reflected in code
Trade-offs:
- Additional dependency
- Learning curve for contributors
- Less flexible than manual parsing
User Cache:
- Cleared on player quit (prevents memory leaks)
- ConcurrentHashMap for thread-safe access
- No unbounded growth
Registry Storage:
- HashMap-based (O(1) lookup)
- Immutable after loading (no synchronization needed)
- Cleared on reload (prevents memory bloat)
Animation State:
- Removed immediately after completion
- No persistent animation history
- Bounded by concurrent player count
Connection Pooling:
- HikariCP for connection management
- Configurable pool size
- Connection validation
Async Operations:
- All DB operations use CompletableFuture
- No main thread blocking
- Batch operations where possible
Caching:
- User data cached in memory
- Keys stored locally (no query per check)
- Opening history loaded on-demand
Interpreter Mode:
- No bytecode compilation overhead
- Lower memory usage
- Acceptable performance for animations/algorithms
Scope Reuse:
- Scopes created per execution (not per phase)
- Shared Rhino Context across all scripts
- Minimal GC pressure
Wrapper Objects:
- Lightweight wrappers (no heavy computation)
- Direct method calls (no reflection)
- Cached references where possible
Listener Registration:
- Single listener instances (not per crate)
- Priority-based execution
- Ignore if not handling event
Event Frequency:
- CratePreOpenEvent: Once per open attempt
- CrateOpenEvent: Once per successful open
- RewardGeneratedEvent: Once per opening + rerolls
- RewardGivenEvent: Once per completion
Threat Model:
- Malicious server admin uploading dangerous scripts
- Exploiting Java reflection to bypass security
- Accessing file system or network
- Escaping sandbox via prototype pollution
Mitigations:
- No Java Package Access:
initSafeStandardObjects()blocks java.* - Method Blacklist: Custom WrapFactory blocks dangerous methods
- Null Parent Scopes: Prevents prototype chain manipulation
- Interpreter Mode: No bytecode execution (no JIT exploits)
- Wrapper Objects: Controlled API surface
Residual Risks:
- Infinite loops (no timeout mechanism)
- Memory exhaustion (no heap limits)
- Malicious animation logic (e.g., inventory spam)
Threat Model:
- Script manipulating unauthorized slots
- Duplication exploits via inventory manipulation
- Item theft via inventory swaps
Mitigations:
- Slot Authorization: Only authorized slots can be modified
- Wrapper Methods: Controlled inventory access
- State Validation: Animation state tracked by manager
Threat Model:
- SQL injection via user input
- Race conditions in user data
- Data loss on crashes
Mitigations:
- Prepared Statements: Sarah ORM uses prepared statements
- Concurrent Collections: Thread-safe user cache
- Transactions: Database operations in transactions
- Async Saves: Non-blocking persistence
Threat Model:
- Unauthorized crate access
- Permission bypass via exploits
Mitigations:
- PermissionCondition: Checks before opening
- Event Cancellation: External plugins can prevent opens
- Key Validation: Keys verified before consumption
Potential Bottlenecks:
- User cache growth (many concurrent players)
- Database connection pool exhaustion
- Animation executor (many concurrent animations)
- Chunk PDC size limits
Mitigation Strategies:
- TTL-based cache eviction
- Configurable pool size
- Queue system for concurrent animations
- Migration to separate placed_crates table if needed
Extension Points:
- Custom OpenConditions via polymorphic registry
- Custom Rewards via polymorphic registry
- Custom DisplayTypes via factory registry
- Custom Hooks via @AutoHook annotation
- Custom ItemsProviders via provider registry
API Stability:
- Semantic versioning for API module
- Deprecation warnings before removal
- Backward compatibility for configuration
Observability:
- Debug logging for troubleshooting
- Event system for external tracking
- Metrics (opening counts, reward distribution)
Health Checks:
- Database connection validation
- Registry population verification
- Script engine health checks
The zCrates architecture prioritizes:
- Modularity - Clear separation between API, implementation, and extensions
- Security - Sandboxed JavaScript, controlled API surface
- Performance - Async operations, efficient caching, optimized data structures
- Extensibility - Hook system, polymorphic types, provider pattern
- Maintainability - Type safety, clear abstractions, comprehensive documentation
For detailed implementation guidance, see:
DEVELOPER_GUIDE.md- Extension and integration guideUSER_GUIDE.md- Configuration and usage guide