# FlectonePulse FlectonePulse is a comprehensive Minecraft plugin and mod that provides complete control over chat, messages, and notifications for Minecraft servers. It supports all popular platforms including Bukkit, Spigot, Paper, Purpur, Folia, Fabric, BungeeCord, Waterfall, and Velocity on versions 1.8.8 to the latest, plus Hytale. The plugin uses Google Guice for dependency injection and performs all operations asynchronously to avoid impacting the main server thread. The project offers extensive customization capabilities including flexible text formatting with MiniMessage tags and legacy color codes, custom textures in messages without resource packs, player-specific localization based on client language, and integrations with external platforms like Discord, Telegram, and Twitch. It provides over 30 commands for various functionality including moderation (ban, mute, warn), social features (mail, ignore, tell), and fun commands (ball, dice, tictactoe). ## FlectonePulse API - Getting the Main Instance The `FlectonePulseAPI` class is the main entry point for plugin integration. It provides static access to the main `FlectonePulse` instance and enables dependency injection through Google Guice. ```java import net.flectone.pulse.FlectonePulse; import net.flectone.pulse.FlectonePulseAPI; import net.flectone.pulse.service.FPlayerService; import net.flectone.pulse.model.entity.FPlayer; // Get the FlectonePulse instance FlectonePulse flectonePulse = FlectonePulseAPI.getInstance(); // Check if the injector is ready before using if (flectonePulse.isReady()) { // Get services through dependency injection FPlayerService playerService = flectonePulse.get(FPlayerService.class); // Get a player by UUID UUID playerUuid = UUID.fromString("069a79f4-44e9-4726-a5be-fca90e38aaf5"); FPlayer fPlayer = playerService.getFPlayer(playerUuid); System.out.println("Player name: " + fPlayer.name()); System.out.println("Player online: " + fPlayer.isOnline()); } ``` ## FPlayer - Platform-Agnostic Player Model `FPlayer` is the central player representation in FlectonePulse. It provides a unified interface for player data across all supported platforms (Bukkit, Fabric, Velocity, etc.) and includes settings, colors, and ignore lists. ```java import net.flectone.pulse.model.entity.FPlayer; import net.flectone.pulse.service.FPlayerService; import net.flectone.pulse.model.FColor; // Get FPlayerService through dependency injection FPlayerService playerService = flectonePulse.get(FPlayerService.class); // Get player by different identifiers FPlayer byUuid = playerService.getFPlayer(playerUuid); FPlayer byName = playerService.getFPlayer("PlayerName"); FPlayer byId = playerService.getFPlayer(123); // database ID FPlayer console = playerService.getConsole(); // or getFPlayer(-1) // Build a new FPlayer using builder pattern FPlayer newPlayer = FPlayer.builder() .name("TestPlayer") .uuid(UUID.randomUUID()) .online(true) .build(); // Check player properties if (!byUuid.isUnknown()) { boolean isOnline = byUuid.isOnline(); boolean isConsole = byUuid.isConsole(); boolean isIntegration = byUuid.isIntegration(); // Discord/Telegram user // Check if player ignores another player boolean ignoresOther = byUuid.isIgnored(anotherPlayer); // Get player settings boolean hasChatEnabled = byUuid.isSetting(ModuleName.MESSAGE_CHAT); // Get player's custom colors Map chatColors = byUuid.getFColors(FColor.Type.CHAT); } // Immutable updates with 'with' methods FPlayer updatedPlayer = byUuid .withOnline(false) .withSetting("MESSAGE_JOIN", false) .withIp("192.168.1.1"); ``` ## FPlayerService - Player Management Service The `FPlayerService` manages player storage, retrieval, and persistence across the plugin. It handles player caching, database operations, and provides methods for accessing online and offline players. ```java import net.flectone.pulse.service.FPlayerService; import net.flectone.pulse.model.entity.FPlayer; import net.flectone.pulse.module.command.ignore.model.Ignore; import net.flectone.pulse.module.command.mail.model.Mail; FPlayerService playerService = flectonePulse.get(FPlayerService.class); // Get online players List onlinePlayers = playerService.getOnlineFPlayers(); List platformPlayers = playerService.getPlatformFPlayers(); List withConsole = playerService.getFPlayersWithConsole(); // Get all players from database List allPlayers = playerService.findAllFPlayers(); // Get random online player FPlayer randomPlayer = playerService.getRandomFPlayer(); // Get visible players for a viewer (respects vanish plugins) FPlayer viewer = playerService.getFPlayer(viewerUuid); List visibleToViewer = playerService.getVisibleFPlayersFor(viewer); // Get players who can see a target List canSeeTarget = playerService.getFPlayersWhoCanSee(targetPlayer); // Manage ignore relationships FPlayer sender = playerService.getFPlayer(senderUuid); FPlayer target = playerService.getFPlayer(targetUuid); FPlayer updatedSender = playerService.saveIgnore(sender, target); // Retrieve ignores for a player List ignores = sender.ignores(); // Send mail between players Mail mail = playerService.saveMail(sender, target, "Hello, this is a mail message!"); List receivedMails = playerService.getReceiverMails(target); List sentMails = playerService.getSenderMails(sender); // Delete mail playerService.deleteMail(mail); // Check group hierarchy (for moderation) boolean canModerate = playerService.hasHigherGroupThan(moderator, target); // Update player locale playerService.updateLocale(player, "en_us"); ``` ## Event System - Listening to FlectonePulse Events FlectonePulse has its own event system using the `@Pulse` annotation. Events support priorities and can be cancelled. Listeners must implement `PulseListener` interface and methods must take a single Event parameter. ```java import net.flectone.pulse.listener.PulseListener; import net.flectone.pulse.annotation.Pulse; import net.flectone.pulse.model.event.Event; import net.flectone.pulse.model.event.player.PlayerJoinEvent; import net.flectone.pulse.model.event.player.PlayerQuitEvent; import net.flectone.pulse.model.event.message.MessageSendEvent; import net.flectone.pulse.model.event.lifecycle.EnableEvent; import net.flectone.pulse.model.event.lifecycle.ReloadEvent; import com.google.inject.Singleton; @Singleton public class CustomPulseListener implements PulseListener { // Listen to player join with LOWEST priority (runs first) @Pulse(priority = Event.Priority.LOWEST, ignoreCancelled = true) public PlayerJoinEvent onPlayerJoin(PlayerJoinEvent event) { FPlayer player = event.player(); System.out.println(player.name() + " joined the server!"); // Modify and return the event return event.withPlayer(player.withIp("updated-ip")); } // Listen to message send events (default NORMAL priority) @Pulse public void onMessageSend(MessageSendEvent event) { FPlayer receiver = event.receiver(); Component message = event.format(); System.out.println("Sending message to: " + receiver.name()); } // Listen to plugin enable @Pulse public void onEnable(EnableEvent event) { System.out.println("FlectonePulse enabled!"); } // Listen to reload with HIGH priority @Pulse(priority = Event.Priority.HIGH) public ReloadEvent onReload(ReloadEvent event) { System.out.println("FlectonePulse reloading..."); return event; // Return unchanged event } // Cancel an event @Pulse public Event onMessage(MessagePrepareEvent event) { if (shouldCancel(event)) { return event.withCancelled(true); } return event; } } ``` ## ListenerRegistry - Registering Custom Listeners The `ListenerRegistry` manages all Pulse event listeners. You can register permanent listeners that persist across reloads or temporary ones that are cleared on reload. ```java import net.flectone.pulse.platform.registry.ListenerRegistry; import net.flectone.pulse.listener.PulseListener; import net.flectone.pulse.model.event.Event; ListenerRegistry listenerRegistry = flectonePulse.get(ListenerRegistry.class); // Register a listener class (must implement PulseListener) listenerRegistry.register(CustomPulseListener.class); // Register with specific priority listenerRegistry.register(CustomPulseListener.class, Event.Priority.HIGH); // Register a listener instance PulseListener myListener = new CustomPulseListener(); listenerRegistry.register(myListener); // Register permanent listener (persists across reloads) listenerRegistry.registerPermanent(myListener); // Register raw event handler programmatically listenerRegistry.register( PlayerJoinEvent.class, Event.Priority.NORMAL, event -> { System.out.println("Player joined!"); return event; } ); // Unregister all listeners listenerRegistry.unregisterAll(); ``` ## EventDispatcher - Dispatching Events The `EventDispatcher` is used to fire events to all registered listeners. Events are processed through priority levels and can be modified or cancelled by handlers. ```java import net.flectone.pulse.execution.dispatcher.EventDispatcher; import net.flectone.pulse.model.event.player.PlayerJoinEvent; import net.flectone.pulse.model.event.lifecycle.ReloadEvent; EventDispatcher eventDispatcher = flectonePulse.get(EventDispatcher.class); // Dispatch an event and get the result PlayerJoinEvent joinEvent = new PlayerJoinEvent(fPlayer); PlayerJoinEvent result = eventDispatcher.dispatch(joinEvent); // Check if event was cancelled if (result.cancelled()) { System.out.println("Join event was cancelled by a listener"); } // Dispatch with custom listener map Map>> customListeners = getCustomListeners(); ReloadEvent reloadResult = eventDispatcher.dispatch(customListeners, new ReloadEvent(instance, null)); ``` ## MessageDispatcher - Sending Messages to Players The `MessageDispatcher` handles sending formatted messages to players with support for localization, range filtering, sounds, and proxy server synchronization. ```java import net.flectone.pulse.execution.dispatcher.MessageDispatcher; import net.flectone.pulse.model.event.EventMetadata; import net.flectone.pulse.model.entity.FPlayer; import net.flectone.pulse.model.util.Range; import net.flectone.pulse.model.util.Destination; MessageDispatcher messageDispatcher = flectonePulse.get(MessageDispatcher.class); // Create event metadata for a message EventMetadata metadata = EventMetadata.builder() .sender(senderPlayer) .format(loc -> loc.format()) // Get format from localization .message("Hello world!") .destination(Destination.EMPTY_CHAT) .range(Range.get(Range.Type.PLAYER)) .proxy() // Enable proxy sync .integration() // Enable external integrations .build(); // Dispatch message to all receivers messageDispatcher.dispatch(myModule, metadata); // Dispatch to specific receiver list List receivers = List.of(player1, player2, player3); messageDispatcher.dispatch(receivers, myModule, metadata); // Dispatch with custom module name messageDispatcher.dispatch(ModuleName.COMMAND_BALL, receivers, myModule, metadata); // Create filtered receivers List filteredReceivers = messageDispatcher.createReceivers(myModule, metadata); ``` ## EventMetadata - Building Message Context `EventMetadata` contains all the context needed for message processing including sender, format, destination, range, sounds, and integration settings. ```java import net.flectone.pulse.model.event.EventMetadata; import net.flectone.pulse.model.util.Destination; import net.flectone.pulse.model.util.Range; import net.flectone.pulse.model.util.Sound; import net.flectone.pulse.util.constant.MessageFlag; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; // Build comprehensive EventMetadata EventMetadata metadata = EventMetadata.builder() // Required: sender entity .sender(fPlayer) // Required: format function (from localization) .format(loc -> loc.messageFormat()) // Or static format .format(" says: ") // Or player-specific format .format((player, loc) -> player.isConsole() ? loc.consoleFormat() : loc.playerFormat()) // Required: range type .range(Range.get(Range.Type.PLAYER)) // To sender only .range(Range.get(Range.Type.LOCAL)) // Nearby players .range(Range.get(Range.Type.GLOBAL)) // All players .range(Range.get(Range.Type.WORLD)) // Same world // Optional: player message content .message("User's message here") // Optional: destination (chat, action bar, etc.) .destination(Destination.EMPTY_CHAT) // Optional: filter players .filter(player -> !player.isIgnored(sender)) .filterPlayer(senderPlayer, true) // Set color context // Optional: message flags .flag(MessageFlag.PLAYER_MESSAGE, true) .flag(MessageFlag.COLOR_CONTEXT_SENDER, true) // Optional: sound to play .sound(Pair.of(sound, permissionSetting)) // Optional: custom tag resolvers .tagResolvers(player -> new TagResolver[] { Placeholder.parsed("custom", "value") }) // Optional: proxy sync for multi-server .proxy(dataOutputStream -> { dataOutputStream.writeString(customData); dataOutputStream.writeInt(someValue); }) // Optional: integration formatting (Discord/Telegram/Twitch) .integration(rawString -> rawString.replace("", "value")) // Build the metadata .build(); ``` ## Creating Custom Modules FlectonePulse uses a modular architecture. Modules implement `ModuleSimple` or `ModuleCommand` interfaces and are managed by the `ModuleController`. ```java import net.flectone.pulse.module.ModuleSimple; import net.flectone.pulse.module.ModuleCommand; import net.flectone.pulse.config.setting.EnableSetting; import net.flectone.pulse.config.setting.PermissionSetting; import net.flectone.pulse.util.constant.ModuleName; import com.google.inject.Singleton; import com.google.inject.Inject; @Singleton public class CustomModule implements ModuleSimple { private final FileFacade fileFacade; @Inject public CustomModule(FileFacade fileFacade) { this.fileFacade = fileFacade; } @Override public void onEnable() { // Called when module is enabled System.out.println("Custom module enabled!"); } @Override public void onDisable() { // Called when module is disabled System.out.println("Custom module disabled!"); } @Override public ModuleName name() { return ModuleName.CUSTOM_MODULE; } @Override public EnableSetting config() { return fileFacade.config().module(); } @Override public PermissionSetting permission() { return fileFacade.permission().module(); } } // Command module example (with /ball command pattern) @Singleton public class MyCommandModule implements ModuleCommand { private final FileFacade fileFacade; private final MessageDispatcher messageDispatcher; private final ModuleCommandController commandController; private final CommandParserProvider commandParserProvider; @Inject public MyCommandModule(FileFacade fileFacade, MessageDispatcher messageDispatcher, ModuleCommandController commandController, CommandParserProvider commandParserProvider) { this.fileFacade = fileFacade; this.messageDispatcher = messageDispatcher; this.commandController = commandController; this.commandParserProvider = commandParserProvider; } @Override public void onEnable() { // Register command with arguments String messagePrompt = commandController.addPrompt(this, 0, Localization.Command.Prompt::message); commandController.registerCommand(this, builder -> builder .permission(permission().name()) .required(messagePrompt, commandParserProvider.nativeMessageParser()) ); } @Override public void execute(FPlayer player, CommandContext context) { String message = commandController.getArgument(this, context, 0); messageDispatcher.dispatch(this, EventMetadata.builder() .sender(player) .format(loc -> loc.format()) .message(message) .destination(config().destination()) .range(config().range()) .build() ); } @Override public ModuleName name() { return ModuleName.COMMAND_MYCOMMAND; } // ... config, permission, localization methods } ``` ## Integration Modules - Discord, Telegram, Twitch FlectonePulse provides integration modules for external platforms. These modules can be used to sync messages between the game server and external chat platforms. ```java import net.flectone.pulse.module.integration.discord.DiscordModule; import net.flectone.pulse.module.integration.telegram.TelegramModule; import net.flectone.pulse.model.entity.FEntity; // Get integration modules DiscordModule discordModule = flectonePulse.get(DiscordModule.class); TelegramModule telegramModule = flectonePulse.get(TelegramModule.class); // Send message to Discord discordModule.sendMessage( sender, "chat_message", rawString -> rawString.replace("", sender.name()) ); // Send message to Telegram telegramModule.sendMessage( sender, "join_message", rawString -> rawString .replace("", sender.name()) .replace("", "MyServer") ); ``` ## Configuration - YAML Config Structure FlectonePulse uses YAML configuration files. The main config.yml contains database settings, proxy configuration, module toggles, and cache settings. ```yaml # config.yml example version: "1.8.2-SNAPSHOT" language: type: "en_us" by_player: true # Detect player's client language database: ignore_existing_driver: false use_playtime_tracking: true type: "H2" # H2, SQLITE, MYSQL, MARIADB, POSTGRESQL name: "flectonepulse" host: "127.0.0.1" port: "3306" user: "root" password: "1234" parameters: "?autoReconnect=true&useSSL=false" prefix: "" proxy: clusters: [] bungeecord: false velocity: false redis: enable: false host: "127.0.0.1" port: 6379 ssl: false user: "" password: "" module: enable: true use_paper_message_sender: false cache: types: COOLDOWN: invalidate_on_reload: false duration: 5 time_unit: "HOURS" size: 5000 OFFLINE_PLAYERS: duration: 1 time_unit: "HOURS" size: 1000 metrics: enable: true ``` ## Localization - Message Formatting Localization files support MiniMessage formatting, custom placeholders, hover text, click actions, and color gradients. ```yaml # en_us.yml localization example cooldown: "Too fast, wait