Class PlayerEventManager

java.lang.Object
com.ultikits.ultitools.manager.PlayerEventManager
All Implemented Interfaces:
org.bukkit.event.Listener

@Internal public class PlayerEventManager extends Object implements org.bukkit.event.Listener
Player event manager. Handles WebSocket messages for player-related events.
  • Field Details

    • webSocketClient

      private volatile UltiPanelWebSocketClient webSocketClient
      Must be volatile. Written on the WebSocket's onOpen thread (initialize(com.ultikits.ultitools.websocket.UltiPanelWebSocketClient)), read on the Bukkit main thread (the seven event handlers). Adding synchronized only to the writer does not establish publication -- the reader never acquires the same monitor, so there is no happens-before edge between the two sides.

      This was accidentally masked before the idempotency guard was added: back then every initialize call also called registerEvents, and Bukkit's own listener registration writes a volatile field on HandlerList, which event dispatch then reads -- so this field's write got piggybacked out for free. Once the guard started skipping registration, that piggyback chain broke: after a reconnect, the handler could keep looking at the old, already-disconnected client, and player events would be silently dropped. See the PR #264 review.

    • listenerRegistered

      private volatile boolean listenerRegistered
      Whether this instance has already hooked itself into the Bukkit event system.

      initialize(UltiPanelWebSocketClient) is called by initializeManagers(), which is itself hung on the WebSocket's onConnectHandler -- it runs on every single onOpen. Without this guard, N disconnect-reconnect cycles would register N copies of the listener, and the same player event would be sent out N times. See issue #180.

  • Constructor Details

    • PlayerEventManager

      public PlayerEventManager()
  • Method Details

    • initialize

      public void initialize(UltiPanelWebSocketClient client)
      Initializes the player event manager.

      The whole method is mutually exclusive with shutdown(). Synchronizing only registerEvents on its own is not enough: a window would be left between "assign the client" and "register the listener", and if a logout happens to land exactly in that window, the listener shutdown() just detached would get reinstalled by the registerEvents that follows right behind it -- leaving listenerRegistered true even after disableCloud(). See the PR #264 review.

      Parameters:
      client - the WebSocket client
    • registerEvents

      private void registerEvents()
      Idempotently registers the event listener: returns immediately if already registered.

      The caller must already hold this object's monitor lock (currently the only caller is initialize(com.ultikits.ultitools.websocket.UltiPanelWebSocketClient)).

    • shutdown

      public void shutdown()
      Unregisters the event listener and detaches the association with the client.

      Called by PluginInitiationUtils.disableCloud() -- i.e. after /ulticloud logout. Distinct from a "disconnect-and-reconnect": in that case initialize gets called back afterward, so unregistering would just mean re-registering again; this method handles the case where the cloud feature was explicitly turned off, and the listener should genuinely be detached.

    • isListenerRegistered

      boolean isListenerRegistered()
      Lets tests assert the idempotency guard's state.
    • onPlayerJoin

      public void onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent event)
      Handles the player-join event.
    • onPlayerQuit

      public void onPlayerQuit(org.bukkit.event.player.PlayerQuitEvent event)
      Handles the player-quit event.
    • onPlayerChat

      public void onPlayerChat(org.bukkit.event.player.PlayerChatEvent event)
      Handles the player-chat event.
    • onPlayerDeath

      public void onPlayerDeath(org.bukkit.event.entity.PlayerDeathEvent event)
      Handles the player-death event.

      Moved here from the former EnhancedPlayerEventListener (#387). That class carried @EventListener but was never registered, so this handler and the three below had never fired. Registering that class alongside this one was not an option: both emitted the same player_event message type, so join/quit/chat would have been sent twice.

    • onPlayerKick

      public void onPlayerKick(org.bukkit.event.player.PlayerKickEvent event)
      Handles the player-kick event.
    • onPlayerCommandPreprocess

      public void onPlayerCommandPreprocess(org.bukkit.event.player.PlayerCommandPreprocessEvent event)
      Handles the player-command event.
    • onPlayerChangedWorld

      public void onPlayerChangedWorld(org.bukkit.event.player.PlayerChangedWorldEvent event)
      Handles the player-world-change event.
    • sendPlayerEvent

      private void sendPlayerEvent(UltiPanelWebSocketClient client, com.google.gson.JsonObject data)
      Sends a player event to UltiPanel.

      The client is passed in by the caller rather than re-reading webSocketClient here: each of the seven event handlers reads that volatile field into a local variable once and threads it through from there. Otherwise "check the connection" and "actually send" could read two different values -- if shutdown() happens to land in between, this would be an NPE.

      Parameters:
      client - the client the event handler read on entry
      data - the event data
    • getServerId

      private String getServerId(UltiPanelWebSocketClient client)
      Gets the server ID.

      Takes the UUID handed to the client at connect time (i.e. CommonUtils.getUltiToolsUUID(), see PluginInitiationUtils.getPanelWebsocketClient), consistent with every sibling manager. Before #180, this returned a hardcoded placeholder string, meaning every server worldwide sent out its player_event claiming the same identity, and the panel side had no way to route them.

      Returns:
      the server ID