[TUT] Intercept and Edit all Messages

Discussion in 'Plugin Development' started by one4me, Sep 2, 2012.

Thread Status:
Not open for further replies.
  1. Offline

    one4me

    Edit - I've gotten reports of this causing random issues that may not show up on a test server, such as dying and getting stuck. Although I've been unable to reproduce any of these issue and have no clue what would cause them, I recommend you use this with caution.


    Well I came across this the other day and remember seeing a few threads where people wanted to know how to do this, so here you go. This will give you the option to edit any message sent to a player, including but not limited to: broadcasts, messages send by other plugins, and player chat.

    Since no event is triggered when a Packet3Chat is sent to a player you're going to make one yourself.

    To save you the trouble of looking for the class that deals with chat, I'll tell you right now that you're going to have to make a class that extends NetServerHandler likeso:
    Code:
    import net.minecraft.server.EntityPlayer;
    import net.minecraft.server.NetServerHandler;
     
    public class ModifiedNSH extends NetServerHandler {
      public ModifiedNSH(EntityPlayer entityplayer) {
        super(entityplayer.server, entityplayer.netServerHandler.networkManager, entityplayer);
      }
    }
    
    This creates a class that extends NetServerHandler.

    You can now override the method that deals with packets - that method being sendPacket(Packet packet). Update your class to look like the following -
    Code:
    import org.bukkit.Bukkit;
    import org.bukkit.Location;
    import org.bukkit.craftbukkit.ChunkCompressionThread;
    import org.bukkit.craftbukkit.TextWrapper;
     
    import net.minecraft.server.EntityPlayer;
    import net.minecraft.server.NetServerHandler;
    import net.minecraft.server.Packet;
    import net.minecraft.server.Packet3Chat;
    import net.minecraft.server.Packet6SpawnPosition;
     
    public class ModifiedNSH extends NetServerHandler {
      public ModifiedNSH(EntityPlayer entityplayer) {
        super(entityplayer.server, entityplayer.netServerHandler.networkManager, entityplayer);
      }
      @Override
      public void sendPacket(Packet packet) {
        if((packet instanceof Packet3Chat)) {
          Packet3Chat packet3chat = (Packet3Chat)packet;
          int i = this.player.getChatFlags();
          if(i == 2) {
            return;
          }
          if((i == 1) && (!packet3chat.isServer())) {
            return;
          }
          Packet3ChatEvent event = new Packet3ChatEvent(packet3chat);
          Bukkit.getServer().getPluginManager().callEvent(event);
          if(event.isCancelled()) {
            return;
          }
          String message = event.getMessage();
          for(String line : TextWrapper.wrapText(message)) {
            this.networkManager.queue(new Packet3Chat(line));
          }
          return;
        }
        if(packet == null) {
          return;
        }
        if((packet instanceof Packet6SpawnPosition)) {
          Packet6SpawnPosition packet6 = (Packet6SpawnPosition)packet;
          this.player.compassTarget = new Location(getPlayer().getWorld(), packet6.x, packet6.y, packet6.z);
        }
        else if(packet.lowPriority == true) {
          ChunkCompressionThread.sendPacket(this.player, packet);
          return;
        }
        this.networkManager.queue(packet);
      }
    }
    
    Note: The only thing different between this and the original method (aside from the @Override) are the following lines.
    Code:
          Packet3ChatEvent event = new Packet3ChatEvent(packet3chat);
          Bukkit.getServer().getPluginManager().callEvent(event);
          if(event.isCancelled()) {
            return;
          }
          String message = event.getMessage();
    
    The first statement creates a new Packet3ChatEvent.
    The second statement calls the event.
    The if statement returns without sending the packet if the event was cancelled
    The last statement changes the message to whatever the event sets it to.
    And with that, you are done with messing around with the NetServerHandler.

    Unless you already made a class for Packet3ChatEvent you'll need to do that now in order get rid of the errors you probably have in the previous class.
    Code:
    import net.minecraft.server.Packet3Chat;
     
    import org.bukkit.event.Cancellable;
    import org.bukkit.event.Event;
    import org.bukkit.event.HandlerList;
     
    public class Packet3ChatEvent extends Event implements Cancellable{
      private final static HandlerList handlers = new HandlerList();
      private Packet3Chat packet;
      private String message;
      private boolean cancel = false;
      public Packet3ChatEvent(Packet3Chat packet) {
        this.packet = packet;
        this.message = packet.message;
      }
      public boolean isCancelled() {
        return this.cancel;
      }
      public void setCancelled(boolean cancel) {
        this.cancel = cancel;
      }
      public boolean isServer() {
        return this.packet.isServer();
      }
      public String getMessage() {
        return this.message;
      }
      public void setMessage(String message) {
        this.message = message;
      }
      public HandlerList getHandlers() {
        return Packet3ChatEvent.handlers;
      }
      public static HandlerList getHandlerList() {
        return Packet3ChatEvent.handlers;
      }
    }
    
    This class above all else, gives you the option to get and set the message of a Packet3Chat packet.

    Now to put the previous two classes to work all you need to do is set up your listeners. They belong in whichever class implements Listener.
    Code:
    @EventHandler
    public void onPlayerJoinEvent(PlayerJoinEvent e) {
      new ModifiedNSH(((CraftPlayer)e.getPlayer()).getHandle());
    }
    @EventHandler
    public void onPacket3ChatEvent(Packet3ChatEvent e) {
      e.setMessage("Intercepted!");
    }
    
    The first listener changes the players NetServerHandler to the class with the updated method.
    The second listener changes all messages to "Intercepted" (You can change it to do whatever you want).

    And there you have it, a ridiculous way to edit any message and at the same time cause compatibility issues with any other plugin that also messes with the NetServerHandler class.

    Edit - I updated the Packet3ChatEvent to implement Cancellable.
     
    Phasesaber, Limeth, Icyene and 2 others like this.
  2. Offline

    CorrieKay

    Thanks for this, exactly what i needed.

    However, quick question, in the constructor for the modified nsh, its not necessary to set the entities nsh to the modified one, as the line in the join event listner does that itsself, does it not?
     
    one4me likes this.
  3. Offline

    one4me

    I'm not sure how I overlooked that, but thanks for noticing.
     
    CorrieKay likes this.
  4. Offline

    CorrieKay

    i figured removing it from the constructor was the best way to deal with that :3
     
  5. Offline

    mbaxter ʇıʞʞnq ɐ sɐɥ ı

    Put more than one plugin on your server doing this and things are going to hell real fast. I wouldn't advise doing this, nor sharing it as a "tutorial"
     
    CorrieKay likes this.
  6. Offline

    evilmidget38

    Maybe it could be a Bukkit Feature?
     
  7. Offline

    CorrieKay

    I am 100% aware that youre not required to give support for modding bukkit, however would there be any reason as to why doing the above method would cause everyone to be unable to move?

    Im gonna revert for now, however, whenever i modded everyone's NSH, it spammed my production server's console with "player moved incorrectly!" "player moved too fast!", yet it didnt do that for my test server...

    and now, noone can move, lol.
     
  8. Offline

    one4me

    I did mention it would cause issues with any other plugin that also messes with the NetServerHandler in the last line of the tutorial, but if there is another easier way to accomplish what this does and with less compatibility issues, please tell me and I'll rewrite the tutorial.

    That would definitely solve any issue.

    Edit -
    CorrieKay
    I've yet to run into any problems like that, but if you want to pm more details about the plugin, I can try to help.
     
  9. Offline

    evilmidget38

    A PacketSendEvent would be useful, and would allow for much easier packet manipulation. Maybe I'll write up a PR if mbaxter doesn't have anything against the idea.
     
    CorrieKay likes this.
  10. Offline

    CorrieKay

    Fantastic idea!

    I did do something that was off from the tutorial, i split the chat packets from all other packets.

    Heres my hook.

    Explination (open)
    Basically, what i did this for is to augment my servers (custom plugin) chat system to skip prepending the chatters name on it, IFF the last packet the player recieved wasnt a chat message from that player.

    basically, if you've got two players, and a third one is chatting it would go like this:
    - player1 sees this -
    Player3: 1
    2

    - player2 sees this -
    Player3: 1
    <some other plugin message>
    Player3: 2

    edit: note that all chat events are actually cancelled with my plugin, so it doesnt actually go through, my plugin sends the messages itsself.
     
  11. Offline

    mbaxter ʇıʞʞnq ɐ sɐɥ ı

    a PacketSendEvent would be dangerously spammy, which is why it's not already in the Bukkit API. If anything, PR a PlayerSentMessageEvent or something, to cover the case described in this topic (catching all messages outgoing to a player's screen). Can't guarantee it'd be accepted but it'd be nifty to see.
     
    CorrieKay likes this.
  12. Offline

    evilmidget38

    CorrieKay

    https://github.com/Bukkit/Bukkit/pull/684
     
    one4me likes this.
  13. Offline

    CorrieKay

  14. Offline

    one4me

    It got closed because apparently plugins shouldn't be able to block other plugins from sending messages (but it's okay for plugins to interact with other plugins in much worse ways), and "there is no good use-case" (I'd say this is a perfectly good use).

    I guess if you want to use this in a plugin, your best bet would be to put a notice saying that it may conflict with other plugins. That, or avoid conflicts altogether by working together with other authors that also extend this class in a plugin.
     
  15. Offline

    CorrieKay

    This is worse for me, because i dont create plugins for other servers. I only got into plugin development because i wanted to make my server better for my players. Hopefully i dont get shot down for my opinion, but pandering to the administrators who arent thoughtful enough to see if one plugin is compatable with another plugin, and also refusing to implement a bit of api that allows plugins to not conflict as hard as being forced to create a modified NSH (which inexperienced developers, probably such as myself and beyond) will be utterly confused as to why their plugins are suddenly incompatable with another plugin... Its... idk. Its not good.

    But hey, w/e. It (my feature) wasnt necessary. I wont push it.
     
  16. Offline

    one4me

    Sorry to hear that; I did look at your code, but nothing stood out that would mess with a player's movement.
     
Thread Status:
Not open for further replies.

Share This Page