package org.dynmap.forge_1_13_2; import java.io.File; import java.io.InputStream; import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; 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.Optional; 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.BlockFlowingFluid; import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; import net.minecraft.command.CommandException; import net.minecraft.command.CommandSource; import net.minecraft.command.Commands; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.fluid.IFluidState; import net.minecraft.init.Blocks; 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.particles.IParticleData; import net.minecraft.server.MinecraftServer; import net.minecraft.server.management.UserListBans; import net.minecraft.server.management.UserListIPBans; import net.minecraft.server.management.UserListOps; import net.minecraft.server.management.PlayerProfileCache; import net.minecraft.util.ObjectIntIdentityMap; 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.registry.IRegistry; import net.minecraft.util.registry.RegistryNamespaced; import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.TextComponentString; import net.minecraft.world.IBlockReader; import net.minecraft.world.IWorld; 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.ChunkSection; import net.minecraft.world.chunk.IChunk; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.ServerChatEvent; import net.minecraftforge.event.world.ChunkEvent; import net.minecraftforge.event.world.WorldEvent; import net.minecraftforge.fml.ModList; import net.minecraftforge.fml.ModContainer; 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.registries.ForgeRegistries; import net.minecraftforge.registries.ForgeRegistry; import net.minecraftforge.registries.RegistryManager; import net.minecraftforge.fml.common.gameevent.TickEvent; import net.minecraftforge.fml.common.registry.GameRegistry; import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo; import net.minecraftforge.fml.loading.moddiscovery.ModInfo; import net.minecraftforge.forgespi.language.IModInfo; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.maven.artifact.versioning.ArtifactVersion; 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_13_2.DmapCommand; import org.dynmap.forge_1_13_2.DmarkerCommand; import org.dynmap.forge_1_13_2.DynmapCommand; import org.dynmap.forge_1_13_2.DynmapMod; import org.dynmap.forge_1_13_2.permissions.FilePermissions; import org.dynmap.forge_1_13_2.permissions.OpPermissions; import org.dynmap.forge_1_13_2.permissions.PermissionProvider; 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; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.minecraft.state.IProperty; import net.minecraftforge.eventbus.api.SubscribeEvent; 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 IWorld 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 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 { IWorld 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*32]; // Simple map - scale as needed Arrays.fill(stateByID, DynmapBlockState.AIR); // Default to air ObjectIntIdentityMap bsids = Block.BLOCK_STATE_IDS; DynmapBlockState basebs = null; Block baseb = null; int baseidx = 0; Iterator iter = bsids.iterator(); while (iter.hasNext()) { IBlockState bs = iter.next(); int idx = bsids.get(bs); if (idx >= stateByID.length) { int plen = stateByID.length; stateByID = Arrays.copyOf(stateByID, idx*11/10); // grow array by 10% Arrays.fill(stateByID, plen, stateByID.length, DynmapBlockState.AIR); } Block b = bs.getBlock(); // If this is new block vs last, it's the base block state if (b != baseb) { basebs = null; baseidx = idx; baseb = b; } ResourceLocation ui = b.getRegistryName(); if (ui == null) { continue; } String bn = ui.getNamespace() + ":" + ui.getPath(); // Only do defined names, and not "air" if (!bn.equals(DynmapBlockState.AIR_BLOCK)) { Material mat = bs.getMaterial(); String statename = ""; for(IProperty p : bs.getProperties()) { if (statename.length() > 0) { statename += ","; } statename += p.getName() + "=" + bs.get(p).toString(); } //Log.info("bn=" + bn + ", statenme=" + statename + ",idx=" + idx + ",baseidx=" + baseidx); DynmapBlockState dbs = new DynmapBlockState(basebs, idx - baseidx, bn, statename, mat.toString(), idx); stateByID[idx] = dbs; if (basebs == null) { basebs = dbs; } if (mat.isSolid()) { dbs.setSolid(); } if (mat == Material.AIR) { dbs.setAir(); } if (mat == Material.WOOD) { dbs.setLog(); } if (mat == Material.LEAVES) { dbs.setLeaves(); } if ((!bs.getFluidState().isEmpty()) && !(bs.getBlock() instanceof BlockFlowingFluid)) { dbs.setWaterlogged(); } } } for (int gidx = 0; gidx < DynmapBlockState.getGlobalIndexMax(); gidx++) { DynmapBlockState bs = DynmapBlockState.getStateByGlobalIndex(gidx); //Log.info(gidx + ":" + bs.toString() + ", gidx=" + bs.globalStateIndex + ", sidx=" + bs.stateIndex); } } public static final Item getItemByID(int id) { return Item.getItemById(id); } public static final String getBlockUnlocalizedName(Block b) { return b.getNameTextComponent().getString(); } private static Biome[] biomelist = null; public static final Biome[] getBiomeList() { if (biomelist == null) { biomelist = new Biome[256]; Iterator iter = ForgeRegistries.BIOMES.iterator(); while (iter.hasNext()) { Biome b = iter.next(); int bidx = RegistryNamespaced.BIOME.getId(b); if (bidx >= biomelist.length) { biomelist = Arrays.copyOf(biomelist, bidx + biomelist.length); } biomelist[bidx] = b; } } return biomelist; } public static final NetworkManager getNetworkManager(NetHandlerPlayServer nh) { return nh.netManager; } private ForgePlayer getOrAddPlayer(EntityPlayer p) { String name = p.getEntity().getName().getString(); 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); } } } /** TODO: depends on forge chunk manager 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; } public boolean isOp(String player) { String[] ops = server.getPlayerList().getOppedPlayers().getKeys(); for (String op : ops) { if (op.equalsIgnoreCase(player)) { return true; } } return (server.isSinglePlayer() && player.equalsIgnoreCase(server.getServerOwner())); } private boolean hasPerm(EntityPlayer psender, String permission) { PermissionsHandler ph = PermissionsHandler.getHandler(); if((psender != null) && (ph != null) && ph.hasPermission(psender.getEntity().getName().getString(), permission)) { return true; } return permissions.has(psender, permission); } private boolean hasPermNode(EntityPlayer psender, String permission) { PermissionsHandler ph = PermissionsHandler.getHandler(); if((psender != null) && (ph != null) && ph.hasPermissionNode(psender.getEntity().getName().getString(), permission)) { return true; } return permissions.hasPermissionNode(psender, 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() { } private GameProfile getProfileByName(String player) { PlayerProfileCache cache = server.getPlayerProfileCache(); return cache.getGameProfileForUsername(player); } @Override public int getBlockIDAt(String wname, int x, int y, int z) { return -1; } @Override public int isSignAt(String wname, int x, int y, int z) { return -1; } @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().getPlayers(); 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().getPlayers(); for (Object o : players) { EntityPlayer p = (EntityPlayer)o; if (p.getEntity().getName().getString().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(getProfileByName(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().sendMessage(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(getProfileByName(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(getProfileByName(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(); //TODO //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(); IBlockState bs = r.w.getBlockState(new BlockPos(r.x, r.y, r.z)); int idx = Block.BLOCK_STATE_IDS.get(bs); if(!org.dynmap.hdmap.HDBlockModels.isChangeIgnoredBlock(stateByID[idx])) { if(onblockchange_with_id) mapManager.touch(r.wid, r.x, r.y, r.z, "blockchange[" + idx + "]"); 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) { //TODO //doIdleOutOfWorlds(); } } @Override public boolean isModLoaded(String name) { boolean loaded = ModList.get().isLoaded(name); if (loaded) { modsused.add(name); } return loaded; } @Override public String getModVersion(String name) { Optional mod = ModList.get().getModContainerById(name); // Try case sensitive lookup if (mod.isPresent()) { ArtifactVersion vi = mod.get().getModInfo().getVersion(); return vi.getMajorVersion() + "." + vi.getMinorVersion() + "." + vi.getIncrementalVersion(); } return null; } @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) { ModFileInfo mfi = ModList.get().getModFileById(name); // Try case sensitive lookup if (mfi != null) { File f = mfi.getFile().getFilePath().toFile(); return f; } return null; } @Override public List getModList() { List mil = ModList.get().getMods(); List lst = new ArrayList(); for (ModInfo mi : mil) { lst.add(mi.getModId()); } return lst; } @Override public Map getBlockIDMap() { Map map = new HashMap(); return map; } @Override public InputStream openResource(String modid, String rname) { if (modid != null) { Optional mc = ModList.get().getModContainerById(modid); Object mod = (mc.isPresent()) ? mc.get().getMod() : null; if (mod != null) { InputStream is = mod.getClass().getClassLoader().getResourceAsStream(rname); if (is != null) { return is; } } } List mcl = ModList.get().getMods(); for (ModInfo mci : mcl) { Optional mc = ModList.get().getModContainerById(mci.getModId()); Object mod = (mc.isPresent()) ? mc.get().getMod() : null; 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(); return map; } /** * Get item unique ID map (module:itemid) */ @Override public Map getItemUniqueIDMap() { HashMap map = new HashMap(); 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.getDecoder().decode(textureProperty.getValue()), StandardCharsets.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) { String n = player.getEntity().getName().getString();; return n; } else return "[Server]"; } @Override public String getDisplayName() { if(player != null) { String n = player.getDisplayName().getString(); return n; } else return "[Server]"; } @Override public boolean isOnline() { return true; } @Override public DynmapLocation getLocation() { if (player == null) { return null; } return toLoc(player.world, player.posX, player.posY, player.posZ); } @Override public String getWorld() { if (player == null) { return null; } if (player.world != null) { return DynmapPlugin.this.getWorld(player.world).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.getEntity().getName().getString()); } @Override public void sendMessage(String msg) { ITextComponent ichatcomponent = new TextComponentString(msg); player.sendMessage(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 CommandSource sender; protected ForgeCommandSender() { sender = null; } public ForgeCommandSender(CommandSource 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.sendFeedback(ichatcomponent, false); } } @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.getRegistryName().getPath(); float tmp = bb.getDefaultTemperature(), hum = bb.getDownfall(); int watermult = bb.getWaterColor(); Log.verboseinfo("biome[" + i + "]: hum=" + hum + ", tmp=" + tmp + ", mult=" + Integer.toHexString(watermult)); BiomeMap bmap = BiomeMap.byBiomeID(i); if (bmap.isDefault()) { bmap = new BiomeMap(i, id, tmp, hum); Log.verboseinfo("Add custom biome [" + bmap.toString() + "] (" + i + ")"); cnt++; } else { bmap.setTemperature(tmp); bmap.setRainfall(hum); } if (watermult != -1) { bmap.setWaterColorMultiplier(watermult); Log.verboseinfo("Set watercolormult for " + bmap.toString() + " (" + i + ") to " + Integer.toHexString(watermult)); } } } 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.getRegistryName().getPath(); } } 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(DynmapMod.ver); core.setMinecraftVersion(mcver); core.setDataFolder(dataDirectory); core.setServer(fserver); ForgeMapChunkCache.init(); core.setTriggerDefault(TRIGGER_DEFAULTS); core.setBiomeNames(getBiomeNames()); if(!core.initConfiguration(null)) { return; } // Extract default permission example, if needed File filepermexample = new File(core.getDataFolder(), "permissions.yml.example"); core.createDefaultFileFromResource("/permissions.yml.example", filepermexample); DynmapCommonAPIListener.apiInitialized(core); } private static int test(CommandSource source) throws CommandSyntaxException { Log.warning(source.toString()); return 1; } private DynmapCommand dynmapCmd; private DmapCommand dmapCmd; private DmarkerCommand dmarkerCmd; private DynmapExpCommand dynmapexpCmd; public void onStarting(CommandDispatcher cd) { /* Register command hander */ dynmapCmd = new DynmapCommand(this); dmapCmd = new DmapCommand(this); dmarkerCmd = new DmarkerCommand(this); dynmapexpCmd = new DynmapExpCommand(this); dynmapCmd.register(cd); dmapCmd.register(cd); dmarkerCmd.register(cd); dynmapexpCmd.register(cd); Log.info("Register commands"); } 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.getWorlds() != null) { for (WorldServer world : server.getWorlds()) { 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"); /* 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(CommandSource sender, String cmd, String[] args) { DynmapCommandSender dsender; EntityPlayer psender; try { psender = sender.asPlayer(); } catch (com.mojang.brigadier.exceptions.CommandSyntaxException x) { psender = null; } if (psender != null) { dsender = new ForgePlayer(psender); } else { dsender = new ForgeCommandSender(sender); } try { core.processCommand(dsender, cmd, cmd, args); } catch (Exception x) { dsender.sendMessage("Command internal error: " + x.getMessage()); Log.severe("Error with command: " + cmd + Arrays.deepToString(args), x); } } 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.getPlayer()); /* 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.getPlayer()); final String name = event.getPlayer().getEntity().getName().getString(); /* 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.getPlayer()); // Freshen player object reference } @SubscribeEvent public void onPlayerRespawn(PlayerRespawnEvent event) { if(!core_enabled) return; getOrAddPlayer(event.getPlayer()); // 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; IWorld w = event.getWorld(); if(!(w instanceof WorldServer)) return; final ForgeWorld fw = getWorld(w); if (fw == null) return; // 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; IWorld 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; IWorld w = event.getWorld(); if(!(w instanceof WorldServer)) return; IChunk c = event.getChunk(); if((c != null) /*TODO && (!c.isTerrainPopulated())*/) { // If new chunk? ForgeWorld fw = getWorld(w, false); if(fw == null) { return; } int ymax = 0; ChunkSection[] sections = c.getSections(); for(int i = 0; i < sections.length; i++) { if((sections[i] != null) && (sections[i].isEmpty() == false)) { ymax = 16*(i+1); } } ChunkPos cp = c.getPos(); int x = cp.x << 4; int z = cp.z << 4; if(ymax > 0) { mapManager.touchVolume(fw.getName(), x, 0, z, x+15, ymax, z+16, "chunkgenerate"); } } } /*TODO @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.x << 4; int z = c.z << 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; IWorld 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 broadcastSound(int p_180440_1_, BlockPos p_180440_2_, int p_180440_3_) { } @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) { } @Override public void notifyBlockUpdate(IBlockReader 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 addParticle(IParticleData particleData, boolean alwaysRender, double x, double y, double z, double xSpeed, double ySpeed, double zSpeed) { // TODO Auto-generated method stub } @Override public void addParticle(IParticleData particleData, boolean ignoreRange, boolean minimizeLevel, double x, double y, double z, double xSpeed, double ySpeed, double zSpeed) { // TODO Auto-generated method stub } } 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(IWorld w) { return getWorld(w, true); } private ForgeWorld getWorld(IWorld 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.getWorld().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.getWorld().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(); } } public MinecraftServer getMCServer() { return server; } } class DynmapCommandHandler { private String cmd; private DynmapPlugin plugin; public DynmapCommandHandler(String cmd, DynmapPlugin p) { this.cmd = cmd; this.plugin = p; } public void register(CommandDispatcher cd) { cd.register(Commands.literal(cmd). then(RequiredArgumentBuilder. argument("args", StringArgumentType.greedyString()). executes((ctx) -> this.execute(plugin.getMCServer(), ctx.getSource(), ctx.getInput()))). executes((ctx) -> this.execute(plugin.getMCServer(), ctx.getSource(), ctx.getInput()))); } // @Override public int execute(MinecraftServer server, CommandSource sender, String cmdline) throws CommandException { String[] args = cmdline.split("\\s+"); plugin.onCommand(sender, cmd, Arrays.copyOfRange(args, 1, args.length)); return 1; } // @Override public String getUsage(CommandSource arg0) { return "Run /" + cmd + " help for details on using command"; } } 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); } }