Skip to content

Instantly share code, notes, and snippets.

@aadnk
Last active December 25, 2015 01:09
Show Gist options
  • Save aadnk/6893017 to your computer and use it in GitHub Desktop.
Save aadnk/6893017 to your computer and use it in GitHub Desktop.
Extend the time player's stay logged on on the server when they log out. This prevents PvP'ers from logging out in the middle of a fight.
package com.comphenix.example;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.player.NetworkObjectInjector;
import com.comphenix.protocol.reflect.FieldUtils;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Uninterruptibles;
public class ExtendLogoutTime extends JavaPlugin implements Listener {
private Set<InetSocketAddress> disconnected = Sets.newSetFromMap(new ConcurrentHashMap<InetSocketAddress, Boolean>());
// The amount of seconds to delay logouts
private static final int DELAY_LOGOUT_SEC = 10;
private volatile boolean hasDetectedSpigot;
@Override
public void onEnable() {
final ProtocolManager manager = ProtocolLibrary.getProtocolManager();
manager.addPacketListener(
new PacketAdapter(this, ConnectionSide.CLIENT_SIDE, GamePhase.LOGIN, Packets.Client.KICK_DISCONNECT, Packets.Client.LOGIN) {
@Override
public void onPacketReceiving(PacketEvent event) {
if (event.getPacket().getID() == Packets.Client.KICK_DISCONNECT) {
disconnected.add(event.getPlayer().getAddress());
// Fall back on simply cancelling the packet
if (hasDetectedSpigot) {
event.setCancelled(true);
return;
}
// Don't flush the real stream
try {
handleOutputStream(manager, event.getPlayer());
// Here's the trick - block this thread for 10 seconds
// If you need more than 60 seconds, you also need to handle
// the KEEP_ALIVE packets.
Uninterruptibles.sleepUninterruptibly(DELAY_LOGOUT_SEC, TimeUnit.SECONDS);
} catch (Exception e) {
getLogger().info("Detected Spigot");
event.setCancelled(true);
hasDetectedSpigot = true;
}
} else {
disconnected.remove(event.getPlayer().getAddress());
}
}
});
// Stop writing packets to the client
manager.addPacketListener(
new PacketAdapter(this, ConnectionSide.SERVER_SIDE, GamePhase.LOGIN, Packets.Server.KEEP_ALIVE) {
@Override
public void onPacketSending(PacketEvent event) {
if (disconnected.contains(event.getPlayer().getAddress())) {
// Don't try to send any packets now ...
event.setCancelled(true);
}
}
});
}
// Okay --- there's another trick. We need to replace the output stream with something harmless
private void handleOutputStream(ProtocolManager manager, Player player) {
try {
//
NetworkObjectInjector injector = new NetworkObjectInjector(getClassLoader(),
ProtocolLibrary.getErrorReporter(), null, (ListenerInvoker) manager, null);
injector.initializePlayer(player);
Object network = injector.getNetworkManager();
OutputStream original = (OutputStream) FieldUtils.readField(network, "output", true);
// Just write a dummy output stream back
FieldUtils.writeField(network, "output",
new DataOutputStream(new ByteArrayOutputStream()), true);
// Clean up the network reference here
original.close();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment