package tc.oc.commons.bukkit.freeze;
import java.time.Duration;
import java.util.Map;
import java.util.WeakHashMap;
import javax.inject.Inject;
import javax.inject.Singleton;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.SetMultimap;
import org.bukkit.World;
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.PlayerQuitEvent;
import org.bukkit.event.world.WorldUnloadEvent;
import tc.oc.commons.bukkit.util.NMSHacks;
import tc.oc.commons.core.plugin.PluginFacet;
import tc.oc.minecraft.api.scheduler.Tickable;
/**
* Freezes players by mounting them on an invisible minecart.
*/
@Singleton
public class PlayerFreezer implements PluginFacet, Listener, Tickable {
private final Map<World, NMSHacks.FakeArmorStand> armorStands = new WeakHashMap<>();
private final SetMultimap<Player, FrozenPlayer> frozenPlayers = HashMultimap.create();
@Inject PlayerFreezer() {}
@Override
public Duration tickPeriod() {
return Duration.ofMillis(50);
}
private NMSHacks.FakeArmorStand armorStand(Player player) {
return armorStands.computeIfAbsent(player.getWorld(), NMSHacks.FakeArmorStand::new);
}
public boolean isFrozen(Player player) {
return frozenPlayers.containsKey(player);
}
public FrozenPlayer freeze(Player player) {
final FrozenPlayerImpl frozenPlayer = new FrozenPlayerImpl(player);
final boolean wasFrozen = isFrozen(player);
frozenPlayers.put(player, frozenPlayer);
if(!wasFrozen) {
player.setPaused(true);
player.leaveVehicle(); // TODO: Put them back in the vehicle when thawed?
armorStand(player).spawn(player, player.getLocation());
sendAttach(player);
}
return frozenPlayer;
}
@Override
public void tick() {
// If the player right-clicks on another vehicle while frozen, the client will
// eject them from the freeze entity unconditionally, so we have to spam them
// with these packets to keep them on it.
frozenPlayers.keySet().forEach(this::sendAttach);
}
private void sendAttach(Player player) {
armorStand(player).ride(player, player);
}
@EventHandler(priority = EventPriority.MONITOR)
public void onQuit(PlayerQuitEvent event) {
frozenPlayers.removeAll(event.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR)
public void onUnload(WorldUnloadEvent event) {
armorStands.remove(event.getWorld());
}
private class FrozenPlayerImpl implements FrozenPlayer {
// Might eventually put some state here that can be restored after thawing,
// e.g. gamemode, vehicle, etc. But currently, this class doesn't know enough
// about the player's situation to do that safely.
private final Player player;
private FrozenPlayerImpl(Player player) {
this.player = player;
}
@Override
public void thaw() {
if(frozenPlayers.remove(player, this) && !isFrozen(player) && player.isOnline()) {
armorStand(player).destroy(player);
player.setPaused(false);
}
}
}
}