Skip to main content

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​

LayerResponsibilityPattern
CommandEntry point for /lasers sub-commands. Parses arguments, delegates to a Controller. Extends LasersCommand<E>.One class per sub-command
InventoryGUI menus (chest UIs) and shortcut bars (hotbar UIs). Handles click events, delegates to Controllers. Extends AOpenableInventory or AShortcutBarInventory.Class name ends with *Inventory
ControllerOrchestration layer. Checks permissions, calls Services, sends user feedback via TranslationUtils. Static utility methods (private constructor).*Controller
ServicePure business logic. No permission checks, no player messages. Singleton (getInstance()).*Service
ManagerIn-memory state, caching, data structures. No database access, no business logic. Singleton (getInstance()).*Manager
RepositoryDatabase access (raw SQL queries, JDBC). Static methods. Supports SQLite and MySQL.*Repository
EntityPOJO for persistence. Maps 1:1 to a database table row.*Entity
MapperConverts between Entity ↔ Domain objects. Static methods.*Mapper
ListenerBukkit event handlers. Thin: delegates to Controllers/Services.*EventsListener
EventCustom Bukkit events for decoupling between modules or for third-party plugins.Extends ABeforeActionEvent (cancellable) or AAfterActionEvent
TaskPeriodic game loops (BukkitRunnable). Delegates to a *PeriodicUpdateController.*Task
ExceptionBusiness 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​

ClassPurpose
LasersEnigmaPluginMain plugin entry point (JavaPlugin). Initializes database, commands, listeners, tasks. Access via getInstance().
Area / AArea / UnloadedAreaLoaded puzzle area (with components) / abstract base / lightweight unloaded representation.
LEPlayer / LEPlayersPlayer wrapper (inventory state, checkpoints, current area) / singleton container for all players.
PlayerInventoryManagerPer-player manager for GUI menus (AOpenableInventory) and shortcut bars (AShortcutBarInventory). Handles item clicks, component placement, color selection. Owned by LEPlayer.
LaserParticleCore laser beam: movement step-by-step, collision detection (blocks, components, entities), color mixing, light block placement. Created by LaserSender/Concentrator, moved by LaserTask.
LaserBeamGroups particles sharing the same origin into an ordered sequence for segment rendering.
LaserTask / AreaTaskPeriodic 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 / ATextDisplayComponentComponent interface / abstract base / armor-stand-based components / TextDisplay-based components.
ConfigData / ConfigManagerConfigData defines all config key constants from config.yml. ConfigManager handles loading.
SQLDatabaseDatabase 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.
AchievementApiThe 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.
AreaRunSummaryWhat 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 / AAfterActionEventBase classes for custom events. Before events are Cancellable (can prevent action). After events are informational. Both extend AEvent (→ Bukkit Event).
AbstractLasersExceptionBase exception. Constructor takes a translation code + params. Message is auto-resolved via TranslationUtils. All business exceptions must extend this.
LoggerWrapper around Bukkit logger with level filtering (finest to severe). Level configurable via debug_level in config.yml. Access via LasersEnigmaPlugin.getInstance().getLasersEnigmaLogger().
TranslationUtilsSends translated messages to players. Resolves translation codes from core/src/main/resources/lang/*.json. Supports per-player language.
WorldEditHelper / WECopy / WEPasteOptional WorldEdit integration for area clipboard/schematic operations.
ProtocolLibIntegration / ProtocolLibEntityVisibilityOptional 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:

  1. Create a class extending LasersCommand<Player> (or CommandSender).
  2. In the constructor: set permission via super.setPermission(), add arguments via super.addArgument(name, required, ArgumentType.xxx()).
  3. Register in the parent container via super.registerSubCommand(new MyCommand()).
  4. 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/:

DependencyIntegration classPurpose
WorldEditworldedit/WorldEditHelperArea clipboard & schematic operations
ProtocolLibprotocollib/ProtocolLibIntegrationPer-player entity visibility
MapEnginemapengine/MapEngineIntegrationComponent info map rendering