package org.dynmap.forge_1_10_2; import java.io.File; import java.io.InputStream; import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.PriorityQueue; import java.util.Set; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.regex.Pattern; import net.minecraft.block.Block; import net.minecraft.block.material.Material; import net.minecraft.block.properties.IProperty; import net.minecraft.block.state.IBlockState; import net.minecraft.command.CommandBase; import net.minecraft.command.CommandException; import net.minecraft.command.CommandHandler; import net.minecraft.command.ICommandManager; import net.minecraft.command.ICommandSender; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.Item; import net.minecraft.network.NetHandlerPlayServer; import net.minecraft.network.NetworkManager; import net.minecraft.network.play.server.SPacketTitle; import net.minecraft.server.MinecraftServer; import net.minecraft.server.management.UserListBans; import net.minecraft.server.management.UserListIPBans; import net.minecraft.util.ResourceLocation; import net.minecraft.util.SoundCategory; import net.minecraft.util.SoundEvent; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.ChunkPos; import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.TextComponentString; import net.minecraft.world.IWorldEventListener; import net.minecraft.world.World; import net.minecraft.world.WorldServer; import net.minecraft.world.biome.Biome; import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.storage.ExtendedBlockStorage; import net.minecraftforge.common.ForgeChunkManager; import net.minecraftforge.common.ForgeChunkManager.Ticket; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.ServerChatEvent; import net.minecraftforge.event.terraingen.PopulateChunkEvent; import net.minecraftforge.event.world.ChunkEvent; import net.minecraftforge.event.world.WorldEvent; import net.minecraftforge.fml.common.Loader; import net.minecraftforge.fml.common.ModContainer; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerChangedDimensionEvent; import net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerLoggedInEvent; import net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerLoggedOutEvent; import net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerRespawnEvent; import net.minecraftforge.fml.common.gameevent.TickEvent; import org.apache.commons.codec.Charsets; import org.apache.commons.codec.binary.Base64; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dynmap.ConfigurationNode; import org.dynmap.DynmapChunk; import org.dynmap.DynmapCommonAPIListener; import org.dynmap.DynmapCore; import org.dynmap.DynmapLocation; import org.dynmap.DynmapWorld; import org.dynmap.Log; import org.dynmap.MapManager; import org.dynmap.PlayerList; import org.dynmap.common.BiomeMap; import org.dynmap.common.DynmapCommandSender; import org.dynmap.common.DynmapPlayer; import org.dynmap.common.DynmapServerInterface; import org.dynmap.common.DynmapListenerManager.EventType; import org.dynmap.debug.Debug; import org.dynmap.forge_1_10_2.DmapCommand; import org.dynmap.forge_1_10_2.DmarkerCommand; import org.dynmap.forge_1_10_2.DynmapCommand; import org.dynmap.forge_1_10_2.DynmapMod; import org.dynmap.forge_1_10_2.permissions.FilePermissions; import org.dynmap.forge_1_10_2.permissions.OpPermissions; import org.dynmap.forge_1_10_2.permissions.PermissionProvider; import org.dynmap.forge_1_10_2.ForgeWorld; import org.dynmap.forge_1_10_2.DynmapPlugin.WorldUpdateTracker; import org.dynmap.permissions.PermissionsHandler; import org.dynmap.renderer.DynmapBlockState; import org.dynmap.utils.DynIntHashMap; import org.dynmap.utils.DynmapLogger; import org.dynmap.utils.MapChunkCache; import org.dynmap.utils.VisibilityLimit; import com.google.common.collect.Iterables; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonParseException; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; public class DynmapPlugin { private DynmapCore core; private PermissionProvider permissions; private boolean core_enabled; public SnapshotCache sscache; public PlayerList playerList; private MapManager mapManager; private net.minecraft.server.MinecraftServer server; public static DynmapPlugin plugin; private ChatHandler chathandler; private HashMap sortWeights = new HashMap(); // Drop world load ticket after 30 seconds private long worldIdleTimeoutNS = 30 * 1000000000L; private HashMap worlds = new HashMap(); private World last_world; private ForgeWorld last_fworld; private Map players = new HashMap(); //TODO private ForgeMetrics metrics; private HashSet modsused = new HashSet(); private ForgeServer fserver = new ForgeServer(); private boolean tickregistered = false; // TPS calculator private double tps; private long lasttick; private long avgticklen; // Per tick limit, in nsec private long perTickLimit = (50000000); // 50 ms private boolean isMCPC = false; private boolean useSaveFolder = true; private Field displayName = null; // MCPC+ display name private static final int SIGNPOST_ID = 63; private static final int WALLSIGN_ID = 68; private static final String[] TRIGGER_DEFAULTS = { "blockupdate", "chunkpopulate", "chunkgenerate" }; private static final Pattern patternControlCode = Pattern.compile("(?i)\\u00A7[0-9A-FK-OR]"); public static class BlockUpdateRec { World w; String wid; int x, y, z; } ConcurrentLinkedQueue blockupdatequeue = new ConcurrentLinkedQueue(); public static DynmapBlockState[] stateByID; /** * Initialize block states (org.dynmap.blockstate.DynmapBlockState) */ public void initializeBlockStates() { stateByID = new DynmapBlockState[512*16]; // Simple meta+id map Arrays.fill(stateByID, DynmapBlockState.AIR); // Default to air for (Block b : Block.REGISTRY) { if (b == null) continue; int i = Block.getIdFromBlock(b); if (i >= (stateByID.length >> 4)) { int plen = stateByID.length; stateByID = Arrays.copyOf(stateByID, (i+1) << 4); Arrays.fill(stateByID, plen, stateByID.length, DynmapBlockState.AIR); } ResourceLocation ui = null; try { ui = Block.REGISTRY.getNameForObject(b); } catch (Exception x) { Log.warning("Exception caught reading unique ID for block " + i); } if (ui != null) { String bn = ui.getResourceDomain() + ":" + ui.getResourcePath(); // Only do defined names, and not "air" if (!bn.equals(DynmapBlockState.AIR_BLOCK)) { DynmapBlockState basebs = null; for (int m = 0; m < 16; m++) { IBlockState blkstate = null; try { blkstate = b.getStateFromMeta(m); } catch (Exception x) { // Invalid meta } Material mat = Material.AIR; String statename = "meta=" + m; if (blkstate != null) { mat = blkstate.getMaterial(); String pstate = null; for(Entry, Comparable> p : blkstate.getProperties().entrySet()) { if (pstate == null) pstate = ""; else pstate += ","; pstate += p.getKey().getName() + "=" + p.getValue().toString(); } if (pstate != null) statename = pstate; } DynmapBlockState bs = new DynmapBlockState(basebs, m, bn, statename, mat.toString(), i); if (basebs == null) basebs = bs; stateByID[(i << 4) + m] = bs; if (mat.isSolid()) { bs.setSolid(); } if (mat == Material.AIR) { bs.setAir(); } if (mat == Material.WOOD) { bs.setLog(); } if (mat == Material.LEAVES) { bs.setLeaves(); } } } } } //for (int gidx = 0; gidx < DynmapBlockState.getGlobalIndexMax(); gidx++) { // DynmapBlockState bs = DynmapBlockState.getStateByGlobalIndex(gidx); // Log.verboseinfo(gidx + ":" + bs.toString() + ", gidx=" + bs.globalStateIndex + ", sidx=" + bs.stateIndex); //} } public static final int getBlockID(World w, int x, int y, int z) { // Block.getIdFromBlock(w.getBlockType(x,y,z)) return Block.getIdFromBlock(w.getBlockState(new BlockPos(x, y, z)).getBlock()); } public static final Block getBlockByID(int id) { return Block.getBlockById(id); } public static final Item getItemByID(int id) { return Item.getItemById(id); } public static final String getBlockUnlocalizedName(Block b) { String s = b.getUnlocalizedName(); if (s.startsWith("tile.")) { s = s.substring(5); } return s; } private static Biome[] biomelist = null; public static final Biome[] getBiomeList() { if (biomelist == null) { biomelist = new Biome[256]; for (int i = 0; i < biomelist.length; i++) { biomelist[i] = Biome.getBiome(i); } } return biomelist; } public static final NetworkManager getNetworkManager(NetHandlerPlayServer nh) { return nh.netManager; } private ForgePlayer getOrAddPlayer(EntityPlayer p) { String name = p.getCommandSenderEntity().getName(); ForgePlayer fp = players.get(name); if(fp != null) { fp.player = p; } else { fp = new ForgePlayer(p); players.put(name, fp); } return fp; } private static class TaskRecord implements Comparable { private long ticktorun; private long id; private FutureTask future; @Override public int compareTo(Object o) { TaskRecord tr = (TaskRecord)o; if (this.ticktorun < tr.ticktorun) { return -1; } else if (this.ticktorun > tr.ticktorun) { return 1; } else if (this.id < tr.id) { return -1; } else if (this.id > tr.id) { return 1; } else { return 0; } } } private class ChatMessage { String message; EntityPlayer sender; } private ConcurrentLinkedQueue msgqueue = new ConcurrentLinkedQueue(); public class ChatHandler { @SubscribeEvent public void handleChat(ServerChatEvent event) { String msg = event.getMessage(); if(!msg.startsWith("/")) { ChatMessage cm = new ChatMessage(); cm.message = msg; cm.sender = event.getPlayer(); msgqueue.add(cm); } } } private static class WorldBusyRecord { long last_ts; Ticket ticket; } private static HashMap busy_worlds = new HashMap(); private void setBusy(World w) { setBusy(w, null); } static void setBusy(World w, Ticket t) { if(w == null) return; if (!DynmapMod.useforcedchunks) return; WorldBusyRecord wbr = busy_worlds.get(w.provider.getDimension()); if(wbr == null) { // Not busy, make ticket and keep spawn loaded Debug.debug("World " + w.getWorldInfo().getWorldName() + "/"+ w.provider.getDimensionType().getName() + " is busy"); wbr = new WorldBusyRecord(); if(t != null) wbr.ticket = t; else wbr.ticket = ForgeChunkManager.requestTicket(DynmapMod.instance, w, ForgeChunkManager.Type.NORMAL); if(wbr.ticket != null) { BlockPos cc = w.getSpawnPoint(); ChunkPos ccip = new ChunkPos(cc.getX() >> 4, cc.getZ() >> 4); ForgeChunkManager.forceChunk(wbr.ticket, ccip); busy_worlds.put(w.provider.getDimension(), wbr); // Add to busy list } } wbr.last_ts = System.nanoTime(); } private void doIdleOutOfWorlds() { if (!DynmapMod.useforcedchunks) return; long ts = System.nanoTime() - worldIdleTimeoutNS; for(Iterator itr = busy_worlds.values().iterator(); itr.hasNext();) { WorldBusyRecord wbr = itr.next(); if(wbr.last_ts < ts) { World w = wbr.ticket.world; Debug.debug("World " + w.getWorldInfo().getWorldName() + "/" + wbr.ticket.world.provider.getDimensionType().getName() + " is idle"); if (wbr.ticket != null) ForgeChunkManager.releaseTicket(wbr.ticket); // Release hold on world itr.remove(); } } } public static class OurLog implements DynmapLogger { Logger log; public static final String DM = "[Dynmap] "; OurLog() { log = LogManager.getLogger("Dynmap"); } @Override public void info(String s) { log.info(DM + s); } @Override public void severe(Throwable t) { log.fatal(t); } @Override public void severe(String s) { log.fatal(DM + s); } @Override public void severe(String s, Throwable t) { log.fatal(DM + s, t); } @Override public void verboseinfo(String s) { log.info(DM + s); } @Override public void warning(String s) { log.warn(DM + s); } @Override public void warning(String s, Throwable t) { log.warn(DM + s, t); } } public DynmapPlugin(MinecraftServer srv) { plugin = this; this.server = srv; displayName = null; try { displayName = EntityPlayerMP.class.getField("displayName"); } catch (SecurityException e) { } catch (NoSuchFieldException e) { } } public boolean isOp(String player) { player = player.toLowerCase(); return (server.getPlayerList().getOppedPlayers().getGameProfileFromName(player) != null) || (server.isSinglePlayer() && player.equalsIgnoreCase(server.getServerOwner())); } private boolean hasPerm(ICommandSender sender, String permission) { PermissionsHandler ph = PermissionsHandler.getHandler(); if(ph != null) { if((sender instanceof EntityPlayer) && ph.hasPermission(sender.getCommandSenderEntity().getName(), permission)) { return true; } } return permissions.has(sender, permission); } private boolean hasPermNode(ICommandSender sender, String permission) { PermissionsHandler ph = PermissionsHandler.getHandler(); if(ph != null) { if((sender instanceof EntityPlayer) && ph.hasPermissionNode(sender.getCommandSenderEntity().getName(), permission)) { return true; } } return permissions.hasPermissionNode(sender, permission); } private Set hasOfflinePermissions(String player, Set perms) { Set rslt = null; PermissionsHandler ph = PermissionsHandler.getHandler(); if(ph != null) { rslt = ph.hasOfflinePermissions(player, perms); } Set rslt2 = hasOfflinePermissions(player, perms); if((rslt != null) && (rslt2 != null)) { Set newrslt = new HashSet(rslt); newrslt.addAll(rslt2); rslt = newrslt; } else if(rslt2 != null) { rslt = rslt2; } return rslt; } private boolean hasOfflinePermission(String player, String perm) { PermissionsHandler ph = PermissionsHandler.getHandler(); if(ph != null) { if(ph.hasOfflinePermission(player, perm)) { return true; } } return permissions.hasOfflinePermission(player, perm); } /** * Server access abstraction class */ public class ForgeServer extends DynmapServerInterface { /* Server thread scheduler */ private Object schedlock = new Object(); private long cur_tick; private long next_id; private long cur_tick_starttime; private PriorityQueue runqueue = new PriorityQueue(); public ForgeServer() { } @Override public int getBlockIDAt(String wname, int x, int y, int z) { DynmapWorld dw = this.getWorldByName(wname); if (dw != null) { World w = ((ForgeWorld)dw).getWorld(); if((w != null) && w.isBlockLoaded(new BlockPos(x, y, z))) { return getBlockID(w, x, y, z); } } return -1; } @Override public int isSignAt(String wname, int x, int y, int z) { int blkid = getBlockIDAt(wname, x, y, z); if (blkid == -1) return -1; if((blkid == WALLSIGN_ID) || (blkid == SIGNPOST_ID)) { return 1; } else { return 0; } } @Override public void scheduleServerTask(Runnable run, long delay) { TaskRecord tr = new TaskRecord(); tr.future = new FutureTask(run, null); /* Add task record to queue */ synchronized (schedlock) { tr.id = next_id++; tr.ticktorun = cur_tick + delay; runqueue.add(tr); } } @Override public DynmapPlayer[] getOnlinePlayers() { if(server.getPlayerList() == null) return new DynmapPlayer[0]; List playlist = server.getPlayerList().getPlayerList(); int pcnt = playlist.size(); DynmapPlayer[] dplay = new DynmapPlayer[pcnt]; for (int i = 0; i < pcnt; i++) { EntityPlayer p = (EntityPlayer)playlist.get(i); dplay[i] = getOrAddPlayer(p); } return dplay; } @Override public void reload() { plugin.onDisable(); plugin.onEnable(); plugin.onStart(); } @Override public DynmapPlayer getPlayer(String name) { List players = server.getPlayerList().getPlayerList(); for (Object o : players) { EntityPlayer p = (EntityPlayer)o; if (p.getCommandSenderEntity().getName().equalsIgnoreCase(name)) { return getOrAddPlayer(p); } } return null; } @Override public Set getIPBans() { UserListIPBans bl = server.getPlayerList().getBannedIPs(); Set ips = new HashSet(); for (String s : bl.getKeys()) { ips.add(s); } return ips; } @Override public Future callSyncMethod(Callable task) { return callSyncMethod(task, 0); } public Future callSyncMethod(Callable task, long delay) { TaskRecord tr = new TaskRecord(); FutureTask ft = new FutureTask(task); tr.future = ft; /* Add task record to queue */ synchronized (schedlock) { tr.id = next_id++; tr.ticktorun = cur_tick + delay; runqueue.add(tr); } return ft; } @Override public String getServerName() { String sn; if (server.isSinglePlayer()) sn = "Integrated"; else sn = server.getServerHostname(); if(sn == null) sn = "Unknown Server"; return sn; } @Override public boolean isPlayerBanned(String pid) { UserListBans bl = server.getPlayerList().getBannedPlayers(); return bl.isBanned(new GameProfile(null, pid)); } @Override public String stripChatColor(String s) { return patternControlCode.matcher(s).replaceAll(""); } private Set registered = new HashSet(); @Override public boolean requestEventNotification(EventType type) { if (registered.contains(type)) { return true; } switch (type) { case WORLD_LOAD: case WORLD_UNLOAD: /* Already called for normal world activation/deactivation */ break; case WORLD_SPAWN_CHANGE: /*TODO pm.registerEvents(new Listener() { @EventHandler(priority=EventPriority.MONITOR) public void onSpawnChange(SpawnChangeEvent evt) { DynmapWorld w = new BukkitWorld(evt.getWorld()); core.listenerManager.processWorldEvent(EventType.WORLD_SPAWN_CHANGE, w); } }, DynmapPlugin.this); */ break; case PLAYER_JOIN: case PLAYER_QUIT: /* Already handled */ break; case PLAYER_BED_LEAVE: /*TODO pm.registerEvents(new Listener() { @EventHandler(priority=EventPriority.MONITOR) public void onPlayerBedLeave(PlayerBedLeaveEvent evt) { DynmapPlayer p = new BukkitPlayer(evt.getPlayer()); core.listenerManager.processPlayerEvent(EventType.PLAYER_BED_LEAVE, p); } }, DynmapPlugin.this); */ break; case PLAYER_CHAT: if (chathandler == null) { chathandler = new ChatHandler(); MinecraftForge.EVENT_BUS.register(chathandler); } break; case BLOCK_BREAK: /*TODO pm.registerEvents(new Listener() { @EventHandler(priority=EventPriority.MONITOR) public void onBlockBreak(BlockBreakEvent evt) { if(evt.isCancelled()) return; Block b = evt.getBlock(); if(b == null) return; Location l = b.getLocation(); core.listenerManager.processBlockEvent(EventType.BLOCK_BREAK, b.getType().getId(), BukkitWorld.normalizeWorldName(l.getWorld().getName()), l.getBlockX(), l.getBlockY(), l.getBlockZ()); } }, DynmapPlugin.this); */ break; case SIGN_CHANGE: /*TODO pm.registerEvents(new Listener() { @EventHandler(priority=EventPriority.MONITOR) public void onSignChange(SignChangeEvent evt) { if(evt.isCancelled()) return; Block b = evt.getBlock(); Location l = b.getLocation(); String[] lines = evt.getLines(); DynmapPlayer dp = null; Player p = evt.getPlayer(); if(p != null) dp = new BukkitPlayer(p); core.listenerManager.processSignChangeEvent(EventType.SIGN_CHANGE, b.getType().getId(), BukkitWorld.normalizeWorldName(l.getWorld().getName()), l.getBlockX(), l.getBlockY(), l.getBlockZ(), lines, dp); } }, DynmapPlugin.this); */ break; default: Log.severe("Unhandled event type: " + type); return false; } registered.add(type); return true; } @Override public boolean sendWebChatEvent(String source, String name, String msg) { return DynmapCommonAPIListener.fireWebChatEvent(source, name, msg); } @Override public void broadcastMessage(String msg) { ITextComponent component = new TextComponentString(msg); server.getPlayerList().sendChatMsg(component); Log.info(stripChatColor(msg)); } @Override public String[] getBiomeIDs() { BiomeMap[] b = BiomeMap.values(); String[] bname = new String[b.length]; for (int i = 0; i < bname.length; i++) { bname[i] = b[i].toString(); } return bname; } @Override public double getCacheHitRate() { if(sscache != null) return sscache.getHitRate(); return 0.0; } @Override public void resetCacheStats() { if(sscache != null) sscache.resetStats(); } @Override public DynmapWorld getWorldByName(String wname) { return DynmapPlugin.this.getWorldByName(wname); } @Override public DynmapPlayer getOfflinePlayer(String name) { /* OfflinePlayer op = getServer().getOfflinePlayer(name); if(op != null) { return new BukkitPlayer(op); } */ return null; } @Override public Set checkPlayerPermissions(String player, Set perms) { net.minecraft.server.management.PlayerList scm = server.getPlayerList(); if (scm == null) return Collections.emptySet(); UserListBans bl = scm.getBannedPlayers(); if (bl == null) return Collections.emptySet(); if(bl.isBanned(new GameProfile(null, player))) { return Collections.emptySet(); } Set rslt = hasOfflinePermissions(player, perms); if (rslt == null) { rslt = new HashSet(); if(plugin.isOp(player)) { rslt.addAll(perms); } } return rslt; } @Override public boolean checkPlayerPermission(String player, String perm) { net.minecraft.server.management.PlayerList scm = server.getPlayerList(); if (scm == null) return false; UserListBans bl = scm.getBannedPlayers(); if (bl == null) return false; if(bl.isBanned(new GameProfile(null, player))) { return false; } return hasOfflinePermission(player, perm); } /** * Render processor helper - used by code running on render threads to request chunk snapshot cache from server/sync thread */ @Override public MapChunkCache createMapChunkCache(DynmapWorld w, List chunks, boolean blockdata, boolean highesty, boolean biome, boolean rawbiome) { ForgeMapChunkCache c = (ForgeMapChunkCache) w.getChunkCache(chunks); if(c == null) { return null; } if (w.visibility_limits != null) { for (VisibilityLimit limit: w.visibility_limits) { c.setVisibleRange(limit); } c.setHiddenFillStyle(w.hiddenchunkstyle); } if (w.hidden_limits != null) { for (VisibilityLimit limit: w.hidden_limits) { c.setHiddenRange(limit); } c.setHiddenFillStyle(w.hiddenchunkstyle); } if (c.setChunkDataTypes(blockdata, biome, highesty, rawbiome) == false) { Log.severe("CraftBukkit build does not support biome APIs"); } if (chunks.size() == 0) /* No chunks to get? */ { c.loadChunks(0); return c; } //Now handle any chunks in server thread that are already loaded (on server thread) final ForgeMapChunkCache cc = c; Future f = this.callSyncMethod(new Callable() { public Boolean call() throws Exception { // Update busy state on world ForgeWorld fw = (ForgeWorld)cc.getWorld(); setBusy(fw.getWorld()); cc.getLoadedChunks(); return true; } }, 0); try { f.get(); } catch (CancellationException cx) { return null; } catch (ExecutionException xx) { Log.severe("Exception while loading chunks", xx.getCause()); return null; } catch (Exception ix) { Log.severe(ix); return null; } if(w.isLoaded() == false) { return null; } // Now, do rest of chunk reading from calling thread c.readChunks(chunks.size()); return c; } @Override public int getMaxPlayers() { return server.getMaxPlayers(); } @Override public int getCurrentPlayers() { return server.getPlayerList().getCurrentPlayerCount(); } @SubscribeEvent public void tickEvent(TickEvent.ServerTickEvent event) { if (event.phase == TickEvent.Phase.START) { return; } cur_tick_starttime = System.nanoTime(); long elapsed = cur_tick_starttime - lasttick; lasttick = cur_tick_starttime; avgticklen = ((avgticklen * 99) / 100) + (elapsed / 100); tps = (double)1E9 / (double)avgticklen; // Tick core if (core != null) { core.serverTick(tps); } boolean done = false; TaskRecord tr = null; while(!blockupdatequeue.isEmpty()) { BlockUpdateRec r = blockupdatequeue.remove(); int id = 0; int meta = 0; if((r.w != null) && (r.w.getChunkProvider().getLoadedChunk(r.x >> 4, r.z >> 4) != null)) { id = getBlockID(r.w, r.x, r.y, r.z); IBlockState bs = r.w.getBlockState(new BlockPos(r.x, r.y, r.z)); meta = bs.getBlock().getMetaFromState(bs); } if(!org.dynmap.hdmap.HDBlockModels.isChangeIgnoredBlock(stateByID[(id << 4) + meta])) { if(onblockchange_with_id) mapManager.touch(r.wid, r.x, r.y, r.z, "blockchange[" + id + ":" + meta + "]"); else mapManager.touch(r.wid, r.x, r.y, r.z, "blockchange"); } } long now; synchronized(schedlock) { cur_tick++; now = System.nanoTime(); tr = runqueue.peek(); /* Nothing due to run */ if((tr == null) || (tr.ticktorun > cur_tick) || ((now - cur_tick_starttime) > perTickLimit)) { done = true; } else { tr = runqueue.poll(); } } while (!done) { tr.future.run(); synchronized(schedlock) { tr = runqueue.peek(); now = System.nanoTime(); /* Nothing due to run */ if((tr == null) || (tr.ticktorun > cur_tick) || ((now - cur_tick_starttime) > perTickLimit)) { done = true; } else { tr = runqueue.poll(); } } } while(!msgqueue.isEmpty()) { ChatMessage cm = msgqueue.poll(); DynmapPlayer dp = null; if(cm.sender != null) dp = getOrAddPlayer(cm.sender); else dp = new ForgePlayer(null); core.listenerManager.processChatEvent(EventType.PLAYER_CHAT, dp, cm.message); } /* Check for idle worlds */ if((cur_tick % 20) == 0) { doIdleOutOfWorlds(); } } @Override public boolean isModLoaded(String name) { boolean loaded = Loader.isModLoaded(name); if (loaded) { modsused.add(name); } return loaded; } @Override public String getModVersion(String name) { Map list = Loader.instance().getIndexedModList(); ModContainer mod = list.get(name); // Try case sensitive lookup if (mod == null) { for (Entry ent : list.entrySet()) { if (ent.getKey().equalsIgnoreCase(name)) { mod = ent.getValue(); break; } } } if (mod == null) return null; return mod.getVersion(); } @Override public double getServerTPS() { return tps; } @Override public String getServerIP() { if (server.isSinglePlayer()) return "0.0.0.0"; else return server.getServerHostname(); } @Override public File getModContainerFile(String name) { ModContainer mod = Loader.instance().getIndexedModList().get(name); if (mod == null) return null; return mod.getSource(); } @Override public List getModList() { return new ArrayList(Loader.instance().getIndexedModList().keySet()); } @Override public Map getBlockIDMap() { Map map = new HashMap(); for (int i = 0; i < 4096; i++) { Block b = getBlockByID(i); if (b == null) continue; ResourceLocation ui = Block.REGISTRY.getNameForObject(b); if (ui != null) { map.put(i, ui.getResourceDomain() + ":" + ui.getResourcePath()); } } return map; } @Override public InputStream openResource(String modid, String rname) { if (modid != null) { ModContainer mc = Loader.instance().getIndexedModList().get(modid); Object mod = (mc != null) ? mc.getMod() : null; if (mod != null) { InputStream is = mod.getClass().getClassLoader().getResourceAsStream(rname); if (is != null) { return is; } } } List mcl = Loader.instance().getModList(); for (ModContainer mc : mcl) { Object mod = mc.getMod(); if (mod == null) continue; InputStream is = mod.getClass().getClassLoader().getResourceAsStream(rname); if (is != null) { return is; } } return null; } /** * Get block unique ID map (module:blockid) */ @Override public Map getBlockUniqueIDMap() { HashMap map = new HashMap(); for (int i = 0; i < 4096; i++) { Block b = getBlockByID(i); if (b == null) continue; ResourceLocation ui = null; try { ui = Block.REGISTRY.getNameForObject(b); } catch (Exception x) { Log.warning("Exception caught reading unique ID for block " + i); } if (ui != null) { map.put(ui.getResourceDomain() + ":" + ui.getResourcePath(), i); } } return map; } /** * Get item unique ID map (module:itemid) */ @Override public Map getItemUniqueIDMap() { HashMap map = new HashMap(); for (int i = 0; i < 32000; i++) { Item itm = getItemByID(i); if (itm == null) continue; ResourceLocation ui = null; try { ui = Item.REGISTRY.getNameForObject(itm); } catch (Exception x) { Log.warning("Exception caught reading unique ID for item " + i); } if (ui != null) { map.put(ui.getResourceDomain() + ":" + ui.getResourcePath(), i - 256); } } return map; } } private static final Gson gson = new GsonBuilder().create(); public class TexturesPayload { public long timestamp; public String profileId; public String profileName; public boolean isPublic; public Map textures; } public class ProfileTexture { public String url; } /** * Player access abstraction class */ public class ForgePlayer extends ForgeCommandSender implements DynmapPlayer { private EntityPlayer player; private final String skinurl; private final UUID uuid; public ForgePlayer(EntityPlayer p) { player = p; String url = null; if (player != null) { uuid = player.getUniqueID(); GameProfile prof = player.getGameProfile(); if (prof != null) { Property textureProperty = Iterables.getFirst(prof.getProperties().get("textures"), null); if (textureProperty != null) { TexturesPayload result = null; try { String json = new String(Base64.decodeBase64(textureProperty.getValue()), Charsets.UTF_8); result = gson.fromJson(json, TexturesPayload.class); } catch (JsonParseException e) { } if ((result != null) && (result.textures != null) && (result.textures.containsKey("SKIN"))) { url = result.textures.get("SKIN").url; } } } } else { uuid = null; } skinurl = url; } @Override public boolean isConnected() { return true; } @Override public String getName() { if(player != null) return player.getCommandSenderEntity().getName(); else return "[Server]"; } @Override public String getDisplayName() { if(player != null) { if (displayName != null) { try { return (String) displayName.get(player); } catch (IllegalArgumentException e) { } catch (IllegalAccessException e) { } } return player.getDisplayName().getUnformattedText(); } else return "[Server]"; } @Override public boolean isOnline() { return true; } @Override public DynmapLocation getLocation() { if (player == null) { return null; } return toLoc(player.worldObj, player.posX, player.posY, player.posZ); } @Override public String getWorld() { if (player == null) { return null; } if (player.worldObj != null) { return DynmapPlugin.this.getWorld(player.worldObj).getName(); } return null; } @Override public InetSocketAddress getAddress() { if((player != null) && (player instanceof EntityPlayerMP)) { NetHandlerPlayServer nsh = ((EntityPlayerMP)player).connection; if((nsh != null) && (getNetworkManager(nsh) != null)) { SocketAddress sa = getNetworkManager(nsh).getRemoteAddress(); if(sa instanceof InetSocketAddress) { return (InetSocketAddress)sa; } } } return null; } @Override public boolean isSneaking() { if (player != null) { return player.isSneaking(); } return false; } @Override public double getHealth() { if (player != null) { double h = player.getHealth(); if(h > 20) h = 20; return h; // Scale to 20 range } else { return 0; } } @Override public int getArmorPoints() { if (player != null) { return player.getTotalArmorValue(); } else { return 0; } } @Override public DynmapLocation getBedSpawnLocation() { return null; } @Override public long getLastLoginTime() { return 0; } @Override public long getFirstLoginTime() { return 0; } @Override public boolean hasPrivilege(String privid) { if(player != null) return hasPerm(player, privid); return false; } @Override public boolean isOp() { return DynmapPlugin.this.isOp(player.getCommandSenderEntity().getName()); } @Override public void sendMessage(String msg) { ITextComponent ichatcomponent = new TextComponentString(msg); player.addChatComponentMessage(ichatcomponent); } @Override public boolean isInvisible() { if(player != null) { return player.isInvisible(); } return false; } @Override public int getSortWeight() { Integer wt = sortWeights.get(getName()); if (wt != null) return wt; return 0; } @Override public void setSortWeight(int wt) { if (wt == 0) { sortWeights.remove(getName()); } else { sortWeights.put(getName(), wt); } } @Override public boolean hasPermissionNode(String node) { if(player != null) return hasPermNode(player, node); return false; } @Override public String getSkinURL() { return skinurl; } @Override public UUID getUUID() { return uuid; } /** * Send title and subtitle text (called from server thread) */ @Override public void sendTitleText(String title, String subtitle, int fadeInTicks, int stayTicks, int fadeOutTicks) { if (player instanceof EntityPlayerMP) { EntityPlayerMP mp = (EntityPlayerMP) player; SPacketTitle times = new SPacketTitle(fadeInTicks, stayTicks, fadeOutTicks); mp.connection.sendPacket(times); if (title != null) { SPacketTitle titlepkt = new SPacketTitle(SPacketTitle.Type.TITLE, new TextComponentString(title)); mp.connection.sendPacket(titlepkt); } if (subtitle != null) { SPacketTitle subtitlepkt = new SPacketTitle(SPacketTitle.Type.SUBTITLE, new TextComponentString(subtitle)); mp.connection.sendPacket(subtitlepkt); } } } } /* Handler for generic console command sender */ public class ForgeCommandSender implements DynmapCommandSender { private ICommandSender sender; protected ForgeCommandSender() { sender = null; } public ForgeCommandSender(ICommandSender send) { sender = send; } @Override public boolean hasPrivilege(String privid) { return true; } @Override public void sendMessage(String msg) { if(sender != null) { ITextComponent ichatcomponent = new TextComponentString(msg); sender.addChatMessage(ichatcomponent); } } @Override public boolean isConnected() { return false; } @Override public boolean isOp() { return true; } @Override public boolean hasPermissionNode(String node) { return true; } } public void loadExtraBiomes(String mcver) { int cnt = 0; BiomeMap.loadWellKnownByVersion(mcver); Biome[] list = getBiomeList(); for(int i = 0; i < list.length; i++) { Biome bb = list[i]; if(bb != null) { String id = bb.getBiomeName(); float tmp = bb.getTemperature(), hum = bb.getRainfall(); BiomeMap bmap = BiomeMap.byBiomeID(i); if (bmap.isDefault()) { BiomeMap m = new BiomeMap(i, id, tmp, hum); Log.verboseinfo("Add custom biome [" + m.toString() + "] (" + i + ")"); cnt++; } else { bmap.setTemperature(tmp); bmap.setRainfall(hum); } } } if(cnt > 0) Log.info("Added " + cnt + " custom biome mappings"); } private String[] getBiomeNames() { Biome[] list = getBiomeList(); String[] lst = new String[list.length]; for(int i = 0; i < list.length; i++) { Biome bb = list[i]; if (bb != null) { lst[i] = bb.getBiomeName(); } } return lst; } public void onEnable() { /* Get MC version */ String mcver = server.getMinecraftVersion(); /* Load extra biomes */ loadExtraBiomes(mcver); /* Set up player login/quit event handler */ registerPlayerLoginListener(); /* Initialize permissions handler */ permissions = FilePermissions.create(); if(permissions == null) { permissions = new OpPermissions(new String[] { "webchat", "marker.icons", "marker.list", "webregister", "stats", "hide.self", "show.self" }); } /* Get and initialize data folder */ File dataDirectory = new File("dynmap"); if (dataDirectory.exists() == false) { dataDirectory.mkdirs(); } /* Instantiate core */ if (core == null) { core = new DynmapCore(); } /* Inject dependencies */ core.setPluginJarFile(DynmapMod.jarfile); core.setPluginVersion(Version.VER); core.setMinecraftVersion(mcver); core.setDataFolder(dataDirectory); core.setServer(fserver); ForgeMapChunkCache.init(); core.setTriggerDefault(TRIGGER_DEFAULTS); core.setBiomeNames(getBiomeNames()); if(!core.initConfiguration(null)) { return; } DynmapCommonAPIListener.apiInitialized(core); } public void onStart() { initializeBlockStates(); /* Enable core */ if (!core.enableCore(null)) { return; } core_enabled = true; VersionCheck.runCheck(core); // Get per tick time limit perTickLimit = core.getMaxTickUseMS() * 1000000; // Prep TPS lasttick = System.nanoTime(); tps = 20.0; /* Register tick handler */ if(!tickregistered) { MinecraftForge.EVENT_BUS.register(fserver); tickregistered = true; } playerList = core.playerList; sscache = new SnapshotCache(core.getSnapShotCacheSize(), core.useSoftRefInSnapShotCache()); /* Get map manager from core */ mapManager = core.getMapManager(); /* Load saved world definitions */ loadWorlds(); /* Initialized the currently loaded worlds */ if(server.worldServers != null) { for (WorldServer world : server.worldServers) { ForgeWorld w = this.getWorld(world); /*NOTYET - need rest of forge if(DimensionManager.getWorld(world.provider.getDimensionId()) == null) { // If not loaded w.setWorldUnloaded(); } */ } } for(ForgeWorld w : worlds.values()) { if (core.processWorldLoad(w)) { /* Have core process load first - fire event listeners if good load after */ if(w.isLoaded()) { core.listenerManager.processWorldEvent(EventType.WORLD_LOAD, w); } } } core.updateConfigHashcode(); /* Register our update trigger events */ registerEvents(); Log.info("Register events"); /* Register command hander */ ICommandManager cm = server.getCommandManager(); if(cm instanceof CommandHandler) { CommandHandler scm = (CommandHandler)cm; scm.registerCommand(new DynmapCommand(this)); scm.registerCommand(new DmapCommand(this)); scm.registerCommand(new DmarkerCommand(this)); scm.registerCommand(new DynmapExpCommand(this)); Log.info("Register commands"); } /* Submit metrics to mcstats.org */ initMetrics(); //DynmapCommonAPIListener.apiInitialized(core); Log.info("Enabled"); } public void onDisable() { DynmapCommonAPIListener.apiTerminated(); //if (metrics != null) { // metrics.stop(); // metrics = null; //} /* Save worlds */ saveWorlds(); /* Purge tick queue */ fserver.runqueue.clear(); /* Disable core */ core.disableCore(); core_enabled = false; if (sscache != null) { sscache.cleanup(); sscache = null; } Log.info("Disabled"); } void onCommand(ICommandSender sender, String cmd, String[] args) { DynmapCommandSender dsender; if (sender instanceof EntityPlayer) { dsender = getOrAddPlayer((EntityPlayer)sender); } else { dsender = new ForgeCommandSender(sender); } core.processCommand(dsender, cmd, cmd, args); } private DynmapLocation toLoc(World worldObj, double x, double y, double z) { return new DynmapLocation(DynmapPlugin.this.getWorld(worldObj).getName(), x, y, z); } public class PlayerTracker { @SubscribeEvent public void onPlayerLogin(PlayerLoggedInEvent event) { if(!core_enabled) return; final DynmapPlayer dp = getOrAddPlayer(event.player); /* This event can be called from off server thread, so push processing there */ core.getServer().scheduleServerTask(new Runnable() { public void run() { core.listenerManager.processPlayerEvent(EventType.PLAYER_JOIN, dp); } }, 2); } @SubscribeEvent public void onPlayerLogout(PlayerLoggedOutEvent event) { if(!core_enabled) return; final DynmapPlayer dp = getOrAddPlayer(event.player); final String name = event.player.getCommandSenderEntity().getName(); /* This event can be called from off server thread, so push processing there */ core.getServer().scheduleServerTask(new Runnable() { public void run() { core.listenerManager.processPlayerEvent(EventType.PLAYER_QUIT, dp); players.remove(name); } }, 0); } @SubscribeEvent public void onPlayerChangedDimension(PlayerChangedDimensionEvent event) { if(!core_enabled) return; getOrAddPlayer(event.player); // Freshen player object reference } @SubscribeEvent public void onPlayerRespawn(PlayerRespawnEvent event) { if(!core_enabled) return; getOrAddPlayer(event.player); // Freshen player object reference } } private PlayerTracker playerTracker = null; private void registerPlayerLoginListener() { if (playerTracker == null) { playerTracker = new PlayerTracker(); MinecraftForge.EVENT_BUS.register(playerTracker); } } public class WorldTracker { @SubscribeEvent public void handleWorldLoad(WorldEvent.Load event) { if(!core_enabled) return; World w = event.getWorld(); if(!(w instanceof WorldServer)) return; final ForgeWorld fw = getWorld(w); // This event can be called from off server thread, so push processing there core.getServer().scheduleServerTask(new Runnable() { public void run() { if(core.processWorldLoad(fw)) // Have core process load first - fire event listeners if good load after core.listenerManager.processWorldEvent(EventType.WORLD_LOAD, fw); } }, 0); } @SubscribeEvent public void handleWorldUnload(WorldEvent.Unload event) { if(!core_enabled) return; World w = event.getWorld(); if(!(w instanceof WorldServer)) return; final ForgeWorld fw = getWorld(w); if(fw != null) { // This event can be called from off server thread, so push processing there core.getServer().scheduleServerTask(new Runnable() { public void run() { core.listenerManager.processWorldEvent(EventType.WORLD_UNLOAD, fw); core.processWorldUnload(fw); } }, 0); // Set world unloaded (needs to be immediate, since it may be invalid after event) fw.setWorldUnloaded(); // Clean up tracker WorldUpdateTracker wut = updateTrackers.remove(fw.getName()); if(wut != null) wut.world = null; } } @SubscribeEvent public void handleChunkLoad(ChunkEvent.Load event) { if(!core_enabled) return; if(!onchunkgenerate) return; World w = event.getWorld(); if(!(w instanceof WorldServer)) return; Chunk c = event.getChunk(); if((c != null) && (!c.isTerrainPopulated())) { // If new chunk? ForgeWorld fw = getWorld(w, false); if(fw == null) { return; } int ymax = 0; ExtendedBlockStorage[] sections = c.getBlockStorageArray(); for(int i = 0; i < sections.length; i++) { if((sections[i] != null) && (sections[i].isEmpty() == false)) { ymax = 16*(i+1); } } int x = c.xPosition << 4; int z = c.zPosition << 4; if(ymax > 0) { mapManager.touchVolume(fw.getName(), x, 0, z, x+15, ymax, z+16, "chunkgenerate"); } } } @SubscribeEvent public void handleChunkPopulate(PopulateChunkEvent.Post event) { if(!core_enabled) return; if(!onchunkpopulate) return; World w = event.getWorld(); if(!(w instanceof WorldServer)) return; Chunk c = w.getChunkFromChunkCoords(event.getChunkX(), event.getChunkZ()); int ymin = 0, ymax = 0; if(c != null) { ForgeWorld fw = getWorld(event.getWorld(), false); if (fw == null) return; ExtendedBlockStorage[] sections = c.getBlockStorageArray(); for(int i = 0; i < sections.length; i++) { if((sections[i] != null) && (sections[i].isEmpty() == false)) { ymax = 16*(i+1); } } int x = c.xPosition << 4; int z = c.zPosition << 4; if(ymax > 0) mapManager.touchVolume(fw.getName(), x, ymin, z, x+15, ymax, z+16, "chunkpopulate"); } } } private boolean onblockchange = false; private boolean onlightingchange = false; private boolean onchunkpopulate = false; private boolean onchunkgenerate = false; private boolean onblockchange_with_id = false; public class WorldUpdateTracker implements IWorldEventListener { String worldid; World world; @Override public void notifyLightSet(BlockPos pos) { if(sscache != null) sscache.invalidateSnapshot(worldid, pos.getX(), pos.getY(), pos.getZ()); if(onlightingchange) { mapManager.touch(worldid, pos.getX(), pos.getY(), pos.getZ(), "lightingchange"); } } @Override public void markBlockRangeForRenderUpdate(int x1, int y1, int z1, int x2, int y2, int z2) { } @Override public void onEntityAdded(Entity entityIn) { } @Override public void onEntityRemoved(Entity entityIn) { } @Override public void sendBlockBreakProgress(int breakerId, BlockPos pos, int progress) { } @Override public void spawnParticle(int particleID, boolean ignoreRange, double xCoord, double yCoord, double zCoord, double xOffset, double yOffset, double zOffset, int... p_180442_15_) { } @Override public void broadcastSound(int p_180440_1_, BlockPos p_180440_2_, int p_180440_3_) { } @Override public void notifyBlockUpdate(World worldIn, BlockPos pos, IBlockState oldState, IBlockState newState, int flags) { if(sscache != null) sscache.invalidateSnapshot(worldid, pos.getX(), pos.getY(), pos.getZ()); if(onblockchange) { BlockUpdateRec r = new BlockUpdateRec(); r.w = world; r.wid = worldid; r.x = pos.getX(); r.y = pos.getY(); r.z = pos.getZ(); blockupdatequeue.add(r); } } @Override public void playSoundToAllNearExcept(EntityPlayer player, SoundEvent soundIn, SoundCategory category, double x, double y, double z, float volume, float pitch) { } @Override public void playRecord(SoundEvent soundIn, BlockPos pos) { } @Override public void playEvent(EntityPlayer arg0, int arg1, BlockPos arg2, int arg3) { } } private WorldTracker worldTracker = null; private HashMap updateTrackers = new HashMap(); private void registerEvents() { if(worldTracker == null) { worldTracker = new WorldTracker(); MinecraftForge.EVENT_BUS.register(worldTracker); } // To trigger rendering. onblockchange = core.isTrigger("blockupdate"); onlightingchange = core.isTrigger("lightingupdate"); onchunkpopulate = core.isTrigger("chunkpopulate"); onchunkgenerate = core.isTrigger("chunkgenerate"); onblockchange_with_id = core.isTrigger("blockupdate-with-id"); if(onblockchange_with_id) onblockchange = true; } private ForgeWorld getWorldByName(String name) { return worlds.get(name); } private ForgeWorld getWorld(World w) { return getWorld(w, true); } private ForgeWorld getWorld(World w, boolean add_if_not_found) { if(last_world == w) { return last_fworld; } String wname = ForgeWorld.getWorldName(w); for(ForgeWorld fw : worlds.values()) { if(fw.getRawName().equals(wname)) { last_world = w; last_fworld = fw; if(fw.isLoaded() == false) { fw.setWorldLoaded(w); // Add tracker WorldUpdateTracker wit = new WorldUpdateTracker(); wit.worldid = fw.getName(); wit.world = w; updateTrackers.put(fw.getName(), wit); w.addEventListener(wit); } return fw; } } ForgeWorld fw = null; if(add_if_not_found) { /* Add to list if not found */ fw = new ForgeWorld(w); worlds.put(fw.getName(), fw); // Add tracker WorldUpdateTracker wit = new WorldUpdateTracker(); wit.worldid = fw.getName(); wit.world = w; updateTrackers.put(fw.getName(), wit); w.addEventListener(wit); } last_world = w; last_fworld = fw; return fw; } /* private void removeWorld(ForgeWorld fw) { WorldUpdateTracker wit = updateTrackers.remove(fw.getName()); if(wit != null) { //fw.getWorld().removeWorldAccess(wit); } worlds.remove(fw.getName()); if(last_fworld == fw) { last_world = null; last_fworld = null; } } */ private void initMetrics() { /* try { Mod m = DynmapMod.class.getAnnotation(Mod.class); metrics = new ForgeMetrics(m.name(), m.version()); ; ForgeMetrics.Graph features = metrics.createGraph("Features Used"); features.addPlotter(new ForgeMetrics.Plotter("Internal Web Server") { @Override public int getValue() { if (!core.configuration.getBoolean("disable-webserver", false)) return 1; return 0; } }); features.addPlotter(new ForgeMetrics.Plotter("Login Security") { @Override public int getValue() { if(core.configuration.getBoolean("login-enabled", false)) return 1; return 0; } }); features.addPlotter(new ForgeMetrics.Plotter("Player Info Protected") { @Override public int getValue() { if(core.player_info_protected) return 1; return 0; } }); ForgeMetrics.Graph maps = metrics.createGraph("Map Data"); maps.addPlotter(new ForgeMetrics.Plotter("Worlds") { @Override public int getValue() { if(core.mapManager != null) return core.mapManager.getWorlds().size(); return 0; } }); maps.addPlotter(new ForgeMetrics.Plotter("Maps") { @Override public int getValue() { int cnt = 0; if(core.mapManager != null) { for(DynmapWorld w :core.mapManager.getWorlds()) { cnt += w.maps.size(); } } return cnt; } }); maps.addPlotter(new ForgeMetrics.Plotter("HD Maps") { @Override public int getValue() { int cnt = 0; if(core.mapManager != null) { for(DynmapWorld w :core.mapManager.getWorlds()) { for(MapType mt : w.maps) { if(mt instanceof HDMap) { cnt++; } } } } return cnt; } }); for (String mod : modsused) { features.addPlotter(new ForgeMetrics.Plotter(mod + " Blocks") { @Override public int getValue() { return 1; } }); } metrics.start(); } catch (IOException e) { // Failed to submit the stats :-( } */ } private void saveWorlds() { File f = new File(core.getDataFolder(), "forgeworlds.yml"); ConfigurationNode cn = new ConfigurationNode(f); ArrayList> lst = new ArrayList>(); for(DynmapWorld fw : core.mapManager.getWorlds()) { HashMap vals = new HashMap(); vals.put("name", fw.getRawName()); vals.put("height", fw.worldheight); vals.put("sealevel", fw.sealevel); vals.put("nether", fw.isNether()); vals.put("the_end", ((ForgeWorld)fw).isTheEnd()); vals.put("title", fw.getTitle()); lst.add(vals); } cn.put("worlds", lst); cn.put("isMCPC", isMCPC); cn.put("useSaveFolderAsName", useSaveFolder); cn.put("maxWorldHeight", ForgeWorld.getMaxWorldHeight()); cn.save(); } private void loadWorlds() { isMCPC = server.getServerModName().contains("mcpc"); File f = new File(core.getDataFolder(), "forgeworlds.yml"); if(f.canRead() == false) { useSaveFolder = true; if (isMCPC) { ForgeWorld.setMCPCMapping(); } else { ForgeWorld.setSaveFolderMapping(); } return; } ConfigurationNode cn = new ConfigurationNode(f); cn.load(); // If defined, use maxWorldHeight ForgeWorld.setMaxWorldHeight(cn.getInteger("maxWorldHeight", 256)); // If existing, only switch to save folder if MCPC+ useSaveFolder = isMCPC; // If setting defined, use it if (cn.containsKey("useSaveFolderAsName")) { useSaveFolder = cn.getBoolean("useSaveFolderAsName", useSaveFolder); } if (isMCPC) { ForgeWorld.setMCPCMapping(); } else if (useSaveFolder) { ForgeWorld.setSaveFolderMapping(); } // If inconsistent between MCPC and non-MCPC if (isMCPC != cn.getBoolean("isMCPC", false)) { return; } List> lst = cn.getMapList("worlds"); if(lst == null) { Log.warning("Discarding bad forgeworlds.yml"); return; } for(Map world : lst) { try { String name = (String)world.get("name"); int height = (Integer)world.get("height"); int sealevel = (Integer)world.get("sealevel"); boolean nether = (Boolean)world.get("nether"); boolean theend = (Boolean)world.get("the_end"); String title = (String)world.get("title"); if(name != null) { ForgeWorld fw = new ForgeWorld(name, height, sealevel, nether, theend, title); fw.setWorldUnloaded(); core.processWorldLoad(fw); worlds.put(fw.getName(), fw); } } catch (Exception x) { Log.warning("Unable to load saved worlds from forgeworlds.yml"); return; } } } public void serverStarted() { this.onStart(); if (core != null) { core.serverStarted(); } } } class DynmapCommandHandler extends CommandBase { private String cmd; private DynmapPlugin plugin; public DynmapCommandHandler(String cmd, DynmapPlugin p) { this.cmd = cmd; this.plugin = p; } @Override public String getCommandName() { return cmd; } @Override public String getCommandUsage(ICommandSender icommandsender) { return "Run /" + cmd + " help for details on using command"; } @Override public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException { plugin.onCommand(sender, cmd, args); } } class DynmapCommand extends DynmapCommandHandler { DynmapCommand(DynmapPlugin p) { super("dynmap", p); } } class DmapCommand extends DynmapCommandHandler { DmapCommand(DynmapPlugin p) { super("dmap", p); } } class DmarkerCommand extends DynmapCommandHandler { DmarkerCommand(DynmapPlugin p) { super("dmarker", p); } } class DynmapExpCommand extends DynmapCommandHandler { DynmapExpCommand(DynmapPlugin p) { super("dynmapexp", p); } }