Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@aadnk
Created October 10, 2013 02:47
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aadnk/6912248 to your computer and use it in GitHub Desktop.
Save aadnk/6912248 to your computer and use it in GitHub Desktop.
Second attempt at extending the logout time of players. This time we also handle players that simply quit the application.
package com.comphenix.example;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FilterInputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
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.FuzzyReflection;
import com.comphenix.protocol.reflect.VolatileField;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.Uninterruptibles;
public class ExtendedLogoutTime2 extends JavaPlugin implements Listener {
// Represents every injected player
private static class InjectedPlayer {
public VolatileField inputField;
public VolatileField outputField;
public boolean disconnected;
public void uninject() {
inputField.revertValue();
outputField.revertValue();
}
}
private Map<Player, InjectedPlayer> injections = Maps.newConcurrentMap();
@Override
public void onEnable() {
getServer().getPluginManager().registerEvents(this, this);
registerProtocolLib();
}
private void registerProtocolLib() {
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) {
InjectedPlayer injection = injections.get(event.getPlayer());
if (injection != null)
injection.disconnected = true;
event.setCancelled(true);
}
}
});
}
@EventHandler(priority = EventPriority.LOW)
public void onPlayerLogin(PlayerJoinEvent e) {
try {
NetworkObjectInjector injector = new NetworkObjectInjector(getClassLoader(),
ProtocolLibrary.getErrorReporter(), null,
(ListenerInvoker) ProtocolLibrary.getProtocolManager(), null);
injector.initializePlayer(e.getPlayer());
// Prepare the injection
InjectedPlayer injection = new InjectedPlayer();
Object network = injector.getNetworkManager();
FuzzyReflection fuzzy = FuzzyReflection.fromObject(network, true);
// Shared time lock
TimedLock lock = new TimedLock(10, TimeUnit.SECONDS);
injection.inputField = new VolatileField(fuzzy.getFieldByType("input", DataInputStream.class), network, true);
injection.outputField = new VolatileField(fuzzy.getFieldByType("output", DataOutputStream.class), network, true);
// Inject both the input and the output
injection.inputField.setValue(new DataInputStream(
new DelayedInputStream((InputStream) injection.inputField.getValue(), lock)
));
injection.outputField.setValue(new DataOutputStream(
new DelayedOutputStream((OutputStream) injection.outputField.getValue(), lock)
));
injections.put(e.getPlayer(), injection);
} catch (IllegalAccessException e1) {
e1.printStackTrace();
}
}
@EventHandler
public void onPlayerLogout(PlayerQuitEvent e) {
InjectedPlayer injection = injections.remove(e.getPlayer());
if (injection != null) {
injection.uninject();
}
}
@Override
public void onDisable() {
for (InjectedPlayer injection : injections.values()) {
injection.uninject();
}
injections.clear();
}
private static class TimedLock {
private int delayAmount;
private TimeUnit delayUnit;
// State machine
private volatile boolean done;
// The shared lock
private Object lock = new Object();
public TimedLock(int delayAmount, TimeUnit delayUnit) {
this.delayAmount = delayAmount;
this.delayUnit = delayUnit;
}
public void await() {
if (Bukkit.isPrimaryThread()) {
Exception ex = new Exception();
ex.printStackTrace();
}
if (!done) {
// The other thread may block
synchronized (lock) { }
}
}
public void delay() {
synchronized (lock) {
if (!done) {
Uninterruptibles.sleepUninterruptibly(delayAmount, delayUnit);
done = true;
}
}
}
}
private static class DelayedOutputStream extends FilterOutputStream {
private TimedLock lock;
public DelayedOutputStream(OutputStream out, TimedLock lock) {
super(out);
this.lock = lock;
}
@Override
public void write(int b) throws IOException {
try {
lock.await();
super.write(b);
} catch (IOException e) {
lock.delay();
throw e;
}
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
try {
lock.await();
super.write(b, off, len);
} catch (IOException e) {
lock.delay();
throw e;
}
}
@Override
public void flush() throws IOException {
try {
lock.await();
super.flush();
} catch (IOException e) {
lock.delay();
throw e;
}
}
@Override
public void close() throws IOException {
lock.delay();
super.close();
}
}
private static class DelayedInputStream extends FilterInputStream {
private TimedLock lock;
public DelayedInputStream(InputStream in, TimedLock lock) {
super(in);
this.lock = lock;
}
@Override
public int read() throws IOException {
try {
lock.await();
int nextByte = super.read();
if (nextByte < 0)
lock.delay();
return nextByte;
} catch (IOException e) {
lock.delay();
throw e;
}
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
try {
lock.await();
int bytesRead = super.read(b, off, len);
// Reached end of stream - wait
if (bytesRead < 0)
lock.delay();
return bytesRead;
} catch (IOException e) {
lock.delay();
throw e;
}
}
@Override
public synchronized void close() throws IOException {
lock.delay();
super.close();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment