Created
October 10, 2013 02:47
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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