/* PopulationDensity Server Plugin for Minecraft Copyright (C) 2011 Ryan Hamshire This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package me.ryanhamshire.PopulationDensity; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import org.bukkit.Location; import org.bukkit.entity.Animals; import org.bukkit.entity.Entity; import org.bukkit.entity.Minecart; import org.bukkit.entity.Player; import org.bukkit.entity.Villager; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.*; import org.bukkit.event.player.PlayerLoginEvent.Result; public class PlayerEventHandler implements Listener { private DataStore dataStore; // queue of players waiting to join the server public ArrayList<LoginQueueEntry> loginQueue = new ArrayList<LoginQueueEntry>(); // typical constructor, yawn public PlayerEventHandler(DataStore dataStore, PopulationDensity plugin) { this.dataStore = dataStore; } // when a player attempts to join the server... @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onPlayerLoginEvent(PlayerLoginEvent event) { if (!PopulationDensity.instance.enableLoginQueue) return; if (event.getResult() != Result.ALLOWED) return; Player player = event.getPlayer(); PlayerData playerData = this.dataStore.getPlayerData(player); /* * PopulationDensity.AddLogEntry(""); * PopulationDensity.AddLogEntry("QUEUE STATUS================"); * PopulationDensity.AddLogEntry(""); * * for(int i = 0; i < this.loginQueue.size(); i++) { LoginQueueEntry * entry = this.loginQueue.get(i); DateFormat timeFormat = * DateFormat.getTimeInstance(DateFormat.SHORT); * PopulationDensity.AddLogEntry("\t" + entry.playerName + " " + * entry.priority + " " + timeFormat.format((new * Date(entry.lastRefreshed)))); } * * PopulationDensity.AddLogEntry(""); * PopulationDensity.AddLogEntry("END QUEUE STATUS================"); * PopulationDensity.AddLogEntry(""); * * PopulationDensity.AddLogEntry("attempting to log in " + * player.getName()); */ @SuppressWarnings("unchecked") Collection<Player> playersOnline = (Collection<Player>)PopulationDensity.instance.getServer().getOnlinePlayers(); int totalSlots = PopulationDensity.instance.getServer().getMaxPlayers(); // determine player's effective priority int effectivePriority = playerData.loginPriority; // PopulationDensity.AddLogEntry("\tlogin priority " + // playerData.loginPriority); // if the player last disconnected within the last two minutes, treat // the player with very high priority Calendar twoMinutesAgo = Calendar.getInstance(); twoMinutesAgo.add(Calendar.MINUTE, -2); if (playerData.lastDisconnect.compareTo(twoMinutesAgo.getTime()) == 1 && playerData.loginPriority < 99) { effectivePriority = 99; } // cap priority at 100 if (effectivePriority > 100) effectivePriority = 100; // PopulationDensity.AddLogEntry("\teffective priority " + // effectivePriority); // if the player has maximum priority if (effectivePriority > 99) { // PopulationDensity.AddLogEntry("\thas admin level priority"); // if there's room, log him in without consulting the queue if (playersOnline.size() <= totalSlots - 2) { // PopulationDensity.AddLogEntry("\tserver has room, so instant login"); return; } } // scan the queue for the player, removing any expired queue entries long nowTimestamp = Calendar.getInstance().getTimeInMillis(); int queuePosition = -1; for (int i = 0; i < this.loginQueue.size(); i++) { LoginQueueEntry entry = this.loginQueue.get(i); // if this entry has expired, remove it if ((nowTimestamp - entry.lastRefreshed) > 180000 /* three minutes */) { // PopulationDensity.AddLogEntry("\t\tremoved expired entry for " // + entry.playerName); this.loginQueue.remove(i--); } // otherwise compare the name in the entry else if (entry.playerName.equals(player.getName())) { queuePosition = i; // PopulationDensity.AddLogEntry("\t\trefreshed existing entry at position " // + queuePosition); entry.lastRefreshed = nowTimestamp; break; } } // if not in the queue, find the appropriate place in the queue to // insert if (queuePosition == -1) { // PopulationDensity.AddLogEntry("\tnot in the queue "); if (this.loginQueue.size() == 0) { // PopulationDensity.AddLogEntry("\tqueue empty, will insert in position 0"); queuePosition = 0; } else { // PopulationDensity.AddLogEntry("\tsearching for best place based on rank"); for (int i = this.loginQueue.size() - 1; i >= 0; i--) { LoginQueueEntry entry = this.loginQueue.get(i); if (entry.priority >= effectivePriority) { queuePosition = i + 1; // PopulationDensity.AddLogEntry("\tinserting in position" // + queuePosition + " behind " + entry.playerName + // ", pri " + entry.priority); break; } } if (queuePosition == -1) queuePosition = 0; } this.loginQueue.add(queuePosition, new LoginQueueEntry(player.getName(), effectivePriority, nowTimestamp)); } // PopulationDensity.AddLogEntry("\tplayer count " + // playersOnline.length + " / " + totalSlots); // if the player can log in if (totalSlots - 1 - playersOnline.size() - PopulationDensity.instance.reservedSlotsForAdmins > queuePosition) { // PopulationDensity.AddLogEntry("\tcan log in now, removed from queue"); // remove from queue this.loginQueue.remove(queuePosition); // allow login return; } else { // otherwise, kick, notify about position in queue, and give // instructions // PopulationDensity.AddLogEntry("\tcant log in yet"); event.setResult(Result.KICK_FULL); String kickMessage = PopulationDensity.instance.queueMessage; kickMessage = kickMessage.replace("%queuePosition%", String.valueOf(queuePosition + 1)); kickMessage = kickMessage.replace("%queueLength%", String.valueOf(this.loginQueue.size())); event.setKickMessage("" + (queuePosition + 1) + " of " + this.loginQueue.size() + " in queue. Reconnect within 3 minutes to keep your place. :)"); event.disallow(event.getResult(), event.getKickMessage()); } } // when a player successfully joins the server... @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void onPlayerJoin(PlayerJoinEvent event) { Player joiningPlayer = event.getPlayer(); PopulationDensity.instance.resetIdleTimer(joiningPlayer); PlayerData playerData = this.dataStore.getPlayerData(joiningPlayer); if (playerData.lastObservedLocation == null) { playerData.lastObservedLocation = joiningPlayer.getLocation(); } // if the player doesn't have a home region yet (he hasn't logged in // since the plugin was installed) RegionCoordinates homeRegion = playerData.homeRegion; if (homeRegion == null) { // if he's never been on the server before if(!joiningPlayer.hasPlayedBefore()) { // his home region is the open region RegionCoordinates openRegion = this.dataStore.getOpenRegion(); playerData.homeRegion = openRegion; PopulationDensity.AddLogEntry("Assigned new player " + joiningPlayer.getName() + " to region " + this.dataStore.getRegionName(openRegion) + " at " + openRegion.toString() + "."); // entirely new players who've not visited the server before will // spawn in their home region by default. // if configured as such, teleport him there in a couple of seconds if (PopulationDensity.instance.newPlayersSpawnInHomeRegion && joiningPlayer.getLocation().distanceSquared(joiningPlayer.getWorld().getSpawnLocation()) < 625) { PlaceNewPlayerTask task = new PlaceNewPlayerTask(joiningPlayer, playerData.homeRegion); PopulationDensity.instance.getServer().getScheduler().scheduleSyncDelayedTask(PopulationDensity.instance, task, 1L); } // otherwise allow other plugins to control spawning a new player else { // unless pop density is configured to force a precise world spawn point if(PopulationDensity.instance.preciseWorldSpawn) { TeleportPlayerTask task = new TeleportPlayerTask(joiningPlayer, joiningPlayer.getWorld().getSpawnLocation(), false); PopulationDensity.instance.getServer().getScheduler().scheduleSyncDelayedTask(PopulationDensity.instance, task, 1L); } // always remove monsters around the new player's spawn point to prevent ambushes PopulationDensity.removeMonstersAround(joiningPlayer.getWorld().getSpawnLocation()); } } //otherwise if he's played before, guess his home region as best we can else { if(joiningPlayer.getBedSpawnLocation() != null) { playerData.homeRegion = RegionCoordinates.fromLocation(joiningPlayer.getBedSpawnLocation()); } else { playerData.homeRegion = RegionCoordinates.fromLocation(joiningPlayer.getLocation()); } if(playerData.homeRegion == null) { playerData.homeRegion = PopulationDensity.instance.dataStore.getOpenRegion(); } } this.dataStore.savePlayerData(joiningPlayer, playerData); } } // when a player disconnects... @EventHandler(ignoreCancelled = true) public void onPlayerQuit(PlayerQuitEvent event) { this.onPlayerDisconnect(event.getPlayer()); } // when a player gets kicked... @EventHandler(ignoreCancelled = true) public void onPlayerKicked(PlayerKickEvent event) { this.onPlayerDisconnect(event.getPlayer()); } // when a player disconnects... private void onPlayerDisconnect(Player player) { PlayerData playerData = this.dataStore.getPlayerData(player); // note logout timestamp playerData.lastDisconnect = Calendar.getInstance().getTime(); //note login priority based on permissions // assert permission-based priority if (player.hasPermission("populationdensity.prioritylogin") && playerData.loginPriority < 25) { playerData.loginPriority = 25; } if (player.hasPermission("populationdensity.elitelogin") && playerData.loginPriority < 50) { playerData.loginPriority = 50; } // if the player has kicktologin permission, treat the player with // highest priority if (player.hasPermission("populationdensity.adminlogin")) { // PopulationDensity.AddLogEntry("\tcan fill administrative slots"); playerData.loginPriority = 100; } this.dataStore.savePlayerData(player, playerData); // cancel any existing afk check task if (playerData.afkCheckTaskID >= 0) { PopulationDensity.instance.getServer().getScheduler() .cancelTask(playerData.afkCheckTaskID); playerData.afkCheckTaskID = -1; } // clear any cached data for this player in the data store this.dataStore.clearCachedPlayerData(player); } // when a player respawns after death... @EventHandler(ignoreCancelled = true) public void onPlayerRespawn(PlayerRespawnEvent respawnEvent) { if (!PopulationDensity.instance.respawnInHomeRegion) { if(PopulationDensity.ManagedWorld == respawnEvent.getRespawnLocation().getWorld()) { PopulationDensity.removeMonstersAround(respawnEvent.getRespawnLocation()); } return; } Player player = respawnEvent.getPlayer(); // if it's NOT a bed respawn, redirect it to the player's home region // post // this keeps players near where they live, even when they die (haha) if (!respawnEvent.isBedSpawn()) { PlayerData playerData = this.dataStore.getPlayerData(player); // find the center of his home region Location homeRegionCenter = PopulationDensity.getRegionCenter(playerData.homeRegion, false); // aim for two blocks above the highest block and teleport homeRegionCenter.setY(PopulationDensity.ManagedWorld .getHighestBlockYAt(homeRegionCenter) + 2); respawnEvent.setRespawnLocation(homeRegionCenter); PopulationDensity.removeMonstersAround(homeRegionCenter); } } @EventHandler(ignoreCancelled = true) public synchronized void onPlayerChat(AsyncPlayerChatEvent event) { String msg = event.getMessage(); if(msg.equalsIgnoreCase(PopulationDensity.instance.dataStore.getMessage(Messages.Lag))) { Player player = event.getPlayer(); event.getRecipients().clear(); event.getRecipients().add(player); PopulationDensity.instance.reportTPS(player); } } @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) public void onPlayerInteractEntity(PlayerInteractEntityEvent event) { Entity entity = event.getRightClicked(); if(entity instanceof Animals || entity instanceof Minecart || entity instanceof Villager) { entity.setTicksLived(1); } } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void onPlayerToggleFlight(PlayerToggleFlightEvent event) { if(PopulationDensity.instance.isFallDamageImmune(event.getPlayer())) { event.setCancelled(true); } } }