package mineplex.hub.server; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Random; import java.util.Set; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.Sound; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.entity.EntityPortalEnterEvent; import org.bukkit.event.entity.EntityPortalEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerPortalEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.util.Vector; import mineplex.core.MiniPlugin; import mineplex.core.account.CoreClientManager; import mineplex.core.common.Rank; import mineplex.core.common.util.C; import mineplex.core.common.util.Callback; import mineplex.core.common.util.F; import mineplex.core.common.util.NautHashMap; import mineplex.core.common.util.UtilAction; import mineplex.core.common.util.UtilAlg; import mineplex.core.common.util.UtilPlayer; import mineplex.core.common.util.UtilTime; import mineplex.core.common.util.UtilTime.TimeUnit; import mineplex.core.donation.DonationManager; import mineplex.core.itemstack.ItemStackFactory; import mineplex.core.party.Party; import mineplex.core.party.PartyManager; import mineplex.core.portal.Portal; import mineplex.core.recharge.Recharge; import mineplex.core.shop.ShopBase; import mineplex.core.status.ServerStatusManager; import mineplex.core.updater.UpdateType; import mineplex.core.updater.event.UpdateEvent; import mineplex.hub.HubManager; import mineplex.hub.modules.StackerManager; import mineplex.hub.queue.QueueManager; import mineplex.hub.queue.ui.QueueShop; import mineplex.hub.server.ui.LobbyShop; import mineplex.hub.server.ui.QuickShop; import mineplex.hub.server.ui.ServerCountSorter; import mineplex.hub.server.ui.ServerNpcShop; import mineplex.hub.server.ui.clans.ClansServerShop; import mineplex.serverdata.Region; import mineplex.serverdata.data.MinecraftServer; import mineplex.serverdata.data.ServerGroup; public class ServerManager extends MiniPlugin { private static final Long FREE_PORTAL_TIMER = 20000L; private static final Long BETA_PORTAL_TIMER = 120000L; private static final Random random = new Random(); public final int TOP_SERVERS = 3; // The number of top contending servers for auto-joining games public final int MIN_SLOTS_REQUIRED = 12; // The number of slots the max server must have for auto-join public final long QUEUE_COOLDOWN = 2000; // Cooldown (in milliseconds) between queueing again for players private CoreClientManager _clientManager; private DonationManager _donationManager; private Portal _portal; private PartyManager _partyManager; private ServerStatusManager _statusManager; private HubManager _hubManager; private StackerManager _stackerManager; private QueueManager _queueManager; private NautHashMap _queueCooldowns = new NautHashMap(); private NautHashMap> _serverKeyInfoMap = new NautHashMap>(); private NautHashMap _serverPlayerCounts = new NautHashMap(); private NautHashMap _serverNpcShopMap = new NautHashMap(); private NautHashMap _serverInfoMap = new NautHashMap(); private NautHashMap _serverUpdate = new NautHashMap(); private NautHashMap _portalToServerKey = new NautHashMap(); private ClansServerShop _clansShop; // Join Time for Free Players Timer private NautHashMap _joinTime = new NautHashMap(); private QueueShop _domShop; private QuickShop _quickShop; private LobbyShop _lobbyShop; private boolean _alternateUpdateFire = false; private boolean _retrieving = false; private long _lastRetrieve = 0; public ServerManager(JavaPlugin plugin, CoreClientManager clientManager, DonationManager donationManager, Portal portal, PartyManager partyManager, ServerStatusManager statusManager, HubManager hubManager, StackerManager stackerManager, QueueManager queueManager) { super("Server Manager", plugin); _clientManager = clientManager; _donationManager = donationManager; _portal = portal; _partyManager = partyManager; _statusManager = statusManager; _hubManager = hubManager; _stackerManager = stackerManager; _queueManager = queueManager; plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, "BungeeCord"); loadServers(); _quickShop = new QuickShop(this, clientManager, donationManager, "Quick Menu"); _lobbyShop = new LobbyShop(this, clientManager, donationManager, "Lobby Menu"); //_domShop = new new QueueShop(_queueManager, clientManager, donationManager, "Dominate"); // TODO: Find more appropriate place to initialize Clans server shop? _clansShop = new ClansServerShop(this, _clientManager, _donationManager); } @EventHandler(priority = EventPriority.LOW) public void playerPortalEvent(PlayerPortalEvent event) { event.setCancelled(true); } @EventHandler(priority = EventPriority.LOW) public void entityPortalEvent(EntityPortalEvent event) { event.setCancelled(true); } @EventHandler(priority = EventPriority.LOW) public void playerCheckPortalEvent(EntityPortalEnterEvent event) { if (!(event.getEntity() instanceof Player)) { if (event.getEntity() instanceof LivingEntity) UtilAction.velocity(event.getEntity(), UtilAlg.getTrajectory(event.getEntity().getLocation(), _hubManager.GetSpawn()), 1, true, 0.8, 0, 1, true); return; } final Player player = (Player)event.getEntity(); if (!Recharge.Instance.use(player, "Portal Server", 1000, false, false)) return; long timeUntilPortal = getMillisecondsUntilPortal(player, false); if (!_hubManager.CanPortal(player) || timeUntilPortal > 0) { player.closeInventory(); if (timeUntilPortal > 0) { player.playSound(player.getEyeLocation(), Sound.CHICKEN_EGG_POP, 2, 2); UtilPlayer.message(player, F.main("Server Portal", "You cannot join a server for " + C.cGreen + UtilTime.convertString(timeUntilPortal, 0, TimeUnit.SECONDS))); } UtilAction.velocity(player, UtilAlg.getTrajectory(player.getLocation(), _hubManager.GetSpawn()), 1.5, true, 0.8, 0, 1.0, true); // Need to set their velocity again a tick later // Setting Y-Velocity while in a portal doesn't seem to do anything... Science! _plugin.getServer().getScheduler().runTask(_plugin, new Runnable() { @Override public void run() { if (player != null && player.isOnline()) { UtilAction.velocity(player, UtilAlg.getTrajectory(player.getLocation(), _hubManager.GetSpawn()), 1, true, 0.5, 0, 1.0, true); } } }); return; } String serverKey = _portalToServerKey.get(player.getLocation().getBlock().getLocation().toVector()); if (serverKey != null) { List serverList = new ArrayList(); Collection servers = getServerList(serverKey); if (servers != null && servers.size() > 0) serverList.addAll(servers); int slots = 1; if (serverList.size() > 0) { slots = getRequiredSlots(player, serverList.get(0).ServerType); } try { Collections.sort(serverList, new ServerSorter(slots)); for (ServerInfo serverInfo : serverList) { if ((serverInfo.MOTD.contains("Starting") || serverInfo.MOTD.contains("Recruiting") || serverInfo.MOTD.contains("Waiting") || serverInfo.MOTD.contains("Cup")) && (serverInfo.MaxPlayers - serverInfo.CurrentPlayers) >= slots) { selectServer(player, serverInfo); return; } } } catch (Exception exception) { exception.printStackTrace(); } UtilPlayer.message(player, F.main("Server Portal", "There are currently no joinable servers!")); } } @EventHandler public void checkQueuePrompts(UpdateEvent event) { if (event.getType() != UpdateType.SEC) return; /* for (final Player player : _queueManager.findPlayersNeedingPrompt()) { player.playSound(player.getLocation(), Sound.ENDERDRAGON_GROWL, 5f, 1f); Bukkit.getScheduler().runTaskLater(getPlugin(), new Runnable() { public void run() { if (player.isOnline()) { _domShop.attemptShopOpen(player); } } }, 20); } */ } @EventHandler(priority = EventPriority.LOW) public void playerJoin(PlayerJoinEvent event) { event.getPlayer().getInventory().addItem(ItemStackFactory.Instance.CreateStack(Material.COMPASS.getId(), (byte)0, 1, ChatColor.GREEN + "Game Menu")); event.getPlayer().getInventory().addItem(ItemStackFactory.Instance.CreateStack(Material.WATCH.getId(), (byte)0, 1, ChatColor.GREEN + "Lobby Menu")); if (_clientManager.Get(event.getPlayer()).GetRank() == Rank.ALL) { _joinTime.put(event.getPlayer().getName(), System.currentTimeMillis()); } } @EventHandler public void playerQuit(PlayerQuitEvent event) { _joinTime.remove(event.getPlayer().getName()); } @EventHandler(priority = EventPriority.LOWEST) public void playerInteract(PlayerInteractEvent event) { if (event.getItem() != null && event.getItem().getType() == Material.COMPASS) { _quickShop.attemptShopOpen(event.getPlayer()); } else if (event.getItem() != null && event.getItem().getType() == Material.WATCH) { _lobbyShop.attemptShopOpen(event.getPlayer()); } } public Long getMillisecondsUntilPortal(Player player, boolean beta) { // Party party = _partyManager.GetParty(player); long timeLeft = 0; if (_joinTime.containsKey(player.getName())) { timeLeft = (_joinTime.get(player.getName()) - System.currentTimeMillis()) + (beta ? BETA_PORTAL_TIMER : FREE_PORTAL_TIMER); if (timeLeft <= 0) timeLeft = 0; } return timeLeft; } public void addServerGroup(ServerGroup serverGroup) { _serverKeyInfoMap.put(serverGroup.getPrefix(), new HashSet()); } public void addServerNpc(ServerGroup serverGroup) { _serverNpcShopMap.put(serverGroup.getServerNpcName(), new ServerNpcShop(this, _clientManager, _donationManager, serverGroup)); } public void removeServerNpc(String serverNpcName) { Set mappedServers = _serverKeyInfoMap.remove(serverNpcName); _serverNpcShopMap.remove(serverNpcName); if (mappedServers != null) { for (ServerInfo mappedServer : mappedServers) { boolean isMappedElseWhere = false; for (String key : _serverKeyInfoMap.keySet()) { for (ServerInfo value : _serverKeyInfoMap.get(key)) { if (value.Name.equalsIgnoreCase(mappedServer.Name)) { isMappedElseWhere = true; break; } } if (isMappedElseWhere) break; } if (!isMappedElseWhere) _serverInfoMap.remove(mappedServer.Name); } } } public Collection getServerList(String serverKey) { return _serverKeyInfoMap.get(serverKey); } public Set getAllServers() { return _serverInfoMap.keySet(); } public ServerInfo getServerInfo(String serverName) { return _serverInfoMap.get(serverName); } @EventHandler public void updatePages(UpdateEvent event) { if (event.getType() != UpdateType.SEC) return; _quickShop.UpdatePages(); for (ServerNpcShop shop : _serverNpcShopMap.values()) { shop.UpdatePages(); } } @EventHandler public void updateServers(UpdateEvent event) { if (event.getType() != UpdateType.SEC || (_retrieving && System.currentTimeMillis() - _lastRetrieve <= 5000)) return; _alternateUpdateFire = !_alternateUpdateFire; if (!_alternateUpdateFire) return; _retrieving = true; _statusManager.retrieveServerGroups(new Callback>() { public void run(final Collection serverGroups) { final NautHashMap serverGroupMap = new NautHashMap(); for (ServerGroup serverGroup : serverGroups) { serverGroupMap.put(serverGroup.getName(), serverGroup); } _statusManager.retrieveServerStatuses(new Callback>() { public void run(Collection serverStatusList) { _serverPlayerCounts.clear(); for (MinecraftServer serverStatus : serverStatusList) { if (!_serverInfoMap.containsKey(serverStatus.getName())) { ServerInfo newServerInfo = new ServerInfo(); newServerInfo.Name = serverStatus.getName(); _serverInfoMap.put(serverStatus.getName(), newServerInfo); } String[] args = serverStatus.getMotd().split("\\|"); String tag = (serverStatus.getName() != null && serverStatus.getName().contains("-")) ? serverStatus.getName().split("-")[0] : "N/A"; //Private Servers if (serverGroupMap.containsKey(serverStatus.getGroup())) { ServerGroup serverGroup = serverGroupMap.get(serverStatus.getGroup()); if (serverGroup.getHost() != null && !serverGroup.getHost().isEmpty()) tag = "MPS"; } ServerInfo serverInfo = _serverInfoMap.get(serverStatus.getName()); serverInfo.MOTD = args.length > 0 ? args[0] : serverStatus.getMotd(); serverInfo.CurrentPlayers = serverStatus.getPlayerCount(); serverInfo.MaxPlayers = serverStatus.getMaxPlayerCount(); for (String arg : args) { if (arg != null && arg.startsWith("HostRank.") && arg.length() > "HostRank.".length()) { String rankEnum = arg.split("\\.")[1]; try { serverInfo.HostRank = Rank.valueOf(rankEnum); } catch (Exception e) { // Ignore } break; } } if (args.length > 1) serverInfo.ServerType = args[1]; if (args.length > 2) serverInfo.Game = args[2]; if (args.length > 3) serverInfo.Map = args[3]; _serverUpdate.put(serverStatus.getName(), System.currentTimeMillis()); if (_serverKeyInfoMap.containsKey(tag)) { _serverKeyInfoMap.get(tag).add(serverInfo); if (!_serverPlayerCounts.containsKey(tag)) _serverPlayerCounts.put(tag, 0); _serverPlayerCounts.put(tag, _serverPlayerCounts.get(tag) + serverInfo.CurrentPlayers); } } for (String name : _serverUpdate.keySet()) { if (_serverUpdate.get(name) != -1L && System.currentTimeMillis() - _serverUpdate.get(name) > 5000) { ServerInfo serverInfo = _serverInfoMap.get(name); serverInfo.MOTD = ChatColor.DARK_RED + "OFFLINE"; serverInfo.CurrentPlayers = 0; serverInfo.MaxPlayers = 0; _serverUpdate.put(name, -1L); } } // Reset _retrieving = false; _lastRetrieve = System.currentTimeMillis(); } }); } }); updateCooldowns(); } public void help(Player caller, String message) { UtilPlayer.message(caller, F.main(_moduleName, "Commands List:")); UtilPlayer.message(caller, F.help("/servernpc create ", " is name of npc.", Rank.OWNER)); UtilPlayer.message(caller, F.help("/servernpc delete ", " is name of npc.", Rank.OWNER)); UtilPlayer.message(caller, F.help("/servernpc addserver | ", "Adds server.", Rank.OWNER)); UtilPlayer.message(caller, F.help("/servernpc removeserver ", "Removes server.", Rank.OWNER)); UtilPlayer.message(caller, F.help("/servernpc listnpcs", "Lists all server npcs.", Rank.OWNER)); UtilPlayer.message(caller, F.help("/servernpc listservers ", "Lists all servers.", Rank.OWNER)); UtilPlayer.message(caller, F.help("/servernpc listoffline", "Shows all servers offline.", Rank.OWNER)); if (message != null) UtilPlayer.message(caller, F.main(_moduleName, ChatColor.RED + message)); } public void help(Player caller) { help(caller, null); } public PartyManager getPartyManager() { return _partyManager; } public void selectServer(org.bukkit.entity.Player player, ServerInfo serverInfo) { Party party = _partyManager.GetParty(player); if (party == null || player.getName().equals(party.GetLeader())) { player.leaveVehicle(); player.eject(); _portal.sendPlayerToServer(player, serverInfo.Name); } } /** * Select a {@code serverType} for a {@code player} that wishes to automatically join the best server * available for that server type. * @param player - the player hoping to select a server * @param serverType - the name of the type of server to be joined */ public void selectServer(Player player, String serverType) { if (isOnCooldown(player)) { return; } ServerInfo bestServer = getBestServer(player, serverType); if (bestServer != null) { selectServer(player, bestServer); addCooldown(player); } } private boolean isOnCooldown(Player player) { if (_queueCooldowns.containsKey(player.getName())) { long elapsed = System.currentTimeMillis() - _queueCooldowns.get(player.getName()); return elapsed < QUEUE_COOLDOWN; } return false; } private void addCooldown(Player player) { _queueCooldowns.put(player.getName(), System.currentTimeMillis()); } private void updateCooldowns() { for (Iterator playerIterator = _queueCooldowns.keySet().iterator(); playerIterator.hasNext();) { Player player = Bukkit.getPlayer(playerIterator.next()); if (player == null || !isOnCooldown(player)) { playerIterator.remove(); } } } /** * @param serverType - the type of server that should be fetched * @return the best server that a new player should join according to a {@code serverType} constraint. */ public ServerInfo getBestServer(Player player, String serverKey) { Collection serverList = getServerList(serverKey); if (serverList == null) return null; List servers = new ArrayList(serverList); servers = fetchOpenServers(player, servers, servers.size()); // Removes all full servers from list Collections.sort(servers, new ServerCountSorter()); int count = Math.min(servers.size(), TOP_SERVERS); if (count > 0) { ServerInfo largestServer = servers.get(0); if (largestServer.getAvailableSlots() >= MIN_SLOTS_REQUIRED || largestServer.MaxPlayers > 40) { return largestServer; } else { return servers.get(random.nextInt(count)); } } return null; } public List fetchOpenServers(Player player, List servers, int count) { List results = new ArrayList(); int requiredSlots = (servers.size() > 0) ? getRequiredSlots(player, servers.get(0).ServerType) : 0; for (ServerInfo server : servers) { if (isInProgress(server)) continue; if (results.size() >= count) break; if (server.getAvailableSlots() > requiredSlots) { results.add(server); } } return results; } private boolean isInProgress(ServerInfo serverInfo) { return serverInfo.MOTD.contains("Progress") || serverInfo.MOTD.contains("Restarting"); } public void listServerNpcs(Player caller) { UtilPlayer.message(caller, F.main(getName(), "Listing Server Npcs:")); for (String serverNpc : _serverKeyInfoMap.keySet()) { UtilPlayer.message(caller, F.main(getName(), C.cYellow + serverNpc)); } } public void listServers(Player caller, String serverNpcName) { UtilPlayer.message(caller, F.main(getName(), "Listing Servers for '" + serverNpcName + "':")); for (ServerInfo serverNpc : _serverKeyInfoMap.get(serverNpcName)) { UtilPlayer.message(caller, F.main(getName(), C.cYellow + serverNpc.Name + C.cWhite + " - " + serverNpc.MOTD + " " + serverNpc.CurrentPlayers + "/" + serverNpc.MaxPlayers)); } } public void listOfflineServers(Player caller) { UtilPlayer.message(caller, F.main(getName(), "Listing Offline Servers:")); for (ServerInfo serverNpc : _serverInfoMap.values()) { if (serverNpc.MOTD.equalsIgnoreCase(ChatColor.DARK_RED + "OFFLINE")) { UtilPlayer.message(caller, F.main(getName(), C.cYellow + serverNpc.Name + C.cWhite + " - " + F.time(UtilTime.convertString(System.currentTimeMillis() - _serverUpdate.get(serverNpc.Name), 0, TimeUnit.FIT)))); } } } public void loadServers() { _serverInfoMap.clear(); _serverUpdate.clear(); for (String npcName : _serverKeyInfoMap.keySet()) { _serverKeyInfoMap.get(npcName).clear(); } Region region = getPlugin().getConfig().getBoolean("serverstatus.us") ? Region.US : Region.EU; try { for (ServerGroup serverGroup : mineplex.serverdata.servers.ServerManager.getServerRepository(region).getServerGroups(null)) { addServerGroup(serverGroup); if (!serverGroup.getServerNpcName().isEmpty()) addServerNpc(serverGroup); if (!serverGroup.getPortalBottomCornerLocation().isEmpty() && !serverGroup.getPortalTopCornerLocation().isEmpty()) { Vector bottomVector = ParseVector(serverGroup.getPortalBottomCornerLocation()); Vector topVector = ParseVector(serverGroup.getPortalTopCornerLocation()); int blocks = 0; while (blocks < 10 && (bottomVector.getBlockX() != topVector.getBlockX() || bottomVector.getBlockZ() != topVector.getBlockZ())) { _portalToServerKey.put(new Vector(bottomVector.getBlockX(), bottomVector.getBlockY(), bottomVector.getBlockZ()), serverGroup.getPrefix()); if (bottomVector.getBlockX() != topVector.getBlockX()) { bottomVector.add(new Vector(-(bottomVector.getBlockX() - topVector.getBlockX()) / Math.abs(bottomVector.getBlockX() - topVector.getBlockX()), 0, 0)); } else if (bottomVector.getBlockZ() != topVector.getBlockZ()) { bottomVector.add(new Vector(0, 0, -(bottomVector.getBlockZ() - topVector.getBlockZ()) / Math.abs(bottomVector.getBlockZ() - topVector.getBlockZ()))); } blocks++; } _portalToServerKey.put(bottomVector, serverGroup.getPrefix()); } } } catch (Exception e) { System.out.println("ServerManager - Error parsing servergroups : " + e.getMessage()); } // AddServerNpc("Event Servers", "EVENT"); // AddServerNpc("Mineplex Player Servers", "MPS"); ServerGroup eventGroup = new ServerGroup("Event", "Event Servers", "EVENT"); ServerGroup mpsGroup = new ServerGroup("MPS", "Mineplex Player Servers", "MPS"); addServerNpc(eventGroup); addServerGroup(eventGroup); addServerNpc(mpsGroup); addServerGroup(mpsGroup); } public int getRequiredSlots(Player player, String serverType) { int slots = 0; Party party = _partyManager.GetParty(player); if (party != null) { if (player.getName().equals(party.GetLeader())) { for (String name : party.GetPlayers()) { Player partyPlayer = UtilPlayer.searchExact(name); if (partyPlayer == null) continue; if (_clientManager.Get(partyPlayer).GetRank().has(Rank.ULTRA) || _donationManager.Get(partyPlayer.getName()).OwnsUnknownPackage(serverType + " ULTRA")) continue; slots++; } } } else { if (!_clientManager.Get(player).GetRank().has(Rank.ULTRA) && !_donationManager.Get(player.getName()).OwnsUnknownPackage(serverType + " ULTRA")) slots++; } return slots; } public ServerNpcShop getMixedArcadeShop() { return _serverNpcShopMap.get("Mixed Arcade"); } public ServerNpcShop getServerNPCShopByName(String name) { for(String shop : _serverNpcShopMap.keySet()) { if(shop.equalsIgnoreCase(name)) { return _serverNpcShopMap.get(shop); } } return null; } public ServerNpcShop getSuperSmashMobsShop() { return _serverNpcShopMap.get("Super Smash Mobs"); } @SuppressWarnings("rawtypes") public ShopBase getDominateShop() { return _serverNpcShopMap.get("Dominate"); } public ServerNpcShop getCtfShop() { return _serverNpcShopMap.get("Capture the Flag"); } public ServerNpcShop getBridgesShop() { return _serverNpcShopMap.get("The Bridges"); } public ServerNpcShop getSurvivalGamesShop() { return _serverNpcShopMap.get("Survival Games"); } public ServerNpcShop getBlockHuntShop() { return _serverNpcShopMap.get("Block Hunt"); } public ServerNpcShop getBetaShop() { return _serverNpcShopMap.get("Beta Monster Maze"); } public ServerNpcShop getUHCShop() { return _serverNpcShopMap.get("Ultra Hardcore"); } public ServerNpcShop getSKYShop() { return _serverNpcShopMap.get("Skywars"); } public ServerNpcShop getPlayerGamesShop() { return _serverNpcShopMap.get("Mineplex Player Servers"); } public ServerNpcShop getShop(String name) { return _serverNpcShopMap.get(name); } private Vector ParseVector(String vectorString) { Vector vector = new Vector(); String [] parts = vectorString.trim().split(" "); vector.setX(Double.parseDouble(parts[0])); vector.setY(Double.parseDouble(parts[1])); vector.setZ(Double.parseDouble(parts[2])); return vector; } public ServerStatusManager getStatusManager() { return _statusManager; } public ShopBase getCastleSiegeShop() { return _serverNpcShopMap.get("Castle Siege"); } public HubManager getHubManager() { return _hubManager; } public ShopBase getDrawMyThingShop() { return _serverNpcShopMap.get("Draw My Thing"); } public ShopBase getTeamDeathmatchShop() { return _serverNpcShopMap.get("Team Deathmatch"); } public ShopBase getMinestrikeShop() { return _serverNpcShopMap.get("Mine-Strike"); } public ShopBase getWizardShop() { return _serverNpcShopMap.get("Wizards"); } public int getGroupTagPlayerCount(String tag) { if (_serverPlayerCounts.containsKey(tag)) return _serverPlayerCounts.get(tag); else return 0; } public ShopBase getBuildShop() { return _serverNpcShopMap.get("Master Builders"); } public ClansServerShop getClansShop() { return _clansShop; } public ShopBase getTypeWarsShop() { return _serverNpcShopMap.get("Type Wars"); } }