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); } } } }