Architecture
Architecture overview​
Lasers-Enigma is a Spigot/Paper Minecraft plugin. All source code lives in core/src/main/java/eu/lasersenigma/.
The codebase follows a strict layered architecture. Each layer has a single responsibility and calls only the layer(s) below it.
Layer call stacks​
Command → Controller → Service → Manager / (Repository → Entity/Mapper)
Inventory → Controller → Service → Manager / (Repository → Entity/Mapper)
Listener → Controller → Service → ...
Task → Service → ...
Layer responsibilities​
| Layer | Responsibility | Pattern |
|---|---|---|
| Command | Entry point for /lasers sub-commands. Parses arguments, delegates to a Controller. Extends LasersCommand<E>. | One class per sub-command |
| Inventory | GUI menus (chest UIs) and shortcut bars (hotbar UIs). Handles click events, delegates to Controllers. Extends AOpenableInventory or AShortcutBarInventory. | Class name ends with *Inventory |
| Controller | Orchestration layer. Checks permissions, calls Services, sends user feedback via TranslationUtils. Static utility methods (private constructor). | *Controller |
| Service | Pure business logic. No permission checks, no player messages. Singleton (getInstance()). | *Service |
| Manager | In-memory state, caching, data structures. No database access, no business logic. Singleton (getInstance()). | *Manager |
| Repository | Database access (raw SQL queries, JDBC). Static methods. Supports SQLite and MySQL. | *Repository |
| Entity | POJO for persistence. Maps 1:1 to a database table row. | *Entity |
| Mapper | Converts between Entity ↔ Domain objects. Static methods. | *Mapper |
| Listener | Bukkit event handlers. Thin: delegates to Controllers/Services. | *EventsListener |
| Event | Custom Bukkit events for decoupling between modules or for third-party plugins. | Extends ABeforeActionEvent (cancellable) or AAfterActionEvent |
| Task | Periodic game loops (BukkitRunnable). Delegates to a *PeriodicUpdateController. | *Task |
| Exception | Business exceptions with a translation code for user-facing messages. | Extends AbstractLasersException |
Reference controller: area/controller/AreaController.java. Reference service: area/service/area/AreaLifecycleService.java.
Package organization​
eu.lasersenigma/
├── area/ # Puzzle areas: CRUD, configuration, victory, reset, schematics, stats
│ ├── command/ # Commands for area management
│ ├── controller/ # AreaController, AreaCreationController, ComponentController, ...
│ ├── service/area/ # AreaCreationService, AreaQueryService, AreaVictoryService, ...
│ ├── service/component/ # CacheUpdateService, LaserParticleUpdateService, LightingUpdateService
│ ├── manager/ # AreaManager, WorldAreaManager
│ ├── repository/ # AreaRepository
│ ├── entity/ # AreaEntity, UnloadedAreaEntityView
│ ├── mapper/ # AreaMapper, UnloadedAreaMapper
│ ├── listener/ # AreaEventsListener
│ ├── event/ # AreaCreatedEvent, AreaDeletedEvent, ...
│ ├── exception/ # AreaOverlapException, NoAreaFoundException, ...
│ ├── entitydisplay/ # Display entity rendering for area boundaries
│ ├── schematic/ # WorldEdit clipboard/schematic import & export
│ ├── stats/ # Player & server statistics (area/global/puzzle)
│ ├── task/ # AreaTask (periodic area updates)
│ └── inventory/ # Area-related GUI menus
├── component/ # All puzzle components
│ ├── lasersender/ # Reference component implementation (LaserSender)
│ ├── laserreceiver/ # LaserReceiver
│ ├── mirrorsupport/ # MirrorSupport
│ ├── concentrator/ # Concentrator (merges lasers)
│ ├── bonus/ # Bonus collectibles
│ ├── elevator/ # Elevator and CallButton
│ ├── lock/ # Lock
│ ├── lockkeychest/ # KeyChest (+ playerskeys sub-package)
│ ├── mirrorchest/ # MirrorChest
│ ├── burnableblock/ # BurnableBlock
│ ├── filteringsphere/# FilteringSphere
│ ├── gravitationalsphere/ # GravitationalSphere
│ ├── reflectingsphere/ # ReflectingSphere
│ ├── prism/ # Prism (color splitting)
│ ├── redstonesensor/ # RedstoneSensor
│ ├── conditionnalwinnerblock/ # ConditionalWinnerBlock
│ ├── musicblock/ # MusicBlock
│ ├── leaderboard/ # Leaderboard
│ ├── lasersolidifier/# LaserSolidifier — solidifies laser beams into temporary blocks
│ ├── sync/ # Bidirectional sync links between components (controller, service, repository, entity, mapper, event, visualization)
│ ├── clipboard/ # Component copy/paste (ComponentClipboardManager)
│ ├── scheduledactions/ # Timed component actions
│ ├── common/ # Shared component utilities (inventories, laser reception)
│ ├── command/ # Component commands
│ ├── repository/ # ComponentRepository
│ ├── entity/ # ComponentEntity
│ ├── mapper/ # ComponentMapper
│ └── event/ # Component events and event producers
├── laser/ # Laser beam system
│ ├── LaserParticle.java # Core beam logic (movement, collisions, color mixing)
│ ├── entitydisplay/ # Display Entity rendering for laser segments
│ ├── event/ # Laser-specific events (hit block, hit component, hit entity)
│ ├── sound/ # Laser ambient sound (LaserAmbientSoundService)
│ └── task/ # LaserTask (periodic laser updates)
├── achievement/ # Achievements: what each player has accomplished, kept for good
│ ├── api/ # The ONLY public API of the plugin — what companion plugins compile against
│ ├── registry/ # AchievementRegistry: what is registered right now (in-memory, never frozen)
│ ├── service/ # AchievementService: evaluation, awarding, reads, removal
│ ├── rule/ # When one of LE's own achievements is earned (internal, never crosses the API)
│ ├── builtin/ # The achievements LE ships with, registered through its own public API
│ ├── controller/ # AchievementApiController (the API impl), notifications, command feedback
│ ├── manager/ # AchievementNotificationManager: unlocks waiting to be announced
│ ├── inventory/ # AchievementsMenuInventory: the paginated chest menu with category tabs
│ ├── vanilla/ # Neutralising Minecraft's own advancements, and purging what players hold
│ ├── repository/ # AchievementAwardRepository (insert-once rows)
│ ├── entity/ mapper/ # achievement_award row ↔ domain record
│ ├── listener/ # Victory, statistics and join
│ └── command/ # /lasersenigma achievements ...
├── player/ # Player system
│ ├── LEPlayer.java # Plugin's player wrapper
│ ├── LEPlayers.java # Player container singleton
│ ├── PlayerInventoryManager.java # Manages GUI menus & shortcut bars for a player
│ ├── inventory/ # Player-specific GUI menus
│ ├── checkpoint/ # Player checkpoint persistence
│ ├── settings/ # Per-player preferences (player_settings): store, service, command
│ ├── timezone/ # Which time zone a player lives in, and how it is worked out
│ └── listener/ # Player event listeners
├── commands/ # Top-level commands (HelpCommand)
├── componentinfo/ # Component inspector: ray-cast targeting, map display, chat info
│ # (InfoCommand, ComponentInfoController, ComponentInfoRayTracingService)
├── common/ # Shared infrastructure
│ ├── command/ # LasersCommand<E> base class
│ ├── config/ # ConfigData (plugin config keys), ConfigManager
│ ├── database/ # Database, SQLDatabase (connection + migrations)
│ ├── event/ # AEvent, ABeforeActionEvent, AAfterActionEvent
│ ├── exception/ # AbstractLasersException
│ ├── inventory/ # AInventory, AOpenableInventory, AShortcutBarInventory
│ │ └── saving/ # Player inventory save/restore on editor mode toggle
│ ├── items/ # ItemsFactory, ComponentType, LasersColor
│ ├── logger/ # Logger (wrapper with level filtering)
│ ├── message/ # TranslationUtils
│ ├── task/ # ColorChangeTask, ITaskComponent
│ ├── temporaryblock/ # Temporary block placement & automatic restoration (entity, repository, service)
│ ├── util/ # BlockUtils and other utilities
│ └── dependency/ # Optional dependency integrations
│ ├── worldedit/ # WorldEditHelper, WECopy, WEPaste
│ ├── protocollib/# ProtocolLibIntegration, ProtocolLibEntityVisibility
│ └── mapengine/ # MapEngineIntegration
├── checkpoint/ # Checkpoint system (respawn on join/death/world change)
├── editor/ # Editor mode commands
├── permission/ # Permission enum and checks
├── sound/ # Sound effects (SoundLauncher, PlaySoundCause)
├── songs/ # NoteBlock song system (optional)
├── betonquest/ # BetonQuest integration (quests)
└── updatenotifier/ # Plugin update checker
Key classes​
| Class | Purpose |
|---|---|
LasersEnigmaPlugin | Main plugin entry point (JavaPlugin). Initializes database, commands, listeners, tasks. Access via getInstance(). |
Area / AArea / UnloadedArea | Loaded puzzle area (with components) / abstract base / lightweight unloaded representation. |
LEPlayer / LEPlayers | Player wrapper (inventory state, checkpoints, current area) / singleton container for all players. |
PlayerInventoryManager | Per-player manager for GUI menus (AOpenableInventory) and shortcut bars (AShortcutBarInventory). Handles item clicks, component placement, color selection. Owned by LEPlayer. |
LaserParticle | Core laser beam: movement step-by-step, collision detection (blocks, components, entities), color mixing, light block placement. Created by LaserSender/Concentrator, moved by LaserTask. |
LaserBeam | Groups particles sharing the same origin into an ordered sequence for segment rendering. |
LaserTask / AreaTask | Periodic game loops (BukkitRunnable). LaserTask moves all laser particles every tick. AreaTask checks area entry/exit every 10 ticks. Both delegate to *PeriodicUpdateController. |
IComponent / AComponent / AArmorStandComponent / ATextDisplayComponent | Component interface / abstract base / armor-stand-based components / TextDisplay-based components. |
ConfigData / ConfigManager | ConfigData defines all config key constants from config.yml. ConfigManager handles loading. |
SQLDatabase | Database connection (SQLite or MySQL) + schema migrations. Migrations are versioned methods (update_X_Y_Z_to_A_B_C()). Schema version tracked in database_configuration table. See Database-migrations. Which of the two applies depends on what the change is: a brand-new table needs no migration at all — its CREATE TABLE IF NOT EXISTS statement is simply added to createTables(), which re-runs on every boot, so an existing install picks the table up the next time the server starts (that is how achievement_award and player_settings arrived). Altering an existing table — a new column, a changed key, a data backfill — needs a versioned update_X_Y_Z_to_A_B_C() method, because createTables() never touches a table that already exists. |
AchievementApi | The plugin's only public API. A companion plugin describes its achievements, then pushes an award when it decides the fact is true; Lasers-Enigma never evaluates a foreign condition. Frozen by an ArchUnit rule that keeps the package free of internal dependencies. See Use-API › Achievements. |
AreaRunSummary | What a winning run looked like — duration, steps, actions, bonuses, whether it was shared, whether the puzzle was already solved when it started, whether it was the player's very first entry into that puzzle, whether it beat one of their own records there, and the optical facts they brought about during it. Published on AreaPlayerWinAndLeaveEvent, which is how anything can react to how a puzzle was won without reaching into the area's run state. |
ABeforeActionEvent / AAfterActionEvent | Base classes for custom events. Before events are Cancellable (can prevent action). After events are informational. Both extend AEvent (→ Bukkit Event). |
AbstractLasersException | Base exception. Constructor takes a translation code + params. Message is auto-resolved via TranslationUtils. All business exceptions must extend this. |
Logger | Wrapper around Bukkit logger with level filtering (finest to severe). Level configurable via debug_level in config.yml. Access via LasersEnigmaPlugin.getInstance().getLasersEnigmaLogger(). |
TranslationUtils | Sends translated messages to players. Resolves translation codes from core/src/main/resources/lang/*.json. Supports per-player language. |
WorldEditHelper / WECopy / WEPaste | Optional WorldEdit integration for area clipboard/schematic operations. |
ProtocolLibIntegration / ProtocolLibEntityVisibility | Optional ProtocolLib integration for per-player entity visibility (rotation preview arrows). |
laser.entitydisplay.* | Display Entity-based laser rendering: LaserSegment, LaserTrajectory, SegmentRenderer, SegmentRoutingService, SegmentMergeService, MergeOverlaySegment. |
Command tree​
Commands are organized in containers registered in LasersEnigmaPlugin.registerCommands():
/lasers (/le)
├── area/ → eu.lasersenigma.area.command.AreaCommandContainer
├── component/ → eu.lasersenigma.component.command.ComponentCommandContainer
├── schematic/ → eu.lasersenigma.area.schematic.command.SchematicCommandContainer
├── clipboard/ → eu.lasersenigma.area.schematic.command.clipboard.ClipboardCommandContainer
├── stats/ → eu.lasersenigma.area.stats.command.StatsCommandContainer
├── editor → eu.lasersenigma.editor.command.EditorCommand (also the root default command)
├── help → eu.lasersenigma.commands.HelpCommand
└── info → eu.lasersenigma.componentinfo.InfoCommand
When creating a new command:
- Create a class extending
LasersCommand<Player>(orCommandSender). - In the constructor: set permission via
super.setPermission(), add arguments viasuper.addArgument(name, required, ArgumentType.xxx()). - Register in the parent container via
super.registerSubCommand(new MyCommand()). - Add a translation code for the description in
core/src/main/resources/lang/commands/.
Optional dependencies​
Declared in gradle/libs.versions.toml. Integrations in common/dependency/:
| Dependency | Integration class | Purpose |
|---|---|---|
| WorldEdit | worldedit/WorldEditHelper | Area clipboard & schematic operations |
| ProtocolLib | protocollib/ProtocolLibIntegration | Per-player entity visibility |
| MapEngine | mapengine/MapEngineIntegration | Component info map rendering |
Related pages​
- Area lifecycle — load/unload + activate/deactivate axes and the cleanup discipline.
- Component registration — the five-file checklist when adding a new component.
- Database migrations — how schema changes are versioned.
- Wiki update matrix — which wiki page to update for which code change.
- Translations — translation file structure and lifecycle.
- CI/CD — CI/CD dev notes.