From 402a83566d4f0de518f4923cdffdec6ec9054412 Mon Sep 17 00:00:00 2001 From: Michael Primm Date: Sun, 2 Jun 2024 22:37:13 -0500 Subject: [PATCH 01/10] Start NeoForge 1.20.6 --- .../org.eclipse.buildship.core.prefs | 2 +- .../.settings/org.eclipse.jdt.core.prefs | 2 +- neoforge-1.20.6/.gitignore | 2 + neoforge-1.20.6/build.gradle | 64 + .../dynmap/neoforge_1_20_6/ClientProxy.java | 6 + .../org/dynmap/neoforge_1_20_6/DynmapMod.java | 125 + .../dynmap/neoforge_1_20_6/DynmapPlugin.java | 2041 +++++++++++++++++ .../neoforge_1_20_6/ForgeMapChunkCache.java | 109 + .../dynmap/neoforge_1_20_6/ForgeWorld.java | 249 ++ .../java/org/dynmap/neoforge_1_20_6/NBT.java | 126 + .../org/dynmap/neoforge_1_20_6/Proxy.java | 24 + .../dynmap/neoforge_1_20_6/VersionCheck.java | 97 + .../permissions/FilePermissions.java | 103 + .../permissions/OpPermissions.java | 51 + .../permissions/PermissionProvider.java | 15 + .../resources/META-INF/accesstransformer.cfg | 4 + .../resources/META-INF/neoforge.mods.toml | 27 + .../src/main/resources/configuration.txt | 504 ++++ .../src/main/resources/pack.mcmeta | 8 + .../main/resources/permissions.yml.example | 27 + settings.gradle | 3 + 21 files changed, 3587 insertions(+), 2 deletions(-) create mode 100644 neoforge-1.20.6/.gitignore create mode 100644 neoforge-1.20.6/build.gradle create mode 100644 neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/ClientProxy.java create mode 100644 neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/DynmapMod.java create mode 100644 neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/DynmapPlugin.java create mode 100644 neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/ForgeMapChunkCache.java create mode 100644 neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/ForgeWorld.java create mode 100644 neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/NBT.java create mode 100644 neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/Proxy.java create mode 100644 neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/VersionCheck.java create mode 100644 neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/permissions/FilePermissions.java create mode 100644 neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/permissions/OpPermissions.java create mode 100644 neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/permissions/PermissionProvider.java create mode 100644 neoforge-1.20.6/src/main/resources/META-INF/accesstransformer.cfg create mode 100644 neoforge-1.20.6/src/main/resources/META-INF/neoforge.mods.toml create mode 100644 neoforge-1.20.6/src/main/resources/configuration.txt create mode 100644 neoforge-1.20.6/src/main/resources/pack.mcmeta create mode 100644 neoforge-1.20.6/src/main/resources/permissions.yml.example diff --git a/bukkit-helper/.settings/org.eclipse.buildship.core.prefs b/bukkit-helper/.settings/org.eclipse.buildship.core.prefs index 9bf0b860f..633832eeb 100644 --- a/bukkit-helper/.settings/org.eclipse.buildship.core.prefs +++ b/bukkit-helper/.settings/org.eclipse.buildship.core.prefs @@ -2,7 +2,7 @@ arguments= auto.sync=false build.scans.enabled=false connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(6.3)) -connection.project.dir=../forge-1.20.6 +connection.project.dir=../neoforge-1.20.6 eclipse.preferences.version=1 gradle.user.home= java.home=/Library/Java/JavaVirtualMachines/jdk1.8.0_251.jdk/Contents/Home diff --git a/bukkit-helper/.settings/org.eclipse.jdt.core.prefs b/bukkit-helper/.settings/org.eclipse.jdt.core.prefs index 0c7d081d9..94f07c47c 100644 --- a/bukkit-helper/.settings/org.eclipse.jdt.core.prefs +++ b/bukkit-helper/.settings/org.eclipse.jdt.core.prefs @@ -1,5 +1,5 @@ # -#Sun Jun 02 15:52:13 CDT 2024 +#Sun Jun 02 22:23:44 CDT 2024 eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.annotation.nonnull=javax.annotation.Nonnull org.eclipse.jdt.core.compiler.annotation.nullable=javax.annotation.Nullable diff --git a/neoforge-1.20.6/.gitignore b/neoforge-1.20.6/.gitignore new file mode 100644 index 000000000..7c9cf9c2a --- /dev/null +++ b/neoforge-1.20.6/.gitignore @@ -0,0 +1,2 @@ +/build/ +/.gradle/ diff --git a/neoforge-1.20.6/build.gradle b/neoforge-1.20.6/build.gradle new file mode 100644 index 000000000..bb05d5ae9 --- /dev/null +++ b/neoforge-1.20.6/build.gradle @@ -0,0 +1,64 @@ +buildscript { + repositories { + jcenter() + mavenCentral() + } +} +plugins { + id 'eclipse' + id 'net.neoforged.gradle.userdev' version '7.0.133' +} + +eclipse { + project { + name = "Dynmap(NeoForge-1.20.6)" + } +} + +sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(21) // Need this here so eclipse task generates correctly. +java.toolchain.languageVersion = JavaLanguageVersion.of(21) + +println('Java: ' + System.getProperty('java.version') + ' JVM: ' + System.getProperty('java.vm.version') + '(' + System.getProperty('java.vendor') + ') Arch: ' + System.getProperty('os.arch')) + +ext.buildNumber = System.getenv().BUILD_NUMBER ?: "Dev" + + +project.archivesBaseName = "${project.archivesBaseName}-forge-1.20.6" + +dependencies { + implementation project(path: ":DynmapCore", configuration: "shadow") + implementation project(path: ':DynmapCoreAPI') + implementation "net.neoforged:neoforge:20.6.62-beta" +} + +processResources +{ + filesMatching('META-INF/neoforge.mods.toml') { + // replace version and mcversion + expand( + version: project.version + '-' + project.ext.buildNumber, + mcversion: "1.20.6" + ) + } +} + +shadowJar { + dependencies { + include(dependency(':DynmapCore')) + include(dependency("commons-codec:commons-codec:")) + exclude("META-INF/maven/**") + exclude("META-INF/services/**") + } + relocate('org.apache.commons.codec', 'org.dynmap.neoforge_1_20_6.commons.codec') + + archiveBaseName = "Dynmap" + archiveClassifier = "neoforge-1.20.6" + destinationDirectory = file '../target' +} + +shadowJar.doLast { + task -> + ant.checksum file: task.archivePath +} + +build.dependsOn(shadowJar) diff --git a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/ClientProxy.java b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/ClientProxy.java new file mode 100644 index 000000000..3a0ee6e4e --- /dev/null +++ b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/ClientProxy.java @@ -0,0 +1,6 @@ +package org.dynmap.neoforge_1_20_6; + +public class ClientProxy extends Proxy { + public ClientProxy() { + } +} diff --git a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/DynmapMod.java b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/DynmapMod.java new file mode 100644 index 000000000..e8deca096 --- /dev/null +++ b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/DynmapMod.java @@ -0,0 +1,125 @@ +package org.dynmap.neoforge_1_20_6; + +import java.io.File; + +import org.apache.commons.lang3.tuple.Pair; +import org.dynmap.DynmapCommonAPI; +import org.dynmap.DynmapCommonAPIListener; +import org.dynmap.Log; +import org.dynmap.neoforge_1_20_6.DynmapPlugin.OurLog; + +import net.minecraft.server.MinecraftServer; +import net.neoforged.fml.common.Mod; +import net.neoforged.neoforge.event.server.ServerAboutToStartEvent; +import net.neoforged.neoforge.event.server.ServerStartedEvent; +import net.neoforged.neoforge.event.server.ServerStartingEvent; +import net.neoforged.neoforge.event.server.ServerStoppingEvent; + +@Mod("dynmap") +public class DynmapMod +{ + // The instance of your mod that Forge uses. + public static DynmapMod instance; + + // Says where the client and server 'proxy' code is loaded. + public static Proxy proxy = DistExecutor.runForDist(() -> ClientProxy::new, () -> Proxy::new); + + public static DynmapPlugin plugin; + public static File jarfile; + public static String ver; + public static boolean useforcedchunks; + + public class APICallback extends DynmapCommonAPIListener { + @Override + public void apiListenerAdded() { + if(plugin == null) { + plugin = proxy.startServer(server); + } + } + @Override + public void apiEnabled(DynmapCommonAPI api) { + } + } + + //TODO + //public class LoadingCallback implements net.minecraftforge.common.ForgeChunkManager.LoadingCallback { + // @Override + // public void ticketsLoaded(List tickets, World world) { + // if(tickets.size() > 0) { + // DynmapPlugin.setBusy(world, tickets.get(0)); + // for(int i = 1; i < tickets.size(); i++) { + // ForgeChunkManager.releaseTicket(tickets.get(i)); + // } + // } + // } + //} + + public DynmapMod() { + instance = this; + FMLJavaModLoadingContext.get().getModEventBus().addListener(this::setup); + FMLJavaModLoadingContext.get().getModEventBus().addListener(this::init); + + MinecraftForge.EVENT_BUS.register(this); + + ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, + ()->new IExtensionPoint.DisplayTest(()->IExtensionPoint.DisplayTest.IGNORESERVERONLY, (remote, isServer)-> true)); + + Log.setLogger(new OurLog()); + org.dynmap.modsupport.ModSupportImpl.init(); + } + + public void setup(final FMLCommonSetupEvent event) + { + //TOOO + jarfile = ModList.get().getModFileById("dynmap").getFile().getFilePath().toFile(); + + ver = ModList.get().getModContainerById("dynmap").get().getModInfo().getVersion().toString(); + + //// Load configuration file - use suggested (config/WesterosBlocks.cfg) + //Configuration cfg = new Configuration(event.getSuggestedConfigurationFile()); + //try { + // cfg.load(); + // + // useforcedchunks = cfg.get("Settings", "UseForcedChunks", true).getBoolean(true); + //} + //finally + //{ + // cfg.save(); + //} + } + + public void init(FMLLoadCompleteEvent event) + { + /* Set up for chunk loading notice from chunk manager */ + //TODO + //if(useforcedchunks) { + // ForgeChunkManager.setForcedChunkLoadingCallback(DynmapMod.instance, new LoadingCallback()); + //} + //else { + // Log.info("[Dynmap] World loading using forced chunks is disabled"); + //} + } + + private MinecraftServer server; + + @SubscribeEvent + public void onServerStarting(ServerAboutToStartEvent event) { + server = event.getServer(); + if(plugin == null) + plugin = proxy.startServer(server); + plugin.onStarting(server.getCommands().getDispatcher()); + } + + @SubscribeEvent + public void onServerStarted(ServerStartedEvent event) { + DynmapCommonAPIListener.register(new APICallback()); + plugin.serverStarted(); + } + + @SubscribeEvent + public void serverStopping(ServerStoppingEvent event) + { + proxy.stopServer(plugin); + plugin = null; + } +} diff --git a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/DynmapPlugin.java b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/DynmapPlugin.java new file mode 100644 index 000000000..9acca0cbc --- /dev/null +++ b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/DynmapPlugin.java @@ -0,0 +1,2041 @@ +package org.dynmap.neoforge_1_20_6; + +import java.io.File; +import java.io.InputStream; +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.commands.CommandSource; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.core.BlockPos; +import net.minecraft.core.IdMapper; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.network.Connection; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.game.ClientboundSetSubtitleTextPacket; +import net.minecraft.network.protocol.game.ClientboundSetTitleTextPacket; +import net.minecraft.network.protocol.game.ClientboundSetTitlesAnimationPacket; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.server.players.GameProfileCache; +import net.minecraft.server.players.UserBanList; +import net.minecraft.tags.BlockTags; +import net.minecraft.world.entity.Pose; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.LeavesBlock; +import net.minecraft.world.level.block.LiquidBlock; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.ServerChatEvent; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.event.entity.player.PlayerEvent.PlayerChangedDimensionEvent; +import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedInEvent; +import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedOutEvent; +import net.minecraftforge.event.entity.player.PlayerEvent.PlayerRespawnEvent; +import net.minecraftforge.event.level.BlockEvent; +import net.minecraftforge.event.level.ChunkDataEvent; +import net.minecraftforge.event.level.ChunkEvent; +import net.minecraftforge.event.level.LevelEvent; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.ModContainer; +import net.minecraftforge.fml.loading.LoadingModList; +import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo; +import net.minecraftforge.fml.loading.moddiscovery.ModInfo; +import net.minecraft.world.level.chunk.status.ChunkStatus; + +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.common.chunk.GenericChunkCache; +import org.dynmap.neoforge_1_20_6.DmapCommand; +import org.dynmap.neoforge_1_20_6.DmarkerCommand; +import org.dynmap.neoforge_1_20_6.DynmapCommand; +import org.dynmap.neoforge_1_20_6.permissions.FilePermissions; +import org.dynmap.neoforge_1_20_6.permissions.OpPermissions; +import org.dynmap.neoforge_1_20_6.permissions.PermissionProvider; +import org.dynmap.permissions.PermissionsHandler; +import org.dynmap.renderer.DynmapBlockState; +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.RequiredArgumentBuilder; +import com.mojang.brigadier.exceptions.CommandSyntaxException; + +import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import net.minecraftforge.eventbus.api.EventPriority; +import net.minecraftforge.eventbus.api.SubscribeEvent; + +import net.minecraft.world.level.EmptyBlockGetter; + +public class DynmapPlugin +{ + private DynmapCore core; + private PermissionProvider permissions; + private boolean core_enabled; + public GenericChunkCache sscache; + public PlayerList playerList; + private MapManager mapManager; + private static 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 LevelAccessor 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 useSaveFolder = true; + + 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 { + LevelAccessor w; + String wid; + int x, y, z; + } + ConcurrentLinkedQueue blockupdatequeue = new ConcurrentLinkedQueue(); + + public static DynmapBlockState[] stateByID; + + private Map knownloadedchunks = new HashMap(); + private boolean didInitialKnownChunks = false; + private void addKnownChunk(ForgeWorld fw, ChunkPos pos) { + LongOpenHashSet cset = knownloadedchunks.get(fw.getName()); + if (cset == null) { + cset = new LongOpenHashSet(); + knownloadedchunks.put(fw.getName(), cset); + } + cset.add(pos.toLong()); + } + private void removeKnownChunk(ForgeWorld fw, ChunkPos pos) { + LongOpenHashSet cset = knownloadedchunks.get(fw.getName()); + if (cset != null) { + cset.remove(pos.toLong()); + } + } + private boolean checkIfKnownChunk(ForgeWorld fw, ChunkPos pos) { + LongOpenHashSet cset = knownloadedchunks.get(fw.getName()); + if (cset != null) { + return cset.contains(pos.toLong()); + } + return false; + } + + private static Registry reg = null; + + private static Registry getBiomeReg() { + if (reg == null) { + reg = server.registryAccess().registryOrThrow(Registries.BIOME); + } + return reg; + } + + /** + * 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 + + IdMapper bsids = Block.BLOCK_STATE_REGISTRY; + + DynmapBlockState basebs = null; + Block baseb = null; + int baseidx = 0; + + Iterator iter = bsids.iterator(); + DynmapBlockState.Builder bld = new DynmapBlockState.Builder(); + while (iter.hasNext()) { + BlockState bs = iter.next(); + int idx = bsids.getId(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 = BuiltInRegistries.BLOCK.getKey(b); + + if (ui == null) { + continue; + } + String bn = ui.getNamespace() + ":" + ui.getPath(); + // Only do defined names, and not "air" + if (!bn.equals(DynmapBlockState.AIR_BLOCK)) { + String statename = ""; + for (net.minecraft.world.level.block.state.properties.Property p : bs.getProperties()) { + if (statename.length() > 0) { + statename += ","; + } + statename += p.getName() + "=" + bs.getValue(p).toString(); + } + int lightAtten = 15; + try { // Workaround for mods with broken block state logic... + lightAtten =bs.isSolidRender(EmptyBlockGetter.INSTANCE, BlockPos.ZERO) ? 15 : (bs.propagatesSkylightDown(EmptyBlockGetter.INSTANCE, BlockPos.ZERO) ? 0 : 1); + } catch (Exception x) { + Log.warning(String.format("Exception while checking lighting data for block state: %s[%s]", bn, statename)); + Log.verboseinfo("Exception: " + x.toString()); + } + //Log.info("statename=" + bn + "[" + statename + "], lightAtten=" + lightAtten); + // Fill in base attributes + bld.setBaseState(basebs).setStateIndex(idx - baseidx).setBlockName(bn).setStateName(statename).setLegacyBlockID(idx).setAttenuatesLight(lightAtten); + if (bs.getSoundType() != null) { bld.setMaterial(bs.getSoundType().toString()); } + if (bs.isSolid()) { bld.setSolid(); } + if (bs.isAir()) { bld.setAir(); } + if (bs.is(BlockTags.LOGS)) { bld.setLog(); } + if (bs.is(BlockTags.LEAVES)) { bld.setLeaves(); } + if ((!bs.getFluidState().isEmpty()) && !(bs.getBlock() instanceof LiquidBlock)) { + bld.setWaterlogged(); + } + DynmapBlockState dbs = bld.build(); // Build state + stateByID[idx] = dbs; + if (basebs == null) { basebs = dbs; } + } + } + 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); + //} + + private static Biome[] biomelist = null; + + public static final Biome[] getBiomeList() { + if (biomelist == null) { + biomelist = new Biome[256]; + Iterator iter = getBiomeReg().iterator(); + while (iter.hasNext()) { + Biome b = iter.next(); + int bidx = getBiomeReg().getId(b); + if (bidx >= biomelist.length) { + biomelist = Arrays.copyOf(biomelist, bidx + biomelist.length); + } + biomelist[bidx] = b; + } + } + return biomelist; + } + //public static final NetworkManager getNetworkManager(ServerPlayNetHandler nh) { + // return nh.netManager; + //} + + private ForgePlayer getOrAddPlayer(ServerPlayer p) { + String name = p.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; + ServerPlayer sender; + } + private ConcurrentLinkedQueue msgqueue = new ConcurrentLinkedQueue(); + + public class ChatHandler { + @SubscribeEvent + public void handleChat(ServerChatEvent event) { + String msg = event.getMessage().getString(); + 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().getOps().getUserList(); + for (String op : ops) { + if (op.equalsIgnoreCase(player)) { + return true; + } + } + return (server.isSingleplayer() && player.equalsIgnoreCase(server.getSingleplayerProfile().getName())); + } + + private boolean hasPerm(ServerPlayer psender, String permission) { + PermissionsHandler ph = PermissionsHandler.getHandler(); + if ((psender != null) && (ph != null) && ph.hasPermission(psender.getName().getString(), permission)) { + return true; + } + return permissions.has(psender, permission); + } + + private boolean hasPermNode(ServerPlayer psender, String permission) { + PermissionsHandler ph = PermissionsHandler.getHandler(); + if ((psender != null) && (ph != null) && ph.hasPermissionNode(psender.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) { + GameProfileCache cache = server.getProfileCache(); + Optional val = cache.get(player); + return val.isPresent() ? val.get() : null; + } + + @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++) + { + ServerPlayer p = 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 (ServerPlayer p : players) + { + if (p.getName().getString().equalsIgnoreCase(name)) + { + return getOrAddPlayer(p); + } + } + + return null; + } + @Override + public Set getIPBans() + { + UserBanList bl = server.getPlayerList().getBans(); + Set ips = new HashSet(); + + for (String s : bl.getUserList()) { + 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.getLocalIp(); + if(sn == null) sn = "Unknown Server"; + return sn; + } + @Override + public boolean isPlayerBanned(String pid) + { + UserBanList bl = server.getPlayerList().getBans(); + 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) + { + Component component = Component.literal(msg); + server.getPlayerList().broadcastSystemMessage(component, false); + 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.players.PlayerList scm = server.getPlayerList(); + if (scm == null) return Collections.emptySet(); + UserBanList bl = scm.getBans(); + 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.players.PlayerList scm = server.getPlayerList(); + if (scm == null) return false; + UserBanList bl = scm.getBans(); + 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 (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 (InterruptedException 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().getPlayerCount(); + } + + @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(); + BlockState bs = r.w.getBlockState(new BlockPos(r.x, r.y, r.z)); + int idx = Block.BLOCK_STATE_REGISTRY.getId(bs); + if((idx >= 0) && (!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 generated chunks + if((cur_tick % 20) == 0) { + } + } + + @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.getLocalIp(); + } + @Override + public File getModContainerFile(String name) { + ModFileInfo mfi = LoadingModList.get().getModFileById(name); // Try case sensitive lookup + if (mfi != null) { + try { + File f = mfi.getFile().getFilePath().toFile(); + return f; + } + catch (UnsupportedOperationException ex) { + //TODO Implement proper jar in jar method for fetching data +/* + Log.info("Searching for: " + name); + for (IModInfo e: ModList.get().getMods()) { + Log.info("in: " + e.getModId().toString()); + Log.info("resource: "+ ModList.get().getModFileById(e.getModId()).getFile().findResource(String.valueOf(mfi.getFile().getFilePath()))); + } +*/ + Log.warning("jar in jar method found, skipping: " + ex.getMessage()); + } + } + return null; + } + @Override + public List getModList() { + List mil = LoadingModList.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) modid = "minecraft"; + + Optional mc = ModList.get().getModContainerById(modid); + Object mod = (mc.isPresent()) ? mc.get().getMod() : null; + if (mod != null) { + ClassLoader cl = mod.getClass().getClassLoader(); + if (cl == null) cl = ClassLoader.getSystemClassLoader(); + InputStream is = cl.getResourceAsStream(rname); + if (is != null) { + return is; + } + } + List mcl = LoadingModList.get().getMods(); + for (ModInfo mci : mcl) { + mc = ModList.get().getModContainerById(mci.getModId()); + mod = (mc.isPresent()) ? mc.get().getMod() : null; + if (mod == null) continue; + ClassLoader cl = mod.getClass().getClassLoader(); + if (cl == null) cl = ClassLoader.getSystemClassLoader(); + InputStream is = cl.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 ServerPlayer player; + private final String skinurl; + private final UUID uuid; + + + public ForgePlayer(ServerPlayer p) + { + player = p; + String url = null; + if (player != null) { + uuid = player.getUUID(); + 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.value()), 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.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; + } + Vec3 v = player.position(); + return toLoc(player.serverLevel(), v.x, v.y, v.z); + } + @Override + public String getWorld() + { + if (player == null) + { + return null; + } + + if (player.serverLevel() != null) + { + return DynmapPlugin.this.getWorld((ServerLevel)player.serverLevel()).getName(); + } + + return null; + } + public static final Connection getNetworkManager(ServerGamePacketListenerImpl nh) { + return nh.getConnection(); + } + + @Override + public InetSocketAddress getAddress() + { + if((player != null) && (player instanceof ServerPlayer)) { + ServerGamePacketListenerImpl nsh = ((ServerPlayer)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.getPose() == Pose.CROUCHING; + } + + 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.getArmorValue(); + } + 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.getName().getString()); + } + @Override + public void sendMessage(String msg) + { + Component ichatcomponent = Component.literal(msg); + player.sendSystemMessage(ichatcomponent); + } + @Override + public boolean isInvisible() { + if(player != null) { + return player.isInvisible(); + } + return false; + } + @Override + public boolean isSpectator() { + if(player != null) { + return player.isSpectator(); + } + 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 ServerPlayer) { + ServerPlayer mp = (ServerPlayer) player; + ClientboundSetTitlesAnimationPacket times = new ClientboundSetTitlesAnimationPacket(fadeInTicks, stayTicks, fadeOutTicks); + mp.connection.send(times); + if (title != null) { + ClientboundSetTitleTextPacket titlepkt = new ClientboundSetTitleTextPacket(Component.literal(title)); + mp.connection.send(titlepkt); + } + + if (subtitle != null) { + ClientboundSetSubtitleTextPacket subtitlepkt = new ClientboundSetSubtitleTextPacket(Component.literal(subtitle)); + mp.connection.send(subtitlepkt); + } + } + } + } + /* Handler for generic console command sender */ + public class ForgeCommandSender implements DynmapCommandSender + { + private CommandSourceStack sender; + + protected ForgeCommandSender() { + sender = null; + } + + public ForgeCommandSender(CommandSourceStack send) + { + sender = send; + } + + @Override + public boolean hasPrivilege(String privid) + { + return true; + } + + @Override + public void sendMessage(String msg) + { + if(sender != null) { + Component ichatcomponent = Component.literal(msg); + sender.sendSuccess(() -> ichatcomponent, true); + } + } + + @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) { + ResourceLocation regid = getBiomeReg().getKey(bb); + String id = regid.getPath(); + String rl = regid.toString(); + float tmp = bb.getBaseTemperature(), hum = bb.getModifiedClimateSettings().downfall(); + int watermult = bb.getWaterColor(); + Log.verboseinfo("biome[" + i + "]: hum=" + hum + ", tmp=" + tmp + ", mult=" + Integer.toHexString(watermult)); + + BiomeMap bmap = BiomeMap.NULL; + if (rl != null) { // If resource location, lookup by this + bmap = BiomeMap.byBiomeResourceLocation(rl); + } + else { + bmap = BiomeMap.byBiomeID(i); + } + if (bmap.isDefault() || (bmap == BiomeMap.NULL)) { + bmap = new BiomeMap((rl != null) ? BiomeMap.NO_INDEX : i, id, tmp, hum, rl); + 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)); + } + bmap.setBiomeObject(bb); + } + } + 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.toString(); + } + } + return lst; + } + + public void onEnable() + { + /* Get MC version */ + String mcver = server.getServerVersion(); + /* 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); + 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 GenericChunkCache(core.getSnapShotCacheSize(), core.useSoftRefInSnapShotCache()); + /* Get map manager from core */ + mapManager = core.getMapManager(); + + /* Load saved world definitions */ + loadWorlds(); + + /* Initialized the currently loaded worlds */ + for (ServerLevel world : server.getAllLevels()) { + ForgeWorld w = this.getWorld(world); + } + 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"); + + //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(CommandSourceStack commandSourceStack, String cmd, String[] args) + { + DynmapCommandSender dsender; + ServerPlayer psender; + try { + psender = commandSourceStack.getPlayerOrException(); + } catch (com.mojang.brigadier.exceptions.CommandSyntaxException x) { + psender = null; + } + + if (psender != null) + { + dsender = new ForgePlayer(psender); + } + else + { + dsender = new ForgeCommandSender(commandSourceStack); + } + 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(ServerLevel level, double x, double y, double z) + { + return new DynmapLocation(DynmapPlugin.this.getWorld(level).getName(), x, y, z); + } + + public class PlayerTracker { + @SubscribeEvent + public void onPlayerLogin(PlayerLoggedInEvent event) { + if(!core_enabled) return; + final DynmapPlayer dp = getOrAddPlayer((ServerPlayer)event.getEntity()); + /* 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((ServerPlayer)event.getEntity()); + final String name = event.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((ServerPlayer)event.getEntity()); // Freshen player object reference + } + @SubscribeEvent + public void onPlayerRespawn(PlayerRespawnEvent event) { + if(!core_enabled) return; + getOrAddPlayer((ServerPlayer)event.getEntity()); // 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(priority=EventPriority.LOWEST) + public void handleWorldLoad(LevelEvent.Load event) { + if(!core_enabled) return; + LevelAccessor w = event.getLevel(); + if(!(w instanceof ServerLevel)) return; + final ForgeWorld fw = getWorld((ServerLevel)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(priority=EventPriority.LOWEST) + public void handleWorldUnload(LevelEvent.Unload event) { + if(!core_enabled) return; + LevelAccessor w = event.getLevel(); + if(!(w instanceof ServerLevel)) return; + final ForgeWorld fw = getWorld((ServerLevel)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(priority=EventPriority.LOWEST) + public void handleChunkLoad(ChunkEvent.Load event) { + if(!onchunkgenerate) return; + + LevelAccessor w = event.getLevel(); + if(!(w instanceof ServerLevel)) return; + ChunkAccess c = event.getChunk(); + if ((c != null) && (c.getStatus() == ChunkStatus.FULL) && (c instanceof LevelChunk)) { + ForgeWorld fw = getWorld((ServerLevel)w, false); + if (fw != null) { + addKnownChunk(fw, c.getPos()); + } + } + } + @SubscribeEvent(priority=EventPriority.LOWEST) + public void handleChunkUnload(ChunkEvent.Unload event) { + if(!onchunkgenerate) return; + + LevelAccessor w = event.getLevel(); + if(!(w instanceof ServerLevel)) return; + ChunkAccess c = event.getChunk(); + if (c != null) { + ForgeWorld fw = getWorld((ServerLevel)w, false); + ChunkPos cp = c.getPos(); + if (fw != null) { + if (!checkIfKnownChunk(fw, cp)) { + int ymax = Integer.MIN_VALUE; + int ymin = Integer.MAX_VALUE; + LevelChunkSection[] sections = c.getSections(); + for(int i = 0; i < sections.length; i++) { + if((sections[i] != null) && (sections[i].hasOnlyAir() == false)) { + int sy = c.getSectionYFromSectionIndex(i); + if (sy < ymin) ymin = sy; + if ((sy+16) > ymax) ymax = sy + 16; + } + } + int x = cp.x << 4; + int z = cp.z << 4; + // If not empty AND not initial scan + if (ymax != Integer.MIN_VALUE) { + //Log.info(String.format("chunkkeyerate(unload)(%s,%d,%d,%d,%d,%d,%s)", fw.getName(), x, ymin, z, x+15, ymax, z+15)); + mapManager.touchVolume(fw.getName(), x, ymin, z, x+15, ymax, z+15, "chunkgenerate"); + } + } + removeKnownChunk(fw, cp); + } + } + } + @SubscribeEvent(priority=EventPriority.LOWEST) + public void handleChunkDataSave(ChunkDataEvent.Save event) { + if(!onchunkgenerate) return; + + LevelAccessor w = event.getLevel(); + if(!(w instanceof ServerLevel)) return; + ChunkAccess c = event.getChunk(); + if (c != null) { + ForgeWorld fw = getWorld((ServerLevel)w, false); + ChunkPos cp = c.getPos(); + if (fw != null) { + if (!checkIfKnownChunk(fw, cp)) { + int ymax = Integer.MIN_VALUE; + int ymin = Integer.MAX_VALUE; + LevelChunkSection[] sections = c.getSections(); + for(int i = 0; i < sections.length; i++) { + if((sections[i] != null) && (sections[i].hasOnlyAir() == false)) { + int sy = c.getSectionYFromSectionIndex(i); + if (sy < ymin) ymin = sy; + if ((sy+16) > ymax) ymax = sy + 16; + } + } + int x = cp.x << 4; + int z = cp.z << 4; + // If not empty AND not initial scan + if (ymax != Integer.MIN_VALUE) { + //Log.info(String.format("chunkkeyerate(save)(%s,%d,%d,%d,%d,%d,%s)", fw.getName(), x, ymin, z, x+15, ymax, z+15)); + mapManager.touchVolume(fw.getName(), x, ymin, z, x+15, ymax, z+15, "chunkgenerate"); + } + // If cooked, add to known + if ((c.getStatus() == ChunkStatus.FULL) && (c instanceof LevelChunk)) { + addKnownChunk(fw, cp); + } + } + } + } + } + @SubscribeEvent(priority=EventPriority.LOWEST) + public void handleBlockEvent(BlockEvent event) { + if(!core_enabled) return; + if(!onblockchange) return; + BlockUpdateRec r = new BlockUpdateRec(); + r.w = event.getLevel(); + if(!(r.w instanceof ServerLevel)) return; // band-aid to prevent errors in unsupported 'running in client' scenario + ForgeWorld fw = getWorld((ServerLevel)r.w, false); + if (fw == null) return; + r.wid = fw.getName(); + BlockPos p = event.getPos(); + r.x = p.getX(); + r.y = p.getY(); + r.z = p.getZ(); + blockupdatequeue.add(r); + } + } + private WorldTracker worldTracker = null; + private boolean onblockchange = false; + private boolean onchunkpopulate = false; + private boolean onchunkgenerate = false; + private boolean onblockchange_with_id = false; + + private void registerEvents() + { + // To trigger rendering. + onblockchange = core.isTrigger("blockupdate"); + onchunkpopulate = core.isTrigger("chunkpopulate"); + onchunkgenerate = core.isTrigger("chunkgenerate"); + onblockchange_with_id = core.isTrigger("blockupdate-with-id"); + if(onblockchange_with_id) + onblockchange = true; + if ((worldTracker == null) && (onblockchange || onchunkpopulate || onchunkgenerate)) { + worldTracker = new WorldTracker(); + MinecraftForge.EVENT_BUS.register(worldTracker); + } + // Prime the known full chunks + if (onchunkgenerate && (server.getAllLevels() != null)) { + for (ServerLevel world : server.getAllLevels()) { + ForgeWorld fw = getWorld(world); + if (fw == null) continue; + Long2ObjectLinkedOpenHashMap chunks = world.getChunkSource().chunkMap.visibleChunkMap; + for (Entry k : chunks.long2ObjectEntrySet()) { + long key = k.getKey().longValue(); + ChunkHolder ch = k.getValue(); + ChunkAccess c = null; + try { + c = ch.getLastAvailable(); + } catch (Exception x) { } + if (c == null) continue; + ChunkStatus cs = c.getStatus(); + ChunkPos pos = ch.getPos(); + if (cs == ChunkStatus.FULL) { // Cooked? + // Add it as known + addKnownChunk(fw, pos); + } + } + } + } + } + + private ForgeWorld getWorldByName(String name) { + return worlds.get(name); + } + + private ForgeWorld getWorld(ServerLevel w) { + return getWorld(w, true); + } + + private ForgeWorld getWorld(ServerLevel 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); + } + fw.updateWorld(w); + 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); + } + last_world = w; + last_fworld = fw; + return fw; + } + + 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("miny", fw.minY); + 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("useSaveFolderAsName", useSaveFolder); + cn.put("maxWorldHeight", ForgeWorld.getMaxWorldHeight()); + + cn.save(); + } + private void loadWorlds() { + File f = new File(core.getDataFolder(), "forgeworlds.yml"); + if(f.canRead() == false) { + useSaveFolder = true; + return; + } + ConfigurationNode cn = new ConfigurationNode(f); + cn.load(); + // If defined, use maxWorldHeight + ForgeWorld.setMaxWorldHeight(cn.getInteger("maxWorldHeight", 256)); + + // If setting defined, use it + if (cn.containsKey("useSaveFolderAsName")) { + useSaveFolder = cn.getBoolean("useSaveFolderAsName", useSaveFolder); + } + 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"); + Integer miny = (Integer) world.get("miny"); + 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, (miny != null) ? miny : 0); + 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, CommandSourceStack commandSourceStack, + String cmdline) { + String[] args = cmdline.split("\\s+"); + plugin.onCommand(commandSourceStack, 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); + } +} + diff --git a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/ForgeMapChunkCache.java b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/ForgeMapChunkCache.java new file mode 100644 index 000000000..62daf05a6 --- /dev/null +++ b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/ForgeMapChunkCache.java @@ -0,0 +1,109 @@ +package org.dynmap.neoforge_1_20_6; + +import java.util.List; +import java.util.NoSuchElementException; + +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeSpecialEffects; +import org.dynmap.DynmapChunk; +import org.dynmap.Log; +import org.dynmap.common.BiomeMap; +import org.dynmap.common.chunk.GenericChunk; +import org.dynmap.common.chunk.GenericChunkCache; +import org.dynmap.common.chunk.GenericMapChunkCache; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.storage.ChunkSerializer; +import net.minecraft.world.level.chunk.status.ChunkStatus; + +/** + * Container for managing chunks - dependent upon using chunk snapshots, since + * rendering is off server thread + */ +public class ForgeMapChunkCache extends GenericMapChunkCache { + private ServerLevel w; + private ServerChunkCache cps; + /** + * Construct empty cache + */ + public ForgeMapChunkCache(GenericChunkCache cc) { + super(cc); + } + + // Load generic chunk from existing and already loaded chunk + protected GenericChunk getLoadedChunk(DynmapChunk chunk) { + GenericChunk gc = null; + ChunkAccess ch = cps.getChunk(chunk.x, chunk.z, ChunkStatus.FULL, false); + if (ch != null) { + CompoundTag nbt = ChunkSerializer.write(w, ch); + if (nbt != null) { + gc = parseChunkFromNBT(new NBT.NBTCompound(nbt)); + } + } + return gc; + } + // Load generic chunk from unloaded chunk + protected GenericChunk loadChunk(DynmapChunk chunk) { + GenericChunk gc = null; + CompoundTag nbt = readChunk(chunk.x, chunk.z); + // If read was good + if (nbt != null) { + gc = parseChunkFromNBT(new NBT.NBTCompound(nbt)); + } + return gc; + } + + public void setChunks(ForgeWorld dw, List chunks) { + this.w = dw.getWorld(); + if (dw.isLoaded()) { + /* Check if world's provider is ServerChunkProvider */ + cps = this.w.getChunkSource(); + } + super.setChunks(dw, chunks); + } + + private CompoundTag readChunk(int x, int z) { + try { + CompoundTag rslt = cps.chunkMap.read(new ChunkPos(x, z)).join().get(); + if (rslt != null) { + CompoundTag lev = rslt; + if (lev.contains("Level")) { + lev = lev.getCompound("Level"); + } + // Don't load uncooked chunks + String stat = lev.getString("Status"); + ChunkStatus cs = ChunkStatus.byName(stat); + if ((stat == null) || + // Needs to be at least lighted + (!cs.isOrAfter(ChunkStatus.LIGHT))) { + rslt = null; + } + } + // Log.info(String.format("loadChunk(%d,%d)=%s", x, z, (rslt != null) ? + // rslt.toString() : "null")); + return rslt; + } catch (NoSuchElementException nsex) { + return null; + } catch (Exception exc) { + Log.severe(String.format("Error reading chunk: %s,%d,%d", dw.getName(), x, z), exc); + return null; + } + } + @Override + public int getFoliageColor(BiomeMap bm, int[] colormap, int x, int z) { + return bm.getBiomeObject().map(Biome::getSpecialEffects) + .flatMap(BiomeSpecialEffects::getFoliageColorOverride) + .orElse(colormap[bm.biomeLookup()]); + } + + @Override + public int getGrassColor(BiomeMap bm, int[] colormap, int x, int z) { + BiomeSpecialEffects effects = bm.getBiomeObject().map(Biome::getSpecialEffects).orElse(null); + if (effects == null) return colormap[bm.biomeLookup()]; + return effects.getGrassColorModifier().modifyColor(x, z, effects.getGrassColorOverride().orElse(colormap[bm.biomeLookup()])); + } +} diff --git a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/ForgeWorld.java b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/ForgeWorld.java new file mode 100644 index 000000000..cd511574b --- /dev/null +++ b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/ForgeWorld.java @@ -0,0 +1,249 @@ +package org.dynmap.neoforge_1_20_6; +/** + * Forge specific implementation of DynmapWorld + */ +import java.util.List; + +import net.minecraft.world.level.ServerLevelAccessor; +import net.minecraft.world.level.border.WorldBorder; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LightLayer; + +import org.dynmap.DynmapChunk; +import org.dynmap.DynmapLocation; +import org.dynmap.DynmapWorld; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.Polygon; + +public class ForgeWorld extends DynmapWorld +{ + private ServerLevelAccessor world; + private final boolean skylight; + private final boolean isnether; + private final boolean istheend; + private final String env; + private DynmapLocation spawnloc = new DynmapLocation(); + private static int maxWorldHeight = 320; // Maximum allows world height + + public static int getMaxWorldHeight() { + return maxWorldHeight; + } + public static void setMaxWorldHeight(int h) { + maxWorldHeight = h; + } + + public static String getWorldName(ServerLevelAccessor w) { + ResourceKey rk = w.getLevel().dimension(); + String id = rk.location().getNamespace() + "_" + rk.location().getPath(); + if (id.equals("minecraft_overworld")) { // Overworld? + return w.getLevel().serverLevelData.getLevelName(); + } + else if (id.equals("minecraft_the_end")) { + return "DIM1"; + } + else if (id.equals("minecraft_the_nether")) { + return "DIM-1"; + } + else { + return id; + } + } + + public void updateWorld(ServerLevelAccessor w) { + this.updateWorldHeights(w.getLevel().getHeight(), w.getLevel().dimensionType().minY(), w.getLevel().getSeaLevel()); + } + + public ForgeWorld(ServerLevelAccessor w) + { + this(getWorldName(w), + w.getLevel().getHeight(), + w.getLevel().getSeaLevel(), + w.getLevel().dimension() == Level.NETHER, + w.getLevel().dimension() == Level.END, + getWorldName(w), + w.getLevel().dimensionType().minY()); + setWorldLoaded(w); + } + public ForgeWorld(String name, int height, int sealevel, boolean nether, boolean the_end, String deftitle, int miny) + { + super(name, (height > maxWorldHeight)?maxWorldHeight:height, sealevel, miny); + world = null; + setTitle(deftitle); + isnether = nether; + istheend = the_end; + skylight = !(isnether || istheend); + + if (isnether) + { + env = "nether"; + } + else if (istheend) + { + env = "the_end"; + } + else + { + env = "normal"; + } + //Log.info(getName() + ": skylight=" + skylight + ", height=" + this.worldheight + ", isnether=" + isnether + ", istheend=" + istheend); + } + /* Test if world is nether */ + @Override + public boolean isNether() + { + return isnether; + } + public boolean isTheEnd() + { + return istheend; + } + /* Get world spawn location */ + @Override + public DynmapLocation getSpawnLocation() + { + if(world != null) { + BlockPos p = world.getLevel().getSharedSpawnPos(); + spawnloc.x = p.getX(); + spawnloc.y = p.getY(); + spawnloc.z = p.getZ(); + spawnloc.world = this.getName(); + } + return spawnloc; + } + /* Get world time */ + @Override + public long getTime() + { + if(world != null) + return world.getLevel().getDayTime(); + else + return -1; + } + /* World is storming */ + @Override + public boolean hasStorm() + { + if(world != null) + return world.getLevel().isRaining(); + else + return false; + } + /* World is thundering */ + @Override + public boolean isThundering() + { + if(world != null) + return world.getLevel().isThundering(); + else + return false; + } + /* World is loaded */ + @Override + public boolean isLoaded() + { + return (world != null); + } + /* Set world to unloaded */ + @Override + public void setWorldUnloaded() + { + getSpawnLocation(); + world = null; + } + /* Set world to loaded */ + public void setWorldLoaded(ServerLevelAccessor w) { + world = w; + this.sealevel = w.getLevel().getSeaLevel(); // Read actual current sealevel from world + // Update lighting table + for (int i = 0; i < 16; i++) { + // Algorithm based on LightmapTextureManager.getBrightness() + // We can't call that method because it's client-only. + // This means the code below can stop being correct if Mojang ever + // updates the curve; in that case we should reflect the changes. + float value = (float) i / 15.0f; + float brightness = value / (4.0f - 3.0f * value); + this.setBrightnessTableEntry(i, brightness); + //Log.info(getName() + ": light " + i + " = " + light); + } + } + /* Get light level of block */ + @Override + public int getLightLevel(int x, int y, int z) + { + if(world != null) + return world.getLevel().getLightEngine().getRawBrightness(new BlockPos(x, y, z), 0); + else + return -1; + } + /* Get highest Y coord of given location */ + @Override + public int getHighestBlockYAt(int x, int z) + { + if(world != null) { + return world.getLevel().getChunk(x >> 4, z >> 4).getHeight(Heightmap.Types.MOTION_BLOCKING, x & 15, z & 15); + } + else + return -1; + } + /* Test if sky light level is requestable */ + @Override + public boolean canGetSkyLightLevel() + { + return skylight; + } + /* Return sky light level */ + @Override + public int getSkyLightLevel(int x, int y, int z) + { + if(world != null) { + return world.getLevel().getBrightness(LightLayer.SKY, new BlockPos(x, y, z)); + } + else + return -1; + } + /** + * Get world environment ID (lower case - normal, the_end, nether) + */ + @Override + public String getEnvironment() + { + return env; + } + /** + * Get map chunk cache for world + */ + @Override + public MapChunkCache getChunkCache(List chunks) + { + if (world != null) { + ForgeMapChunkCache c = new ForgeMapChunkCache(DynmapPlugin.plugin.sscache); + c.setChunks(this, chunks); + return c; + } + return null; + } + + public ServerLevel getWorld() + { + return world.getLevel(); + } + @Override + public Polygon getWorldBorder() { + if (world != null) { + WorldBorder wb = world.getWorldBorder(); + if ((wb != null) && (wb.getSize() < 5.9E7)) { + Polygon p = new Polygon(); + p.addVertex(wb.getMinX(), wb.getMinZ()); + p.addVertex(wb.getMinX(), wb.getMaxZ()); + p.addVertex(wb.getMaxX(), wb.getMaxZ()); + p.addVertex(wb.getMaxX(), wb.getMinZ()); + return p; + } + } + return null; + } +} diff --git a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/NBT.java b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/NBT.java new file mode 100644 index 000000000..93675bc8a --- /dev/null +++ b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/NBT.java @@ -0,0 +1,126 @@ +package org.dynmap.neoforge_1_20_6; + +import org.dynmap.common.chunk.GenericBitStorage; +import org.dynmap.common.chunk.GenericNBTCompound; +import org.dynmap.common.chunk.GenericNBTList; + +import java.util.Set; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.util.SimpleBitStorage; + +public class NBT { + + public static class NBTCompound implements GenericNBTCompound { + private final CompoundTag obj; + public NBTCompound(CompoundTag t) { + this.obj = t; + } + @Override + public Set getAllKeys() { + return obj.getAllKeys(); + } + @Override + public boolean contains(String s) { + return obj.contains(s); + } + @Override + public boolean contains(String s, int i) { + return obj.contains(s, i); + } + @Override + public byte getByte(String s) { + return obj.getByte(s); + } + @Override + public short getShort(String s) { + return obj.getShort(s); + } + @Override + public int getInt(String s) { + return obj.getInt(s); + } + @Override + public long getLong(String s) { + return obj.getLong(s); + } + @Override + public float getFloat(String s) { + return obj.getFloat(s); + } + @Override + public double getDouble(String s) { + return obj.getDouble(s); + } + @Override + public String getString(String s) { + return obj.getString(s); + } + @Override + public byte[] getByteArray(String s) { + return obj.getByteArray(s); + } + @Override + public int[] getIntArray(String s) { + return obj.getIntArray(s); + } + @Override + public long[] getLongArray(String s) { + return obj.getLongArray(s); + } + @Override + public GenericNBTCompound getCompound(String s) { + return new NBTCompound(obj.getCompound(s)); + } + @Override + public GenericNBTList getList(String s, int i) { + return new NBTList(obj.getList(s, i)); + } + @Override + public boolean getBoolean(String s) { + return obj.getBoolean(s); + } + @Override + public String getAsString(String s) { + return obj.get(s).getAsString(); + } + @Override + public GenericBitStorage makeBitStorage(int bits, int count, long[] data) { + return new OurBitStorage(bits, count, data); + } + public String toString() { + return obj.toString(); + } + } + public static class NBTList implements GenericNBTList { + private final ListTag obj; + public NBTList(ListTag t) { + obj = t; + } + @Override + public int size() { + return obj.size(); + } + @Override + public String getString(int idx) { + return obj.getString(idx); + } + @Override + public GenericNBTCompound getCompound(int idx) { + return new NBTCompound(obj.getCompound(idx)); + } + public String toString() { + return obj.toString(); + } + } + public static class OurBitStorage implements GenericBitStorage { + private final SimpleBitStorage bs; + public OurBitStorage(int bits, int count, long[] data) { + bs = new SimpleBitStorage(bits, count, data); + } + @Override + public int get(int idx) { + return bs.get(idx); + } + } +} diff --git a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/Proxy.java b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/Proxy.java new file mode 100644 index 000000000..85a01235a --- /dev/null +++ b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/Proxy.java @@ -0,0 +1,24 @@ +package org.dynmap.neoforge_1_20_6; + +import net.minecraft.server.MinecraftServer; + +/** + * Server side proxy - methods for creating and cleaning up plugin + */ +public class Proxy +{ + public Proxy() + { + } + public DynmapPlugin startServer(MinecraftServer srv) { + DynmapPlugin plugin = DynmapPlugin.plugin; + if (plugin == null) { + plugin = new DynmapPlugin(srv); + plugin.onEnable(); + } + return plugin; + } + public void stopServer(DynmapPlugin plugin) { + plugin.onDisable(); + } +} diff --git a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/VersionCheck.java b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/VersionCheck.java new file mode 100644 index 000000000..3840bdb8b --- /dev/null +++ b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/VersionCheck.java @@ -0,0 +1,97 @@ +package org.dynmap.neoforge_1_20_6; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; + +import org.dynmap.DynmapCore; +import org.dynmap.Log; + +public class VersionCheck { + private static final String VERSION_URL = "http://mikeprimm.com/dynmap/releases.php"; + public static void runCheck(final DynmapCore core) { + new Thread(new Runnable() { + public void run() { + doCheck(core); + } + }).start(); + } + + private static int getReleaseVersion(String s) { + int index = s.lastIndexOf('-'); + if(index < 0) + index = s.lastIndexOf('.'); + if(index >= 0) + s = s.substring(0, index); + String[] split = s.split("\\."); + int v = 0; + try { + for(int i = 0; (i < split.length) && (i < 3); i++) { + v += Integer.parseInt(split[i]) << (8 * (2 - i)); + } + } catch (NumberFormatException nfx) {} + return v; + } + + private static int getBuildNumber(String s) { + int index = s.lastIndexOf('-'); + if(index < 0) + index = s.lastIndexOf('.'); + if(index >= 0) + s = s.substring(index+1); + try { + return Integer.parseInt(s); + } catch (NumberFormatException nfx) { + return 99999999; + } + } + + private static void doCheck(DynmapCore core) { + String pluginver = core.getDynmapPluginVersion(); + String platform = core.getDynmapPluginPlatform(); + String platver = core.getDynmapPluginPlatformVersion(); + if((pluginver == null) || (platform == null) || (platver == null)) + return; + HttpURLConnection conn = null; + String loc = VERSION_URL; + int cur_ver = getReleaseVersion(pluginver); + int cur_bn = getBuildNumber(pluginver); + try { + while((loc != null) && (!loc.isEmpty())) { + URL url = new URL(loc); + conn = (HttpURLConnection) url.openConnection(); + conn.setRequestProperty("User-Agent", "Dynmap (" + platform + "/" + platver + "/" + pluginver); + conn.connect(); + loc = conn.getHeaderField("Location"); + } + BufferedReader rdr = new BufferedReader(new InputStreamReader(conn.getInputStream())); + String line = null; + while((line = rdr.readLine()) != null) { + String[] split = line.split(":"); + if(split.length < 4) continue; + /* If our platform and version, or wildcard platform version */ + if(split[0].equals(platform) && (split[1].equals("*") || split[1].equals(platver))) { + int recommended_ver = getReleaseVersion(split[2]); + int recommended_bn = getBuildNumber(split[2]); + if((recommended_ver > cur_ver) || ((recommended_ver == cur_ver) && (recommended_bn > cur_bn))) { /* Newer recommended build */ + Log.info("Version obsolete: new recommended version " + split[2] + " is available."); + } + else if(cur_ver > recommended_ver) { /* Running dev or prerelease? */ + int prerel_ver = getReleaseVersion(split[3]); + int prerel_bn = getBuildNumber(split[3]); + if((prerel_ver > cur_ver) || ((prerel_ver == cur_ver) && (prerel_bn > cur_bn))) { + Log.info("Version obsolete: new prerelease version " + split[3] + " is available."); + } + } + } + } + } catch (Exception x) { + Log.info("Error checking for latest version"); + } finally { + if(conn != null) { + conn.disconnect(); + } + } + } +} diff --git a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/permissions/FilePermissions.java b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/permissions/FilePermissions.java new file mode 100644 index 000000000..e02a554b5 --- /dev/null +++ b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/permissions/FilePermissions.java @@ -0,0 +1,103 @@ +package org.dynmap.neoforge_1_20_6.permissions; + +import java.io.File; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.dynmap.ConfigurationNode; +import org.dynmap.Log; +import org.dynmap.neoforge_1_20_6.DynmapPlugin; + +import net.minecraft.server.level.ServerPlayer; + +public class FilePermissions implements PermissionProvider { + private HashMap> perms; + private Set defperms; + + public static FilePermissions create() { + File f = new File("dynmap/permissions.yml"); + if(!f.exists()) + return null; + ConfigurationNode cfg = new ConfigurationNode(f); + cfg.load(); + + Log.info("Using permissions.yml for access control"); + + return new FilePermissions(cfg); + } + + private FilePermissions(ConfigurationNode cfg) { + perms = new HashMap>(); + for(String k : cfg.keySet()) { + List p = cfg.getStrings(k, null); + if(p != null) { + k = k.toLowerCase(); + HashSet pset = new HashSet(); + for(String perm : p) { + pset.add(perm.toLowerCase()); + } + perms.put(k, pset); + if(k.equals("defaultuser")) { + defperms = pset; + } + } + } + } + + private boolean hasPerm(String player, String perm) { + Set ps = perms.get(player); + if((ps != null) && (ps.contains(perm))) { + return true; + } + if(defperms.contains(perm)) { + return true; + } + return false; + } + @Override + public Set hasOfflinePermissions(String player, Set perms) { + player = player.toLowerCase(); + HashSet rslt = new HashSet(); + if(DynmapPlugin.plugin.isOp(player)) { + rslt.addAll(perms); + } + else { + for(String p : perms) { + if(hasPerm(player, p)) { + rslt.add(p); + } + } + } + return rslt; + } + @Override + public boolean hasOfflinePermission(String player, String perm) { + player = player.toLowerCase(); + if(DynmapPlugin.plugin.isOp(player)) { + return true; + } + else { + return hasPerm(player, perm); + } + } + + @Override + public boolean has(ServerPlayer psender, String permission) { + if(psender != null) { + String n = psender.getName().getString().toLowerCase(); + return hasPerm(n, permission); + } + return true; + } + @Override + public boolean hasPermissionNode(ServerPlayer psender, String permission) { + if(psender != null) { + String player = psender.getName().getString().toLowerCase(); + return DynmapPlugin.plugin.isOp(player); + } + return false; + } + +} diff --git a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/permissions/OpPermissions.java b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/permissions/OpPermissions.java new file mode 100644 index 000000000..8d7e74cd6 --- /dev/null +++ b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/permissions/OpPermissions.java @@ -0,0 +1,51 @@ +package org.dynmap.neoforge_1_20_6.permissions; + +import java.util.HashSet; +import java.util.Set; + +import org.dynmap.Log; +import org.dynmap.neoforge_1_20_6.DynmapPlugin; + +import net.minecraft.server.level.ServerPlayer; + +public class OpPermissions implements PermissionProvider { + public HashSet usrCommands = new HashSet(); + + public OpPermissions(String[] usrCommands) { + for (String usrCommand : usrCommands) { + this.usrCommands.add(usrCommand); + } + Log.info("Using ops.txt for access control"); + } + + @Override + public Set hasOfflinePermissions(String player, Set perms) { + HashSet rslt = new HashSet(); + if(DynmapPlugin.plugin.isOp(player)) { + rslt.addAll(perms); + } + return rslt; + } + @Override + public boolean hasOfflinePermission(String player, String perm) { + return DynmapPlugin.plugin.isOp(player); + } + + @Override + public boolean has(ServerPlayer psender, String permission) { + if(psender != null) { + if(usrCommands.contains(permission)) { + return true; + } + return DynmapPlugin.plugin.isOp(psender.getName().getString()); + } + return true; + } + @Override + public boolean hasPermissionNode(ServerPlayer psender, String permission) { + if(psender != null) { + return DynmapPlugin.plugin.isOp(psender.getName().getString()); + } + return true; + } +} diff --git a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/permissions/PermissionProvider.java b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/permissions/PermissionProvider.java new file mode 100644 index 000000000..183baae70 --- /dev/null +++ b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/permissions/PermissionProvider.java @@ -0,0 +1,15 @@ +package org.dynmap.neoforge_1_20_6.permissions; + +import java.util.Set; + +import net.minecraft.server.level.ServerPlayer; + +public interface PermissionProvider { + boolean has(ServerPlayer sender, String permission); + boolean hasPermissionNode(ServerPlayer sender, String permission); + + Set hasOfflinePermissions(String player, Set perms); + + boolean hasOfflinePermission(String player, String perm); + +} diff --git a/neoforge-1.20.6/src/main/resources/META-INF/accesstransformer.cfg b/neoforge-1.20.6/src/main/resources/META-INF/accesstransformer.cfg new file mode 100644 index 000000000..9f6e7211e --- /dev/null +++ b/neoforge-1.20.6/src/main/resources/META-INF/accesstransformer.cfg @@ -0,0 +1,4 @@ +public net.minecraft.world.level.biome.BiomeSpecialEffects$Builder f_48006_ # waterColor +public net.minecraft.server.level.ServerLevel f_8549_ # serverLevelData +public net.minecraft.server.level.ChunkMap f_140130_ # visibleChunkMap +public net.minecraft.server.level.ChunkMap m_214963_(Lnet/minecraft/world/level/ChunkPos;)Ljava/util/concurrent/CompletableFuture; # readChunk( diff --git a/neoforge-1.20.6/src/main/resources/META-INF/neoforge.mods.toml b/neoforge-1.20.6/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 000000000..06fd2abea --- /dev/null +++ b/neoforge-1.20.6/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,27 @@ +modLoader="javafml" +loaderVersion="[2,)" +issueTrackerURL="https://github.com/webbukkit/dynmap/issues" +license="Apache Public License v2" +[[mods]] +modId="dynmap" +version="${version}" +displayName="Dynmap" +authors="mikeprimm" +description=''' +Dynamic, Google-maps style rendered maps for your Minecraft server +''' +[[accessTransformers]] +file="META-INF/accesstransformer.cfg" +[[dependencies.dynmap]] + modId="neoforge" + mandatory=true + versionRange="[20.6,)" + ordering="NONE" + # Side this dependency is applied on - BOTH, CLIENT or SERVER + side="SERVER" +[[dependencies.dynmap]] + modId="minecraft" + mandatory=true + versionRange="[1.20.6,1.21)" + ordering="NONE" + side="SERVER" diff --git a/neoforge-1.20.6/src/main/resources/configuration.txt b/neoforge-1.20.6/src/main/resources/configuration.txt new file mode 100644 index 000000000..38174f817 --- /dev/null +++ b/neoforge-1.20.6/src/main/resources/configuration.txt @@ -0,0 +1,504 @@ +# All paths in this configuration file are relative to Dynmap's data-folder: minecraft_server/dynmap/ + +# All map templates are defined in the templates directory +# To use the HDMap very-low-res (2 ppb) map templates as world defaults, set value to vlowres +# The definitions of these templates are in normal-vlowres.txt, nether-vlowres.txt, and the_end-vlowres.txt +# To use the HDMap low-res (4 ppb) map templates as world defaults, set value to lowres +# The definitions of these templates are in normal-lowres.txt, nether-lowres.txt, and the_end-lowres.txt +# To use the HDMap hi-res (16 ppb) map templates (these can take a VERY long time for initial fullrender), set value to hires +# The definitions of these templates are in normal-hires.txt, nether-hires.txt, and the_end-hires.txt +# To use the HDMap low-res (4 ppb) map templates, with support for boosting resolution selectively to hi-res (16 ppb), set value to low_boost_hi +# The definitions of these templates are in normal-low_boost_hi.txt, nether-low_boost_hi.txt, and the_end-low_boost_hi.txt +# To use the HDMap hi-res (16 ppb) map templates, with support for boosting resolution selectively to vhi-res (32 ppb), set value to hi_boost_vhi +# The definitions of these templates are in normal-hi_boost_vhi.txt, nether-hi_boost_vhi.txt, and the_end-hi_boost_vhi.txt +# To use the HDMap hi-res (16 ppb) map templates, with support for boosting resolution selectively to xhi-res (64 ppb), set value to hi_boost_xhi +# The definitions of these templates are in normal-hi_boost_xhi.txt, nether-hi_boost_xhi.txt, and the_end-hi_boost_xhi.txt +deftemplatesuffix: hires + +# Set default tile scale (0 = 128px x 128x, 1 = 256px x 256px, 2 = 512px x 512px, 3 = 1024px x 1024px, 4 = 2048px x 2048px) - 0 is default +# Note: changing this value will result in all maps that use the default value being required to be fully rendered +#defaulttilescale: 0 + +# Map storage scheme: only uncommoent one 'type' value +# filetree: classic and default scheme: tree of files, with all map data under the directory indicated by 'tilespath' setting +# sqlite: single SQLite database file (this can get VERY BIG), located at 'dbfile' setting (default is file dynmap.db in data directory) +# mysql: MySQL database, at hostname:port in database, accessed via userid with password +# mariadb: MariaDB database, at hostname:port in database, accessed via userid with password +# postgres: PostgreSQL database, at hostname:port in database, accessed via userid with password +storage: + # Filetree storage (standard tree of image files for maps) + type: filetree + # SQLite db for map storage (uses dbfile as storage location) + #type: sqlite + #dbfile: dynmap.db + # MySQL DB for map storage (at 'hostname':'port' in database 'database' using user 'userid' password 'password' and table prefix 'prefix' + #type: mysql + #hostname: localhost + #port: 3306 + #database: dynmap + #userid: dynmap + #password: dynmap + #prefix: "" + # + # AWS S3 backet web site + #type: aws_s3 + #bucketname: "dynmap-bucket-name" + #region: us-east-1 + #aws_access_key_id: "" + #aws_secret_access_key: "" + #prefix: "" + #override_endpoint: "" + +components: + - class: org.dynmap.ClientConfigurationComponent + + # Remember to change the following class to org.dynmap.JsonFileClientUpdateComponent when using an external web server. + - class: org.dynmap.InternalClientUpdateComponent + sendhealth: true + sendposition: true + allowwebchat: true + webchat-interval: 5 + hidewebchatip: false + trustclientname: false + includehiddenplayers: false + # (optional) if true, color codes in player display names are used + use-name-colors: false + # (optional) if true, player login IDs will be used for web chat when their IPs match + use-player-login-ip: true + # (optional) if use-player-login-ip is true, setting this to true will cause chat messages not matching a known player IP to be ignored + require-player-login-ip: false + # (optional) block player login IDs that are banned from chatting + block-banned-player-chat: true + # Require login for web-to-server chat (requires login-enabled: true) + webchat-requires-login: false + # If set to true, users must have dynmap.webchat permission in order to chat + webchat-permissions: false + # Limit length of single chat messages + chatlengthlimit: 256 + # # Optional - make players hidden when they are inside/underground/in shadows (#=light level: 0=full shadow,15=sky) + # hideifshadow: 4 + # # Optional - make player hidden when they are under cover (#=sky light level,0=underground,15=open to sky) + # hideifundercover: 14 + # # (Optional) if true, players that are crouching/sneaking will be hidden + hideifsneaking: false + # optional, if true, players that are in spectator mode will be hidden + hideifspectator: false + # If true, player positions/status is protected (login with ID with dynmap.playermarkers.seeall permission required for info other than self) + protected-player-info: false + # If true, hide players with invisibility potion effects active + hide-if-invisiblity-potion: true + # If true, player names are not shown on map, chat, list + hidenames: false + #- class: org.dynmap.JsonFileClientUpdateComponent + # writeinterval: 1 + # sendhealth: true + # sendposition: true + # allowwebchat: true + # webchat-interval: 5 + # hidewebchatip: false + # includehiddenplayers: false + # use-name-colors: false + # use-player-login-ip: false + # require-player-login-ip: false + # block-banned-player-chat: true + # hideifshadow: 0 + # hideifundercover: 0 + # hideifsneaking: false + # # Require login for web-to-server chat (requires login-enabled: true) + # webchat-requires-login: false + # # If set to true, users must have dynmap.webchat permission in order to chat + # webchat-permissions: false + # # Limit length of single chat messages + # chatlengthlimit: 256 + # hide-if-invisiblity-potion: true + # hidenames: false + + - class: org.dynmap.SimpleWebChatComponent + allowchat: true + # If true, web UI users can supply name for chat using 'playername' URL parameter. 'trustclientname' must also be set true. + allowurlname: false + + # Note: this component is needed for the dmarker commands, and for the Marker API to be available to other plugins + - class: org.dynmap.MarkersComponent + type: markers + showlabel: false + enablesigns: false + # Default marker set for sign markers + default-sign-set: markers + # (optional) add spawn point markers to standard marker layer + showspawn: true + spawnicon: world + spawnlabel: "Spawn" + # (optional) layer for showing offline player's positions (for 'maxofflinetime' minutes after logoff) + showofflineplayers: false + offlinelabel: "Offline" + offlineicon: offlineuser + offlinehidebydefault: true + offlineminzoom: 0 + maxofflinetime: 30 + # (optional) layer for showing player's spawn beds + showspawnbeds: false + spawnbedlabel: "Spawn Beds" + spawnbedicon: bed + spawnbedhidebydefault: true + spawnbedminzoom: 0 + spawnbedformat: "%name%'s bed" + spawnbedremoveonplayerleave: true + # (optional) Show world border (vanilla 1.8+) + showworldborder: true + worldborderlabel: "Border" + + - class: org.dynmap.ClientComponent + type: chat + allowurlname: false + - class: org.dynmap.ClientComponent + type: chatballoon + focuschatballoons: false + - class: org.dynmap.ClientComponent + type: chatbox + showplayerfaces: true + messagettl: 5 + # Optional: set number of lines in scrollable message history: if set, messagettl is not used to age out messages + #scrollback: 100 + # Optional: set maximum number of lines visible for chatbox + #visiblelines: 10 + # Optional: send push button + sendbutton: false + - class: org.dynmap.ClientComponent + type: playermarkers + showplayerfaces: true + showplayerhealth: true + # If true, show player body too (only valid if showplayerfaces=true) + showplayerbody: false + # Option to make player faces small - don't use with showplayerhealth or largeplayerfaces + smallplayerfaces: false + # Option to make player faces larger - don't use with showplayerhealth or smallplayerfaces + largeplayerfaces: false + # Optional - make player faces layer hidden by default + hidebydefault: false + # Optional - ordering priority in layer menu (low goes before high - default is 0) + layerprio: 0 + # Optional - label for player marker layer (default is 'Players') + label: "Players" + + #- class: org.dynmap.ClientComponent + # type: digitalclock + - class: org.dynmap.ClientComponent + type: link + + - class: org.dynmap.ClientComponent + type: timeofdayclock + showdigitalclock: true + #showweather: true + # Mouse pointer world coordinate display + - class: org.dynmap.ClientComponent + type: coord + label: "Location" + hidey: false + show-mcr: false + show-chunk: false + + # Note: more than one logo component can be defined + #- class: org.dynmap.ClientComponent + # type: logo + # text: "Dynmap" + # #logourl: "images/block_surface.png" + # linkurl: "http://forums.bukkit.org/threads/dynmap.489/" + # # Valid positions: top-left, top-right, bottom-left, bottom-right + # position: bottom-right + + #- class: org.dynmap.ClientComponent + # type: inactive + # timeout: 1800 # in seconds (1800 seconds = 30 minutes) + # redirecturl: inactive.html + # #showmessage: 'You were inactive for too long.' + + #- class: org.dynmap.TestComponent + # stuff: "This is some configuration-value" + +# Treat hiddenplayers.txt as a whitelist for players to be shown on the map? (Default false) +display-whitelist: false + +# How often a tile gets rendered (in seconds). +renderinterval: 1 + +# How many tiles on update queue before accelerate render interval +renderacceleratethreshold: 60 + +# How often to render tiles when backlog is above renderacceleratethreshold +renderaccelerateinterval: 0.2 + +# How many update tiles to work on at once (if not defined, default is 1/2 the number of cores) +tiles-rendered-at-once: 2 + +# If true, use normal priority threads for rendering (versus low priority) - this can keep rendering +# from starving on busy Windows boxes (Linux JVMs pretty much ignore thread priority), but may result +# in more competition for CPU resources with other processes +usenormalthreadpriority: true + +# Save and restore pending tile renders - prevents their loss on server shutdown or /reload +saverestorepending: true + +# Save period for pending jobs (in seconds): periodic saving for crash recovery of jobs +save-pending-period: 900 + +# Zoom-out tile update period - how often to scan for and process tile updates into zoom-out tiles (in seconds) +zoomoutperiod: 30 + +# Control whether zoom out tiles are validated on startup (can be needed if zoomout processing is interrupted, but can be expensive on large maps) +initial-zoomout-validate: true + +# Default delay on processing of updated tiles, in seconds. This can reduce potentially expensive re-rendering +# of frequently updated tiles (such as due to machines, pistons, quarries or other automation). Values can +# also be set on individual worlds and individual maps. +tileupdatedelay: 30 + +# Tile hashing is used to minimize tile file updates when no changes have occurred - set to false to disable +enabletilehash: true + +# Optional - hide ores: render as normal stone (so that they aren't revealed by maps) +#hideores: true + +# Optional - enabled BetterGrass style rendering of grass and snow block sides +#better-grass: true + +# Optional - enable smooth lighting by default on all maps supporting it (can be set per map as lighting option) +smooth-lighting: true + +# Optional - use world provider lighting table (good for custom worlds with custom lighting curves, like nether) +# false=classic Dynmap lighting curve +use-brightness-table: true + +# Optional - render specific block names using the textures and models of another block name: can be used to hide/disguise specific +# blocks (e.g. make ores look like stone, hide chests) or to provide simple support for rendering unsupported custom blocks +block-alias: +# "minecraft:quartz_ore": "stone" +# "diamond_ore": "coal_ore" + +# Default image format for HDMaps (png, jpg, jpg-q75, jpg-q80, jpg-q85, jpg-q90, jpg-q95, jpg-q100, webp, webp-q75, webp-q80, webp-q85, webp-q90, webp-q95, webp-q100), +# Note: any webp format requires the presence of the 'webp command line tools' (cwebp, dwebp) (https://developers.google.com/speed/webp/download) +# +# Has no effect on maps with explicit format settings +image-format: jpg-q90 + +# If cwebp or dwebp are not on the PATH, use these settings to provide their full path. Do not use these settings if the tools are on the PATH +# For Windows, include .exe +# +#cwebpPath: /usr/bin/cwebp +#dwebpPath: /usr/bin/dwebp + +# use-generated-textures: if true, use generated textures (same as client); false is static water/lava textures +# correct-water-lighting: if true, use corrected water lighting (same as client); false is legacy water (darker) +# transparent-leaves: if true, leaves are transparent (lighting-wise): false is needed for some Spout versions that break lighting on leaf blocks +use-generated-textures: true +correct-water-lighting: true +transparent-leaves: true + +# ctm-support: if true, Connected Texture Mod (CTM) in texture packs is enabled (default) +ctm-support: true +# custom-colors-support: if true, Custom Colors in texture packs is enabled (default) +custom-colors-support: true + +# Control loading of player faces (if set to false, skins are never fetched) +#fetchskins: false + +# Control updating of player faces, once loaded (if faces are being managed by other apps or manually) +#refreshskins: false + +# Customize URL used for fetching player skins (%player% is macro for name, %uuid% for UUID) +skin-url: "http://skins.minecraft.net/MinecraftSkins/%player%.png" + +# Control behavior for new (1.0+) compass orientation (sunrise moved 90 degrees: east is now what used to be south) +# default is 'newrose' (preserve pre-1.0 maps, rotate rose) +# 'newnorth' is used to rotate maps and rose (requires fullrender of any HDMap map - same as 'newrose' for FlatMap or KzedMap) +compass-mode: newnorth + +# Triggers for automatic updates : blockupdate-with-id is debug for breaking down updates by ID:meta +# To disable, set just 'none' and comment/delete the rest +render-triggers: + - blockupdate + #- blockupdate-with-id + #- lightingupdate + - chunkpopulate + - chunkgenerate + #- none + +# Title for the web page - if not specified, defaults to the server's name (unless it is the default of 'Unknown Server') +#webpage-title: "My Awesome Server Map" + +# The path where the tile-files are placed. +tilespath: web/tiles + +# The path where the web-files are located. +webpath: web + +# If set to false, disable extraction of webpath content (good if using custom web UI or 3rd party web UI) +# Note: web interface is unsupported in this configuration - you're on your own +update-webpath-files: true + +# The path were the /dynmapexp command exports OBJ ZIP files +exportpath: export + +# The path where files can be imported for /dmarker commands +importpath: import + +# The network-interface the webserver will bind to (0.0.0.0 for all interfaces, 127.0.0.1 for only local access). +# If not set, uses same setting as server in server.properties (or 0.0.0.0 if not specified) +#webserver-bindaddress: 0.0.0.0 + +# The TCP-port the webserver will listen on. +webserver-port: 8123 + +# Maximum concurrent session on internal web server - limits resources used in Bukkit server +max-sessions: 30 + +# Disables Webserver portion of Dynmap (Advanced users only) +disable-webserver: false + +# Enable/disable having the web server allow symbolic links (true=compatible with existing code, false=more secure (default)) +allow-symlinks: true + +# Enable login support +login-enabled: false +# Require login to access website (requires login-enabled: true) +login-required: false + +# Period between tile renders for fullrender, in seconds (non-zero to pace fullrenders, lessen CPU load) +timesliceinterval: 0.0 + +# Maximum chunk loads per server tick (1/20th of a second) - reducing this below 90 will impact render performance, but also will reduce server thread load +maxchunkspertick: 200 + +# Progress report interval for fullrender/radiusrender, in tiles. Must be 100 or greater +progressloginterval: 100 + +# Parallel fullrender: if defined, number of concurrent threads used for fullrender or radiusrender +# Note: setting this will result in much more intensive CPU use, some additional memory use. Caution should be used when +# setting this to equal or exceed the number of physical cores on the system. +#parallelrendercnt: 4 + +# Interval the browser should poll for updates. +updaterate: 2000 + +# If nonzero, server will pause fullrender/radiusrender processing when 'fullrenderplayerlimit' or more users are logged in +fullrenderplayerlimit: 0 +# If nonzero, server will pause update render processing when 'updateplayerlimit' or more users are logged in +updateplayerlimit: 0 +# Target limit on server thread use - msec per tick +per-tick-time-limit: 50 +# If TPS of server is below this setting, update renders processing is paused +update-min-tps: 18.0 +# If TPS of server is below this setting, full/radius renders processing is paused +fullrender-min-tps: 18.0 +# If TPS of server is below this setting, zoom out processing is paused +zoomout-min-tps: 18.0 + +showplayerfacesinmenu: true + +# Control whether players that are hidden or not on current map are grayed out (true=yes) +grayplayerswhenhidden: true + +# Set sidebaropened: 'true' to pin menu sidebar opened permanently, 'pinned' to default the sidebar to pinned, but allow it to unpin +#sidebaropened: true + +# Customized HTTP response headers - add 'id: value' pairs to all HTTP response headers (internal web server only) +#http-response-headers: +# Access-Control-Allow-Origin: "my-domain.com" +# X-Custom-Header-Of-Mine: "MyHeaderValue" + +# Trusted proxies for web server - which proxy addresses are trusted to supply valid X-Forwarded-For fields +# This now supports both IP address, and subnet ranges (e.g. 192.168.1.0/24 or 202.24.0.0/14 ) +trusted-proxies: + - "127.0.0.1" + - "0:0:0:0:0:0:0:1" + +joinmessage: "%playername% joined" +quitmessage: "%playername% quit" +spammessage: "You may only chat once every %interval% seconds." +# format for messages from web: %playername% substitutes sender ID (typically IP), %message% includes text +webmsgformat: "&color;2[WEB] %playername%: &color;f%message%" + +# Control whether layer control is presented on the UI (default is true) +showlayercontrol: true + +# Enable checking for banned IPs via banned-ips.txt (internal web server only) +check-banned-ips: true + +# Default selection when map page is loaded +defaultzoom: 0 +defaultworld: world +defaultmap: flat +# (optional) Zoom level and map to switch to when following a player, if possible +#followzoom: 3 +#followmap: surface + +# If true, make persistent record of IP addresses used by player logins, to support web IP to player matching +persist-ids-by-ip: true + +# If true, map text to cyrillic +cyrillic-support: false + +# Messages to customize +msg: + maptypes: "Map Types" + players: "Players" + chatrequireslogin: "Chat Requires Login" + chatnotallowed: "You are not permitted to send chat messages" + hiddennamejoin: "Player joined" + hiddennamequit: "Player quit" + +# URL for client configuration (only need to be tailored for proxies or other non-standard configurations) +url: + # configuration URL + #configuration: "up/configuration" + # update URL + #update: "up/world/{world}/{timestamp}" + # sendmessage URL + #sendmessage: "up/sendmessage" + # login URL + #login: "up/login" + # register URL + #register: "up/register" + # tiles base URL + #tiles: "tiles/" + # markers base URL + #markers: "tiles/" + # Snapshot cache size, in chunks +snapshotcachesize: 500 +# Snapshot cache uses soft references (true), else weak references (false) +soft-ref-cache: true + +# Player enter/exit title messages for map markers +# +# Processing period - how often to check player positions vs markers - default is 1000ms (1 second) +#enterexitperiod: 1000 +# Title message fade in time, in ticks (0.05 second intervals) - default is 10 (1/2 second) +#titleFadeIn: 10 +# Title message stay time, in ticks (0.05 second intervals) - default is 70 (3.5 seconds) +#titleStay: 70 +# Title message fade out time, in ticks (0.05 seocnd intervals) - default is 20 (1 second) +#titleFadeOut: 20 +# Enter/exit messages use on screen titles (true - default), if false chat messages are sent instead +#enterexitUseTitle: true +# Set true if new enter messages should supercede pending exit messages (vs being queued in order), default false +#enterReplacesExits: true + +# Published public URL for Dynmap server (allows users to use 'dynmap url' command to get public URL usable to access server +# If not set, 'dynmap url' will not return anything. URL should be fully qualified (e.g. https://mc.westeroscraft.com/) +#publicURL: http://my.greatserver.com/dynmap + +# Send this message if the player does not have permission to use the command +noPermissionMsg: "You don't have permission to use this command!" + +# Set to true to enable verbose startup messages - can help with debugging map configuration problems +# Set to false for a much quieter startup log +verbose: false + +# Enables debugging. +#debuggers: +# - class: org.dynmap.debug.LogDebugger +# Debug: dump blocks missing render data +dump-missing-blocks: false + +# Log4J defense: string substituted for attempts to use macros in web chat +hackAttemptBlurb: "(IaM5uchA1337Haxr-Ban Me!)" diff --git a/neoforge-1.20.6/src/main/resources/pack.mcmeta b/neoforge-1.20.6/src/main/resources/pack.mcmeta new file mode 100644 index 000000000..64ff3854a --- /dev/null +++ b/neoforge-1.20.6/src/main/resources/pack.mcmeta @@ -0,0 +1,8 @@ +{ + "pack": { + "description": { + "text": "Dynmap resources" + }, + "pack_format": 22 + } +} diff --git a/neoforge-1.20.6/src/main/resources/permissions.yml.example b/neoforge-1.20.6/src/main/resources/permissions.yml.example new file mode 100644 index 000000000..a25f9adca --- /dev/null +++ b/neoforge-1.20.6/src/main/resources/permissions.yml.example @@ -0,0 +1,27 @@ +# +# Sample permissions.yml for dynmap - trivial, flat-file based permissions for dynmap features +# To use, copy this file to dynmap/permissions.yml, and edit appropriate. File is YAML format. +# +# All operators have full permissions to all functions. +# All users receive the permissions under the 'defaultuser' section +# Specific users can be given more permissions by defining a section with their name containing their permisssions +# All permissions correspond to those documented here (https://github.com/webbukkit/dynmap/wiki/Permissions), but +# do NOT have the 'dynmap.' prefix when used here (e.g. 'dynmap.fullrender' permission is just 'fullrender' here). +# +defaultuser: + - render + - show.self + - hide.self + - sendtoweb + - stats + - marker.list + - marker.listsets + - marker.icons + - webregister + - webchat + #- marker.sign + +#playername1: +# - fullrender +# - cancelrender +# - radiusrender diff --git a/settings.gradle b/settings.gradle index d49a761c4..4ea0100aa 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,6 +3,7 @@ pluginManagement { gradlePluginPortal() maven { url "https://papermc.io/repo/repository/maven-public/" } maven { url "https://maven.fabricmc.net/" } + maven { url = 'https://maven.neoforged.net/releases' } } } @@ -40,6 +41,7 @@ include ':fabric-1.17.1' include ':fabric-1.16.4' include ':fabric-1.15.2' include ':fabric-1.14.4' +include ':neoforge-1.20.6' include ':forge-1.20.6' include ':forge-1.20.2' include ':forge-1.20' @@ -82,6 +84,7 @@ project(':fabric-1.17.1').projectDir = "$rootDir/fabric-1.17.1" as File project(':fabric-1.16.4').projectDir = "$rootDir/fabric-1.16.4" as File project(':fabric-1.15.2').projectDir = "$rootDir/fabric-1.15.2" as File project(':fabric-1.14.4').projectDir = "$rootDir/fabric-1.14.4" as File +project(':neoforge-1.20.6').projectDir = "$rootDir/neoforge-1.20.6" as File project(':forge-1.20.6').projectDir = "$rootDir/forge-1.20.6" as File project(':forge-1.20.2').projectDir = "$rootDir/forge-1.20.2" as File project(':forge-1.20').projectDir = "$rootDir/forge-1.20" as File From fb10dfb3714063c70253bb76558c969f21d414b6 Mon Sep 17 00:00:00 2001 From: minimusubi Date: Thu, 9 Jan 2025 01:58:05 -1000 Subject: [PATCH 02/10] feat: NeoForge 1.21.1 support Update Gradle to 8.12 --- gradle/wrapper/gradle-wrapper.jar | Bin 43453 -> 43583 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 6 +- gradlew.bat | 2 + neoforge-1.20.6/.gitignore | 6 +- neoforge-1.20.6/build.gradle | 4 +- neoforge-1.21.1/.gitignore | 4 + neoforge-1.21.1/build.gradle | 70 + .../dynmap/neoforge_1_21_1/ClientProxy.java | 6 + .../org/dynmap/neoforge_1_21_1/DynmapMod.java | 137 ++ .../dynmap/neoforge_1_21_1/DynmapPlugin.java | 2100 +++++++++++++++++ .../java/org/dynmap/neoforge_1_21_1/NBT.java | 126 + .../NeoForgeMapChunkCache.java | 114 + .../dynmap/neoforge_1_21_1/NeoForgeWorld.java | 243 ++ .../org/dynmap/neoforge_1_21_1/Proxy.java | 24 + .../dynmap/neoforge_1_21_1/VersionCheck.java | 100 + .../permissions/FilePermissions.java | 104 + .../permissions/OpPermissions.java | 53 + .../permissions/PermissionProvider.java | 16 + .../resources/META-INF/accesstransformer.cfg | 4 + .../resources/META-INF/neoforge.mods.toml | 27 + .../src/main/resources/configuration.txt | 504 ++++ .../src/main/resources/pack.mcmeta | 8 + .../main/resources/permissions.yml.example | 27 + settings.gradle | 5 +- 25 files changed, 3683 insertions(+), 9 deletions(-) create mode 100644 neoforge-1.21.1/.gitignore create mode 100644 neoforge-1.21.1/build.gradle create mode 100644 neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/ClientProxy.java create mode 100644 neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/DynmapMod.java create mode 100644 neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/DynmapPlugin.java create mode 100644 neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NBT.java create mode 100644 neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeMapChunkCache.java create mode 100644 neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeWorld.java create mode 100644 neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/Proxy.java create mode 100644 neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/VersionCheck.java create mode 100644 neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/permissions/FilePermissions.java create mode 100644 neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/permissions/OpPermissions.java create mode 100644 neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/permissions/PermissionProvider.java create mode 100644 neoforge-1.21.1/src/main/resources/META-INF/accesstransformer.cfg create mode 100644 neoforge-1.21.1/src/main/resources/META-INF/neoforge.mods.toml create mode 100644 neoforge-1.21.1/src/main/resources/configuration.txt create mode 100644 neoforge-1.21.1/src/main/resources/pack.mcmeta create mode 100644 neoforge-1.21.1/src/main/resources/permissions.yml.example diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e6441136f3d4ba8a0da8d277868979cfbc8ad796..a4b76b9530d66f5e68d973ea569d8e19de379189 100644 GIT binary patch delta 12612 zcmY+pRa6|n(lttO3GVLh?(Xh3xVuAe26uONcL=V5;I6?T_zdn2`Oi5I_gl9gx~lft zRjVKRp?B~8Wyrx5$mS3|py!Njy{0Wt4i%@s8v88pK z6fPNA45)|*9+*w5kcg$o)}2g}%JfXe6l9ig4T8ia3Hlw#3f^fAKW63%<~GZJd-0YA z9YjleCs~#Y?V+`#nr+49hhsr$K$k!lg}AZDw@>2j=f7t~5IW6#K|lAX7|^N}lJ)I!km`nrwx> z))1Es16__aXGVzQM0EC8xH+O!nqTFBg9Ci{NwRK*CP<6s`Gq(~#lqb(zOlh6ZDBK* zr$|NDj^s6VanrKa+QC;5>twePaexqRI%RO~OY075y?NN90I|f^(P# zF=b>fZ73b5JzD`#GC3lTQ_B3lMeBWgQUGYnFw*HQC}^z{$6G4j(n4y-pRxPT(d2Wgb%vCH(?+t&Pj z)QM`zc`U`+<~D+9E{4Uj2kc#*6eZMU$4Oj6QMfA^K!rbl`iBix=2sPrs7j@aqIrE zTaZJ2M09>rp$mgyUZ!r2$UK{+DGqgl`n;*qFF~M(r#eh`T{MO?2&j?xgr8FU$u3-` zhRDc_I23LL4)K&xg$^&l-W=!Jp-P(_Ie07q>Je;QLxi8LaEc%;WIacJD_T69egF?7 z;I_Sg_!+qrur8$Hq4grigaiVF>U7uWJ@Hkd&%kmFnQN-P^fq0gB1|uRt!U#X;DnlV zo?yHWTw7g5B;#xxY`adhi4yZn@f(7-Xa(J6S=#d@&rlFw!qfvholE>MEb|VWn^g}G zMSrK&zQ^vDId&ojL!{%{o7?s{7;{+u%L{|tar(gp?Uxq3p?xAysB>0E$eG#$tvkk9 z2Q2gEP17{U6@UD*v({5MP-CTZfvWMItVjb4c;i~WLq&{?Q1(koX&vt7+$z}10{^Id z{KDjGi0JpD7@;~odF__0m|p;5rIrHidOP9^mwKe#-&JX-X@acc)06G{LO1Wu)#gvZ za~y9(fhA%UwkDOVU1LBJ`0ROE z4&)dJKK%mG@+CIm?+wt9f~@xIMr8}UH*K1j| z0pppo{7gv3v{URwxVMeg>Ps!L5IKxm zjac2egjgb0vH5i75$s|sY_RYec#>faqJk|AGgV;v=^%BM(^p{p;(^SVt-88G9f!q; z>p}9E4^f0=01S2pQBE4}9YqE%TV)*hlU^8k9{&=K76+*Ax^r=AkBb%OCP^P2nm0Ri z;D-|Zk?gGeU<12ti2CnPVNA(Pb)02+r|&yTWW-OJO7 zNLb0pps6aN?A~NJp5kj{{IOlf!5KWMleV@-hYLift)D>-7K+tgs=7Ake}oBnIy-y1 z(Hn@Hjw=_(x>dO5ysQsrnE%A*bk0K<-j{1Yqz@#n#jOL^AzCr#wR|WYzqk6i7v)Lf zkXdKxzuu20aP{Tbg$(+9&oh7cd(Uoqqf<#ujb$q4sZ~gxFbQfS zS)kNklyL*{2AELgjZ(LBu*>S(oH5AaJ;YiB@;l@=O%F6B?oanzoYRM^fQ9-<~^=3$H0g^JPMLQo@SZ@QuNvy)tyJ)LSj`+()#fy?{aV4Yg^7dlQ7AQM^3GLCR2dAFR zJjtfKiVqF`l-H_fz0HD|9g>)pOxn}k!vdZ=DO!7Sikm{Z%P6BrRkBS6W?ZB5W&7rT z@uYpf@M@a!z7H&o@-yrcCL^Ff3e7p3T`R9p?@o-acXmbTSa0>ZANzCSgovsd%;i$| zVus`not!oL#(W`L-!9w0jdaECaG4hk{V7IOs676ZquZH~0TX5hDq|)x z6T497l|E?f4)LA>j=S8}b$0LS=I4h|hUFJYJODT8Li@#6kF$k0)@*l{RnM1HQ%?VT ze-Pqlc!~t(oumVC*?5fwR;P6u{tHaZ~*LlD;B)4f? z?lpWfa2P@)g57flVl83Ej%P`2)gGyaPjhvD(%i~{`2b>#3!+y&` z!2nuwHMFA-zUY}f1^0B8<`N)Gr=A4TS@b1qykmd0Pq{?r)+1^^+D(=xasb^Tf!oK9 zBLL+*p6M_#ufgLzgq1zcSwZsZnQWFLC3`Yxdg-2=*tT`J9nrfYt)RF)YryBf8_gW{ zvKbB+oZLehfT)S#<|y1)E0hW^?+AnqPXq9Hu;v3dsMGdr{SVyF63;K<8VcgI#~}1i zLYSBL0K;RTT(;>2x=*!1Di9w0mwr;`CN}kM65|Ay{~z}_^JKOsRaN<~#9O^iiW<5P zYN7r~HV!#Nz~IZU`P>1Xe%4f~K}KcF#X&5kO*G}-)74S*tQ8CietdPcA1Yl;S=Mr# z`#MYY!{s^uo=jn7;k6O%(}fN+*0cWMpt~#n9DR<3NyU?+3D^AgI}S)Cu-Tljg`VY} zX1=fq$?8$DtOeGxE6f8lbS_6Q3C4+LDTO$}_IpM$Xv<|QSC%+Oll^q$y`7o@jD{dp zNDl|&X)r7wETa-#h*d`KXntxI(Y{vLha{$0i7@G8xx^m=c<{lJ9?p-i!^W{%j7-oo z0W^SzZ^(Wkyz*We{lEn%Yhu-ycUOHtrRiVJL4~&S91*D0MrLu}Q>v-Mc?GcWfpyz% zX|UvcN@krFO#@v|CtYM}g|=L3%aMo$E5<@CM%c*;?u>LOTz00@+dt1{yg1y=$h+{|D17U}$*^fE^H&8b431EUE z<9tv0V_#%#&1N#j7AKCj!tTK@J%oFW*ESW<(#Gl#Xs%v<@AitI?s92nLzm<)w3Wkkom1f$gcdUi%g_*jofy&}N#luL<$GVIe{iQkQ)sIHVy zBgItnPBFamrv6Kb{eE($Q(f`ZPeW!Hm%Y@F*OF1sKB{Yy|C>WEv_mfvv-N-jh)B-5 z4a!1WcT@9a+hGaBrc~sz=>G?Q!*Zp^JFRUvBMyNR1;`)j$RhH$6gEyVKhd$&K-CFT zXaWC-Y=fyOnqT84iMn9o5oLEOI(_3fk!W^8-74|q1QhQ|CmT0i=b;6Z3u?E{p7V{? z;f#Q-33!L+4&QQcZ~GAqu$NS{M;u%`+#9=7^Oa5PKvCCCWNG_~l(CidS!+xr-*gg{ z$UQ`_1tLT_9jB=Hckkwu>G{s0b0F4bnR7GibmHo?>TR&<3?D;5Fb#gd8*wYa$$~ar z7epl1qM)L{kwiNjQk}?)CFpNTd?0wAOUZ|gC{Ub|c-7h~+Rm(JbdoRe!RNVBQi!M8 z+~U6E2X&KSA*T6KJvsqwqZl#1&==Dm(#b^&VAKQ>7ygv*Fyr;)q9*^F@dCTg2g!w~ z%hg)UXAUyIpIbLXJv1nZX+a_C)BOH2hUim|>=JHCRf(!dtTidb&*~I!JrfRe+PO>w z@ox$G2a3i9d_N9J=|2$y2m-P&#PTNwe!oLBZFs;z|F5kXvBDn<)WwE0E3$ow=zg3R zK(9;sf0t;VEV3@gAg7jRtnj%-6O@!Hvg*;XcUAw}!=2*aErvB(eQIm(-UGmq^J=XN zTqJo$Y|WKo^HlBF3BXJrA#}7ZLg=r*w`I*~Ix`o&2k8^(0mt8Rp=A>F`&gehhp@Jy z^e^#B2!~$LvNCKugg)8)-G%&THdk~kfextilegP9?#C#()F59U$&eo(h|5>ceo*Em z{PEE79T$YP|Kr7K`WBHbtQwyxFkCl6xX&+oUf90B5xoi3_5KHHCyEE*oPbOQkfMz& z6^hT8_NXd2iWk{q9IKae1{_7hMPH8I7_BMtVOM4 z6jm?E0QJOn$qrgsJ`9w##GB9?G})-GXSQo6(tYS(Q0-Ct$co?Zzl0?NHsDRron?;_ zZZgQg)%XW>P?8_&zoGuF(>Och2kEJXsu1_X&~w87x!b z>~h!a>e7{`p@+#hXF88wI*JeWRZ;J4ev4<}HWf|Z;(7$E!S5l9wzBHFe>^I{2`a;a)QnAwa2xv1e(bq$<}!8o^ofGvYpk7dBR+`*%iE;hUY5 zaHF}OjGO9r*{%lmcK^uFiTHgoUD`^9Nx@~;Bg!V* zuuJ&ti{DQiq7RyJAR94wem{}cPK1J(Yxnn_{=>?USqz-~&QXRStS^s-7TksZ$AEI! z#og36s3JGtGU{CnDHRFtipFqvrE*gw7_K@NN0h+ItTq@4fqN!HeQU1y7*X?9+IfZT4Vxebpt z%#VzgdDK~-&+=Z*#>=n#XUhNvBZp3=Cr41jMqwJkHLf3L7Vm~V#GgJ(Jpii~PmJ#s zA7Ft!{xD@z>9DUb4JbiUBdNEcU4BO$651iN*mp*f)HbRRM`Cx5cR?5IfEcU{IZWwf zz(M6CDv)>xa3x}K6%tP^i15P1&&DOLK=k~+jNR$UK3frSl+|PjSC-dBItvD~LL! z>_g(YYdO4k(5EbPOw+v+;G7~jYm>F@Ai|o`gs%F)F8tDz$dl7Q%aCe|v|$UkAul_R zNlA-beBX^IJU?kgS`E$it7nF4DaI!SJAGq)2P&Few(-|tp z?K+%D3e4{pfkayrcbm0ftu6Ol2ZzdKM+4i!hNP3NRL`EvvZJ3yvNr2MV%igZ4kj``Qrdb_OI$7jWP z;l0DYf&0(-*QcP5zrP`HVznW+SbH63Qx$7_9~NjRNg7eKqI!UJ=XH`g^=t8GiFTu( z?2L{JKEu%jJx&XjNzU(*!ZNmL1@RlJA0G$2_LrAb_7lmjil(GSlSM zwTes`m+3R;3#N~Xg#9owh3ycXV8@ZlaY_16kpPFA={721b~URO4HD3sp%fmkZM}k) zZB0#)kP=RkNB~R-MCk8aljG_bagt4vIb~8)BV%(b8_;)&Kf9GX+%O_cNG|(D$!3&D zL(I8}*LqN5NntipFlN13=`D>6!{D@CFMBH0kW3=HccJV+xW~|$qeFR5i-2{X+iWMu zI2$gepQ)H_B%ip_BlWOQ*|pErXs|4ir{IHccgaIJ84irE{?+$KDABXr&f`jB^V-c% z$$u`uU1YB^{<+UN2cNg#7&0bz@yF?5>j|;)5&IV3wIQp58X#OE-M^$HdyvL|Um5t? zhZlAG!Mz%XkUe3t471JM*Yur}o30vzu6RN7gJyNcf!IItsDO730mcJ*O!~V``y5=3 zNJGp34DZ}wd1H6V`Uuy%es>BiO_aE-S8jzir#$& zyk)@2a5tP$@g%jW^b^JGdo)X@Q%sE`^lDQmY9m%uDFpPX`w9%=yQ+nneMm#OaXcD` z9}{tn5A2b2z9783vL2_jSao?uxJhWJoq%47*RafM4o0@gY(p)F>qT4^XM5GLzV#6j zC+HoGhAne7o_w{WUo(B++z7lU3Y0k1rYv9|TSv0vR-Du(5=VakbbelgZTeDn+a_Wv zq_j-^+Qz1WAl;Zg>ahX|CERbX1V%B!hTKN?M}fGoA07M(WU&NfT&TmN`P@56U2 z^)vLDs|Ln~0iTtn-?KTeQl@T&bskJFuTUS!m+$CS9vnd}8(UMO|Kv6TCfGN9NUu&4 zL{)GTxPq>fwsJ~aU=4Qhuq8*RzDsP(LZh$BHezq&9gK$IS<|DYbm})$QTGCS6T;Dr zEkLct!b+#<1r9OKG@P!f1wm8>=Nz!7OzJm!g<+`?N3;YaA3(P@EL=(sTaRMDD!c8=-XN^4BXp(eVkj$NmEMYPP>YJ4bJ3yUud z<3BeJAJ$6z^TuywnfH5lv#$lgwraNw{IV=tIznPH1DT`v-5yS=!)J<}xxl}uZf9azA2A97Haf!;<3y01hlw?dWNEv@TLi1s-mO4vmIT%O_42nS z$VRWrs9NngqRRkWAnWkn%`Rw@?wH|)7XL`EL5EZu$qyJW31&CB^T_)qwIv!{;E_6 zo-9XAryQRlk-O0>o#-SZO>|6OYq;}<*>Wu1AsVRiXY4f8qb;+sItv3AyS!4Ry+q}) zA!pAB|BmC;=RIOk^^vlsEH(!Q!7_1FK~ZB2err*o!+b(r=m1b?$6d!%zmN+69LXnT z&gRmM+n_R-F@sT*IYv0_mGPvur!u`iWbQO7SqiGFLeY&yga zf`lM&B74FA2C?N@8_z652fjhBEoDUKbP8hL{0{HAF%qDo7)o3=3rg#6)T7%%5^wl% z9R0*S*<~>nzYOdQk2l`9h#t+gJy_xujw6xjV(8S<_DbVg61&pT%Hi42l%D73G?adn znB%UdNM0p}lEF-P2%TAMam2zpQev71e>a$$%i+r~b+D9G9pF|oY_*(-u*89oKsXLY+UIbqq)MQ%(GYS{(*n_S_*RN$*~`zUtab%0aKwhx znc)Yo?{xq1sJCgQD)TeTci1ucvbez9q=A72H(-SB18Kl&6^vHV8^i!p@>iF!DIw17 z+8Q)TNisB7>pwyww4y)yJx*wX6SJO78eLBC-ar1+k$Z9fy;wBD|3kzI{<+l*>PSY^ z_?nLOZaeWbU@C3hfK?X;Di*8CHCPkx2qco6(ZyJdqSzp^TJ_5Lpa0UP{Gy+!b0Lr% z@xYxSjUKoY6L#>$qx~KD$-0=|OF7zhVP~ntMgEALYPIfhj@+ z!;JJ7te>CcovruwHsJH6Lta$nm|%^C@=V-rmhU{+I~0(|XHQ9jt@L7pb{gx#{4r!) zg($FyFTslcgu(~6lYr$nW?)%*l#VJ=R-jxK(x=t1bWlu(nL66T#qj%3aZ@uVhy}Co zDU_q61DD5FqqJ*#c|(M5tV)XBN?Ac^12*q)VN4yKPJ|#==S_`_QD9|0ls!`2)SwuHDRA_OfXQDq3%qW&MZB}Z!=k-9xqev8jHz(H z{^D@cIB~QiK>~wa)A&^Ll^Wi6QgCzU;iv-BHsLBs zH7=jN%|>0S`SjP%M&AF1PNVDp_FZ?2Bm@7`DC&v(pYrw!!yD#4 z6+<=HS0Ln6MhoKxF<%~H`y20{vf#pxh=;j{zY381gvAFekgG|>G1zo8$&az{V=;JR zy_puF4$L$?EMhT?;TpQoR*j16ll`#AS4e96C}yp_aGKkBe?1H|k_;gG-~Xorc<;lI zkB}fB{$c-D2mGA&{rm<*@F5)c3X+6??g~XoEwuzSuch0D@W~P5(2I8v8F$c2$Vw51 zP#YLSBDqtWW^EYBl^QYHF+MA7am6f4DOhwnJM=W9$uvMOsZ%_~?)2C#wb?CkI$7{K zEi)=#|5pFvg^){zK5kpBLjB2kZ+$ZB|L=W|aNwyyb(gC2l7bcpx{E-H@)q6@D6N^xh`{1E%ItF2$eeB_SjI@b2WgTpS1thwg&n`jiIzw^TtXUyB{00($GIq>vbj|}bav}}Q_~wp3>k8!E@hVC;OMUTu|= zAy#vXH*GrUHu7^cNZWe1>y;2(51js9wbu+R3Aa*(wzH9+X0dIsf&gc_x|_LP z>~CF^?(~U}+l~ehe|i>?4eo!xkq&Lk+RR-1duNP#o~>@1x)s&i&u zRaYL@+D&_M|JLI6fHbEr_`U;HgPTh#E3?sB)A$*gqyBgg*ql|a-m*TX5rACbWKCE6 zdeQ`v8m6>g^ugv`p|HY^#1QZrGGUj0^HVDc@{?Q0yhalbBEV{+|HzC^-{&e{5K%z9 z6Bxtnfu1!@Mp+Q&*&~;FOg&*Vm<@4b;{FG0-!UUXX!|)1w}op!B_|7_s~d(+=9Gba zKp8`LaB4D(H=cGcspJ_TjYaOwMb=sGn^gtUVhK!UI~2KKYEE-NC}F>+BEY7IVvy%KRvm00tg!Q`y=er}wpEetX}K@;}(}{s9AzV#q2@ zBy7}->|N?13POrs`;U?(qAG(I$~Gt+Rgw%aNZ_0fs_utVvRJT-7z4!@x36v@=NBX=IqkK{#Kg0w48de@?#Yb4M(Svj5=T+<ONr8-oh7l?Cji@+erqur zFhZ=9|Lk=$`c}v4u`)-!!UI=!9Jo@h&7p4RlS#u! zZ7-prn75JkV?VjptX;@$#`U`{vB!=Z?V`T*FBF>J?vsML7e6@2GbUteMFfX-TUu{2 zLNIG*;dV)8GV8gAgEf#)X3A>p3^CRka1v?~8x^anBhQ=L=LsOl=&pcOYHo98m##ye z34MtGCDK!`ptl?taGMr5q{!zVc? zG00e){TV?`YA9eB;(lA3lXI?RrB4BYQGk?vOmTIUJED=(`_*gtn2DB-t4WW54as*W zb2kD-lWX>lb$+W!VFakki>B^Vc+u$?NLF>)!U%b@Y}gYJ>m2H=^x0=nsE0TF^Yu0h ztgH8-o1%+jCk(+&`|)tTfEVHq0cMeFa{Uz)X$;fCq%Y=SOWML6bYfeP8j5hktL`KK z(18`XrUn&WN9PtFxh&dX`y~YBsmdhi7Kw%tKzM%^VEhdD<_XkulW-x=JN6OPbFI4@ zzDDRN+f=@{0h*MswwOqG6gJ?{NuHx(y-|FUGsxyZ*x0~$MW(eY>vqq4Fh#t7uzw=- zKB?|!0N~!h^AMdLa)oR!Ca#HZ9&Zf)ghuO<^RN)4twRlygHnQG(BE{cDc5E}OF4;xss6gYyV~EcJvJkX)xNWb=@yw!uq0v-sf^rvkp-;?DPWK@*SEw|V;IH=7 zfQqEV_>DjOPT~8X*J|H8=&RnzK4~S7ML~nLX^%s-Vqc^aWy7N$y57qciZGcqy#=zU zs8hcHiI=D$+RB{|62{ohCTiaML6FI4Uhzo5D{Jik@poCs0w7F)*w}F4r0sJ~#u-72 z5bK=ANt=M$Dh5NKnxGsg9NRR?WD-x|FhTwBjd zD<-K>44DB~i%frJOfnzh1R>PRY34kw!6~p3M$JLaD1r@`=h)~Ngks-(gdXh^Q?BTP zZ^Zj5w1AwtuR2$~E7s9iZdF}z%pv1em^V2rM{1tLUY@-+Sc0(9jA|iZWml1;v13=U zHf?y@#mb--7z6$ue>`qjhE~brk$AY-RG90~5wcBbDReXR2)pKg{L>;H(DI`U!MLNQ zY9rFJP@ZQ}jlcMh%WSCo%vf+nd0Gmd*F%KMIe>slCUh)8Ma|;M_I+v#;|ueg9oLg; zq2HtZX%&#F7vdpNlkX?}(C7dGC^y#NB#m4%69RzTNrk%4ol~hSI%>2r6B|*ZkW(*P z;u#s;+faHo{tfy+1L^RzWDi*^JR0iY(zJDB36y_QJ+|E-2x+cY z!V8uLNktH~q>WQZuY!Ap66WP|E!0PA1jK~)^8oJVGbspJs6QL!!-5Qm7 zHYI|_`Actg?vDzdg5{86w@GS$G6ANzff7->6i5pB$T4O}`fZ_;{217Om0gN5zTr12 z5mW{hCzCE-QubjxN$TAE-XgI-8dTY@OZmq`y+y_>dk*(qXF0{nam|q@~i}Utp*k{yurq(DW54hkDT4bbg z=_etM?Nf5W^o-HEu9_?&xEqPg^P^mTxLH8n%u$!mWvFG|{&)jtnU&6|5-`~eaNz0%D1BDo`{ zS1N5(KW5v^2eLdd_%`uaRndF@h0Uo6=M|8?b~KbOLZk{HXEnGmtgZXf2inI*1r%n! zQ3&%RI4r{f&dwW~HwH0Ked9b!k6{>_19H z_Ai>5IChDMY(FfMyG%;30?SQ{iV9KyGru62+Y)~qSQ91}b~}w<&*}R&1c#$O`H@~c z5)2S_eXx}M#N{MuGeQS9@#UJB@;W_j50b}jIhxMPloEFQZdvwxiU^RYycTzgK)-vl3LT&$L8~@68$C8~5_U{cR$E#w*x65(qw&eoL@>%ZHvj zWnEMlSh*(o&oy|J7eJ5OD`ssy%F?*Vp?`Cq;FShyl{ZoKCG5g{y}>usznni#8ki(i zO{w@n{iAj1_ooX@+s*!uW60WcH~*bNOT6z%0jVML5};wVrQp~`Uss_{cO2oud_nNA8^B$?07fJ6?iI)Q zuo9G)O-z)DqstrBqf>B%S05hf-wep0@$BFHKSrkZ{za3D)yVzRz)2{wf8(Wp+xyAM z$rtyx$gi3A=V~V!`Q3;BM0$>*VVtxEM|xDL^gew7ydy3Q6YzD&THRz*q33Ms_D;M- zbCx1Ft#UNB)V3bf`~{ImI72OTp^|bF8?G8#FRj+Biy8ET5#rA3sd|0FR@U(LAJ%w8 zS1%n8Z=Amhw)92rIsof=YVWF4jw&F*j1LG@-`+cR0-~2LqXRH8(Ccne{y#MCPncF64U`0uO zWmi$dlii~1D0rLR{qc|_2M!C$t8^=G7xQY)9!#Y331A|>N)EhmyVdLWL9I3YLJ`7? zZmpqUJB>Ni9oiL)^1IK1UoMyhWE{$9M2M6Xi zPKk7GpMsA6vjZbU7~i+u|J6Nk|Ci!Y3UMUT2|`M;JsNQACdJ%ooo9Yt{?A+0hMpxi znEa~~sxC>rKrU6bd=WRb;%wsH>A#j4{({&1GYSNR57Gama(3)2A;SM>qop}l>Jk2* zn1+C$fIxuwzg3mCU#SOqb-wOCb6mBcYlA5+mt<&_J~sBxc(GQtBFINUO~Mr7<-uu($>P HJ4oML2Lo<@i8BwbL^1~GkG`E7C$SEa_ zF^}Ea+#Je`Xy6;#D0FPnSrR%Y!QGA~NA^{oWmW8C<3dr{x6wWQ{4+bzemqV5W$i5~ z=J0jXZ>uZb>DT@0Ks?4QJ{`z?8JWl3$y;2pj#$XP*pv$>$g(z43{YH9KmmR6<#sIn zA`#=0#sgycaBQ^&}Xba!|KaZ8~b30v~nLt z9%#gz_*=~KD{3t^X~l>480*}PhKN=??g`RV|4Ud{Gyyl187MJ}r(#e+H$GEdI+p1s zq_25h;fV)$EPK%Dw-(G=f`yHB-_tttsC!?k7*#!|4a>`Ahj8nm?&n>NRs%jkZW^3-0P_yMP5&*6a26{MRj1&TPF zyE#|c)5uUHzMWx=rMKpuPih*V=S;W3MzIZTw2uTbr}8`p2bm+Z6Sa%vvWAWSf4H)p(+ zSQ8;EvUa#wqWV+9vmIio(%7wukK2SwjUS8Yl%Rq%=~PU)2$Tvm6`1!r3H@U#_|bB0 zmlT1PS3wPB(b&^+@YY7Y$n4l3mV3-X0$>z|gZp6O*Lhzn&?Gad2ZCF;+#95-Y?#y+ z?*l@Yf=a4w{Px=o!N|3~_XKfk&G;fN>Ps&dp2FpA~qD=0~=!NOS@B#XAKKkND>Y{4>rqxrViKD7;?>j8`R` z&G)3FN|dfsxnaI^!d1G%=>AbTTxZWo;n-DLrQ!sj=f~VAOe5zhGS(dgx|!ls62fbX zV@<7Ck^!}R=`Swr?(7w1rY6Nmq~sfXJ?TiKJLn=&SQdEt9$@0 zA+h1Wbwbri0s-stc8yVq;mRa6@kEf8^KXUz&jcic!+avDvvJFa>k0ioWug=T3oPw; zyj4it&0@>_*uI@2=^+T7sL1_!^aJW@Xfo8aC#3^WtQC7fET8b9C} z*u^ue6Ojn z7@(eskJ2+cNnH9~VyfIh<-|7!je~vGy*odz(sk-u$~SrYF3glruZ*W`{sqnS+9=;Z zh{D@MSG91%lr&ua8%$sJF%y1I<|e;EdfJykY8#D$Hc_81n5`$7;1N|b0tvvPLzSg& zn7!5x?T*@rQUKcUhTIjV(rw*5oQYlm5DbEO?60#mohHfbR$3_x#+PZoYi@Vd4`#YgKyTd^!4n{fN~WZDY61sAOm6 zl!d^i*a01QxpWM9Pcl?&{RgO}uq%ErOk5WpECvnfEh!*YP&1Sl)uTN4hg??Vqs~i5 zYsfufz3?{TtwuBN=`0~Qg1PlWH#OGG$ zLLWU17$v``)CE1cds_7kj8mJ{-+l8{DS|zAQ&3|qpOY=!J|kXUhXue9|H>4gqk|n) z-i34GmxLFj8asb3D#D&=ya*a5`C<=o?G;Ev^LV%;l#nH#O=7Nh@z1Do>j6Q;I5S2P zhg|AZbC&|c7}uSJt57s2IK#rSWuararn-02dkptTjo*R{c5o(bWV}_k3BBnKcE|6l zrHl&ezUyw^DmaMdDFVn<8ZY=7_{u{uW&*F<7Al6};lD(u;SB=RpIwI)PTyL=e25h* zGi{lRT}snjbMK~IUx|EGonH+w;iC2Ws)x>=5_{5$m?K z5(*1jMn%u0V1Y%m@`YS3kskt~`1p(rA4uk;Cs!w^KL$w>MH)+cP6|XKr4FfHIATJH z!EGAK4N>1yFR`-zW|w%ByRe#=&kA&#WyUldDGpt!wf-8SFWiSi!5QZL+l7*CE?u!NW1T$<1rdLJ9y3u{_zvHaM?#Rm4 zFk}^1!ffcrB|XK3gsO-s=wr*sUe&^$yN|KxrA)uW00Gu60%pw_+DcUjW`oW<35OC8 zq2{j8SgC}W$?10pvFU83(SL$%C?Kctu3*cs0aa%q!fjn1%xD*Jrm!F3HGR9-C{b?- zHp(cL;ezXMpL@0-1v0DMWddSDNZ5h?q50cOZyVi#bU3&PWE=(hpVn|M4_KYG5h9LffKNRsfhr^=SYiKg?#r&HNMi2@cd4aYL9lw(5_IvQJ zcB*DD()hUSAD^PdA0y|QrVnqwgI@pUXZXjHq3lG2OU&7sPOxxU$Y3&ytj6Qb=2#cC z;{d-{k|xI*bu+Vy&N+}{i(+1me!M;nshY_*&ZQLTGG*xNw#{RpI`3^eGfHck+*38NRgiGahkFethtVY=czJs#)VVc{T65rhU#3Vf?X)8f0)X{w!J3J{z|Sq|%?)nA+zo?$>L9@o`Kc|*7sJo4UjIqu0Ir~S5k^vEH};6K?-dZ0h*m%-1L zf!VC%YbM1~sZOG5zu&Sh>R;(md*_)kGHP)<;OA44W?y53PI%{&@MEN}9TOiqu+1a3AGetBr$c)Ao3OX>iGxmA;^^_alwS818r4Pn&uYe^;z6dh z)68T|AN=hjNdGpF7n>y+RTAZc9&opTXf zqWfK_dUv=mW{p_vN>|(cIkd(+Jy}qnK{IW%X*3!l`^H~FbAHwof+vLZ0C2ZXN1$v7 zgN&R9c8IO`fkR{6U%ERq8FN<1DQYbAN0-pH7EfcA{A&nhT!Be>jj>J!bNRw4NF|}! z1c70_#fkk!VQ!q1h2ff@`yDyrI1`np>*e#D4-Z~*!T^8#o*$V~!8bWQaie?P@KGBb z8rXc!YDL!$3ZgZZ%;-%~0Kn<+d+{xJ$stQbtN8GWV?MCJvzPU|(E(1z;rFw{&6vy) z3*@y%7Tx8rH-p$boS>bLyod?OKRE8v`QSBvGfY6f}_{Zo1q85xoyOF16n~yHx2W ziydUoYLkJmzq|n&2S(O!ZmLdP1(o1Jsq88cX)x3V-BK5eF&0e_0G!5?U7&3KN0`mc zH&Lt)q8!d_VgzxyL^(@xrbp2y)Hmr^V48));RSfE=*Ly0uh9!$3dv-vMZr2URf@l5zdwLjGZB zugY>7_fd_vbV*Qv1?H~>Z%RD%nEeFSI$n$$Lrpc6g>i4+XdBB!%zM$Bhrz5Swzyg? z$~I~n@~-wTBY3-T&pr+|gC+OHDoR?I(eLWa{Z#Rsh>lc~%u0!&R|s0pA*w<7QZ}{i z*AFr~0F3y~f$MGh_HDL7J_1?SxKL}fWIk!$G}`^{)xh*dZ5kK>xGL9>V`WZZg_ z)^Vm)EQK`yfh5KiR(vb&aHvhich z_5o+{d~0+4BEBqYJXyXBIEb1UgVDs;a!N2$9WA>CbfrWryqT25)S4E4)QXBd*3jN} z?phkAt`1rKW?xoLzEm!*IfkH|P>BtECVr0l8-IGk_`UjE#IWkUGqvyS+dMrCnFl<7RCgSMX^qn|Ld_4iYRldO zY&cHhv)GDo8nKvKwAbfyLR%t?9gG?R7~PSD#4D-;?F&!kV59O}neYut5AGbKwy-(U zqyBi=&Mgj|VIo>$u!DHM`R7O?W8-idbePuxiJMH``6c_5L-chKd}=rGC5Gfrc{f!* zWFEBm?l@_b7kzY7%1RQQbG5V<4=ZlkZ%sF74Q|mKOc7Ak7dP2#quiGcZ0_J%7Q?j{ zv9{WFw;n5G-Mn%r#0R;{jLt{yy}9J6rQ(>X9pJ`7Xy?Zv z=lNit#qXaq?CnElK^zF~sG}U5oCpR0T>FH=ZX}Prju$);?;VOhFH8L3I><9P_A|C+ z{;>~dk%9rrq(snjsEm}oUz2FQ21MCG*e?g)?{!&|eg7PX@I+Q0!hL6C7ZVY|g2E>i zr!Ri2@OfEu$)d52+>+cpgh6Z;cLYCZ&EMR0i<^~4&wEu_bdo;y^6}+U2GIQgW$|Od z_jg{O=pU>0-H$P-EOlWyQy#W0r@@_uT}Lg+!d5NxMii7aT1=|qm6BRaWOf{Pws54v zTu=}LR!V(JzI07>QR;;px0+zq=(s+XH-0~rVbmGp8<)7G+Jf)UYs<$Dd>-K+4}CsD zS}KYLmkbRvjwBO3PB%2@j(vOpm)!JABH_E7X^f#V-bzifSaKtE)|QrczC1$sC<<*Y z$hY*3E10fYk`2W09gM_U<2>+r^+ro$Bqh-O7uSa)cfPE_<#^O) zF+5V;-8LaCLKdIh3UB@idQZL`0Vx8`OE#6*1<;8(zi&E7MWB1S%~HAm%axyIHN2vd zA(pJGm_PraB0Aat3~?obWBs?iSc*NhM!{-l_WNCx4@F7I?)5&oI|z{o@JKd1HZ}zf*#}JjK3$ z-;3V*WJZvUcKvSOBH4c7C{fl8oRw8-vfgKQjNiR|KhQ%k6hWNEke(k8w-Ro| z7Y3)FsY-?7%;VT64vRM)l0%&HI~BXkSAOV#F3Bf#|3QLZM%6C{paqLTb3MU-_)`{R zRdfVQ)uX90VCa3ja$8m;cdtxQ*(tNjIfVb%#TCJWeH?o4RY#LWpyZBJHR| z6G-!4W5O^Z8U}e5GfZ!_M{B``ve{r0Z#CXV0x@~X#Pc;}{{ClY_uw^=wWurj0RKnoFzeY` z;gS!PCLCo*c}-hLc?C&wv&>P1hH75=p#;D3{Q8UZ0ctX!b)_@Ur=WCMEuz>pTs$@s z#7bIutL9Pm2FDb~d+H}uBI#pu6R}T{nzpz9U0XLb9lu@=9bTY&PEyFwhHHtXFX~6C zrcg|qqTk(|MIM%KQ<@j=DOjt|V)+8K26wE_CBNnZTg+Z+s}AU|jp6CFoIptG1{J*# z7Ne~l;ba*=bSwAMQ|Vq#fW~+je4PXA91YFzBubNF?ovIOw-$C-8=Ehed{lGD0}(Id zRe4sh8L>&T%{>8o))he}eE;5_ zxoXk3wX?MyNl-xF!q1d$G?=wp^`@09(jU&X zOqZIBI#dN`2PJNdATR3ivtub|nO$dulSaP|e4)WXF1YAGN1pDQIbIjXFG!oC85Mt; zW$eteoL{y^5t4TMRwP$jNPjZFpGsWnGe=jMMqKtcZm9Y9PFZLi*1p@qoKKub^T@2+ zk$@*KYdQ?Z`}<%4ALwk*Yc{(WTf@#u;as(fvE^9{Gk)lWbJP*SjttWofV0s?AB({~l zZI1hZVWFT~W-T?nfMMcnCS4-#6H-MU7H$KxD;yaM46K4Kc@~Q>xzB+QnD_I`b_l3m zo9pRx46b!p?a^&zCDwygqqV3epjs(s0NQI6ARA1n!Yy-qduipxQ& zUAlqRpNjBS+y-ZheD(!R;F}&^V_}b_gqH%tVZ5%%ziO7k^w=es+wZtK^i*vmrWNLMs{oWu_CIov|s1raZiS)>38>pYu;i+-t zI_DiNe6aA4KTZ2P09qPj(0~K4nUq^0+f(2$g`229zkG4jLzRvJUWE0oF1XHL4t3UN zDH466G56sy9hTZoAJB!C3;@F;ONxEk5u6Mv%zdo}Rq`=* zw1n7MOhfNSV48TS989ArIcj`C%Gk8~93~u>)!Yt2b4ZriKj9x2d`H2HQNJ=I>hkDlcZn zqRj>!;oRMTIOu zx|Zfsu~v76T{z7AC(jxj^c@tnJHZtGPsq$DE!8kqvkDx5W?KUJPL+!Ffpwfa+|5z5 zKPCiOPqZZrAG;2%OH0T$W|`C@C*!Z`@Wkop{CTjB&Tk`+{XPnt`ND`Haz;xV`H^RS zyXYtw@WlqTvToi;=mq1<-|IQ(gcOpU%)b#_46|IuWL#4$oYLbqwuk6=Q@xZaJSKVF zZcHs~ZBl;&lF3=+nK; zF`4gSCeZXlwmC_t4I`#PUNQ*)Uv&oGxMALip|sxv^lyVV73tKI7)+QY5=tEMas{vTD-BaTJ^*Y6gq~PU;F5X!sxqiq$iFCo+Uv7m%1w((=e}Vf*=dtds|6 zbX}91!G?C*KG03eHoN}RZS9DJxa&8YwNCT8?JxMXyZqZr13NA|GB{+vG`08C{V(yy zf*Lw$+tYSU_+dI`3n{bMrPdDb`A=Mkg!O=k>1|*3MC8j~- zXL79J4E=U^H=iBLTeHE_OKzE&dws8RNynsSJ!d;`zK?P92U{f)xvD7VQVosrXZrL+ z6lMVdD1YgL;%(1cq{#bS6yXmp|DS@nax#AqqlZhtUQdh<^2vr5`EpAO

LGYq)sa(w9^3-f}NHy=GR4v%t2YZly3m1G@5y`xBh_HGrD%f z>;|Ty?9FiJAc&UVD(StT4I` zfVQwxhE9bXE6r2mKO8Ag7{L^jCyqQb0QqKDPE=RAgqn8q1O^>(z7h5kE(6va%QqRZ zkIOmp(})rLSS(2{=C12e&@!W2=Jel-^_R``0xHO^+t!(oXbcv5yhD4g*$t_F)_5Dl zSVCgesW%;DtYPCFs{G;GX_o?1J3;QQPPv)rWw;>} zJ&KwnUqwNXloNXlK_+pNDfI~hON#SokVJb&ilg8d7^NWo2ZQymCqQMnjfi>ePibjr z-Z@q!?RGN$Mj}Nk){X_vaj6?Mj$>ACR*z|6MsXy3VZ^PFn@yHkPo(>m(iWepn8SC@ z>D2;R4m+gDRZ=SIX!b+CP(qE=JDIUkn=D$aUu+Ihn9-+k1LS3PreQg0N5eWIG@x${nC3v^7caS>1!PKNAY9J z#}E}Q9w#SP>(GY7Hbj&z4$Li6o5taBO|4+F`yS9zq*LJ<38wy4I>HA9(&GYrk4dLajKGww))BWli6Ln1A^Lda@N~p+snkb9C z@OthI+<##vp8!HVQT4Wk(=@zQ{OvZ$EKWS73+JHb)eYLGD-cqi6^|vd$<+IHuc?Nq zW7JertT~3))4?J|28n$I@nAD0c1%9C&IVhEZX~mUsf{efyS(XNG%ch;!N~d7S(Ri7 zb&=BuON95aVA&kLn6&MVU|x}xPMp7xwWxNU1wS+F6#y}1@^wQZB*(&ecT?RnQcI}Y z2*z!^!D?gDUhc@;M^OpLs4mq>C&p{}OWVv<)S9KMars@0JQ{c_ScGsFo3BJ)Irg++ zAWwypJdTO-_{Uh8m(Z!3KL7K{ZZzKHj;{M8I$mV>k znTM?sa0);^=X^cglL`uC+^J)M7nEa$w=VwFULg~%DJllw+7dJAj3{qnP5i3@wr7%y zjXp?Wl2%Th=my&3u?Q$RV6N5tzKMSPTsc#J+-cDDp~qFB6bL2C8AS7Y3PKtVhdhl) zIaLqH5+OnWPWSt(lQCgkN8lczc-V%_iZ{>#1%Z$N*>lu#S;0MZ$T2Y8Kg!U;hAZj> z6S#%$DQ_`Ic%Zr@?}GgjRXg@qTj^17n`65oJ@Wj0u1X8&+UVd|Xs?J+i_^GZ94m6= zUc96~Q`OJvlKB_Lr15*Yw_PUPEr?f?H&00b^-W%26mD)(n(rGGNfK9~2h=C>p-7BZ zFd&*&Msdu{w~(eyFOglwCPH^Rb}O(N7LtS+nnEwDx*pGD?|&9Si~M43a+*L(b0$5A zv`T`(G3xO;I_sx;FwTP21ZlfDpz zOo?}Vlgf~fo{YWm@n_JyD*frOg{XsvBA~|Tn4V6hu>Gd>89-rblfVJUaGvj6X%NZ} z$tFF9sx=4_$*c~G`9iPLGh@=sV+O{D2-t*K@J7H=`V+oVt}8?04WwU3h1BgS!f%1P zFak-T#7`TtLcR=Yz>g0R!ZQrH!YiZOQN=_V-UyncN1Rc18?KY?#O`v#JK+pq0K$~H z3D@v9DZF42R)b9#BBX{^$DOMlJ!g)Gc za{o-1e%F6NvgKq9tC8pV+9S$;9*zNv{J*)n&dmf~anP1)4~N%~h#c(=B#3*KgzhCKhFdgDoWi2IDog{RVyzK|Y`rCUs3T~pJMmdZJy4?b z&s5G=zhf**(t7Y^oC_mcTsE-{^}wiaoUu&?kojLKs>SJPxjcP>{a5CbXCx92AcBE) zHtqP}LjZ{W>PH?Tu(E0X=%{PBMW@F_?#7b&#!^q`<-5$ur+-q6 z{dn=(^UZw6*3-XM_(=@<1_*i&XM4=0t5u!gm6 z{UlmNGPKgO_;e;q9|#esq~Sq`<}%d{+sRmhvsA{5i*91=tub>OZZ%)xUA#4q$dDyy z1`w4%?OPLg3JeZb#cqSMO?*Xn%|-FCcuH2i2fn_{IFusub6;NQdN|7TD1N?%E8*g? z$apAt@cEe!I%jB=*q$p_3=t_5R0ph%{qaq+QDg!c99Y!Xa!&oDZOeis_ot)gNXr{l zdY$|So2Qed2Y7KMNBrS^E169kG%h<+z{Z_p_;shB!uY)>yAVcK=&!bg`lVg)4T1|7 z0}7FpfydVH4F87K@c!nEG+WGKm{Ouo)Slpl;#qcEIQ0zdMfLA#;dBxYw;p;KoVv6| z3_D5&7rJdG12CnDSvZUW?$UC6^UVSW^|vw|o-_4bz)(w5(3AiVhpeT(|=f#x_}E?s#qHZF#xA6AF_ujl$G z-jHD%q(d2}v2PhXx&6YWps~m(^+RXl91Q#xRRJBhjKl$FG4bk);|ag;ieUZ&!Ii3$ z(iGz1+0m7#g5>ASldBbNZL=ZHh=tmmJt$!71; zIML2GhEz1pg@1rQN(M^_691wAGkJ@Pga_05WuQ6! zG5RkGY2^`@(H~pp7&Ga+Pwh3L!Njj!-rc;^bTIfo5hP@H##1X8xUZJckrx>id`bAd3QUx9GuomqBYZ!uN1-&o zvTxC?;p8vL67&fW8fw(YOqt>L@bdLrEF*3OgYe$4n4{ zEB40LiU#6-0@5jdN`0w}N0qi@c0~oT2FP z)LNk&a82my?jv(tQpiMi$TK_L@lub#lsM$R{Dk?Ya@%%%huZkct~tSWM714c!45k}-ZLVA-bVM`>|_ZBbW_m-7| z3U%xrAhi}n?T(2F{_n4EZ10inkIFl#y09?7$uwBoJgqY8vylwev)fDOn;>0R!aEnV zBz%j0Mqpx~EZU3q@%+oV7;}|vt7$~ou@faEIq{p?FY$XXg&6*K)b_LP=}gi9`Bij3 zN`zEo|B6*|-;>S`rNa^BKRDbDAk>X#MsR`EvL>6bqU@SaDDs z8>bu@3YdRaWs*Te@G-UHjU%F~kTHw5(0PVJ+pwh#ha2u;DB+UMo@A5UYIl#5rtBV- zGX_hIpw}3C@H*Us(Cc-d#-gNrG#w$(9+S=GxO>3SR`SE2fHZ2KrDc#_C^$jI>Y}#; zMwY=R6@+dWi~0RXw(c@3GZ&%~9K(q&ee0Zw;pwL`E_tZak-#8^_b)Dpyi73^he?xV zXJ08&wh5-M&}qy4f7!D&=E)puDD(Nmg1d_(j`4LvxM5x_huNg-pGG%9rYqO6mImyJ@}*3Y>^3OvcnTG%EV1) zq_Ap?Z!Iw__7#D=pOWnQN$gB!Mr0!9yx|g<4icJh{cFOu3B8}&RiYm+Mb;VEK``LK zL(NcpcTiGieOIssSjr?ob}^``nNf&UcJhXyncO9m{6gD$kqSD`S69(aF8dkWz5>!9 zBLe4Sib7Hs2x_L2Ls6Ish$MGVKrGt5+_2zCyP1byaCg3upo+-I}R4&$m)8 zQ7|jc1Z^VWggpuQj*cP;>Zo9LS!VSzrqmZczaf;u`d0J(f%Z9r%An@s!e>n9%y=n!IZ_tVGu{Jmsbp}Fk%HJIU?a+-~bjfLTuH|JExA8EROowzr zqW9{YyZhR0a4clRK>1I4Ncx&WER~{iE;F^$T7K%X@3PGOA%6#Z%p3TS^&M;Dnjw@i z^o!$9nhcsmcHcY4?4j9+ofL_CWsZ4Hcch(rjsGfGD(nsH>w}^ERqGnz%iGj0j{g}h z7wMkJ-2Z2~eS>2!i}0~B63i;>SyFJU2+>VCS^AxaDOx%g6-t0eM^P<3+*z`ztvOqrG3)&#$K?& z_Y0wbWID47@cU`E1A6A&!`aZk0ZE@z-h#l1NqX2#`$Uev2gepW`rf8*!=rD5&;Jb{ zl08rU>dPo=K%-1Ao1~G-@4ve~y5#9E8x;TE0k5d^TC(=Zc>mwjW^c=+U-<9}b0ku~}gj z3sbW>R2M6DR!g#NUP;nxo>)@7*=RP{U18SDop6b2&PHce^&h97@xx3t+VK+!keE#} z;(Uf&89as9k8{$nkLbuB!-d7TP`_VJpL^Xs8OKB~ri$YUbW8fch64}7|0EWoT(TRj{ z*GT<7Y<7DsrCi79ZsM)z#c(!nNOGySOCkY1fAuQOq12&iUVC!a`#O;dBLf=d?&4*B zI~LgAO7E0qxK(uRTM;IgJ}+z^gD+bi-6I!3x{r9`l~%8TRP%UE0V8E*Sz>Nl1NVG<<7(wDHZ+HcOkQm$O&k+vyx)y)x{Pz!U8hS$*m zByc0h6BUI*BOpuL==P+H|Hx%`>7!W+1H!l9vi&)`V zyn2o9{z=lc+VX*!Vh~SF=)L}Z40XeG>LF6cP^b+R$NxSeUqbK^Q*UTalKzP8X%{9@RSCXm_NhF>{=S2 zi}ezam_^P`S!!-cyEW9y7DBbK93roz@Raccy*v}?mKXScU9E_4g;hBU7}zSofAFda zKYEe?{{I54 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 94113f200..cea7a793a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a426..f3b75f3b0 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 7101f8e46..9b42019c7 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## diff --git a/neoforge-1.20.6/.gitignore b/neoforge-1.20.6/.gitignore index 7c9cf9c2a..f811a63e2 100644 --- a/neoforge-1.20.6/.gitignore +++ b/neoforge-1.20.6/.gitignore @@ -1,2 +1,4 @@ -/build/ -/.gradle/ +/.gradle +/bin +/build +/runs diff --git a/neoforge-1.20.6/build.gradle b/neoforge-1.20.6/build.gradle index bb05d5ae9..139129ef6 100644 --- a/neoforge-1.20.6/build.gradle +++ b/neoforge-1.20.6/build.gradle @@ -6,7 +6,7 @@ buildscript { } plugins { id 'eclipse' - id 'net.neoforged.gradle.userdev' version '7.0.133' + id 'net.neoforged.gradle.userdev' version '7.0.177' } eclipse { @@ -50,7 +50,7 @@ shadowJar { exclude("META-INF/services/**") } relocate('org.apache.commons.codec', 'org.dynmap.neoforge_1_20_6.commons.codec') - + archiveBaseName = "Dynmap" archiveClassifier = "neoforge-1.20.6" destinationDirectory = file '../target' diff --git a/neoforge-1.21.1/.gitignore b/neoforge-1.21.1/.gitignore new file mode 100644 index 000000000..f811a63e2 --- /dev/null +++ b/neoforge-1.21.1/.gitignore @@ -0,0 +1,4 @@ +/.gradle +/bin +/build +/runs diff --git a/neoforge-1.21.1/build.gradle b/neoforge-1.21.1/build.gradle new file mode 100644 index 000000000..611dca7a6 --- /dev/null +++ b/neoforge-1.21.1/build.gradle @@ -0,0 +1,70 @@ +buildscript { + repositories { + jcenter() + mavenCentral() + } +} +plugins { + id 'eclipse' + id 'net.neoforged.gradle.userdev' version '7.0.177' +} + +eclipse { + project { + name = "Dynmap(NeoForge-1.21.1)" + } +} + +minecraft { + accessTransformers { + file('src/main/resources/META-INF/accesstransformer.cfg') + } +} + +sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(21) // Need this here so eclipse task generates correctly. +java.toolchain.languageVersion = JavaLanguageVersion.of(21) + +println('Java: ' + System.getProperty('java.version') + ' JVM: ' + System.getProperty('java.vm.version') + '(' + System.getProperty('java.vendor') + ') Arch: ' + System.getProperty('os.arch')) + +ext.buildNumber = System.getenv().BUILD_NUMBER ?: "Dev" + + +project.archivesBaseName = "${project.archivesBaseName}-neoforge-1.21.1" + +dependencies { + implementation project(path: ":DynmapCore", configuration: "shadow") + implementation project(path: ':DynmapCoreAPI') + implementation "net.neoforged:neoforge:21.1.93" +} + +processResources +{ + filesMatching('META-INF/neoforge.mods.toml') { + // replace version and mcversion + expand( + version: project.version + '-' + project.ext.buildNumber, + mcversion: "1.21.1" + ) + } +} + +shadowJar { + dependencies { + include(dependency(':DynmapCore')) + include(dependency("commons-codec:commons-codec:")) + exclude("META-INF/maven/**") + exclude("META-INF/services/**") + } + relocate('org.apache.commons.codec', 'org.dynmap.neoforge_1_21_1.commons.codec') + + archiveBaseName = "Dynmap" + archiveClassifier = "neoforge-1.21.1" + destinationDirectory = file '../target' +} + +shadowJar.doLast { + task -> + ant.checksum file: task.archivePath +} + +build.dependsOn(shadowJar) diff --git a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/ClientProxy.java b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/ClientProxy.java new file mode 100644 index 000000000..b47e40d98 --- /dev/null +++ b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/ClientProxy.java @@ -0,0 +1,6 @@ +package org.dynmap.neoforge_1_21_1; + +public class ClientProxy extends Proxy { + public ClientProxy() { + } +} diff --git a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/DynmapMod.java b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/DynmapMod.java new file mode 100644 index 000000000..b210b5d88 --- /dev/null +++ b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/DynmapMod.java @@ -0,0 +1,137 @@ +package org.dynmap.neoforge_1_21_1; + +import java.io.File; + +import org.dynmap.DynmapCommonAPI; +import org.dynmap.DynmapCommonAPIListener; +import org.dynmap.Log; +import org.dynmap.neoforge_1_21_1.DynmapPlugin.OurLog; + +import net.minecraft.server.MinecraftServer; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.ModList; +import net.neoforged.fml.ModLoadingContext; +import net.neoforged.fml.common.Mod; +import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; +import net.neoforged.fml.event.lifecycle.FMLLoadCompleteEvent; +import net.neoforged.fml.loading.FMLEnvironment; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.server.ServerAboutToStartEvent; +import net.neoforged.neoforge.event.server.ServerStartedEvent; +import net.neoforged.neoforge.event.server.ServerStoppingEvent; + +@Mod("dynmap") +public class DynmapMod { + // The instance of your mod that NeoForge uses. + public static DynmapMod instance; + + // Says where the client and server 'proxy' code is loaded. + public static Proxy proxy; + + public static DynmapPlugin plugin; + public static File jarfile; + public static String ver; + public static boolean useforcedchunks; + + public class APICallback extends DynmapCommonAPIListener { + @Override + public void apiListenerAdded() { + if (plugin == null) { + plugin = proxy.startServer(server); + } + } + + @Override + public void apiEnabled(DynmapCommonAPI api) { + } + } + + // TODO + // public class LoadingCallback implements + // net.minecraftforge.common.ForgeChunkManager.LoadingCallback { + // @Override + // public void ticketsLoaded(List tickets, World world) { + // if (tickets.size() > 0) { + // DynmapPlugin.setBusy(world, tickets.get(0)); + // for (int i = 1; i < tickets.size(); i++) { + // ForgeChunkManager.releaseTicket(tickets.get(i)); + // } + // } + // } + // } + + public DynmapMod() { + instance = this; + + if (FMLEnvironment.dist == Dist.CLIENT) { + proxy = new ClientProxy(); + } else { + proxy = new Proxy(); + } + + ModLoadingContext.get().getActiveContainer().getEventBus().addListener(this::setup); + ModLoadingContext.get().getActiveContainer().getEventBus().addListener(this::init); + + NeoForge.EVENT_BUS.register(this); + + // NeoForge removed DisplayTest, with no current replacement. + // A replacement may arrive in a future networking rework + // ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, + // () -> new IExtensionPoint.DisplayTest(() -> IExtensionPoint.DisplayTest.IGNORESERVERONLY, + // (remote, isServer) -> true)); + + Log.setLogger(new OurLog()); + org.dynmap.modsupport.ModSupportImpl.init(); + } + + public void setup(final FMLCommonSetupEvent event) { + // TOOO + jarfile = ModList.get().getModFileById("dynmap").getFile().getFilePath().toFile(); + + ver = ModList.get().getModContainerById("dynmap").get().getModInfo().getVersion().toString(); + + // // Load configuration file - use suggested (config/WesterosBlocks.cfg) + // Configuration cfg = new Configuration(event.getSuggestedConfigurationFile()); + // try { + // cfg.load(); + + // useforcedchunks = cfg.get("Settings", "UseForcedChunks", + // // true).getBoolean(true); + // } finally { + // cfg.save(); + // } + } + + public void init(FMLLoadCompleteEvent event) { + /* Set up for chunk loading notice from chunk manager */ + // TODO + // if (useforcedchunks) { + // ForgeChunkManager.setForcedChunkLoadingCallback(DynmapMod.instance, new LoadingCallback()); + // } else { + // Log.info("[Dynmap] World loading using forced chunks is disabled"); + // } + } + + private MinecraftServer server; + + @SubscribeEvent + public void onServerStarting(ServerAboutToStartEvent event) { + server = event.getServer(); + if (plugin == null) + plugin = proxy.startServer(server); + plugin.onStarting(server.getCommands().getDispatcher()); + } + + @SubscribeEvent + public void onServerStarted(ServerStartedEvent event) { + DynmapCommonAPIListener.register(new APICallback()); + plugin.serverStarted(); + } + + @SubscribeEvent + public void serverStopping(ServerStoppingEvent event) { + proxy.stopServer(plugin); + plugin = null; + } +} diff --git a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/DynmapPlugin.java b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/DynmapPlugin.java new file mode 100644 index 000000000..ec8c58f6d --- /dev/null +++ b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/DynmapPlugin.java @@ -0,0 +1,2100 @@ +package org.dynmap.neoforge_1_21_1; + +import java.io.File; +import java.io.InputStream; +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.commands.CommandSource; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.core.BlockPos; +import net.minecraft.core.IdMapper; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.network.Connection; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.game.ClientboundSetSubtitleTextPacket; +import net.minecraft.network.protocol.game.ClientboundSetTitleTextPacket; +import net.minecraft.network.protocol.game.ClientboundSetTitlesAnimationPacket; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.server.players.GameProfileCache; +import net.minecraft.server.players.UserBanList; +import net.minecraft.tags.BlockTags; +import net.minecraft.world.entity.Pose; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.EmptyBlockGetter; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.LiquidBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.phys.Vec3; + +import net.neoforged.bus.api.EventPriority; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.ModContainer; +import net.neoforged.fml.ModList; +import net.neoforged.fml.loading.LoadingModList; +import net.neoforged.fml.loading.moddiscovery.ModFileInfo; +import net.neoforged.fml.loading.moddiscovery.ModInfo; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.ServerChatEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent.PlayerChangedDimensionEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent.PlayerLoggedInEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent.PlayerLoggedOutEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent.PlayerRespawnEvent; +import net.neoforged.neoforge.event.level.BlockEvent; +import net.neoforged.neoforge.event.level.ChunkDataEvent; +import net.neoforged.neoforge.event.level.ChunkEvent; +import net.neoforged.neoforge.event.level.LevelEvent; +import net.neoforged.neoforge.event.tick.ServerTickEvent; + +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.DynmapListenerManager.EventType; +import org.dynmap.common.DynmapPlayer; +import org.dynmap.common.DynmapServerInterface; +import org.dynmap.common.chunk.GenericChunkCache; +import org.dynmap.neoforge_1_21_1.permissions.FilePermissions; +import org.dynmap.neoforge_1_21_1.permissions.OpPermissions; +import org.dynmap.neoforge_1_21_1.permissions.PermissionProvider; +import org.dynmap.permissions.PermissionsHandler; +import org.dynmap.renderer.DynmapBlockState; +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.RequiredArgumentBuilder; +import com.mojang.brigadier.exceptions.CommandSyntaxException; + +import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; + +public class DynmapPlugin { + private DynmapCore core; + private PermissionProvider permissions; + private boolean core_enabled; + public GenericChunkCache sscache; + public PlayerList playerList; + private MapManager mapManager; + private static 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 LevelAccessor last_world; + private NeoForgeWorld last_fworld; + private Map players = new HashMap(); + // TODO private ForgeMetrics metrics; + private HashSet modsused = new HashSet(); + private NeoForgeServer fserver = new NeoForgeServer(); + 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 useSaveFolder = true; + + 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 { + LevelAccessor w; + String wid; + int x, y, z; + } + + ConcurrentLinkedQueue blockupdatequeue = new ConcurrentLinkedQueue(); + + public static DynmapBlockState[] stateByID; + + private Map knownloadedchunks = new HashMap(); + private boolean didInitialKnownChunks = false; + + private void addKnownChunk(NeoForgeWorld fw, ChunkPos pos) { + LongOpenHashSet cset = knownloadedchunks.get(fw.getName()); + if (cset == null) { + cset = new LongOpenHashSet(); + knownloadedchunks.put(fw.getName(), cset); + } + cset.add(pos.toLong()); + } + + private void removeKnownChunk(NeoForgeWorld fw, ChunkPos pos) { + LongOpenHashSet cset = knownloadedchunks.get(fw.getName()); + if (cset != null) { + cset.remove(pos.toLong()); + } + } + + private boolean checkIfKnownChunk(NeoForgeWorld fw, ChunkPos pos) { + LongOpenHashSet cset = knownloadedchunks.get(fw.getName()); + if (cset != null) { + return cset.contains(pos.toLong()); + } + return false; + } + + private static Registry reg = null; + + private static Registry getBiomeReg() { + if (reg == null) { + reg = server.registryAccess().registryOrThrow(Registries.BIOME); + } + return reg; + } + + /** + * 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 + + IdMapper bsids = Block.BLOCK_STATE_REGISTRY; + + DynmapBlockState basebs = null; + Block baseb = null; + int baseidx = 0; + + Iterator iter = bsids.iterator(); + DynmapBlockState.Builder bld = new DynmapBlockState.Builder(); + while (iter.hasNext()) { + BlockState bs = iter.next(); + int idx = bsids.getId(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 = BuiltInRegistries.BLOCK.getKey(b); + + if (ui == null) { + continue; + } + String bn = ui.getNamespace() + ":" + ui.getPath(); + // Only do defined names, and not "air" + if (!bn.equals(DynmapBlockState.AIR_BLOCK)) { + String statename = ""; + for (net.minecraft.world.level.block.state.properties.Property p : bs.getProperties()) { + if (statename.length() > 0) { + statename += ","; + } + statename += p.getName() + "=" + bs.getValue(p).toString(); + } + int lightAtten = 15; + try { // Workaround for mods with broken block state logic... + lightAtten = bs.isSolidRender(EmptyBlockGetter.INSTANCE, BlockPos.ZERO) ? 15 + : (bs.propagatesSkylightDown(EmptyBlockGetter.INSTANCE, BlockPos.ZERO) ? 0 : 1); + } catch (Exception x) { + Log.warning(String.format("Exception while checking lighting data for block state: %s[%s]", bn, + statename)); + Log.verboseinfo("Exception: " + x.toString()); + } + // Log.info("statename=" + bn + "[" + statename + "], lightAtten=" + lightAtten); + // Fill in base attributes + bld.setBaseState(basebs).setStateIndex(idx - baseidx).setBlockName(bn).setStateName(statename) + .setLegacyBlockID(idx).setAttenuatesLight(lightAtten); + if (bs.getSoundType() != null) { + bld.setMaterial(bs.getSoundType().toString()); + } + if (bs.isSolid()) { + bld.setSolid(); + } + if (bs.isAir()) { + bld.setAir(); + } + if (bs.is(BlockTags.LOGS)) { + bld.setLog(); + } + if (bs.is(BlockTags.LEAVES)) { + bld.setLeaves(); + } + if ((!bs.getFluidState().isEmpty()) && !(bs.getBlock() instanceof LiquidBlock)) { + bld.setWaterlogged(); + } + DynmapBlockState dbs = bld.build(); // Build state + stateByID[idx] = dbs; + if (basebs == null) { + basebs = dbs; + } + } + } + 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); + // } + + private static Biome[] biomelist = null; + + public static final Biome[] getBiomeList() { + if (biomelist == null) { + biomelist = new Biome[256]; + Iterator iter = getBiomeReg().iterator(); + while (iter.hasNext()) { + Biome b = iter.next(); + int bidx = getBiomeReg().getId(b); + if (bidx >= biomelist.length) { + biomelist = Arrays.copyOf(biomelist, bidx + biomelist.length); + } + biomelist[bidx] = b; + } + } + return biomelist; + } + + // public static final NetworkManager getNetworkManager(ServerPlayNetHandler nh) { + // return nh.netManager; + // } + + private NeoForgePlayer getOrAddPlayer(ServerPlayer p) { + String name = p.getName().getString(); + NeoForgePlayer fp = players.get(name); + if (fp != null) { + fp.player = p; + } else { + fp = new NeoForgePlayer(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; + ServerPlayer sender; + } + + private ConcurrentLinkedQueue msgqueue = new ConcurrentLinkedQueue(); + + public class ChatHandler { + @SubscribeEvent + public void handleChat(ServerChatEvent event) { + String msg = event.getMessage().getString(); + 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().getOps().getUserList(); + for (String op : ops) { + if (op.equalsIgnoreCase(player)) { + return true; + } + } + return (server.isSingleplayer() && player.equalsIgnoreCase(server.getSingleplayerProfile().getName())); + } + + private boolean hasPerm(ServerPlayer psender, String permission) { + PermissionsHandler ph = PermissionsHandler.getHandler(); + if ((psender != null) && (ph != null) && ph.hasPermission(psender.getName().getString(), permission)) { + return true; + } + return permissions.has(psender, permission); + } + + private boolean hasPermNode(ServerPlayer psender, String permission) { + PermissionsHandler ph = PermissionsHandler.getHandler(); + if ((psender != null) && (ph != null) && ph.hasPermissionNode(psender.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 NeoForgeServer 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 NeoForgeServer() { + } + + private GameProfile getProfileByName(String player) { + GameProfileCache cache = server.getProfileCache(); + Optional val = cache.get(player); + return val.isPresent() ? val.get() : null; + } + + @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++) { + ServerPlayer p = 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 (ServerPlayer p : players) { + if (p.getName().getString().equalsIgnoreCase(name)) { + return getOrAddPlayer(p); + } + } + + return null; + } + + @Override + public Set getIPBans() { + UserBanList bl = server.getPlayerList().getBans(); + Set ips = new HashSet(); + + for (String s : bl.getUserList()) { + 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.getLocalIp(); + if (sn == null) + sn = "Unknown Server"; + return sn; + } + + @Override + public boolean isPlayerBanned(String pid) { + UserBanList bl = server.getPlayerList().getBans(); + 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(); + NeoForge.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) { + Component component = Component.literal(msg); + server.getPlayerList().broadcastSystemMessage(component, false); + 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.players.PlayerList scm = server.getPlayerList(); + if (scm == null) + return Collections.emptySet(); + UserBanList bl = scm.getBans(); + 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.players.PlayerList scm = server.getPlayerList(); + if (scm == null) + return false; + UserBanList bl = scm.getBans(); + 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) { + NeoForgeMapChunkCache c = (NeoForgeMapChunkCache) 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 (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 NeoForgeMapChunkCache cc = c; + Future f = this.callSyncMethod(new Callable() { + public Boolean call() throws Exception { + // Update busy state on world + NeoForgeWorld fw = (NeoForgeWorld) cc.getWorld(); + // TODO + // setBusy(fw.getWorld()); + cc.getLoadedChunks(); + return true; + } + }, 0); + try { + f.get(); + } catch (CancellationException cx) { + return null; + } catch (InterruptedException 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().getPlayerCount(); + } + + @SubscribeEvent + public void tickEvent(ServerTickEvent.Post event) { + 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(); + BlockState bs = r.w.getBlockState(new BlockPos(r.x, r.y, r.z)); + int idx = Block.BLOCK_STATE_REGISTRY.getId(bs); + if ((idx >= 0) && (!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 NeoForgePlayer(null); + + core.listenerManager.processChatEvent(EventType.PLAYER_CHAT, dp, cm.message); + } + // Check for generated chunks + if ((cur_tick % 20) == 0) { + } + } + + @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.getLocalIp(); + } + + @Override + public File getModContainerFile(String name) { + ModFileInfo mfi = LoadingModList.get().getModFileById(name); // Try case sensitive lookup + if (mfi != null) { + try { + File f = mfi.getFile().getFilePath().toFile(); + return f; + } catch (UnsupportedOperationException ex) { + // TODO Implement proper jar in jar method for fetching data + /* + Log.info("Searching for: " + name); + for (IModInfo e: ModList.get().getMods()) { + Log.info("in: " + e.getModId().toString()); + Log.info("resource: "+ ModList.get().getModFileById(e.getModId()).getFile().findResource(String.valueOf(mfi.getFile().getFilePath()))); + } + */ + Log.warning("jar in jar method found, skipping: " + ex.getMessage()); + } + } + return null; + } + + @Override + public List getModList() { + List mil = LoadingModList.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) { + // NeoForge removed ModContainer#getMod with no replacement + /* if (modid == null) + modid = "minecraft"; + + Optional mc = ModList.get().getModContainerById(modid); + Object mod = (mc.isPresent()) ? mc.get().getMod() : null; + if (mod != null) { + ClassLoader cl = mod.getClass().getClassLoader(); + if (cl == null) + cl = ClassLoader.getSystemClassLoader(); + InputStream is = cl.getResourceAsStream(rname); + if (is != null) { + return is; + } + } + List mcl = LoadingModList.get().getMods(); + for (ModInfo mci : mcl) { + mc = ModList.get().getModContainerById(mci.getModId()); + mod = (mc.isPresent()) ? mc.get().getMod() : null; + if (mod == null) + continue; + ClassLoader cl = mod.getClass().getClassLoader(); + if (cl == null) + cl = ClassLoader.getSystemClassLoader(); + InputStream is = cl.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 NeoForgePlayer extends NeoForgeCommandSender implements DynmapPlayer { + private ServerPlayer player; + private final String skinurl; + private final UUID uuid; + + public NeoForgePlayer(ServerPlayer p) { + player = p; + String url = null; + if (player != null) { + uuid = player.getUUID(); + 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.value()), + 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.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; + } + Vec3 v = player.position(); + return toLoc(player.serverLevel(), v.x, v.y, v.z); + } + + @Override + public String getWorld() { + if (player == null) { + return null; + } + + if (player.serverLevel() != null) { + return DynmapPlugin.this.getWorld((ServerLevel) player.serverLevel()).getName(); + } + + return null; + } + + public static final Connection getNetworkManager(ServerGamePacketListenerImpl nh) { + return nh.getConnection(); + } + + @Override + public InetSocketAddress getAddress() { + if ((player != null) && (player instanceof ServerPlayer)) { + ServerGamePacketListenerImpl nsh = ((ServerPlayer) 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.getPose() == Pose.CROUCHING; + } + + 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.getArmorValue(); + } 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.getName().getString()); + } + + @Override + public void sendMessage(String msg) { + Component ichatcomponent = Component.literal(msg); + player.sendSystemMessage(ichatcomponent); + } + + @Override + public boolean isInvisible() { + if (player != null) { + return player.isInvisible(); + } + return false; + } + + @Override + public boolean isSpectator() { + if (player != null) { + return player.isSpectator(); + } + 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 ServerPlayer) { + ServerPlayer mp = (ServerPlayer) player; + ClientboundSetTitlesAnimationPacket times = new ClientboundSetTitlesAnimationPacket(fadeInTicks, + stayTicks, fadeOutTicks); + mp.connection.send(times); + if (title != null) { + ClientboundSetTitleTextPacket titlepkt = new ClientboundSetTitleTextPacket( + Component.literal(title)); + mp.connection.send(titlepkt); + } + + if (subtitle != null) { + ClientboundSetSubtitleTextPacket subtitlepkt = new ClientboundSetSubtitleTextPacket( + Component.literal(subtitle)); + mp.connection.send(subtitlepkt); + } + } + } + } + + /* Handler for generic console command sender */ + public class NeoForgeCommandSender implements DynmapCommandSender { + private CommandSourceStack sender; + + protected NeoForgeCommandSender() { + sender = null; + } + + public NeoForgeCommandSender(CommandSourceStack send) { + sender = send; + } + + @Override + public boolean hasPrivilege(String privid) { + return true; + } + + @Override + public void sendMessage(String msg) { + if (sender != null) { + Component ichatcomponent = Component.literal(msg); + sender.sendSuccess(() -> ichatcomponent, true); + } + } + + @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) { + ResourceLocation regid = getBiomeReg().getKey(bb); + String id = regid.getPath(); + String rl = regid.toString(); + float tmp = bb.getBaseTemperature(), hum = bb.getModifiedClimateSettings().downfall(); + int watermult = bb.getWaterColor(); + Log.verboseinfo( + "biome[" + i + "]: hum=" + hum + ", tmp=" + tmp + ", mult=" + Integer.toHexString(watermult)); + + BiomeMap bmap = BiomeMap.NULL; + if (rl != null) { // If resource location, lookup by this + bmap = BiomeMap.byBiomeResourceLocation(rl); + } else { + bmap = BiomeMap.byBiomeID(i); + } + if (bmap.isDefault() || (bmap == BiomeMap.NULL)) { + bmap = new BiomeMap((rl != null) ? BiomeMap.NO_INDEX : i, id, tmp, hum, rl); + 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)); + } + bmap.setBiomeObject(bb); + } + } + 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.toString(); + } + } + return lst; + } + + public void onEnable() { + /* Get MC version */ + String mcver = server.getServerVersion(); + /* 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); + 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) { + NeoForge.EVENT_BUS.register(fserver); + tickregistered = true; + } + + playerList = core.playerList; + sscache = new GenericChunkCache(core.getSnapShotCacheSize(), core.useSoftRefInSnapShotCache()); + /* Get map manager from core */ + mapManager = core.getMapManager(); + + /* Load saved world definitions */ + loadWorlds(); + + /* Initialized the currently loaded worlds */ + for (ServerLevel world : server.getAllLevels()) { + NeoForgeWorld w = this.getWorld(world); + } + for (NeoForgeWorld 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"); + + // 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(CommandSourceStack commandSourceStack, String cmd, String[] args) { + DynmapCommandSender dsender; + ServerPlayer psender; + try { + psender = commandSourceStack.getPlayerOrException(); + } catch (com.mojang.brigadier.exceptions.CommandSyntaxException x) { + psender = null; + } + + if (psender != null) { + dsender = new NeoForgePlayer(psender); + } else { + dsender = new NeoForgeCommandSender(commandSourceStack); + } + 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(ServerLevel level, double x, double y, double z) { + return new DynmapLocation(DynmapPlugin.this.getWorld(level).getName(), x, y, z); + } + + public class PlayerTracker { + @SubscribeEvent + public void onPlayerLogin(PlayerLoggedInEvent event) { + if (!core_enabled) + return; + final DynmapPlayer dp = getOrAddPlayer((ServerPlayer) event.getEntity()); + /* 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((ServerPlayer) event.getEntity()); + final String name = event.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((ServerPlayer) event.getEntity()); // Freshen player object reference + } + + @SubscribeEvent + public void onPlayerRespawn(PlayerRespawnEvent event) { + if (!core_enabled) + return; + getOrAddPlayer((ServerPlayer) event.getEntity()); // Freshen player object reference + } + } + + private PlayerTracker playerTracker = null; + + private void registerPlayerLoginListener() { + if (playerTracker == null) { + playerTracker = new PlayerTracker(); + NeoForge.EVENT_BUS.register(playerTracker); + } + } + + public class WorldTracker { + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleWorldLoad(LevelEvent.Load event) { + if (!core_enabled) + return; + LevelAccessor w = event.getLevel(); + if (!(w instanceof ServerLevel)) + return; + final NeoForgeWorld fw = getWorld((ServerLevel) 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(priority = EventPriority.LOWEST) + public void handleWorldUnload(LevelEvent.Unload event) { + if (!core_enabled) + return; + LevelAccessor w = event.getLevel(); + if (!(w instanceof ServerLevel)) + return; + final NeoForgeWorld fw = getWorld((ServerLevel) 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(priority = EventPriority.LOWEST) + public void handleChunkLoad(ChunkEvent.Load event) { + if (!onchunkgenerate) + return; + + LevelAccessor w = event.getLevel(); + if (!(w instanceof ServerLevel)) + return; + ChunkAccess c = event.getChunk(); + if ((c != null) && (c.getPersistedStatus() == ChunkStatus.FULL) && (c instanceof LevelChunk)) { + NeoForgeWorld fw = getWorld((ServerLevel) w, false); + if (fw != null) { + addKnownChunk(fw, c.getPos()); + } + } + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleChunkUnload(ChunkEvent.Unload event) { + if (!onchunkgenerate) + return; + + LevelAccessor w = event.getLevel(); + if (!(w instanceof ServerLevel)) + return; + ChunkAccess c = event.getChunk(); + if (c != null) { + NeoForgeWorld fw = getWorld((ServerLevel) w, false); + ChunkPos cp = c.getPos(); + if (fw != null) { + if (!checkIfKnownChunk(fw, cp)) { + int ymax = Integer.MIN_VALUE; + int ymin = Integer.MAX_VALUE; + LevelChunkSection[] sections = c.getSections(); + for (int i = 0; i < sections.length; i++) { + if ((sections[i] != null) && (sections[i].hasOnlyAir() == false)) { + int sy = c.getSectionYFromSectionIndex(i); + if (sy < ymin) + ymin = sy; + if ((sy + 16) > ymax) + ymax = sy + 16; + } + } + int x = cp.x << 4; + int z = cp.z << 4; + // If not empty AND not initial scan + if (ymax != Integer.MIN_VALUE) { + // Log.info(String.format("chunkkeyerate(unload)(%s,%d,%d,%d,%d,%d,%s)", fw.getName(), x, ymin, z, x+15, ymax, z+15)); + mapManager.touchVolume(fw.getName(), x, ymin, z, x + 15, ymax, z + 15, "chunkgenerate"); + } + } + removeKnownChunk(fw, cp); + } + } + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleChunkDataSave(ChunkDataEvent.Save event) { + if (!onchunkgenerate) + return; + + LevelAccessor w = event.getLevel(); + if (!(w instanceof ServerLevel)) + return; + ChunkAccess c = event.getChunk(); + if (c != null) { + NeoForgeWorld fw = getWorld((ServerLevel) w, false); + ChunkPos cp = c.getPos(); + if (fw != null) { + if (!checkIfKnownChunk(fw, cp)) { + int ymax = Integer.MIN_VALUE; + int ymin = Integer.MAX_VALUE; + LevelChunkSection[] sections = c.getSections(); + for (int i = 0; i < sections.length; i++) { + if ((sections[i] != null) && (sections[i].hasOnlyAir() == false)) { + int sy = c.getSectionYFromSectionIndex(i); + if (sy < ymin) + ymin = sy; + if ((sy + 16) > ymax) + ymax = sy + 16; + } + } + int x = cp.x << 4; + int z = cp.z << 4; + // If not empty AND not initial scan + if (ymax != Integer.MIN_VALUE) { + // Log.info(String.format("chunkkeyerate(save)(%s,%d,%d,%d,%d,%d,%s)", fw.getName(), x, ymin, z, x+15, ymax, z+15)); + mapManager.touchVolume(fw.getName(), x, ymin, z, x + 15, ymax, z + 15, "chunkgenerate"); + } + // If cooked, add to known + if ((c.getPersistedStatus() == ChunkStatus.FULL) && (c instanceof LevelChunk)) { + addKnownChunk(fw, cp); + } + } + } + } + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleBlockToolModificationEvent(BlockEvent.BlockToolModificationEvent event) { + handleBlockEvent(event); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleBreakEvent(BlockEvent.BreakEvent event) { + handleBlockEvent(event); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleEntityMultiPlaceEvent(BlockEvent.EntityMultiPlaceEvent event) { + handleBlockEvent(event); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleEntityPlaceEvent(BlockEvent.EntityPlaceEvent event) { + handleBlockEvent(event); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleFarmlandTrampleEvent(BlockEvent.FarmlandTrampleEvent event) { + handleBlockEvent(event); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleFluidPlaceBlockEvent(BlockEvent.FluidPlaceBlockEvent event) { + handleBlockEvent(event); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleNeighborNotifyEvent(BlockEvent.NeighborNotifyEvent event) { + handleBlockEvent(event); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handlePortalSpawnEvent(BlockEvent.PortalSpawnEvent event) { + handleBlockEvent(event); + } + + private void handleBlockEvent(BlockEvent event) { + if (!core_enabled) + return; + if (!onblockchange) + return; + BlockUpdateRec r = new BlockUpdateRec(); + r.w = event.getLevel(); + if (!(r.w instanceof ServerLevel)) + return; // band-aid to prevent errors in unsupported 'running in client' scenario + NeoForgeWorld fw = getWorld((ServerLevel) r.w, false); + if (fw == null) + return; + r.wid = fw.getName(); + BlockPos p = event.getPos(); + r.x = p.getX(); + r.y = p.getY(); + r.z = p.getZ(); + blockupdatequeue.add(r); + } + } + + private WorldTracker worldTracker = null; + private boolean onblockchange = false; + private boolean onchunkpopulate = false; + private boolean onchunkgenerate = false; + private boolean onblockchange_with_id = false; + + private void registerEvents() { + // To trigger rendering. + onblockchange = core.isTrigger("blockupdate"); + onchunkpopulate = core.isTrigger("chunkpopulate"); + onchunkgenerate = core.isTrigger("chunkgenerate"); + onblockchange_with_id = core.isTrigger("blockupdate-with-id"); + if (onblockchange_with_id) + onblockchange = true; + if ((worldTracker == null) && (onblockchange || onchunkpopulate || onchunkgenerate)) { + worldTracker = new WorldTracker(); + NeoForge.EVENT_BUS.register(worldTracker); + } + // Prime the known full chunks + if (onchunkgenerate && (server.getAllLevels() != null)) { + for (ServerLevel world : server.getAllLevels()) { + NeoForgeWorld fw = getWorld(world); + if (fw == null) + continue; + Long2ObjectLinkedOpenHashMap chunks = world.getChunkSource().chunkMap.visibleChunkMap; + for (Entry k : chunks.long2ObjectEntrySet()) { + long key = k.getKey().longValue(); + ChunkHolder ch = k.getValue(); + ChunkAccess c = null; + try { + c = ch.getChunkToSend(); + } catch (Exception x) { + } + if (c == null) + continue; + ChunkStatus cs = c.getPersistedStatus(); + ChunkPos pos = ch.getPos(); + if (cs == ChunkStatus.FULL) { // Cooked? + // Add it as known + addKnownChunk(fw, pos); + } + } + } + } + } + + private NeoForgeWorld getWorldByName(String name) { + return worlds.get(name); + } + + private NeoForgeWorld getWorld(ServerLevel w) { + return getWorld(w, true); + } + + private NeoForgeWorld getWorld(ServerLevel w, boolean add_if_not_found) { + if (last_world == w) { + return last_fworld; + } + String wname = NeoForgeWorld.getWorldName(w); + + for (NeoForgeWorld fw : worlds.values()) { + if (fw.getRawName().equals(wname)) { + last_world = w; + last_fworld = fw; + if (fw.isLoaded() == false) { + fw.setWorldLoaded(w); + } + fw.updateWorld(w); + return fw; + } + } + NeoForgeWorld fw = null; + if (add_if_not_found) { + /* Add to list if not found */ + fw = new NeoForgeWorld(w); + worlds.put(fw.getName(), fw); + } + last_world = w; + last_fworld = fw; + return fw; + } + + private void saveWorlds() { + File f = new File(core.getDataFolder(), "neoforgeworlds.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("miny", fw.minY); + vals.put("sealevel", fw.sealevel); + vals.put("nether", fw.isNether()); + vals.put("the_end", ((NeoForgeWorld) fw).isTheEnd()); + vals.put("title", fw.getTitle()); + lst.add(vals); + } + cn.put("worlds", lst); + cn.put("useSaveFolderAsName", useSaveFolder); + cn.put("maxWorldHeight", NeoForgeWorld.getMaxWorldHeight()); + + cn.save(); + } + + private void loadWorlds() { + File f = new File(core.getDataFolder(), "neoforgeworlds.yml"); + if (f.canRead() == false) { + useSaveFolder = true; + return; + } + ConfigurationNode cn = new ConfigurationNode(f); + cn.load(); + // If defined, use maxWorldHeight + NeoForgeWorld.setMaxWorldHeight(cn.getInteger("maxWorldHeight", 256)); + + // If setting defined, use it + if (cn.containsKey("useSaveFolderAsName")) { + useSaveFolder = cn.getBoolean("useSaveFolderAsName", useSaveFolder); + } + List> lst = cn.getMapList("worlds"); + if (lst == null) { + Log.warning("Discarding bad neoforgeworlds.yml"); + return; + } + + for (Map world : lst) { + try { + String name = (String) world.get("name"); + int height = (Integer) world.get("height"); + Integer miny = (Integer) world.get("miny"); + 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) { + NeoForgeWorld fw = new NeoForgeWorld(name, height, sealevel, nether, theend, title, + (miny != null) ? miny : 0); + fw.setWorldUnloaded(); + core.processWorldLoad(fw); + worlds.put(fw.getName(), fw); + } + } catch (Exception x) { + Log.warning("Unable to load saved worlds from neoforgeworlds.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, CommandSourceStack commandSourceStack, + String cmdline) { + String[] args = cmdline.split("\\s+"); + plugin.onCommand(commandSourceStack, 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); + } +} diff --git a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NBT.java b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NBT.java new file mode 100644 index 000000000..840b393b0 --- /dev/null +++ b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NBT.java @@ -0,0 +1,126 @@ +package org.dynmap.neoforge_1_21_1; + +import org.dynmap.common.chunk.GenericBitStorage; +import org.dynmap.common.chunk.GenericNBTCompound; +import org.dynmap.common.chunk.GenericNBTList; + +import java.util.Set; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.util.SimpleBitStorage; + +public class NBT { + + public static class NBTCompound implements GenericNBTCompound { + private final CompoundTag obj; + public NBTCompound(CompoundTag t) { + this.obj = t; + } + @Override + public Set getAllKeys() { + return obj.getAllKeys(); + } + @Override + public boolean contains(String s) { + return obj.contains(s); + } + @Override + public boolean contains(String s, int i) { + return obj.contains(s, i); + } + @Override + public byte getByte(String s) { + return obj.getByte(s); + } + @Override + public short getShort(String s) { + return obj.getShort(s); + } + @Override + public int getInt(String s) { + return obj.getInt(s); + } + @Override + public long getLong(String s) { + return obj.getLong(s); + } + @Override + public float getFloat(String s) { + return obj.getFloat(s); + } + @Override + public double getDouble(String s) { + return obj.getDouble(s); + } + @Override + public String getString(String s) { + return obj.getString(s); + } + @Override + public byte[] getByteArray(String s) { + return obj.getByteArray(s); + } + @Override + public int[] getIntArray(String s) { + return obj.getIntArray(s); + } + @Override + public long[] getLongArray(String s) { + return obj.getLongArray(s); + } + @Override + public GenericNBTCompound getCompound(String s) { + return new NBTCompound(obj.getCompound(s)); + } + @Override + public GenericNBTList getList(String s, int i) { + return new NBTList(obj.getList(s, i)); + } + @Override + public boolean getBoolean(String s) { + return obj.getBoolean(s); + } + @Override + public String getAsString(String s) { + return obj.get(s).getAsString(); + } + @Override + public GenericBitStorage makeBitStorage(int bits, int count, long[] data) { + return new OurBitStorage(bits, count, data); + } + public String toString() { + return obj.toString(); + } + } + public static class NBTList implements GenericNBTList { + private final ListTag obj; + public NBTList(ListTag t) { + obj = t; + } + @Override + public int size() { + return obj.size(); + } + @Override + public String getString(int idx) { + return obj.getString(idx); + } + @Override + public GenericNBTCompound getCompound(int idx) { + return new NBTCompound(obj.getCompound(idx)); + } + public String toString() { + return obj.toString(); + } + } + public static class OurBitStorage implements GenericBitStorage { + private final SimpleBitStorage bs; + public OurBitStorage(int bits, int count, long[] data) { + bs = new SimpleBitStorage(bits, count, data); + } + @Override + public int get(int idx) { + return bs.get(idx); + } + } +} diff --git a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeMapChunkCache.java b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeMapChunkCache.java new file mode 100644 index 000000000..b7ea60456 --- /dev/null +++ b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeMapChunkCache.java @@ -0,0 +1,114 @@ +package org.dynmap.neoforge_1_21_1; + +import java.util.List; +import java.util.NoSuchElementException; + +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeSpecialEffects; +import org.dynmap.DynmapChunk; +import org.dynmap.Log; +import org.dynmap.common.BiomeMap; +import org.dynmap.common.chunk.GenericChunk; +import org.dynmap.common.chunk.GenericChunkCache; +import org.dynmap.common.chunk.GenericMapChunkCache; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.storage.ChunkSerializer; +import net.minecraft.world.level.chunk.status.ChunkStatus; + +/** + * Container for managing chunks - dependent upon using chunk snapshots, since + * rendering is off server thread + */ +public class NeoForgeMapChunkCache extends GenericMapChunkCache { + private ServerLevel w; + private ServerChunkCache cps; + + /** + * Construct empty cache + */ + public NeoForgeMapChunkCache(GenericChunkCache cc) { + super(cc); + } + + // Load generic chunk from existing and already loaded chunk + protected GenericChunk getLoadedChunk(DynmapChunk chunk) { + GenericChunk gc = null; + ChunkAccess ch = cps.getChunk(chunk.x, chunk.z, ChunkStatus.FULL, false); + if (ch != null) { + CompoundTag nbt = ChunkSerializer.write(w, ch); + if (nbt != null) { + gc = parseChunkFromNBT(new NBT.NBTCompound(nbt)); + } + } + return gc; + } + + // Load generic chunk from unloaded chunk + protected GenericChunk loadChunk(DynmapChunk chunk) { + GenericChunk gc = null; + CompoundTag nbt = readChunk(chunk.x, chunk.z); + // If read was good + if (nbt != null) { + gc = parseChunkFromNBT(new NBT.NBTCompound(nbt)); + } + return gc; + } + + public void setChunks(NeoForgeWorld dw, List chunks) { + this.w = dw.getWorld(); + if (dw.isLoaded()) { + /* Check if world's provider is ServerChunkProvider */ + cps = this.w.getChunkSource(); + } + super.setChunks(dw, chunks); + } + + private CompoundTag readChunk(int x, int z) { + try { + CompoundTag rslt = cps.chunkMap.read(new ChunkPos(x, z)).join().get(); + if (rslt != null) { + CompoundTag lev = rslt; + if (lev.contains("Level")) { + lev = lev.getCompound("Level"); + } + // Don't load uncooked chunks + String stat = lev.getString("Status"); + ChunkStatus cs = ChunkStatus.byName(stat); + if ((stat == null) || + // Needs to be at least lighted + (!cs.isOrAfter(ChunkStatus.LIGHT))) { + rslt = null; + } + } + // Log.info(String.format("loadChunk(%d,%d)=%s", x, z, (rslt != null) ? + // rslt.toString() : "null")); + return rslt; + } catch (NoSuchElementException nsex) { + return null; + } catch (Exception exc) { + Log.severe(String.format("Error reading chunk: %s,%d,%d", dw.getName(), x, z), exc); + return null; + } + } + + @Override + public int getFoliageColor(BiomeMap bm, int[] colormap, int x, int z) { + return bm.getBiomeObject().map(Biome::getSpecialEffects) + .flatMap(BiomeSpecialEffects::getFoliageColorOverride) + .orElse(colormap[bm.biomeLookup()]); + } + + @Override + public int getGrassColor(BiomeMap bm, int[] colormap, int x, int z) { + BiomeSpecialEffects effects = bm.getBiomeObject().map(Biome::getSpecialEffects).orElse(null); + if (effects == null) + return colormap[bm.biomeLookup()]; + return effects.getGrassColorModifier().modifyColor(x, z, + effects.getGrassColorOverride().orElse(colormap[bm.biomeLookup()])); + } +} diff --git a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeWorld.java b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeWorld.java new file mode 100644 index 000000000..2ebd37a2d --- /dev/null +++ b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeWorld.java @@ -0,0 +1,243 @@ +package org.dynmap.neoforge_1_21_1; + +/** + * NeoForge specific implementation of DynmapWorld + */ +import java.util.List; + +import net.minecraft.world.level.ServerLevelAccessor; +import net.minecraft.world.level.border.WorldBorder; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LightLayer; + +import org.dynmap.DynmapChunk; +import org.dynmap.DynmapLocation; +import org.dynmap.DynmapWorld; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.Polygon; + +public class NeoForgeWorld extends DynmapWorld { + private ServerLevelAccessor world; + private final boolean skylight; + private final boolean isnether; + private final boolean istheend; + private final String env; + private DynmapLocation spawnloc = new DynmapLocation(); + private static int maxWorldHeight = 320; // Maximum allows world height + + public static int getMaxWorldHeight() { + return maxWorldHeight; + } + + public static void setMaxWorldHeight(int h) { + maxWorldHeight = h; + } + + public static String getWorldName(ServerLevelAccessor w) { + ResourceKey rk = w.getLevel().dimension(); + String id = rk.location().getNamespace() + "_" + rk.location().getPath(); + if (id.equals("minecraft_overworld")) { // Overworld? + return w.getLevel().serverLevelData.getLevelName(); + } else if (id.equals("minecraft_the_end")) { + return "DIM1"; + } else if (id.equals("minecraft_the_nether")) { + return "DIM-1"; + } else { + return id; + } + } + + public void updateWorld(ServerLevelAccessor w) { + this.updateWorldHeights(w.getLevel().getHeight(), w.getLevel().dimensionType().minY(), + w.getLevel().getSeaLevel()); + } + + public NeoForgeWorld(ServerLevelAccessor w) { + this(getWorldName(w), + w.getLevel().getHeight(), + w.getLevel().getSeaLevel(), + w.getLevel().dimension() == Level.NETHER, + w.getLevel().dimension() == Level.END, + getWorldName(w), + w.getLevel().dimensionType().minY()); + setWorldLoaded(w); + } + + public NeoForgeWorld(String name, int height, int sealevel, boolean nether, boolean the_end, String deftitle, + int miny) { + super(name, (height > maxWorldHeight) ? maxWorldHeight : height, sealevel, miny); + world = null; + setTitle(deftitle); + isnether = nether; + istheend = the_end; + skylight = !(isnether || istheend); + + if (isnether) { + env = "nether"; + } else if (istheend) { + env = "the_end"; + } else { + env = "normal"; + } + // Log.info(getName() + ": skylight=" + skylight + ", height=" + + // this.worldheight + ", isnether=" + isnether + ", istheend=" + istheend); + } + + /* Test if world is nether */ + @Override + public boolean isNether() { + return isnether; + } + + public boolean isTheEnd() { + return istheend; + } + + /* Get world spawn location */ + @Override + public DynmapLocation getSpawnLocation() { + if (world != null) { + BlockPos p = world.getLevel().getSharedSpawnPos(); + spawnloc.x = p.getX(); + spawnloc.y = p.getY(); + spawnloc.z = p.getZ(); + spawnloc.world = this.getName(); + } + return spawnloc; + } + + /* Get world time */ + @Override + public long getTime() { + if (world != null) + return world.getLevel().getDayTime(); + else + return -1; + } + + /* World is storming */ + @Override + public boolean hasStorm() { + if (world != null) + return world.getLevel().isRaining(); + else + return false; + } + + /* World is thundering */ + @Override + public boolean isThundering() { + if (world != null) + return world.getLevel().isThundering(); + else + return false; + } + + /* World is loaded */ + @Override + public boolean isLoaded() { + return (world != null); + } + + /* Set world to unloaded */ + @Override + public void setWorldUnloaded() { + getSpawnLocation(); + world = null; + } + + /* Set world to loaded */ + public void setWorldLoaded(ServerLevelAccessor w) { + world = w; + this.sealevel = w.getLevel().getSeaLevel(); // Read actual current sealevel from world + // Update lighting table + for (int i = 0; i < 16; i++) { + // Algorithm based on LightmapTextureManager.getBrightness() + // We can't call that method because it's client-only. + // This means the code below can stop being correct if Mojang ever + // updates the curve; in that case we should reflect the changes. + float value = (float) i / 15.0f; + float brightness = value / (4.0f - 3.0f * value); + this.setBrightnessTableEntry(i, brightness); + // Log.info(getName() + ": light " + i + " = " + light); + } + } + + /* Get light level of block */ + @Override + public int getLightLevel(int x, int y, int z) { + if (world != null) + return world.getLevel().getLightEngine().getRawBrightness(new BlockPos(x, y, z), 0); + else + return -1; + } + + /* Get highest Y coord of given location */ + @Override + public int getHighestBlockYAt(int x, int z) { + if (world != null) { + return world.getLevel().getChunk(x >> 4, z >> 4).getHeight(Heightmap.Types.MOTION_BLOCKING, x & 15, z & 15); + } else + return -1; + } + + /* Test if sky light level is requestable */ + @Override + public boolean canGetSkyLightLevel() { + return skylight; + } + + /* Return sky light level */ + @Override + public int getSkyLightLevel(int x, int y, int z) { + if (world != null) { + return world.getLevel().getBrightness(LightLayer.SKY, new BlockPos(x, y, z)); + } else + return -1; + } + + /** + * Get world environment ID (lower case - normal, the_end, nether) + */ + @Override + public String getEnvironment() { + return env; + } + + /** + * Get map chunk cache for world + */ + @Override + public MapChunkCache getChunkCache(List chunks) { + if (world != null) { + NeoForgeMapChunkCache c = new NeoForgeMapChunkCache(DynmapPlugin.plugin.sscache); + c.setChunks(this, chunks); + return c; + } + return null; + } + + public ServerLevel getWorld() { + return world.getLevel(); + } + + @Override + public Polygon getWorldBorder() { + if (world != null) { + WorldBorder wb = world.getWorldBorder(); + if ((wb != null) && (wb.getSize() < 5.9E7)) { + Polygon p = new Polygon(); + p.addVertex(wb.getMinX(), wb.getMinZ()); + p.addVertex(wb.getMinX(), wb.getMaxZ()); + p.addVertex(wb.getMaxX(), wb.getMaxZ()); + p.addVertex(wb.getMaxX(), wb.getMinZ()); + return p; + } + } + return null; + } +} diff --git a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/Proxy.java b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/Proxy.java new file mode 100644 index 000000000..670f88452 --- /dev/null +++ b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/Proxy.java @@ -0,0 +1,24 @@ +package org.dynmap.neoforge_1_21_1; + +import net.minecraft.server.MinecraftServer; + +/** + * Server side proxy - methods for creating and cleaning up plugin + */ +public class Proxy { + public Proxy() { + } + + public DynmapPlugin startServer(MinecraftServer srv) { + DynmapPlugin plugin = DynmapPlugin.plugin; + if (plugin == null) { + plugin = new DynmapPlugin(srv); + plugin.onEnable(); + } + return plugin; + } + + public void stopServer(DynmapPlugin plugin) { + plugin.onDisable(); + } +} diff --git a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/VersionCheck.java b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/VersionCheck.java new file mode 100644 index 000000000..0a337a47b --- /dev/null +++ b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/VersionCheck.java @@ -0,0 +1,100 @@ +package org.dynmap.neoforge_1_21_1; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; + +import org.dynmap.DynmapCore; +import org.dynmap.Log; + +public class VersionCheck { + private static final String VERSION_URL = "http://mikeprimm.com/dynmap/releases.php"; + + public static void runCheck(final DynmapCore core) { + new Thread(new Runnable() { + public void run() { + doCheck(core); + } + }).start(); + } + + private static int getReleaseVersion(String s) { + int index = s.lastIndexOf('-'); + if (index < 0) + index = s.lastIndexOf('.'); + if (index >= 0) + s = s.substring(0, index); + String[] split = s.split("\\."); + int v = 0; + try { + for (int i = 0; (i < split.length) && (i < 3); i++) { + v += Integer.parseInt(split[i]) << (8 * (2 - i)); + } + } catch (NumberFormatException nfx) { + } + return v; + } + + private static int getBuildNumber(String s) { + int index = s.lastIndexOf('-'); + if (index < 0) + index = s.lastIndexOf('.'); + if (index >= 0) + s = s.substring(index + 1); + try { + return Integer.parseInt(s); + } catch (NumberFormatException nfx) { + return 99999999; + } + } + + private static void doCheck(DynmapCore core) { + String pluginver = core.getDynmapPluginVersion(); + String platform = core.getDynmapPluginPlatform(); + String platver = core.getDynmapPluginPlatformVersion(); + if ((pluginver == null) || (platform == null) || (platver == null)) + return; + HttpURLConnection conn = null; + String loc = VERSION_URL; + int cur_ver = getReleaseVersion(pluginver); + int cur_bn = getBuildNumber(pluginver); + try { + while ((loc != null) && (!loc.isEmpty())) { + URL url = new URL(loc); + conn = (HttpURLConnection) url.openConnection(); + conn.setRequestProperty("User-Agent", "Dynmap (" + platform + "/" + platver + "/" + pluginver); + conn.connect(); + loc = conn.getHeaderField("Location"); + } + BufferedReader rdr = new BufferedReader(new InputStreamReader(conn.getInputStream())); + String line = null; + while ((line = rdr.readLine()) != null) { + String[] split = line.split(":"); + if (split.length < 4) + continue; + /* If our platform and version, or wildcard platform version */ + if (split[0].equals(platform) && (split[1].equals("*") || split[1].equals(platver))) { + int recommended_ver = getReleaseVersion(split[2]); + int recommended_bn = getBuildNumber(split[2]); + if ((recommended_ver > cur_ver) || ((recommended_ver == cur_ver) + && (recommended_bn > cur_bn))) { /* Newer recommended build */ + Log.info("Version obsolete: new recommended version " + split[2] + " is available."); + } else if (cur_ver > recommended_ver) { /* Running dev or prerelease? */ + int prerel_ver = getReleaseVersion(split[3]); + int prerel_bn = getBuildNumber(split[3]); + if ((prerel_ver > cur_ver) || ((prerel_ver == cur_ver) && (prerel_bn > cur_bn))) { + Log.info("Version obsolete: new prerelease version " + split[3] + " is available."); + } + } + } + } + } catch (Exception x) { + Log.info("Error checking for latest version"); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + } +} diff --git a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/permissions/FilePermissions.java b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/permissions/FilePermissions.java new file mode 100644 index 000000000..b2da6ef08 --- /dev/null +++ b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/permissions/FilePermissions.java @@ -0,0 +1,104 @@ +package org.dynmap.neoforge_1_21_1.permissions; + +import java.io.File; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.dynmap.ConfigurationNode; +import org.dynmap.Log; +import org.dynmap.neoforge_1_21_1.DynmapPlugin; + +import net.minecraft.server.level.ServerPlayer; + +public class FilePermissions implements PermissionProvider { + private HashMap> perms; + private Set defperms; + + public static FilePermissions create() { + File f = new File("dynmap/permissions.yml"); + if (!f.exists()) + return null; + ConfigurationNode cfg = new ConfigurationNode(f); + cfg.load(); + + Log.info("Using permissions.yml for access control"); + + return new FilePermissions(cfg); + } + + private FilePermissions(ConfigurationNode cfg) { + perms = new HashMap>(); + for (String k : cfg.keySet()) { + List p = cfg.getStrings(k, null); + if (p != null) { + k = k.toLowerCase(); + HashSet pset = new HashSet(); + for (String perm : p) { + pset.add(perm.toLowerCase()); + } + perms.put(k, pset); + if (k.equals("defaultuser")) { + defperms = pset; + } + } + } + } + + private boolean hasPerm(String player, String perm) { + Set ps = perms.get(player); + if ((ps != null) && (ps.contains(perm))) { + return true; + } + if (defperms.contains(perm)) { + return true; + } + return false; + } + + @Override + public Set hasOfflinePermissions(String player, Set perms) { + player = player.toLowerCase(); + HashSet rslt = new HashSet(); + if (DynmapPlugin.plugin.isOp(player)) { + rslt.addAll(perms); + } else { + for (String p : perms) { + if (hasPerm(player, p)) { + rslt.add(p); + } + } + } + return rslt; + } + + @Override + public boolean hasOfflinePermission(String player, String perm) { + player = player.toLowerCase(); + if (DynmapPlugin.plugin.isOp(player)) { + return true; + } else { + return hasPerm(player, perm); + } + } + + @Override + public boolean has(ServerPlayer psender, String permission) { + if (psender != null) { + String n = psender.getName().getString().toLowerCase(); + return hasPerm(n, permission); + } + return true; + } + + @Override + public boolean hasPermissionNode(ServerPlayer psender, String permission) { + if (psender != null) { + String player = psender.getName().getString().toLowerCase(); + return DynmapPlugin.plugin.isOp(player); + } + return false; + } + +} diff --git a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/permissions/OpPermissions.java b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/permissions/OpPermissions.java new file mode 100644 index 000000000..fa06f6264 --- /dev/null +++ b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/permissions/OpPermissions.java @@ -0,0 +1,53 @@ +package org.dynmap.neoforge_1_21_1.permissions; + +import java.util.HashSet; +import java.util.Set; + +import org.dynmap.Log; +import org.dynmap.neoforge_1_21_1.DynmapPlugin; + +import net.minecraft.server.level.ServerPlayer; + +public class OpPermissions implements PermissionProvider { + public HashSet usrCommands = new HashSet(); + + public OpPermissions(String[] usrCommands) { + for (String usrCommand : usrCommands) { + this.usrCommands.add(usrCommand); + } + Log.info("Using ops.txt for access control"); + } + + @Override + public Set hasOfflinePermissions(String player, Set perms) { + HashSet rslt = new HashSet(); + if (DynmapPlugin.plugin.isOp(player)) { + rslt.addAll(perms); + } + return rslt; + } + + @Override + public boolean hasOfflinePermission(String player, String perm) { + return DynmapPlugin.plugin.isOp(player); + } + + @Override + public boolean has(ServerPlayer psender, String permission) { + if (psender != null) { + if (usrCommands.contains(permission)) { + return true; + } + return DynmapPlugin.plugin.isOp(psender.getName().getString()); + } + return true; + } + + @Override + public boolean hasPermissionNode(ServerPlayer psender, String permission) { + if (psender != null) { + return DynmapPlugin.plugin.isOp(psender.getName().getString()); + } + return true; + } +} diff --git a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/permissions/PermissionProvider.java b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/permissions/PermissionProvider.java new file mode 100644 index 000000000..7d5c5fcda --- /dev/null +++ b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/permissions/PermissionProvider.java @@ -0,0 +1,16 @@ +package org.dynmap.neoforge_1_21_1.permissions; + +import java.util.Set; + +import net.minecraft.server.level.ServerPlayer; + +public interface PermissionProvider { + boolean has(ServerPlayer sender, String permission); + + boolean hasPermissionNode(ServerPlayer sender, String permission); + + Set hasOfflinePermissions(String player, Set perms); + + boolean hasOfflinePermission(String player, String perm); + +} diff --git a/neoforge-1.21.1/src/main/resources/META-INF/accesstransformer.cfg b/neoforge-1.21.1/src/main/resources/META-INF/accesstransformer.cfg new file mode 100644 index 000000000..af53a2e93 --- /dev/null +++ b/neoforge-1.21.1/src/main/resources/META-INF/accesstransformer.cfg @@ -0,0 +1,4 @@ +public net.minecraft.world.level.biome.BiomeSpecialEffects$Builder waterColor +public net.minecraft.server.level.ServerLevel serverLevelData +public net.minecraft.server.level.ChunkMap visibleChunkMap +public net.minecraft.server.level.ChunkMap readChunk(Lnet/minecraft/world/level/ChunkPos;)Ljava/util/concurrent/CompletableFuture; diff --git a/neoforge-1.21.1/src/main/resources/META-INF/neoforge.mods.toml b/neoforge-1.21.1/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 000000000..b8bb55f32 --- /dev/null +++ b/neoforge-1.21.1/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,27 @@ +modLoader="javafml" +loaderVersion="[2,)" +issueTrackerURL="https://github.com/webbukkit/dynmap/issues" +license="Apache Public License v2" +[[mods]] +modId="dynmap" +version="${version}" +displayName="Dynmap" +authors="mikeprimm" +description=''' +Dynamic, Google-maps style rendered maps for your Minecraft server +''' +[[accessTransformers]] +file="META-INF/accesstransformer.cfg" +[[dependencies.dynmap]] + modId="neoforge" + mandatory=true + versionRange="[21.1,)" + ordering="NONE" + # Side this dependency is applied on - BOTH, CLIENT or SERVER + side="SERVER" +[[dependencies.dynmap]] + modId="minecraft" + mandatory=true + versionRange="[1.21.1,)" + ordering="NONE" + side="SERVER" diff --git a/neoforge-1.21.1/src/main/resources/configuration.txt b/neoforge-1.21.1/src/main/resources/configuration.txt new file mode 100644 index 000000000..38174f817 --- /dev/null +++ b/neoforge-1.21.1/src/main/resources/configuration.txt @@ -0,0 +1,504 @@ +# All paths in this configuration file are relative to Dynmap's data-folder: minecraft_server/dynmap/ + +# All map templates are defined in the templates directory +# To use the HDMap very-low-res (2 ppb) map templates as world defaults, set value to vlowres +# The definitions of these templates are in normal-vlowres.txt, nether-vlowres.txt, and the_end-vlowres.txt +# To use the HDMap low-res (4 ppb) map templates as world defaults, set value to lowres +# The definitions of these templates are in normal-lowres.txt, nether-lowres.txt, and the_end-lowres.txt +# To use the HDMap hi-res (16 ppb) map templates (these can take a VERY long time for initial fullrender), set value to hires +# The definitions of these templates are in normal-hires.txt, nether-hires.txt, and the_end-hires.txt +# To use the HDMap low-res (4 ppb) map templates, with support for boosting resolution selectively to hi-res (16 ppb), set value to low_boost_hi +# The definitions of these templates are in normal-low_boost_hi.txt, nether-low_boost_hi.txt, and the_end-low_boost_hi.txt +# To use the HDMap hi-res (16 ppb) map templates, with support for boosting resolution selectively to vhi-res (32 ppb), set value to hi_boost_vhi +# The definitions of these templates are in normal-hi_boost_vhi.txt, nether-hi_boost_vhi.txt, and the_end-hi_boost_vhi.txt +# To use the HDMap hi-res (16 ppb) map templates, with support for boosting resolution selectively to xhi-res (64 ppb), set value to hi_boost_xhi +# The definitions of these templates are in normal-hi_boost_xhi.txt, nether-hi_boost_xhi.txt, and the_end-hi_boost_xhi.txt +deftemplatesuffix: hires + +# Set default tile scale (0 = 128px x 128x, 1 = 256px x 256px, 2 = 512px x 512px, 3 = 1024px x 1024px, 4 = 2048px x 2048px) - 0 is default +# Note: changing this value will result in all maps that use the default value being required to be fully rendered +#defaulttilescale: 0 + +# Map storage scheme: only uncommoent one 'type' value +# filetree: classic and default scheme: tree of files, with all map data under the directory indicated by 'tilespath' setting +# sqlite: single SQLite database file (this can get VERY BIG), located at 'dbfile' setting (default is file dynmap.db in data directory) +# mysql: MySQL database, at hostname:port in database, accessed via userid with password +# mariadb: MariaDB database, at hostname:port in database, accessed via userid with password +# postgres: PostgreSQL database, at hostname:port in database, accessed via userid with password +storage: + # Filetree storage (standard tree of image files for maps) + type: filetree + # SQLite db for map storage (uses dbfile as storage location) + #type: sqlite + #dbfile: dynmap.db + # MySQL DB for map storage (at 'hostname':'port' in database 'database' using user 'userid' password 'password' and table prefix 'prefix' + #type: mysql + #hostname: localhost + #port: 3306 + #database: dynmap + #userid: dynmap + #password: dynmap + #prefix: "" + # + # AWS S3 backet web site + #type: aws_s3 + #bucketname: "dynmap-bucket-name" + #region: us-east-1 + #aws_access_key_id: "" + #aws_secret_access_key: "" + #prefix: "" + #override_endpoint: "" + +components: + - class: org.dynmap.ClientConfigurationComponent + + # Remember to change the following class to org.dynmap.JsonFileClientUpdateComponent when using an external web server. + - class: org.dynmap.InternalClientUpdateComponent + sendhealth: true + sendposition: true + allowwebchat: true + webchat-interval: 5 + hidewebchatip: false + trustclientname: false + includehiddenplayers: false + # (optional) if true, color codes in player display names are used + use-name-colors: false + # (optional) if true, player login IDs will be used for web chat when their IPs match + use-player-login-ip: true + # (optional) if use-player-login-ip is true, setting this to true will cause chat messages not matching a known player IP to be ignored + require-player-login-ip: false + # (optional) block player login IDs that are banned from chatting + block-banned-player-chat: true + # Require login for web-to-server chat (requires login-enabled: true) + webchat-requires-login: false + # If set to true, users must have dynmap.webchat permission in order to chat + webchat-permissions: false + # Limit length of single chat messages + chatlengthlimit: 256 + # # Optional - make players hidden when they are inside/underground/in shadows (#=light level: 0=full shadow,15=sky) + # hideifshadow: 4 + # # Optional - make player hidden when they are under cover (#=sky light level,0=underground,15=open to sky) + # hideifundercover: 14 + # # (Optional) if true, players that are crouching/sneaking will be hidden + hideifsneaking: false + # optional, if true, players that are in spectator mode will be hidden + hideifspectator: false + # If true, player positions/status is protected (login with ID with dynmap.playermarkers.seeall permission required for info other than self) + protected-player-info: false + # If true, hide players with invisibility potion effects active + hide-if-invisiblity-potion: true + # If true, player names are not shown on map, chat, list + hidenames: false + #- class: org.dynmap.JsonFileClientUpdateComponent + # writeinterval: 1 + # sendhealth: true + # sendposition: true + # allowwebchat: true + # webchat-interval: 5 + # hidewebchatip: false + # includehiddenplayers: false + # use-name-colors: false + # use-player-login-ip: false + # require-player-login-ip: false + # block-banned-player-chat: true + # hideifshadow: 0 + # hideifundercover: 0 + # hideifsneaking: false + # # Require login for web-to-server chat (requires login-enabled: true) + # webchat-requires-login: false + # # If set to true, users must have dynmap.webchat permission in order to chat + # webchat-permissions: false + # # Limit length of single chat messages + # chatlengthlimit: 256 + # hide-if-invisiblity-potion: true + # hidenames: false + + - class: org.dynmap.SimpleWebChatComponent + allowchat: true + # If true, web UI users can supply name for chat using 'playername' URL parameter. 'trustclientname' must also be set true. + allowurlname: false + + # Note: this component is needed for the dmarker commands, and for the Marker API to be available to other plugins + - class: org.dynmap.MarkersComponent + type: markers + showlabel: false + enablesigns: false + # Default marker set for sign markers + default-sign-set: markers + # (optional) add spawn point markers to standard marker layer + showspawn: true + spawnicon: world + spawnlabel: "Spawn" + # (optional) layer for showing offline player's positions (for 'maxofflinetime' minutes after logoff) + showofflineplayers: false + offlinelabel: "Offline" + offlineicon: offlineuser + offlinehidebydefault: true + offlineminzoom: 0 + maxofflinetime: 30 + # (optional) layer for showing player's spawn beds + showspawnbeds: false + spawnbedlabel: "Spawn Beds" + spawnbedicon: bed + spawnbedhidebydefault: true + spawnbedminzoom: 0 + spawnbedformat: "%name%'s bed" + spawnbedremoveonplayerleave: true + # (optional) Show world border (vanilla 1.8+) + showworldborder: true + worldborderlabel: "Border" + + - class: org.dynmap.ClientComponent + type: chat + allowurlname: false + - class: org.dynmap.ClientComponent + type: chatballoon + focuschatballoons: false + - class: org.dynmap.ClientComponent + type: chatbox + showplayerfaces: true + messagettl: 5 + # Optional: set number of lines in scrollable message history: if set, messagettl is not used to age out messages + #scrollback: 100 + # Optional: set maximum number of lines visible for chatbox + #visiblelines: 10 + # Optional: send push button + sendbutton: false + - class: org.dynmap.ClientComponent + type: playermarkers + showplayerfaces: true + showplayerhealth: true + # If true, show player body too (only valid if showplayerfaces=true) + showplayerbody: false + # Option to make player faces small - don't use with showplayerhealth or largeplayerfaces + smallplayerfaces: false + # Option to make player faces larger - don't use with showplayerhealth or smallplayerfaces + largeplayerfaces: false + # Optional - make player faces layer hidden by default + hidebydefault: false + # Optional - ordering priority in layer menu (low goes before high - default is 0) + layerprio: 0 + # Optional - label for player marker layer (default is 'Players') + label: "Players" + + #- class: org.dynmap.ClientComponent + # type: digitalclock + - class: org.dynmap.ClientComponent + type: link + + - class: org.dynmap.ClientComponent + type: timeofdayclock + showdigitalclock: true + #showweather: true + # Mouse pointer world coordinate display + - class: org.dynmap.ClientComponent + type: coord + label: "Location" + hidey: false + show-mcr: false + show-chunk: false + + # Note: more than one logo component can be defined + #- class: org.dynmap.ClientComponent + # type: logo + # text: "Dynmap" + # #logourl: "images/block_surface.png" + # linkurl: "http://forums.bukkit.org/threads/dynmap.489/" + # # Valid positions: top-left, top-right, bottom-left, bottom-right + # position: bottom-right + + #- class: org.dynmap.ClientComponent + # type: inactive + # timeout: 1800 # in seconds (1800 seconds = 30 minutes) + # redirecturl: inactive.html + # #showmessage: 'You were inactive for too long.' + + #- class: org.dynmap.TestComponent + # stuff: "This is some configuration-value" + +# Treat hiddenplayers.txt as a whitelist for players to be shown on the map? (Default false) +display-whitelist: false + +# How often a tile gets rendered (in seconds). +renderinterval: 1 + +# How many tiles on update queue before accelerate render interval +renderacceleratethreshold: 60 + +# How often to render tiles when backlog is above renderacceleratethreshold +renderaccelerateinterval: 0.2 + +# How many update tiles to work on at once (if not defined, default is 1/2 the number of cores) +tiles-rendered-at-once: 2 + +# If true, use normal priority threads for rendering (versus low priority) - this can keep rendering +# from starving on busy Windows boxes (Linux JVMs pretty much ignore thread priority), but may result +# in more competition for CPU resources with other processes +usenormalthreadpriority: true + +# Save and restore pending tile renders - prevents their loss on server shutdown or /reload +saverestorepending: true + +# Save period for pending jobs (in seconds): periodic saving for crash recovery of jobs +save-pending-period: 900 + +# Zoom-out tile update period - how often to scan for and process tile updates into zoom-out tiles (in seconds) +zoomoutperiod: 30 + +# Control whether zoom out tiles are validated on startup (can be needed if zoomout processing is interrupted, but can be expensive on large maps) +initial-zoomout-validate: true + +# Default delay on processing of updated tiles, in seconds. This can reduce potentially expensive re-rendering +# of frequently updated tiles (such as due to machines, pistons, quarries or other automation). Values can +# also be set on individual worlds and individual maps. +tileupdatedelay: 30 + +# Tile hashing is used to minimize tile file updates when no changes have occurred - set to false to disable +enabletilehash: true + +# Optional - hide ores: render as normal stone (so that they aren't revealed by maps) +#hideores: true + +# Optional - enabled BetterGrass style rendering of grass and snow block sides +#better-grass: true + +# Optional - enable smooth lighting by default on all maps supporting it (can be set per map as lighting option) +smooth-lighting: true + +# Optional - use world provider lighting table (good for custom worlds with custom lighting curves, like nether) +# false=classic Dynmap lighting curve +use-brightness-table: true + +# Optional - render specific block names using the textures and models of another block name: can be used to hide/disguise specific +# blocks (e.g. make ores look like stone, hide chests) or to provide simple support for rendering unsupported custom blocks +block-alias: +# "minecraft:quartz_ore": "stone" +# "diamond_ore": "coal_ore" + +# Default image format for HDMaps (png, jpg, jpg-q75, jpg-q80, jpg-q85, jpg-q90, jpg-q95, jpg-q100, webp, webp-q75, webp-q80, webp-q85, webp-q90, webp-q95, webp-q100), +# Note: any webp format requires the presence of the 'webp command line tools' (cwebp, dwebp) (https://developers.google.com/speed/webp/download) +# +# Has no effect on maps with explicit format settings +image-format: jpg-q90 + +# If cwebp or dwebp are not on the PATH, use these settings to provide their full path. Do not use these settings if the tools are on the PATH +# For Windows, include .exe +# +#cwebpPath: /usr/bin/cwebp +#dwebpPath: /usr/bin/dwebp + +# use-generated-textures: if true, use generated textures (same as client); false is static water/lava textures +# correct-water-lighting: if true, use corrected water lighting (same as client); false is legacy water (darker) +# transparent-leaves: if true, leaves are transparent (lighting-wise): false is needed for some Spout versions that break lighting on leaf blocks +use-generated-textures: true +correct-water-lighting: true +transparent-leaves: true + +# ctm-support: if true, Connected Texture Mod (CTM) in texture packs is enabled (default) +ctm-support: true +# custom-colors-support: if true, Custom Colors in texture packs is enabled (default) +custom-colors-support: true + +# Control loading of player faces (if set to false, skins are never fetched) +#fetchskins: false + +# Control updating of player faces, once loaded (if faces are being managed by other apps or manually) +#refreshskins: false + +# Customize URL used for fetching player skins (%player% is macro for name, %uuid% for UUID) +skin-url: "http://skins.minecraft.net/MinecraftSkins/%player%.png" + +# Control behavior for new (1.0+) compass orientation (sunrise moved 90 degrees: east is now what used to be south) +# default is 'newrose' (preserve pre-1.0 maps, rotate rose) +# 'newnorth' is used to rotate maps and rose (requires fullrender of any HDMap map - same as 'newrose' for FlatMap or KzedMap) +compass-mode: newnorth + +# Triggers for automatic updates : blockupdate-with-id is debug for breaking down updates by ID:meta +# To disable, set just 'none' and comment/delete the rest +render-triggers: + - blockupdate + #- blockupdate-with-id + #- lightingupdate + - chunkpopulate + - chunkgenerate + #- none + +# Title for the web page - if not specified, defaults to the server's name (unless it is the default of 'Unknown Server') +#webpage-title: "My Awesome Server Map" + +# The path where the tile-files are placed. +tilespath: web/tiles + +# The path where the web-files are located. +webpath: web + +# If set to false, disable extraction of webpath content (good if using custom web UI or 3rd party web UI) +# Note: web interface is unsupported in this configuration - you're on your own +update-webpath-files: true + +# The path were the /dynmapexp command exports OBJ ZIP files +exportpath: export + +# The path where files can be imported for /dmarker commands +importpath: import + +# The network-interface the webserver will bind to (0.0.0.0 for all interfaces, 127.0.0.1 for only local access). +# If not set, uses same setting as server in server.properties (or 0.0.0.0 if not specified) +#webserver-bindaddress: 0.0.0.0 + +# The TCP-port the webserver will listen on. +webserver-port: 8123 + +# Maximum concurrent session on internal web server - limits resources used in Bukkit server +max-sessions: 30 + +# Disables Webserver portion of Dynmap (Advanced users only) +disable-webserver: false + +# Enable/disable having the web server allow symbolic links (true=compatible with existing code, false=more secure (default)) +allow-symlinks: true + +# Enable login support +login-enabled: false +# Require login to access website (requires login-enabled: true) +login-required: false + +# Period between tile renders for fullrender, in seconds (non-zero to pace fullrenders, lessen CPU load) +timesliceinterval: 0.0 + +# Maximum chunk loads per server tick (1/20th of a second) - reducing this below 90 will impact render performance, but also will reduce server thread load +maxchunkspertick: 200 + +# Progress report interval for fullrender/radiusrender, in tiles. Must be 100 or greater +progressloginterval: 100 + +# Parallel fullrender: if defined, number of concurrent threads used for fullrender or radiusrender +# Note: setting this will result in much more intensive CPU use, some additional memory use. Caution should be used when +# setting this to equal or exceed the number of physical cores on the system. +#parallelrendercnt: 4 + +# Interval the browser should poll for updates. +updaterate: 2000 + +# If nonzero, server will pause fullrender/radiusrender processing when 'fullrenderplayerlimit' or more users are logged in +fullrenderplayerlimit: 0 +# If nonzero, server will pause update render processing when 'updateplayerlimit' or more users are logged in +updateplayerlimit: 0 +# Target limit on server thread use - msec per tick +per-tick-time-limit: 50 +# If TPS of server is below this setting, update renders processing is paused +update-min-tps: 18.0 +# If TPS of server is below this setting, full/radius renders processing is paused +fullrender-min-tps: 18.0 +# If TPS of server is below this setting, zoom out processing is paused +zoomout-min-tps: 18.0 + +showplayerfacesinmenu: true + +# Control whether players that are hidden or not on current map are grayed out (true=yes) +grayplayerswhenhidden: true + +# Set sidebaropened: 'true' to pin menu sidebar opened permanently, 'pinned' to default the sidebar to pinned, but allow it to unpin +#sidebaropened: true + +# Customized HTTP response headers - add 'id: value' pairs to all HTTP response headers (internal web server only) +#http-response-headers: +# Access-Control-Allow-Origin: "my-domain.com" +# X-Custom-Header-Of-Mine: "MyHeaderValue" + +# Trusted proxies for web server - which proxy addresses are trusted to supply valid X-Forwarded-For fields +# This now supports both IP address, and subnet ranges (e.g. 192.168.1.0/24 or 202.24.0.0/14 ) +trusted-proxies: + - "127.0.0.1" + - "0:0:0:0:0:0:0:1" + +joinmessage: "%playername% joined" +quitmessage: "%playername% quit" +spammessage: "You may only chat once every %interval% seconds." +# format for messages from web: %playername% substitutes sender ID (typically IP), %message% includes text +webmsgformat: "&color;2[WEB] %playername%: &color;f%message%" + +# Control whether layer control is presented on the UI (default is true) +showlayercontrol: true + +# Enable checking for banned IPs via banned-ips.txt (internal web server only) +check-banned-ips: true + +# Default selection when map page is loaded +defaultzoom: 0 +defaultworld: world +defaultmap: flat +# (optional) Zoom level and map to switch to when following a player, if possible +#followzoom: 3 +#followmap: surface + +# If true, make persistent record of IP addresses used by player logins, to support web IP to player matching +persist-ids-by-ip: true + +# If true, map text to cyrillic +cyrillic-support: false + +# Messages to customize +msg: + maptypes: "Map Types" + players: "Players" + chatrequireslogin: "Chat Requires Login" + chatnotallowed: "You are not permitted to send chat messages" + hiddennamejoin: "Player joined" + hiddennamequit: "Player quit" + +# URL for client configuration (only need to be tailored for proxies or other non-standard configurations) +url: + # configuration URL + #configuration: "up/configuration" + # update URL + #update: "up/world/{world}/{timestamp}" + # sendmessage URL + #sendmessage: "up/sendmessage" + # login URL + #login: "up/login" + # register URL + #register: "up/register" + # tiles base URL + #tiles: "tiles/" + # markers base URL + #markers: "tiles/" + # Snapshot cache size, in chunks +snapshotcachesize: 500 +# Snapshot cache uses soft references (true), else weak references (false) +soft-ref-cache: true + +# Player enter/exit title messages for map markers +# +# Processing period - how often to check player positions vs markers - default is 1000ms (1 second) +#enterexitperiod: 1000 +# Title message fade in time, in ticks (0.05 second intervals) - default is 10 (1/2 second) +#titleFadeIn: 10 +# Title message stay time, in ticks (0.05 second intervals) - default is 70 (3.5 seconds) +#titleStay: 70 +# Title message fade out time, in ticks (0.05 seocnd intervals) - default is 20 (1 second) +#titleFadeOut: 20 +# Enter/exit messages use on screen titles (true - default), if false chat messages are sent instead +#enterexitUseTitle: true +# Set true if new enter messages should supercede pending exit messages (vs being queued in order), default false +#enterReplacesExits: true + +# Published public URL for Dynmap server (allows users to use 'dynmap url' command to get public URL usable to access server +# If not set, 'dynmap url' will not return anything. URL should be fully qualified (e.g. https://mc.westeroscraft.com/) +#publicURL: http://my.greatserver.com/dynmap + +# Send this message if the player does not have permission to use the command +noPermissionMsg: "You don't have permission to use this command!" + +# Set to true to enable verbose startup messages - can help with debugging map configuration problems +# Set to false for a much quieter startup log +verbose: false + +# Enables debugging. +#debuggers: +# - class: org.dynmap.debug.LogDebugger +# Debug: dump blocks missing render data +dump-missing-blocks: false + +# Log4J defense: string substituted for attempts to use macros in web chat +hackAttemptBlurb: "(IaM5uchA1337Haxr-Ban Me!)" diff --git a/neoforge-1.21.1/src/main/resources/pack.mcmeta b/neoforge-1.21.1/src/main/resources/pack.mcmeta new file mode 100644 index 000000000..64ff3854a --- /dev/null +++ b/neoforge-1.21.1/src/main/resources/pack.mcmeta @@ -0,0 +1,8 @@ +{ + "pack": { + "description": { + "text": "Dynmap resources" + }, + "pack_format": 22 + } +} diff --git a/neoforge-1.21.1/src/main/resources/permissions.yml.example b/neoforge-1.21.1/src/main/resources/permissions.yml.example new file mode 100644 index 000000000..a25f9adca --- /dev/null +++ b/neoforge-1.21.1/src/main/resources/permissions.yml.example @@ -0,0 +1,27 @@ +# +# Sample permissions.yml for dynmap - trivial, flat-file based permissions for dynmap features +# To use, copy this file to dynmap/permissions.yml, and edit appropriate. File is YAML format. +# +# All operators have full permissions to all functions. +# All users receive the permissions under the 'defaultuser' section +# Specific users can be given more permissions by defining a section with their name containing their permisssions +# All permissions correspond to those documented here (https://github.com/webbukkit/dynmap/wiki/Permissions), but +# do NOT have the 'dynmap.' prefix when used here (e.g. 'dynmap.fullrender' permission is just 'fullrender' here). +# +defaultuser: + - render + - show.self + - hide.self + - sendtoweb + - stats + - marker.list + - marker.listsets + - marker.icons + - webregister + - webchat + #- marker.sign + +#playername1: +# - fullrender +# - cancelrender +# - radiusrender diff --git a/settings.gradle b/settings.gradle index 447e72630..39d3a38cd 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,7 +2,7 @@ pluginManagement { repositories { gradlePluginPortal() maven { url "https://maven.fabricmc.net/" } - maven { url = 'https://maven.neoforged.net/releases' } + maven { url "https://maven.neoforged.net/releases" } maven { url "https://papermc.io/repo/repository/maven-public/" } } } @@ -48,6 +48,7 @@ include ':fabric-1.17.1' include ':fabric-1.16.4' include ':fabric-1.15.2' include ':fabric-1.14.4' +include ':neoforge-1.21.1' include ':neoforge-1.20.6' include ':forge-1.21' include ':forge-1.20.6' @@ -99,6 +100,7 @@ project(':fabric-1.17.1').projectDir = "$rootDir/fabric-1.17.1" as File project(':fabric-1.16.4').projectDir = "$rootDir/fabric-1.16.4" as File project(':fabric-1.15.2').projectDir = "$rootDir/fabric-1.15.2" as File project(':fabric-1.14.4').projectDir = "$rootDir/fabric-1.14.4" as File +project(':neoforge-1.21.1').projectDir = "$rootDir/neoforge-1.21.1" as File project(':neoforge-1.20.6').projectDir = "$rootDir/neoforge-1.20.6" as File project(':forge-1.21').projectDir = "$rootDir/forge-1.21" as File project(':forge-1.20.6').projectDir = "$rootDir/forge-1.20.6" as File @@ -110,4 +112,3 @@ project(':forge-1.17.1').projectDir = "$rootDir/forge-1.17.1" as File project(':forge-1.16.5').projectDir = "$rootDir/forge-1.16.5" as File project(':forge-1.15.2').projectDir = "$rootDir/forge-1.15.2" as File project(':forge-1.14.4').projectDir = "$rootDir/forge-1.14.4" as File - From 317cd7c904c86a0fdfc578431b38a8b799951fd5 Mon Sep 17 00:00:00 2001 From: minimusubi Date: Thu, 9 Jan 2025 02:59:45 -1000 Subject: [PATCH 03/10] fix: fabric-loom for Gradle 8.12 --- fabric-1.14.4/build.gradle | 2 +- fabric-1.15.2/build.gradle | 2 +- fabric-1.16.4/build.gradle | 2 +- fabric-1.17.1/build.gradle | 2 +- fabric-1.18.2/build.gradle | 2 +- fabric-1.19.4/build.gradle | 2 +- fabric-1.20.2/build.gradle | 2 +- fabric-1.20.4/build.gradle | 2 +- fabric-1.20.6/build.gradle | 2 +- fabric-1.20/build.gradle | 2 +- fabric-1.21/build.gradle | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/fabric-1.14.4/build.gradle b/fabric-1.14.4/build.gradle index b5bf304ec..5c7c4cbf3 100644 --- a/fabric-1.14.4/build.gradle +++ b/fabric-1.14.4/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '1.9-SNAPSHOT' + id 'fabric-loom' version '1.9.2' } apply plugin: 'eclipse' diff --git a/fabric-1.15.2/build.gradle b/fabric-1.15.2/build.gradle index b3eb8ff72..8b5606e93 100644 --- a/fabric-1.15.2/build.gradle +++ b/fabric-1.15.2/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '1.9-SNAPSHOT' + id 'fabric-loom' version '1.9.2' } archivesBaseName = "Dynmap" diff --git a/fabric-1.16.4/build.gradle b/fabric-1.16.4/build.gradle index ff98556bc..31afb1fec 100644 --- a/fabric-1.16.4/build.gradle +++ b/fabric-1.16.4/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '1.9-SNAPSHOT' + id 'fabric-loom' version '1.9.2' } archivesBaseName = "Dynmap" diff --git a/fabric-1.17.1/build.gradle b/fabric-1.17.1/build.gradle index 868cff37d..4e88353ac 100644 --- a/fabric-1.17.1/build.gradle +++ b/fabric-1.17.1/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '1.9-SNAPSHOT' + id 'fabric-loom' version '1.9.2' } archivesBaseName = "Dynmap" diff --git a/fabric-1.18.2/build.gradle b/fabric-1.18.2/build.gradle index 4576b0ce5..5253654ce 100644 --- a/fabric-1.18.2/build.gradle +++ b/fabric-1.18.2/build.gradle @@ -1,6 +1,6 @@ plugins { - id 'fabric-loom' version '1.9-SNAPSHOT' + id 'fabric-loom' version '1.9.2' } archivesBaseName = "Dynmap" diff --git a/fabric-1.19.4/build.gradle b/fabric-1.19.4/build.gradle index c3fab0335..013de32c3 100644 --- a/fabric-1.19.4/build.gradle +++ b/fabric-1.19.4/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '1.9-SNAPSHOT' + id 'fabric-loom' version '1.9.2' } archivesBaseName = "Dynmap" diff --git a/fabric-1.20.2/build.gradle b/fabric-1.20.2/build.gradle index 85b5b3d73..6eab9e066 100644 --- a/fabric-1.20.2/build.gradle +++ b/fabric-1.20.2/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '1.9-SNAPSHOT' + id 'fabric-loom' version '1.9.2' } archivesBaseName = "Dynmap" diff --git a/fabric-1.20.4/build.gradle b/fabric-1.20.4/build.gradle index 6fc35be8d..588c43e1e 100644 --- a/fabric-1.20.4/build.gradle +++ b/fabric-1.20.4/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '1.9-SNAPSHOT' + id 'fabric-loom' version '1.9.2' } archivesBaseName = "Dynmap" diff --git a/fabric-1.20.6/build.gradle b/fabric-1.20.6/build.gradle index ef8a9b0e3..25ee87c2e 100644 --- a/fabric-1.20.6/build.gradle +++ b/fabric-1.20.6/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '1.9-SNAPSHOT' + id 'fabric-loom' version '1.9.2' } archivesBaseName = "Dynmap" diff --git a/fabric-1.20/build.gradle b/fabric-1.20/build.gradle index 0e09bef16..bb99cdd24 100644 --- a/fabric-1.20/build.gradle +++ b/fabric-1.20/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '1.9-SNAPSHOT' + id 'fabric-loom' version '1.9.2' } archivesBaseName = "Dynmap" diff --git a/fabric-1.21/build.gradle b/fabric-1.21/build.gradle index cd51a9b01..9012a9e2b 100644 --- a/fabric-1.21/build.gradle +++ b/fabric-1.21/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '1.9-SNAPSHOT' + id 'fabric-loom' version '1.9.2' } archivesBaseName = "Dynmap" From 1c6a9b12d4f755fb527319e0150fbaece72b7395 Mon Sep 17 00:00:00 2001 From: minimusubi Date: Fri, 17 Jan 2025 14:13:32 -1000 Subject: [PATCH 04/10] refactor(neoforge 1.21.1): split DynmapPlugin --- .../dynmap/neoforge_1_21_1/DynmapPlugin.java | 1144 ++--------------- .../NeoForgeCommandSender.java | 49 + .../neoforge_1_21_1/NeoForgePlayer.java | 280 ++++ .../neoforge_1_21_1/NeoForgeServer.java | 702 ++++++++++ .../dynmap/neoforge_1_21_1/NeoForgeWorld.java | 1 + 5 files changed, 1123 insertions(+), 1053 deletions(-) create mode 100644 neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeCommandSender.java create mode 100644 neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgePlayer.java create mode 100644 neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeServer.java diff --git a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/DynmapPlugin.java b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/DynmapPlugin.java index ec8c58f6d..b531efef1 100644 --- a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/DynmapPlugin.java +++ b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/DynmapPlugin.java @@ -1,31 +1,16 @@ package org.dynmap.neoforge_1_21_1; import java.io.File; -import java.io.InputStream; -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.commands.CommandSource; import net.minecraft.commands.CommandSourceStack; @@ -35,21 +20,12 @@ import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; -import net.minecraft.network.Connection; -import net.minecraft.network.chat.Component; -import net.minecraft.network.protocol.game.ClientboundSetSubtitleTextPacket; -import net.minecraft.network.protocol.game.ClientboundSetTitleTextPacket; -import net.minecraft.network.protocol.game.ClientboundSetTitlesAnimationPacket; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; -import net.minecraft.server.network.ServerGamePacketListenerImpl; -import net.minecraft.server.players.GameProfileCache; -import net.minecraft.server.players.UserBanList; import net.minecraft.tags.BlockTags; -import net.minecraft.world.entity.Pose; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.EmptyBlockGetter; import net.minecraft.world.level.LevelAccessor; @@ -61,17 +37,9 @@ import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.chunk.status.ChunkStatus; -import net.minecraft.world.phys.Vec3; - import net.neoforged.bus.api.EventPriority; import net.neoforged.bus.api.SubscribeEvent; -import net.neoforged.fml.ModContainer; -import net.neoforged.fml.ModList; -import net.neoforged.fml.loading.LoadingModList; -import net.neoforged.fml.loading.moddiscovery.ModFileInfo; -import net.neoforged.fml.loading.moddiscovery.ModInfo; import net.neoforged.neoforge.common.NeoForge; -import net.neoforged.neoforge.event.ServerChatEvent; import net.neoforged.neoforge.event.entity.player.PlayerEvent.PlayerChangedDimensionEvent; import net.neoforged.neoforge.event.entity.player.PlayerEvent.PlayerLoggedInEvent; import net.neoforged.neoforge.event.entity.player.PlayerEvent.PlayerLoggedOutEvent; @@ -80,16 +48,12 @@ import net.neoforged.neoforge.event.level.ChunkDataEvent; import net.neoforged.neoforge.event.level.ChunkEvent; import net.neoforged.neoforge.event.level.LevelEvent; -import net.neoforged.neoforge.event.tick.ServerTickEvent; 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; @@ -98,7 +62,6 @@ import org.dynmap.common.DynmapCommandSender; import org.dynmap.common.DynmapListenerManager.EventType; import org.dynmap.common.DynmapPlayer; -import org.dynmap.common.DynmapServerInterface; import org.dynmap.common.chunk.GenericChunkCache; import org.dynmap.neoforge_1_21_1.permissions.FilePermissions; import org.dynmap.neoforge_1_21_1.permissions.OpPermissions; @@ -106,15 +69,7 @@ import org.dynmap.permissions.PermissionsHandler; import org.dynmap.renderer.DynmapBlockState; 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.RequiredArgumentBuilder; @@ -124,16 +79,15 @@ import it.unimi.dsi.fastutil.longs.LongOpenHashSet; public class DynmapPlugin { - private DynmapCore core; - private PermissionProvider permissions; + DynmapCore core; + PermissionProvider permissions; private boolean core_enabled; public GenericChunkCache sscache; public PlayerList playerList; - private MapManager mapManager; + MapManager mapManager; private static net.minecraft.server.MinecraftServer server; public static DynmapPlugin plugin; - private ChatHandler chathandler; - private HashMap sortWeights = new HashMap(); + HashMap sortWeights = new HashMap(); // Drop world load ticket after 30 seconds private long worldIdleTimeoutNS = 30 * 1000000000L; private HashMap worlds = new HashMap(); @@ -141,21 +95,12 @@ public class DynmapPlugin { private NeoForgeWorld last_fworld; private Map players = new HashMap(); // TODO private ForgeMetrics metrics; - private HashSet modsused = new HashSet(); - private NeoForgeServer fserver = new NeoForgeServer(); + private NeoForgeServer fserver; 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 useSaveFolder = true; 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 { LevelAccessor w; String wid; @@ -256,7 +201,8 @@ public void initializeBlockStates() { statename)); Log.verboseinfo("Exception: " + x.toString()); } - // Log.info("statename=" + bn + "[" + statename + "], lightAtten=" + lightAtten); + // Log.info("statename=" + bn + "[" + statename + "], lightAtten=" + + // lightAtten); // Fill in base attributes bld.setBaseState(basebs).setStateIndex(idx - baseidx).setBlockName(bn).setStateName(statename) .setLegacyBlockID(idx).setAttenuatesLight(lightAtten); @@ -287,12 +233,13 @@ public void initializeBlockStates() { } for (int gidx = 0; gidx < DynmapBlockState.getGlobalIndexMax(); gidx++) { DynmapBlockState bs = DynmapBlockState.getStateByGlobalIndex(gidx); - // Log.info(gidx + ":" + bs.toString() + ", gidx=" + bs.globalStateIndex + ", sidx=" + bs.stateIndex); + // Log.info(gidx + ":" + bs.toString() + ", gidx=" + bs.globalStateIndex + ", + // sidx=" + bs.stateIndex); } } // public static final Item getItemByID(int id) { - // return Item.getItemById(id); + // return Item.getItemById(id); // } private static Biome[] biomelist = null; @@ -313,111 +260,75 @@ public static final Biome[] getBiomeList() { return biomelist; } - // public static final NetworkManager getNetworkManager(ServerPlayNetHandler nh) { - // return nh.netManager; + // public static final NetworkManager getNetworkManager(ServerPlayNetHandler nh) + // { + // return nh.netManager; // } - private NeoForgePlayer getOrAddPlayer(ServerPlayer p) { + NeoForgePlayer getOrAddPlayer(ServerPlayer p) { String name = p.getName().getString(); NeoForgePlayer fp = players.get(name); if (fp != null) { fp.player = p; } else { - fp = new NeoForgePlayer(p); + fp = new NeoForgePlayer(this, 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; - ServerPlayer sender; - } - - private ConcurrentLinkedQueue msgqueue = new ConcurrentLinkedQueue(); - - public class ChatHandler { - @SubscribeEvent - public void handleChat(ServerChatEvent event) { - String msg = event.getMessage().getString(); - 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(); - } - } - } - */ + /** + * 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; @@ -466,6 +377,7 @@ public void warning(String s, Throwable t) { public DynmapPlugin(MinecraftServer srv) { plugin = this; this.server = srv; + fserver = new NeoForgeServer(this, srv); } public boolean isOp(String player) { @@ -478,7 +390,7 @@ public boolean isOp(String player) { return (server.isSingleplayer() && player.equalsIgnoreCase(server.getSingleplayerProfile().getName())); } - private boolean hasPerm(ServerPlayer psender, String permission) { + boolean hasPerm(ServerPlayer psender, String permission) { PermissionsHandler ph = PermissionsHandler.getHandler(); if ((psender != null) && (ph != null) && ph.hasPermission(psender.getName().getString(), permission)) { return true; @@ -486,7 +398,7 @@ private boolean hasPerm(ServerPlayer psender, String permission) { return permissions.has(psender, permission); } - private boolean hasPermNode(ServerPlayer psender, String permission) { + boolean hasPermNode(ServerPlayer psender, String permission) { PermissionsHandler ph = PermissionsHandler.getHandler(); if ((psender != null) && (ph != null) && ph.hasPermissionNode(psender.getName().getString(), permission)) { return true; @@ -494,7 +406,7 @@ private boolean hasPermNode(ServerPlayer psender, String permission) { return permissions.hasPermissionNode(psender, permission); } - private Set hasOfflinePermissions(String player, Set perms) { + Set hasOfflinePermissions(String player, Set perms) { Set rslt = null; PermissionsHandler ph = PermissionsHandler.getHandler(); if (ph != null) { @@ -511,7 +423,7 @@ private Set hasOfflinePermissions(String player, Set perms) { return rslt; } - private boolean hasOfflinePermission(String player, String perm) { + boolean hasOfflinePermission(String player, String perm) { PermissionsHandler ph = PermissionsHandler.getHandler(); if (ph != null) { if (ph.hasOfflinePermission(player, perm)) { @@ -521,593 +433,6 @@ private boolean hasOfflinePermission(String player, String perm) { return permissions.hasOfflinePermission(player, perm); } - /** - * Server access abstraction class - */ - public class NeoForgeServer 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 NeoForgeServer() { - } - - private GameProfile getProfileByName(String player) { - GameProfileCache cache = server.getProfileCache(); - Optional val = cache.get(player); - return val.isPresent() ? val.get() : null; - } - - @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++) { - ServerPlayer p = 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 (ServerPlayer p : players) { - if (p.getName().getString().equalsIgnoreCase(name)) { - return getOrAddPlayer(p); - } - } - - return null; - } - - @Override - public Set getIPBans() { - UserBanList bl = server.getPlayerList().getBans(); - Set ips = new HashSet(); - - for (String s : bl.getUserList()) { - 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.getLocalIp(); - if (sn == null) - sn = "Unknown Server"; - return sn; - } - - @Override - public boolean isPlayerBanned(String pid) { - UserBanList bl = server.getPlayerList().getBans(); - 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(); - NeoForge.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) { - Component component = Component.literal(msg); - server.getPlayerList().broadcastSystemMessage(component, false); - 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.players.PlayerList scm = server.getPlayerList(); - if (scm == null) - return Collections.emptySet(); - UserBanList bl = scm.getBans(); - 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.players.PlayerList scm = server.getPlayerList(); - if (scm == null) - return false; - UserBanList bl = scm.getBans(); - 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) { - NeoForgeMapChunkCache c = (NeoForgeMapChunkCache) 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 (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 NeoForgeMapChunkCache cc = c; - Future f = this.callSyncMethod(new Callable() { - public Boolean call() throws Exception { - // Update busy state on world - NeoForgeWorld fw = (NeoForgeWorld) cc.getWorld(); - // TODO - // setBusy(fw.getWorld()); - cc.getLoadedChunks(); - return true; - } - }, 0); - try { - f.get(); - } catch (CancellationException cx) { - return null; - } catch (InterruptedException 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().getPlayerCount(); - } - - @SubscribeEvent - public void tickEvent(ServerTickEvent.Post event) { - 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(); - BlockState bs = r.w.getBlockState(new BlockPos(r.x, r.y, r.z)); - int idx = Block.BLOCK_STATE_REGISTRY.getId(bs); - if ((idx >= 0) && (!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 NeoForgePlayer(null); - - core.listenerManager.processChatEvent(EventType.PLAYER_CHAT, dp, cm.message); - } - // Check for generated chunks - if ((cur_tick % 20) == 0) { - } - } - - @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.getLocalIp(); - } - - @Override - public File getModContainerFile(String name) { - ModFileInfo mfi = LoadingModList.get().getModFileById(name); // Try case sensitive lookup - if (mfi != null) { - try { - File f = mfi.getFile().getFilePath().toFile(); - return f; - } catch (UnsupportedOperationException ex) { - // TODO Implement proper jar in jar method for fetching data - /* - Log.info("Searching for: " + name); - for (IModInfo e: ModList.get().getMods()) { - Log.info("in: " + e.getModId().toString()); - Log.info("resource: "+ ModList.get().getModFileById(e.getModId()).getFile().findResource(String.valueOf(mfi.getFile().getFilePath()))); - } - */ - Log.warning("jar in jar method found, skipping: " + ex.getMessage()); - } - } - return null; - } - - @Override - public List getModList() { - List mil = LoadingModList.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) { - // NeoForge removed ModContainer#getMod with no replacement - /* if (modid == null) - modid = "minecraft"; - - Optional mc = ModList.get().getModContainerById(modid); - Object mod = (mc.isPresent()) ? mc.get().getMod() : null; - if (mod != null) { - ClassLoader cl = mod.getClass().getClassLoader(); - if (cl == null) - cl = ClassLoader.getSystemClassLoader(); - InputStream is = cl.getResourceAsStream(rname); - if (is != null) { - return is; - } - } - List mcl = LoadingModList.get().getMods(); - for (ModInfo mci : mcl) { - mc = ModList.get().getModContainerById(mci.getModId()); - mod = (mc.isPresent()) ? mc.get().getMod() : null; - if (mod == null) - continue; - ClassLoader cl = mod.getClass().getClassLoader(); - if (cl == null) - cl = ClassLoader.getSystemClassLoader(); - InputStream is = cl.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; @@ -1121,290 +446,6 @@ public class ProfileTexture { public String url; } - /** - * Player access abstraction class - */ - public class NeoForgePlayer extends NeoForgeCommandSender implements DynmapPlayer { - private ServerPlayer player; - private final String skinurl; - private final UUID uuid; - - public NeoForgePlayer(ServerPlayer p) { - player = p; - String url = null; - if (player != null) { - uuid = player.getUUID(); - 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.value()), - 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.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; - } - Vec3 v = player.position(); - return toLoc(player.serverLevel(), v.x, v.y, v.z); - } - - @Override - public String getWorld() { - if (player == null) { - return null; - } - - if (player.serverLevel() != null) { - return DynmapPlugin.this.getWorld((ServerLevel) player.serverLevel()).getName(); - } - - return null; - } - - public static final Connection getNetworkManager(ServerGamePacketListenerImpl nh) { - return nh.getConnection(); - } - - @Override - public InetSocketAddress getAddress() { - if ((player != null) && (player instanceof ServerPlayer)) { - ServerGamePacketListenerImpl nsh = ((ServerPlayer) 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.getPose() == Pose.CROUCHING; - } - - 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.getArmorValue(); - } 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.getName().getString()); - } - - @Override - public void sendMessage(String msg) { - Component ichatcomponent = Component.literal(msg); - player.sendSystemMessage(ichatcomponent); - } - - @Override - public boolean isInvisible() { - if (player != null) { - return player.isInvisible(); - } - return false; - } - - @Override - public boolean isSpectator() { - if (player != null) { - return player.isSpectator(); - } - 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 ServerPlayer) { - ServerPlayer mp = (ServerPlayer) player; - ClientboundSetTitlesAnimationPacket times = new ClientboundSetTitlesAnimationPacket(fadeInTicks, - stayTicks, fadeOutTicks); - mp.connection.send(times); - if (title != null) { - ClientboundSetTitleTextPacket titlepkt = new ClientboundSetTitleTextPacket( - Component.literal(title)); - mp.connection.send(titlepkt); - } - - if (subtitle != null) { - ClientboundSetSubtitleTextPacket subtitlepkt = new ClientboundSetSubtitleTextPacket( - Component.literal(subtitle)); - mp.connection.send(subtitlepkt); - } - } - } - } - - /* Handler for generic console command sender */ - public class NeoForgeCommandSender implements DynmapCommandSender { - private CommandSourceStack sender; - - protected NeoForgeCommandSender() { - sender = null; - } - - public NeoForgeCommandSender(CommandSourceStack send) { - sender = send; - } - - @Override - public boolean hasPrivilege(String privid) { - return true; - } - - @Override - public void sendMessage(String msg) { - if (sender != null) { - Component ichatcomponent = Component.literal(msg); - sender.sendSuccess(() -> ichatcomponent, true); - } - } - - @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); @@ -1538,10 +579,7 @@ public void onStart() { core_enabled = true; VersionCheck.runCheck(core); // Get per tick time limit - perTickLimit = core.getMaxTickUseMS() * 1000000; - // Prep TPS - lasttick = System.nanoTime(); - tps = 20.0; + fserver.onStart(core.getMaxTickUseMS() * 1000000); /* Register tick handler */ if (!tickregistered) { @@ -1583,8 +621,8 @@ public void onDisable() { DynmapCommonAPIListener.apiTerminated(); // if (metrics != null) { - // metrics.stop(); - // metrics = null; + // metrics.stop(); + // metrics = null; // } /* Save worlds */ saveWorlds(); @@ -1614,7 +652,7 @@ void onCommand(CommandSourceStack commandSourceStack, String cmd, String[] args) } if (psender != null) { - dsender = new NeoForgePlayer(psender); + dsender = new NeoForgePlayer(this, psender); } else { dsender = new NeoForgeCommandSender(commandSourceStack); } @@ -1626,10 +664,6 @@ void onCommand(CommandSourceStack commandSourceStack, String cmd, String[] args) } } - private DynmapLocation toLoc(ServerLevel level, double x, double y, double z) { - return new DynmapLocation(DynmapPlugin.this.getWorld(level).getName(), x, y, z); - } - public class PlayerTracker { @SubscribeEvent public void onPlayerLogin(PlayerLoggedInEvent event) { @@ -1695,7 +729,8 @@ public void handleWorldLoad(LevelEvent.Load event) { // 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 + if (core.processWorldLoad(fw)) // Have core process load first - fire event listeners if good load + // after core.listenerManager.processWorldEvent(EventType.WORLD_LOAD, fw); } }, 0); @@ -1717,7 +752,8 @@ public void run() { core.processWorldUnload(fw); } }, 0); - // Set world unloaded (needs to be immediate, since it may be invalid after event) + // 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()); @@ -1772,7 +808,8 @@ public void handleChunkUnload(ChunkEvent.Unload event) { int z = cp.z << 4; // If not empty AND not initial scan if (ymax != Integer.MIN_VALUE) { - // Log.info(String.format("chunkkeyerate(unload)(%s,%d,%d,%d,%d,%d,%s)", fw.getName(), x, ymin, z, x+15, ymax, z+15)); + // Log.info(String.format("chunkkeyerate(unload)(%s,%d,%d,%d,%d,%d,%s)", + // fw.getName(), x, ymin, z, x+15, ymax, z+15)); mapManager.touchVolume(fw.getName(), x, ymin, z, x + 15, ymax, z + 15, "chunkgenerate"); } } @@ -1811,7 +848,8 @@ public void handleChunkDataSave(ChunkDataEvent.Save event) { int z = cp.z << 4; // If not empty AND not initial scan if (ymax != Integer.MIN_VALUE) { - // Log.info(String.format("chunkkeyerate(save)(%s,%d,%d,%d,%d,%d,%s)", fw.getName(), x, ymin, z, x+15, ymax, z+15)); + // Log.info(String.format("chunkkeyerate(save)(%s,%d,%d,%d,%d,%d,%s)", + // fw.getName(), x, ymin, z, x+15, ymax, z+15)); mapManager.touchVolume(fw.getName(), x, ymin, z, x + 15, ymax, z + 15, "chunkgenerate"); } // If cooked, add to known @@ -1888,7 +926,7 @@ private void handleBlockEvent(BlockEvent event) { private boolean onblockchange = false; private boolean onchunkpopulate = false; private boolean onchunkgenerate = false; - private boolean onblockchange_with_id = false; + boolean onblockchange_with_id = false; private void registerEvents() { // To trigger rendering. @@ -1934,7 +972,7 @@ private NeoForgeWorld getWorldByName(String name) { return worlds.get(name); } - private NeoForgeWorld getWorld(ServerLevel w) { + NeoForgeWorld getWorld(ServerLevel w) { return getWorld(w, true); } @@ -1967,7 +1005,7 @@ private NeoForgeWorld getWorld(ServerLevel w, boolean add_if_not_found) { } private void saveWorlds() { - File f = new File(core.getDataFolder(), "neoforgeworlds.yml"); + File f = new File(core.getDataFolder(), NeoForgeWorld.SAVED_WORLDS_FILE); ConfigurationNode cn = new ConfigurationNode(f); ArrayList> lst = new ArrayList>(); for (DynmapWorld fw : core.mapManager.getWorlds()) { @@ -1989,7 +1027,7 @@ private void saveWorlds() { } private void loadWorlds() { - File f = new File(core.getDataFolder(), "neoforgeworlds.yml"); + File f = new File(core.getDataFolder(), NeoForgeWorld.SAVED_WORLDS_FILE); if (f.canRead() == false) { useSaveFolder = true; return; @@ -2005,7 +1043,7 @@ private void loadWorlds() { } List> lst = cn.getMapList("worlds"); if (lst == null) { - Log.warning("Discarding bad neoforgeworlds.yml"); + Log.warning(String.format("Discarding bad %s", NeoForgeWorld.SAVED_WORLDS_FILE)); return; } @@ -2026,7 +1064,7 @@ private void loadWorlds() { worlds.put(fw.getName(), fw); } } catch (Exception x) { - Log.warning("Unable to load saved worlds from neoforgeworlds.yml"); + Log.warning(String.format("Unable to load saved worlds from %s", NeoForgeWorld.SAVED_WORLDS_FILE)); return; } } diff --git a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeCommandSender.java b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeCommandSender.java new file mode 100644 index 000000000..f9b1d5a5d --- /dev/null +++ b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeCommandSender.java @@ -0,0 +1,49 @@ +package org.dynmap.neoforge_1_21_1; + +import org.dynmap.common.DynmapCommandSender; + +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.Component; + +/** + * Handler for generic console command sender + */ +public class NeoForgeCommandSender implements DynmapCommandSender { + private CommandSourceStack sender; + + protected NeoForgeCommandSender() { + sender = null; + } + + public NeoForgeCommandSender(CommandSourceStack send) { + sender = send; + } + + @Override + public boolean hasPrivilege(String privid) { + return true; + } + + @Override + public void sendMessage(String msg) { + if (sender != null) { + Component ichatcomponent = Component.literal(msg); + sender.sendSuccess(() -> ichatcomponent, true); + } + } + + @Override + public boolean isConnected() { + return false; + } + + @Override + public boolean isOp() { + return true; + } + + @Override + public boolean hasPermissionNode(String node) { + return true; + } +} diff --git a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgePlayer.java b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgePlayer.java new file mode 100644 index 000000000..293921c02 --- /dev/null +++ b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgePlayer.java @@ -0,0 +1,280 @@ +package org.dynmap.neoforge_1_21_1; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.UUID; + +import org.dynmap.DynmapLocation; +import org.dynmap.common.DynmapPlayer; +import org.dynmap.neoforge_1_21_1.DynmapPlugin.TexturesPayload; + +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 net.minecraft.network.Connection; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.game.ClientboundSetSubtitleTextPacket; +import net.minecraft.network.protocol.game.ClientboundSetTitleTextPacket; +import net.minecraft.network.protocol.game.ClientboundSetTitlesAnimationPacket; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.world.entity.Pose; +import net.minecraft.world.phys.Vec3; + +/** + * Player access abstraction class + */ +public class NeoForgePlayer extends NeoForgeCommandSender implements DynmapPlayer { + private DynmapPlugin plugin; + ServerPlayer player; + private final String skinurl; + private final UUID uuid; + + private static final Gson gson = new GsonBuilder().create(); + + public NeoForgePlayer(DynmapPlugin plugin, ServerPlayer player) { + this.plugin = plugin; + this.player = player; + String url = null; + if (player != null) { + uuid = player.getUUID(); + 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.value()), + 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.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; + } + Vec3 v = player.position(); + return toLoc(player.serverLevel(), v.x, v.y, v.z); + } + + private DynmapLocation toLoc(ServerLevel level, double x, double y, double z) { + return new DynmapLocation(plugin.getWorld(level).getName(), x, y, z); + } + + @Override + public String getWorld() { + if (player == null) { + return null; + } + + if (player.serverLevel() != null) { + return plugin.getWorld((ServerLevel) player.serverLevel()).getName(); + } + + return null; + } + + public static final Connection getNetworkManager(ServerGamePacketListenerImpl nh) { + return nh.getConnection(); + } + + @Override + public InetSocketAddress getAddress() { + if ((player != null) && (player instanceof ServerPlayer)) { + ServerGamePacketListenerImpl nsh = ((ServerPlayer) 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.getPose() == Pose.CROUCHING; + } + + 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.getArmorValue(); + } 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 plugin.hasPerm(player, privid); + return false; + } + + @Override + public boolean isOp() { + return plugin.isOp(player.getName().getString()); + } + + @Override + public void sendMessage(String msg) { + Component ichatcomponent = Component.literal(msg); + player.sendSystemMessage(ichatcomponent); + } + + @Override + public boolean isInvisible() { + if (player != null) { + return player.isInvisible(); + } + return false; + } + + @Override + public boolean isSpectator() { + if (player != null) { + return player.isSpectator(); + } + return false; + } + + @Override + public int getSortWeight() { + Integer wt = plugin.sortWeights.get(getName()); + if (wt != null) + return wt; + return 0; + } + + @Override + public void setSortWeight(int wt) { + if (wt == 0) { + plugin.sortWeights.remove(getName()); + } else { + plugin.sortWeights.put(getName(), wt); + } + } + + @Override + public boolean hasPermissionNode(String node) { + if (player != null) + return plugin.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 ServerPlayer) { + ServerPlayer mp = (ServerPlayer) player; + ClientboundSetTitlesAnimationPacket times = new ClientboundSetTitlesAnimationPacket(fadeInTicks, + stayTicks, fadeOutTicks); + mp.connection.send(times); + if (title != null) { + ClientboundSetTitleTextPacket titlepkt = new ClientboundSetTitleTextPacket( + Component.literal(title)); + mp.connection.send(titlepkt); + } + + if (subtitle != null) { + ClientboundSetSubtitleTextPacket subtitlepkt = new ClientboundSetSubtitleTextPacket( + Component.literal(subtitle)); + mp.connection.send(subtitlepkt); + } + } + } +} diff --git a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeServer.java b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeServer.java new file mode 100644 index 000000000..0b8e0be7e --- /dev/null +++ b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeServer.java @@ -0,0 +1,702 @@ +package org.dynmap.neoforge_1_21_1; + +import java.io.File; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.PriorityQueue; +import java.util.Set; +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 org.apache.maven.artifact.versioning.ArtifactVersion; +import org.dynmap.DynmapChunk; +import org.dynmap.DynmapCommonAPIListener; +import org.dynmap.DynmapWorld; +import org.dynmap.Log; +import org.dynmap.common.BiomeMap; +import org.dynmap.common.DynmapListenerManager.EventType; +import org.dynmap.common.DynmapPlayer; +import org.dynmap.common.DynmapServerInterface; +import org.dynmap.neoforge_1_21_1.DynmapPlugin.BlockUpdateRec; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.VisibilityLimit; + +import com.mojang.authlib.GameProfile; + +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.players.GameProfileCache; +import net.minecraft.server.players.UserBanList; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.ModContainer; +import net.neoforged.fml.ModList; +import net.neoforged.fml.loading.LoadingModList; +import net.neoforged.fml.loading.moddiscovery.ModFileInfo; +import net.neoforged.fml.loading.moddiscovery.ModInfo; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.ServerChatEvent; +import net.neoforged.neoforge.event.tick.ServerTickEvent; + +/** + * Server access abstraction class + */ +public class NeoForgeServer extends DynmapServerInterface { + private DynmapPlugin plugin; + private MinecraftServer server; + /* Server thread scheduler */ + private Object schedlock = new Object(); + private long cur_tick; + private long next_id; + private long cur_tick_starttime; + PriorityQueue runqueue = new PriorityQueue(); + private ChatHandler chathandler; + private ConcurrentLinkedQueue msgqueue = new ConcurrentLinkedQueue(); + private HashSet modsused = new HashSet(); + // TPS calculator + private double tps; + long lasttick; + long avgticklen; + // Per tick limit, in nsec + private long perTickLimit = (50000000); // 50 ms + + private static final Pattern patternControlCode = Pattern.compile("(?i)\\u00A7[0-9A-FK-OR]"); + + public NeoForgeServer(DynmapPlugin plugin, MinecraftServer server) { + this.plugin = plugin; + this.server = server; + } + + void onStart(long perTickLimit) { + this.perTickLimit = perTickLimit; + // Prep TPS + lasttick = System.nanoTime(); + tps = 20.0; + } + + private GameProfile getProfileByName(String player) { + GameProfileCache cache = server.getProfileCache(); + Optional val = cache.get(player); + return val.isPresent() ? val.get() : null; + } + + @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++) { + ServerPlayer p = playlist.get(i); + dplay[i] = plugin.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 (ServerPlayer p : players) { + if (p.getName().getString().equalsIgnoreCase(name)) { + return plugin.getOrAddPlayer(p); + } + } + + return null; + } + + @Override + public Set getIPBans() { + UserBanList bl = server.getPlayerList().getBans(); + Set ips = new HashSet(); + + for (String s : bl.getUserList()) { + 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.getLocalIp(); + if (sn == null) + sn = "Unknown Server"; + return sn; + } + + @Override + public boolean isPlayerBanned(String pid) { + UserBanList bl = server.getPlayerList().getBans(); + 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(); + NeoForge.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) { + Component component = Component.literal(msg); + server.getPlayerList().broadcastSystemMessage(component, false); + 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 (plugin.sscache != null) + return plugin.sscache.getHitRate(); + return 0.0; + } + + @Override + public void resetCacheStats() { + if (plugin.sscache != null) + plugin.sscache.resetStats(); + } + + @Override + public DynmapWorld getWorldByName(String wname) { + return 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.players.PlayerList scm = server.getPlayerList(); + if (scm == null) + return Collections.emptySet(); + UserBanList bl = scm.getBans(); + if (bl == null) + return Collections.emptySet(); + if (bl.isBanned(getProfileByName(player))) { + return Collections.emptySet(); + } + Set rslt = plugin.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.players.PlayerList scm = server.getPlayerList(); + if (scm == null) + return false; + UserBanList bl = scm.getBans(); + if (bl == null) + return false; + if (bl.isBanned(getProfileByName(player))) { + return false; + } + return plugin.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) { + NeoForgeMapChunkCache c = (NeoForgeMapChunkCache) 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 (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 NeoForgeMapChunkCache cc = c; + Future f = this.callSyncMethod(new Callable() { + public Boolean call() throws Exception { + // Update busy state on world + NeoForgeWorld fw = (NeoForgeWorld) cc.getWorld(); + // TODO + // setBusy(fw.getWorld()); + cc.getLoadedChunks(); + return true; + } + }, 0); + try { + f.get(); + } catch (CancellationException cx) { + return null; + } catch (InterruptedException 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().getPlayerCount(); + } + + @SubscribeEvent + public void tickEvent(ServerTickEvent.Post event) { + 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 (plugin.core != null) { + plugin.core.serverTick(tps); + } + + boolean done = false; + TaskRecord tr = null; + + while (!plugin.blockupdatequeue.isEmpty()) { + BlockUpdateRec r = plugin.blockupdatequeue.remove(); + BlockState bs = r.w.getBlockState(new BlockPos(r.x, r.y, r.z)); + int idx = Block.BLOCK_STATE_REGISTRY.getId(bs); + if ((idx >= 0) && (!org.dynmap.hdmap.HDBlockModels.isChangeIgnoredBlock(DynmapPlugin.stateByID[idx]))) { + if (plugin.onblockchange_with_id) + plugin.mapManager.touch(r.wid, r.x, r.y, r.z, "blockchange[" + idx + "]"); + else + plugin.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 = plugin.getOrAddPlayer(cm.sender); + else + dp = new NeoForgePlayer(plugin, null); + + plugin.core.listenerManager.processChatEvent(EventType.PLAYER_CHAT, dp, cm.message); + } + // Check for generated chunks + if ((cur_tick % 20) == 0) { + } + } + + @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.getLocalIp(); + } + + @Override + public File getModContainerFile(String name) { + ModFileInfo mfi = LoadingModList.get().getModFileById(name); // Try case sensitive lookup + if (mfi != null) { + try { + File f = mfi.getFile().getFilePath().toFile(); + return f; + } catch (UnsupportedOperationException ex) { + // TODO Implement proper jar in jar method for fetching data + /* + Log.info("Searching for: " + name); + for (IModInfo e : ModList.get().getMods()) { + Log.info("in: " + e.getModId().toString()); + Log.info("resource: " + ModList.get().getModFileById(e.getModId()).getFile() + .findResource(String.valueOf(mfi.getFile().getFilePath()))); + } + */ + Log.warning("jar in jar method found, skipping: " + ex.getMessage()); + } + } + return null; + } + + @Override + public List getModList() { + List mil = LoadingModList.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) { + // NeoForge removed ModContainer#getMod with no replacement + /* if (modid == null) + modid = "minecraft"; + + Optional mc = ModList.get().getModContainerById(modid); + Object mod = (mc.isPresent()) ? mc.get().getMod() : null; + if (mod != null) { + ClassLoader cl = mod.getClass().getClassLoader(); + if (cl == null) + cl = ClassLoader.getSystemClassLoader(); + InputStream is = cl.getResourceAsStream(rname); + if (is != null) { + return is; + } + } + List mcl = LoadingModList.get().getMods(); + for (ModInfo mci : mcl) { + mc = ModList.get().getModContainerById(mci.getModId()); + mod = (mc.isPresent()) ? mc.get().getMod() : null; + if (mod == null) + continue; + ClassLoader cl = mod.getClass().getClassLoader(); + if (cl == null) + cl = ClassLoader.getSystemClassLoader(); + InputStream is = cl.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 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; + ServerPlayer sender; + } + + public class ChatHandler { + @SubscribeEvent + public void handleChat(ServerChatEvent event) { + String msg = event.getMessage().getString(); + if (!msg.startsWith("/")) { + ChatMessage cm = new ChatMessage(); + cm.message = msg; + cm.sender = event.getPlayer(); + msgqueue.add(cm); + } + } + } + +} diff --git a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeWorld.java b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeWorld.java index 2ebd37a2d..349c44246 100644 --- a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeWorld.java +++ b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeWorld.java @@ -21,6 +21,7 @@ import org.dynmap.utils.Polygon; public class NeoForgeWorld extends DynmapWorld { + public static final String SAVED_WORLDS_FILE = "neoforgeworlds.yml"; private ServerLevelAccessor world; private final boolean skylight; private final boolean isnether; From 61bb05add6f23e2e22f7d3c5c7d6dd2fdd04f23e Mon Sep 17 00:00:00 2001 From: minimusubi Date: Fri, 17 Jan 2025 15:26:55 -1000 Subject: [PATCH 05/10] chore(neoforge): bump NeoGradle --- neoforge-1.20.6/build.gradle | 2 +- neoforge-1.21.1/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/neoforge-1.20.6/build.gradle b/neoforge-1.20.6/build.gradle index 139129ef6..b431abd9c 100644 --- a/neoforge-1.20.6/build.gradle +++ b/neoforge-1.20.6/build.gradle @@ -6,7 +6,7 @@ buildscript { } plugins { id 'eclipse' - id 'net.neoforged.gradle.userdev' version '7.0.177' + id 'net.neoforged.gradle.userdev' version '7.0.178' } eclipse { diff --git a/neoforge-1.21.1/build.gradle b/neoforge-1.21.1/build.gradle index 611dca7a6..a9e4665b5 100644 --- a/neoforge-1.21.1/build.gradle +++ b/neoforge-1.21.1/build.gradle @@ -6,7 +6,7 @@ buildscript { } plugins { id 'eclipse' - id 'net.neoforged.gradle.userdev' version '7.0.177' + id 'net.neoforged.gradle.userdev' version '7.0.178' } eclipse { From 4cfc15e1619fc5bc242f0ab076c7e02a14e0d781 Mon Sep 17 00:00:00 2001 From: minimusubi Date: Sat, 18 Jan 2025 02:13:56 -1000 Subject: [PATCH 06/10] style(neoforge 1.21.1): organize imports --- .../org/dynmap/neoforge_1_21_1/DynmapMod.java | 10 ++--- .../dynmap/neoforge_1_21_1/DynmapPlugin.java | 4 +- .../java/org/dynmap/neoforge_1_21_1/NBT.java | 38 +++++++++++++++++-- .../NeoForgeCommandSender.java | 4 +- .../NeoForgeMapChunkCache.java | 16 ++++---- .../neoforge_1_21_1/NeoForgePlayer.java | 22 +++++------ .../neoforge_1_21_1/NeoForgeServer.java | 30 +++++++-------- .../dynmap/neoforge_1_21_1/NeoForgeWorld.java | 6 +-- .../permissions/FilePermissions.java | 4 +- .../permissions/OpPermissions.java | 4 +- 10 files changed, 84 insertions(+), 54 deletions(-) diff --git a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/DynmapMod.java b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/DynmapMod.java index b210b5d88..704c836a0 100644 --- a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/DynmapMod.java +++ b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/DynmapMod.java @@ -2,11 +2,6 @@ import java.io.File; -import org.dynmap.DynmapCommonAPI; -import org.dynmap.DynmapCommonAPIListener; -import org.dynmap.Log; -import org.dynmap.neoforge_1_21_1.DynmapPlugin.OurLog; - import net.minecraft.server.MinecraftServer; import net.neoforged.api.distmarker.Dist; import net.neoforged.bus.api.SubscribeEvent; @@ -21,6 +16,11 @@ import net.neoforged.neoforge.event.server.ServerStartedEvent; import net.neoforged.neoforge.event.server.ServerStoppingEvent; +import org.dynmap.DynmapCommonAPI; +import org.dynmap.DynmapCommonAPIListener; +import org.dynmap.Log; +import org.dynmap.neoforge_1_21_1.DynmapPlugin.OurLog; + @Mod("dynmap") public class DynmapMod { // The instance of your mod that NeoForge uses. diff --git a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/DynmapPlugin.java b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/DynmapPlugin.java index b531efef1..3ec52888c 100644 --- a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/DynmapPlugin.java +++ b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/DynmapPlugin.java @@ -285,7 +285,7 @@ NeoForgePlayer getOrAddPlayer(ServerPlayer p) { * } * private static HashMap busy_worlds = new * HashMap(); - * + * * private void setBusy(World w) { * setBusy(w, null); * } @@ -311,7 +311,7 @@ NeoForgePlayer getOrAddPlayer(ServerPlayer p) { * } * wbr.last_ts = System.nanoTime(); * } - * + * * private void doIdleOutOfWorlds() { * if (!DynmapMod.useforcedchunks) return; * long ts = System.nanoTime() - worldIdleTimeoutNS; diff --git a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NBT.java b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NBT.java index 840b393b0..8504c9913 100644 --- a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NBT.java +++ b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NBT.java @@ -1,123 +1,153 @@ package org.dynmap.neoforge_1_21_1; -import org.dynmap.common.chunk.GenericBitStorage; -import org.dynmap.common.chunk.GenericNBTCompound; -import org.dynmap.common.chunk.GenericNBTList; - import java.util.Set; + import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.util.SimpleBitStorage; +import org.dynmap.common.chunk.GenericBitStorage; +import org.dynmap.common.chunk.GenericNBTCompound; +import org.dynmap.common.chunk.GenericNBTList; + public class NBT { public static class NBTCompound implements GenericNBTCompound { private final CompoundTag obj; + public NBTCompound(CompoundTag t) { this.obj = t; } + @Override public Set getAllKeys() { return obj.getAllKeys(); } + @Override public boolean contains(String s) { return obj.contains(s); } + @Override public boolean contains(String s, int i) { return obj.contains(s, i); } + @Override public byte getByte(String s) { return obj.getByte(s); } + @Override public short getShort(String s) { return obj.getShort(s); } + @Override public int getInt(String s) { return obj.getInt(s); } + @Override public long getLong(String s) { return obj.getLong(s); } + @Override public float getFloat(String s) { return obj.getFloat(s); } + @Override public double getDouble(String s) { return obj.getDouble(s); } + @Override public String getString(String s) { return obj.getString(s); } + @Override public byte[] getByteArray(String s) { return obj.getByteArray(s); } + @Override public int[] getIntArray(String s) { return obj.getIntArray(s); } + @Override public long[] getLongArray(String s) { return obj.getLongArray(s); } + @Override public GenericNBTCompound getCompound(String s) { return new NBTCompound(obj.getCompound(s)); } + @Override public GenericNBTList getList(String s, int i) { return new NBTList(obj.getList(s, i)); } + @Override public boolean getBoolean(String s) { return obj.getBoolean(s); } + @Override public String getAsString(String s) { return obj.get(s).getAsString(); } + @Override public GenericBitStorage makeBitStorage(int bits, int count, long[] data) { return new OurBitStorage(bits, count, data); } + public String toString() { return obj.toString(); } } + public static class NBTList implements GenericNBTList { private final ListTag obj; + public NBTList(ListTag t) { obj = t; } + @Override public int size() { return obj.size(); } + @Override public String getString(int idx) { return obj.getString(idx); } + @Override public GenericNBTCompound getCompound(int idx) { return new NBTCompound(obj.getCompound(idx)); } + public String toString() { return obj.toString(); } } + public static class OurBitStorage implements GenericBitStorage { private final SimpleBitStorage bs; + public OurBitStorage(int bits, int count, long[] data) { bs = new SimpleBitStorage(bits, count, data); } + @Override public int get(int idx) { return bs.get(idx); diff --git a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeCommandSender.java b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeCommandSender.java index f9b1d5a5d..be99bb231 100644 --- a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeCommandSender.java +++ b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeCommandSender.java @@ -1,10 +1,10 @@ package org.dynmap.neoforge_1_21_1; -import org.dynmap.common.DynmapCommandSender; - import net.minecraft.commands.CommandSourceStack; import net.minecraft.network.chat.Component; +import org.dynmap.common.DynmapCommandSender; + /** * Handler for generic console command sender */ diff --git a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeMapChunkCache.java b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeMapChunkCache.java index b7ea60456..a01262ce5 100644 --- a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeMapChunkCache.java +++ b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeMapChunkCache.java @@ -3,8 +3,16 @@ import java.util.List; import java.util.NoSuchElementException; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.BiomeSpecialEffects; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.chunk.storage.ChunkSerializer; + import org.dynmap.DynmapChunk; import org.dynmap.Log; import org.dynmap.common.BiomeMap; @@ -12,14 +20,6 @@ import org.dynmap.common.chunk.GenericChunkCache; import org.dynmap.common.chunk.GenericMapChunkCache; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.server.level.ServerChunkCache; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.ChunkPos; -import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.chunk.storage.ChunkSerializer; -import net.minecraft.world.level.chunk.status.ChunkStatus; - /** * Container for managing chunks - dependent upon using chunk snapshots, since * rendering is off server thread diff --git a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgePlayer.java b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgePlayer.java index 293921c02..8b77a7402 100644 --- a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgePlayer.java +++ b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgePlayer.java @@ -6,17 +6,6 @@ import java.util.Base64; import java.util.UUID; -import org.dynmap.DynmapLocation; -import org.dynmap.common.DynmapPlayer; -import org.dynmap.neoforge_1_21_1.DynmapPlugin.TexturesPayload; - -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 net.minecraft.network.Connection; import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.game.ClientboundSetSubtitleTextPacket; @@ -28,6 +17,17 @@ import net.minecraft.world.entity.Pose; import net.minecraft.world.phys.Vec3; +import org.dynmap.DynmapLocation; +import org.dynmap.common.DynmapPlayer; +import org.dynmap.neoforge_1_21_1.DynmapPlugin.TexturesPayload; + +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; + /** * Player access abstraction class */ diff --git a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeServer.java b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeServer.java index 0b8e0be7e..6fdd580e0 100644 --- a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeServer.java +++ b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeServer.java @@ -19,21 +19,6 @@ import java.util.concurrent.FutureTask; import java.util.regex.Pattern; -import org.apache.maven.artifact.versioning.ArtifactVersion; -import org.dynmap.DynmapChunk; -import org.dynmap.DynmapCommonAPIListener; -import org.dynmap.DynmapWorld; -import org.dynmap.Log; -import org.dynmap.common.BiomeMap; -import org.dynmap.common.DynmapListenerManager.EventType; -import org.dynmap.common.DynmapPlayer; -import org.dynmap.common.DynmapServerInterface; -import org.dynmap.neoforge_1_21_1.DynmapPlugin.BlockUpdateRec; -import org.dynmap.utils.MapChunkCache; -import org.dynmap.utils.VisibilityLimit; - -import com.mojang.authlib.GameProfile; - import net.minecraft.core.BlockPos; import net.minecraft.network.chat.Component; import net.minecraft.server.MinecraftServer; @@ -52,6 +37,21 @@ import net.neoforged.neoforge.event.ServerChatEvent; import net.neoforged.neoforge.event.tick.ServerTickEvent; +import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.dynmap.DynmapChunk; +import org.dynmap.DynmapCommonAPIListener; +import org.dynmap.DynmapWorld; +import org.dynmap.Log; +import org.dynmap.common.BiomeMap; +import org.dynmap.common.DynmapListenerManager.EventType; +import org.dynmap.common.DynmapPlayer; +import org.dynmap.common.DynmapServerInterface; +import org.dynmap.neoforge_1_21_1.DynmapPlugin.BlockUpdateRec; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.VisibilityLimit; + +import com.mojang.authlib.GameProfile; + /** * Server access abstraction class */ diff --git a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeWorld.java b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeWorld.java index 349c44246..4dda1cc87 100644 --- a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeWorld.java +++ b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/NeoForgeWorld.java @@ -5,14 +5,14 @@ */ import java.util.List; -import net.minecraft.world.level.ServerLevelAccessor; -import net.minecraft.world.level.border.WorldBorder; -import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.core.BlockPos; import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.Level; import net.minecraft.world.level.LightLayer; +import net.minecraft.world.level.ServerLevelAccessor; +import net.minecraft.world.level.border.WorldBorder; +import net.minecraft.world.level.levelgen.Heightmap; import org.dynmap.DynmapChunk; import org.dynmap.DynmapLocation; diff --git a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/permissions/FilePermissions.java b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/permissions/FilePermissions.java index b2da6ef08..7e0f6c4a9 100644 --- a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/permissions/FilePermissions.java +++ b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/permissions/FilePermissions.java @@ -6,12 +6,12 @@ import java.util.List; import java.util.Set; +import net.minecraft.server.level.ServerPlayer; + import org.dynmap.ConfigurationNode; import org.dynmap.Log; import org.dynmap.neoforge_1_21_1.DynmapPlugin; -import net.minecraft.server.level.ServerPlayer; - public class FilePermissions implements PermissionProvider { private HashMap> perms; private Set defperms; diff --git a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/permissions/OpPermissions.java b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/permissions/OpPermissions.java index fa06f6264..36a4dcf92 100644 --- a/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/permissions/OpPermissions.java +++ b/neoforge-1.21.1/src/main/java/org/dynmap/neoforge_1_21_1/permissions/OpPermissions.java @@ -3,11 +3,11 @@ import java.util.HashSet; import java.util.Set; +import net.minecraft.server.level.ServerPlayer; + import org.dynmap.Log; import org.dynmap.neoforge_1_21_1.DynmapPlugin; -import net.minecraft.server.level.ServerPlayer; - public class OpPermissions implements PermissionProvider { public HashSet usrCommands = new HashSet(); From 530ac77bc1774dbaa87556473adab932cbb5c959 Mon Sep 17 00:00:00 2001 From: minimusubi Date: Sat, 18 Jan 2025 02:18:01 -1000 Subject: [PATCH 07/10] feat(neoforge 1.21.4): add support --- neoforge-1.21.4/.gitignore | 4 + neoforge-1.21.4/build.gradle | 70 + .../dynmap/neoforge_1_21_4/ClientProxy.java | 6 + .../org/dynmap/neoforge_1_21_4/DynmapMod.java | 137 ++ .../dynmap/neoforge_1_21_4/DynmapPlugin.java | 1136 +++++++++++++++++ .../java/org/dynmap/neoforge_1_21_4/NBT.java | 156 +++ .../NeoForgeCommandSender.java | 49 + .../NeoForgeMapChunkCache.java | 115 ++ .../neoforge_1_21_4/NeoForgePlayer.java | 280 ++++ .../neoforge_1_21_4/NeoForgeServer.java | 702 ++++++++++ .../dynmap/neoforge_1_21_4/NeoForgeWorld.java | 244 ++++ .../org/dynmap/neoforge_1_21_4/Proxy.java | 24 + .../dynmap/neoforge_1_21_4/VersionCheck.java | 100 ++ .../permissions/FilePermissions.java | 104 ++ .../permissions/OpPermissions.java | 53 + .../permissions/PermissionProvider.java | 16 + .../resources/META-INF/accesstransformer.cfg | 4 + .../resources/META-INF/neoforge.mods.toml | 27 + .../src/main/resources/configuration.txt | 504 ++++++++ .../src/main/resources/pack.mcmeta | 8 + .../main/resources/permissions.yml.example | 27 + settings.gradle | 2 + 22 files changed, 3768 insertions(+) create mode 100644 neoforge-1.21.4/.gitignore create mode 100644 neoforge-1.21.4/build.gradle create mode 100644 neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/ClientProxy.java create mode 100644 neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/DynmapMod.java create mode 100644 neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/DynmapPlugin.java create mode 100644 neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/NBT.java create mode 100644 neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/NeoForgeCommandSender.java create mode 100644 neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/NeoForgeMapChunkCache.java create mode 100644 neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/NeoForgePlayer.java create mode 100644 neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/NeoForgeServer.java create mode 100644 neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/NeoForgeWorld.java create mode 100644 neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/Proxy.java create mode 100644 neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/VersionCheck.java create mode 100644 neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/permissions/FilePermissions.java create mode 100644 neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/permissions/OpPermissions.java create mode 100644 neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/permissions/PermissionProvider.java create mode 100644 neoforge-1.21.4/src/main/resources/META-INF/accesstransformer.cfg create mode 100644 neoforge-1.21.4/src/main/resources/META-INF/neoforge.mods.toml create mode 100644 neoforge-1.21.4/src/main/resources/configuration.txt create mode 100644 neoforge-1.21.4/src/main/resources/pack.mcmeta create mode 100644 neoforge-1.21.4/src/main/resources/permissions.yml.example diff --git a/neoforge-1.21.4/.gitignore b/neoforge-1.21.4/.gitignore new file mode 100644 index 000000000..f811a63e2 --- /dev/null +++ b/neoforge-1.21.4/.gitignore @@ -0,0 +1,4 @@ +/.gradle +/bin +/build +/runs diff --git a/neoforge-1.21.4/build.gradle b/neoforge-1.21.4/build.gradle new file mode 100644 index 000000000..844c8b684 --- /dev/null +++ b/neoforge-1.21.4/build.gradle @@ -0,0 +1,70 @@ +buildscript { + repositories { + jcenter() + mavenCentral() + } +} +plugins { + id 'eclipse' + id 'net.neoforged.gradle.userdev' version '7.0.178' +} + +eclipse { + project { + name = "Dynmap(NeoForge-1.21.4)" + } +} + +minecraft { + accessTransformers { + file('src/main/resources/META-INF/accesstransformer.cfg') + } +} + +sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(21) // Need this here so eclipse task generates correctly. +java.toolchain.languageVersion = JavaLanguageVersion.of(21) + +println('Java: ' + System.getProperty('java.version') + ' JVM: ' + System.getProperty('java.vm.version') + '(' + System.getProperty('java.vendor') + ') Arch: ' + System.getProperty('os.arch')) + +ext.buildNumber = System.getenv().BUILD_NUMBER ?: "Dev" + + +project.archivesBaseName = "${project.archivesBaseName}-neoforge-1.21.4" + +dependencies { + implementation project(path: ":DynmapCore", configuration: "shadow") + implementation project(path: ':DynmapCoreAPI') + implementation "net.neoforged:neoforge:21.4.59-beta" +} + +processResources +{ + filesMatching('META-INF/neoforge.mods.toml') { + // replace version and mcversion + expand( + version: project.version + '-' + project.ext.buildNumber, + mcversion: "1.21.4" + ) + } +} + +shadowJar { + dependencies { + include(dependency(':DynmapCore')) + include(dependency("commons-codec:commons-codec:")) + exclude("META-INF/maven/**") + exclude("META-INF/services/**") + } + relocate('org.apache.commons.codec', 'org.dynmap.neoforge_1_21_4.commons.codec') + + archiveBaseName = "Dynmap" + archiveClassifier = "neoforge-1.21.4" + destinationDirectory = file '../target' +} + +shadowJar.doLast { + task -> + ant.checksum file: task.archivePath +} + +build.dependsOn(shadowJar) diff --git a/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/ClientProxy.java b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/ClientProxy.java new file mode 100644 index 000000000..1e6377638 --- /dev/null +++ b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/ClientProxy.java @@ -0,0 +1,6 @@ +package org.dynmap.neoforge_1_21_4; + +public class ClientProxy extends Proxy { + public ClientProxy() { + } +} diff --git a/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/DynmapMod.java b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/DynmapMod.java new file mode 100644 index 000000000..e3a3818ab --- /dev/null +++ b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/DynmapMod.java @@ -0,0 +1,137 @@ +package org.dynmap.neoforge_1_21_4; + +import java.io.File; + +import net.minecraft.server.MinecraftServer; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.ModList; +import net.neoforged.fml.ModLoadingContext; +import net.neoforged.fml.common.Mod; +import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; +import net.neoforged.fml.event.lifecycle.FMLLoadCompleteEvent; +import net.neoforged.fml.loading.FMLEnvironment; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.server.ServerAboutToStartEvent; +import net.neoforged.neoforge.event.server.ServerStartedEvent; +import net.neoforged.neoforge.event.server.ServerStoppingEvent; + +import org.dynmap.DynmapCommonAPI; +import org.dynmap.DynmapCommonAPIListener; +import org.dynmap.Log; +import org.dynmap.neoforge_1_21_4.DynmapPlugin.OurLog; + +@Mod("dynmap") +public class DynmapMod { + // The instance of your mod that NeoForge uses. + public static DynmapMod instance; + + // Says where the client and server 'proxy' code is loaded. + public static Proxy proxy; + + public static DynmapPlugin plugin; + public static File jarfile; + public static String ver; + public static boolean useforcedchunks; + + public class APICallback extends DynmapCommonAPIListener { + @Override + public void apiListenerAdded() { + if (plugin == null) { + plugin = proxy.startServer(server); + } + } + + @Override + public void apiEnabled(DynmapCommonAPI api) { + } + } + + // TODO + // public class LoadingCallback implements + // net.minecraftforge.common.ForgeChunkManager.LoadingCallback { + // @Override + // public void ticketsLoaded(List tickets, World world) { + // if (tickets.size() > 0) { + // DynmapPlugin.setBusy(world, tickets.get(0)); + // for (int i = 1; i < tickets.size(); i++) { + // ForgeChunkManager.releaseTicket(tickets.get(i)); + // } + // } + // } + // } + + public DynmapMod() { + instance = this; + + if (FMLEnvironment.dist == Dist.CLIENT) { + proxy = new ClientProxy(); + } else { + proxy = new Proxy(); + } + + ModLoadingContext.get().getActiveContainer().getEventBus().addListener(this::setup); + ModLoadingContext.get().getActiveContainer().getEventBus().addListener(this::init); + + NeoForge.EVENT_BUS.register(this); + + // NeoForge removed DisplayTest, with no current replacement. + // A replacement may arrive in a future networking rework + // ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, + // () -> new IExtensionPoint.DisplayTest(() -> IExtensionPoint.DisplayTest.IGNORESERVERONLY, + // (remote, isServer) -> true)); + + Log.setLogger(new OurLog()); + org.dynmap.modsupport.ModSupportImpl.init(); + } + + public void setup(final FMLCommonSetupEvent event) { + // TOOO + jarfile = ModList.get().getModFileById("dynmap").getFile().getFilePath().toFile(); + + ver = ModList.get().getModContainerById("dynmap").get().getModInfo().getVersion().toString(); + + // // Load configuration file - use suggested (config/WesterosBlocks.cfg) + // Configuration cfg = new Configuration(event.getSuggestedConfigurationFile()); + // try { + // cfg.load(); + + // useforcedchunks = cfg.get("Settings", "UseForcedChunks", + // // true).getBoolean(true); + // } finally { + // cfg.save(); + // } + } + + public void init(FMLLoadCompleteEvent event) { + /* Set up for chunk loading notice from chunk manager */ + // TODO + // if (useforcedchunks) { + // ForgeChunkManager.setForcedChunkLoadingCallback(DynmapMod.instance, new LoadingCallback()); + // } else { + // Log.info("[Dynmap] World loading using forced chunks is disabled"); + // } + } + + private MinecraftServer server; + + @SubscribeEvent + public void onServerStarting(ServerAboutToStartEvent event) { + server = event.getServer(); + if (plugin == null) + plugin = proxy.startServer(server); + plugin.onStarting(server.getCommands().getDispatcher()); + } + + @SubscribeEvent + public void onServerStarted(ServerStartedEvent event) { + DynmapCommonAPIListener.register(new APICallback()); + plugin.serverStarted(); + } + + @SubscribeEvent + public void serverStopping(ServerStoppingEvent event) { + proxy.stopServer(plugin); + plugin = null; + } +} diff --git a/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/DynmapPlugin.java b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/DynmapPlugin.java new file mode 100644 index 000000000..5ba7f1c82 --- /dev/null +++ b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/DynmapPlugin.java @@ -0,0 +1,1136 @@ +package org.dynmap.neoforge_1_21_4; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +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.Set; +import java.util.concurrent.ConcurrentLinkedQueue; + +import net.minecraft.commands.CommandSource; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.core.BlockPos; +import net.minecraft.core.IdMapper; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.tags.BlockTags; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.LiquidBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.neoforged.bus.api.EventPriority; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.entity.player.PlayerEvent.PlayerChangedDimensionEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent.PlayerLoggedInEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent.PlayerLoggedOutEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent.PlayerRespawnEvent; +import net.neoforged.neoforge.event.level.BlockEvent; +import net.neoforged.neoforge.event.level.ChunkDataEvent; +import net.neoforged.neoforge.event.level.ChunkEvent; +import net.neoforged.neoforge.event.level.LevelEvent; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dynmap.ConfigurationNode; +import org.dynmap.DynmapCommonAPIListener; +import org.dynmap.DynmapCore; +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.DynmapListenerManager.EventType; +import org.dynmap.common.DynmapPlayer; +import org.dynmap.common.chunk.GenericChunkCache; +import org.dynmap.neoforge_1_21_4.permissions.FilePermissions; +import org.dynmap.neoforge_1_21_4.permissions.OpPermissions; +import org.dynmap.neoforge_1_21_4.permissions.PermissionProvider; +import org.dynmap.permissions.PermissionsHandler; +import org.dynmap.renderer.DynmapBlockState; +import org.dynmap.utils.DynmapLogger; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.exceptions.CommandSyntaxException; + +import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; + +public class DynmapPlugin { + DynmapCore core; + PermissionProvider permissions; + private boolean core_enabled; + public GenericChunkCache sscache; + public PlayerList playerList; + MapManager mapManager; + private static net.minecraft.server.MinecraftServer server; + public static DynmapPlugin plugin; + HashMap sortWeights = new HashMap(); + // Drop world load ticket after 30 seconds + private long worldIdleTimeoutNS = 30 * 1000000000L; + private HashMap worlds = new HashMap(); + private LevelAccessor last_world; + private NeoForgeWorld last_fworld; + private Map players = new HashMap(); + // TODO private ForgeMetrics metrics; + private NeoForgeServer fserver; + private boolean tickregistered = false; + private boolean useSaveFolder = true; + + private static final String[] TRIGGER_DEFAULTS = { "blockupdate", "chunkpopulate", "chunkgenerate" }; + + public static class BlockUpdateRec { + LevelAccessor w; + String wid; + int x, y, z; + } + + ConcurrentLinkedQueue blockupdatequeue = new ConcurrentLinkedQueue(); + + public static DynmapBlockState[] stateByID; + + private Map knownloadedchunks = new HashMap(); + private boolean didInitialKnownChunks = false; + + private void addKnownChunk(NeoForgeWorld fw, ChunkPos pos) { + LongOpenHashSet cset = knownloadedchunks.get(fw.getName()); + if (cset == null) { + cset = new LongOpenHashSet(); + knownloadedchunks.put(fw.getName(), cset); + } + cset.add(pos.toLong()); + } + + private void removeKnownChunk(NeoForgeWorld fw, ChunkPos pos) { + LongOpenHashSet cset = knownloadedchunks.get(fw.getName()); + if (cset != null) { + cset.remove(pos.toLong()); + } + } + + private boolean checkIfKnownChunk(NeoForgeWorld fw, ChunkPos pos) { + LongOpenHashSet cset = knownloadedchunks.get(fw.getName()); + if (cset != null) { + return cset.contains(pos.toLong()); + } + return false; + } + + private static Registry reg = null; + + private static Registry getBiomeReg() { + if (reg == null) { + reg = server.registryAccess().lookup(Registries.BIOME).get(); + } + return reg; + } + + /** + * 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 + + IdMapper bsids = Block.BLOCK_STATE_REGISTRY; + + DynmapBlockState basebs = null; + Block baseb = null; + int baseidx = 0; + + Iterator iter = bsids.iterator(); + DynmapBlockState.Builder bld = new DynmapBlockState.Builder(); + while (iter.hasNext()) { + BlockState bs = iter.next(); + int idx = bsids.getId(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 = BuiltInRegistries.BLOCK.getKey(b); + + if (ui == null) { + continue; + } + String bn = ui.getNamespace() + ":" + ui.getPath(); + // Only do defined names, and not "air" + if (!bn.equals(DynmapBlockState.AIR_BLOCK)) { + String statename = ""; + for (net.minecraft.world.level.block.state.properties.Property p : bs.getProperties()) { + if (statename.length() > 0) { + statename += ","; + } + statename += p.getName() + "=" + bs.getValue(p).toString(); + } + int lightAtten = 15; + try { // Workaround for mods with broken block state logic... + lightAtten = bs.isSolidRender() ? 15 : (bs.propagatesSkylightDown() ? 0 : 1); + } catch (Exception x) { + Log.warning(String.format("Exception while checking lighting data for block state: %s[%s]", bn, + statename)); + Log.verboseinfo("Exception: " + x.toString()); + } + // Log.info("statename=" + bn + "[" + statename + "], lightAtten=" + + // lightAtten); + // Fill in base attributes + bld.setBaseState(basebs).setStateIndex(idx - baseidx).setBlockName(bn).setStateName(statename) + .setLegacyBlockID(idx).setAttenuatesLight(lightAtten); + if (bs.getSoundType() != null) { + bld.setMaterial(bs.getSoundType().toString()); + } + if (bs.isSolid()) { + bld.setSolid(); + } + if (bs.isAir()) { + bld.setAir(); + } + if (bs.is(BlockTags.LOGS)) { + bld.setLog(); + } + if (bs.is(BlockTags.LEAVES)) { + bld.setLeaves(); + } + if ((!bs.getFluidState().isEmpty()) && !(bs.getBlock() instanceof LiquidBlock)) { + bld.setWaterlogged(); + } + DynmapBlockState dbs = bld.build(); // Build state + stateByID[idx] = dbs; + if (basebs == null) { + basebs = dbs; + } + } + } + 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); + // } + + private static Biome[] biomelist = null; + + public static final Biome[] getBiomeList() { + if (biomelist == null) { + biomelist = new Biome[256]; + Iterator iter = getBiomeReg().iterator(); + while (iter.hasNext()) { + Biome b = iter.next(); + int bidx = getBiomeReg().getId(b); + if (bidx >= biomelist.length) { + biomelist = Arrays.copyOf(biomelist, bidx + biomelist.length); + } + biomelist[bidx] = b; + } + } + return biomelist; + } + + // public static final NetworkManager getNetworkManager(ServerPlayNetHandler nh) + // { + // return nh.netManager; + // } + + NeoForgePlayer getOrAddPlayer(ServerPlayer p) { + String name = p.getName().getString(); + NeoForgePlayer fp = players.get(name); + if (fp != null) { + fp.player = p; + } else { + fp = new NeoForgePlayer(this, p); + players.put(name, fp); + } + return fp; + } + + /** + * 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; + fserver = new NeoForgeServer(this, srv); + } + + public boolean isOp(String player) { + String[] ops = server.getPlayerList().getOps().getUserList(); + for (String op : ops) { + if (op.equalsIgnoreCase(player)) { + return true; + } + } + return (server.isSingleplayer() && player.equalsIgnoreCase(server.getSingleplayerProfile().getName())); + } + + boolean hasPerm(ServerPlayer psender, String permission) { + PermissionsHandler ph = PermissionsHandler.getHandler(); + if ((psender != null) && (ph != null) && ph.hasPermission(psender.getName().getString(), permission)) { + return true; + } + return permissions.has(psender, permission); + } + + boolean hasPermNode(ServerPlayer psender, String permission) { + PermissionsHandler ph = PermissionsHandler.getHandler(); + if ((psender != null) && (ph != null) && ph.hasPermissionNode(psender.getName().getString(), permission)) { + return true; + } + return permissions.hasPermissionNode(psender, permission); + } + + 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; + } + + 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); + } + + public class TexturesPayload { + public long timestamp; + public String profileId; + public String profileName; + public boolean isPublic; + public Map textures; + + } + + public class ProfileTexture { + public String url; + } + + 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) { + ResourceLocation regid = getBiomeReg().getKey(bb); + String id = regid.getPath(); + String rl = regid.toString(); + float tmp = bb.getBaseTemperature(), hum = bb.getModifiedClimateSettings().downfall(); + int watermult = bb.getWaterColor(); + Log.verboseinfo( + "biome[" + i + "]: hum=" + hum + ", tmp=" + tmp + ", mult=" + Integer.toHexString(watermult)); + + BiomeMap bmap = BiomeMap.NULL; + if (rl != null) { // If resource location, lookup by this + bmap = BiomeMap.byBiomeResourceLocation(rl); + } else { + bmap = BiomeMap.byBiomeID(i); + } + if (bmap.isDefault() || (bmap == BiomeMap.NULL)) { + bmap = new BiomeMap((rl != null) ? BiomeMap.NO_INDEX : i, id, tmp, hum, rl); + 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)); + } + bmap.setBiomeObject(bb); + } + } + 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.toString(); + } + } + return lst; + } + + public void onEnable() { + /* Get MC version */ + String mcver = server.getServerVersion(); + /* 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); + 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 + fserver.onStart(core.getMaxTickUseMS() * 1000000); + + /* Register tick handler */ + if (!tickregistered) { + NeoForge.EVENT_BUS.register(fserver); + tickregistered = true; + } + + playerList = core.playerList; + sscache = new GenericChunkCache(core.getSnapShotCacheSize(), core.useSoftRefInSnapShotCache()); + /* Get map manager from core */ + mapManager = core.getMapManager(); + + /* Load saved world definitions */ + loadWorlds(); + + /* Initialized the currently loaded worlds */ + for (ServerLevel world : server.getAllLevels()) { + NeoForgeWorld w = this.getWorld(world); + } + for (NeoForgeWorld 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"); + + // 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(CommandSourceStack commandSourceStack, String cmd, String[] args) { + DynmapCommandSender dsender; + ServerPlayer psender; + try { + psender = commandSourceStack.getPlayerOrException(); + } catch (com.mojang.brigadier.exceptions.CommandSyntaxException x) { + psender = null; + } + + if (psender != null) { + dsender = new NeoForgePlayer(this, psender); + } else { + dsender = new NeoForgeCommandSender(commandSourceStack); + } + 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); + } + } + + public class PlayerTracker { + @SubscribeEvent + public void onPlayerLogin(PlayerLoggedInEvent event) { + if (!core_enabled) + return; + final DynmapPlayer dp = getOrAddPlayer((ServerPlayer) event.getEntity()); + /* 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((ServerPlayer) event.getEntity()); + final String name = event.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((ServerPlayer) event.getEntity()); // Freshen player object reference + } + + @SubscribeEvent + public void onPlayerRespawn(PlayerRespawnEvent event) { + if (!core_enabled) + return; + getOrAddPlayer((ServerPlayer) event.getEntity()); // Freshen player object reference + } + } + + private PlayerTracker playerTracker = null; + + private void registerPlayerLoginListener() { + if (playerTracker == null) { + playerTracker = new PlayerTracker(); + NeoForge.EVENT_BUS.register(playerTracker); + } + } + + public class WorldTracker { + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleWorldLoad(LevelEvent.Load event) { + if (!core_enabled) + return; + LevelAccessor w = event.getLevel(); + if (!(w instanceof ServerLevel)) + return; + final NeoForgeWorld fw = getWorld((ServerLevel) 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(priority = EventPriority.LOWEST) + public void handleWorldUnload(LevelEvent.Unload event) { + if (!core_enabled) + return; + LevelAccessor w = event.getLevel(); + if (!(w instanceof ServerLevel)) + return; + final NeoForgeWorld fw = getWorld((ServerLevel) 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(priority = EventPriority.LOWEST) + public void handleChunkLoad(ChunkEvent.Load event) { + if (!onchunkgenerate) + return; + + LevelAccessor w = event.getLevel(); + if (!(w instanceof ServerLevel)) + return; + ChunkAccess c = event.getChunk(); + if ((c != null) && (c.getPersistedStatus() == ChunkStatus.FULL) && (c instanceof LevelChunk)) { + NeoForgeWorld fw = getWorld((ServerLevel) w, false); + if (fw != null) { + addKnownChunk(fw, c.getPos()); + } + } + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleChunkUnload(ChunkEvent.Unload event) { + if (!onchunkgenerate) + return; + + LevelAccessor w = event.getLevel(); + if (!(w instanceof ServerLevel)) + return; + ChunkAccess c = event.getChunk(); + if (c != null) { + NeoForgeWorld fw = getWorld((ServerLevel) w, false); + ChunkPos cp = c.getPos(); + if (fw != null) { + if (!checkIfKnownChunk(fw, cp)) { + int ymax = Integer.MIN_VALUE; + int ymin = Integer.MAX_VALUE; + LevelChunkSection[] sections = c.getSections(); + for (int i = 0; i < sections.length; i++) { + if ((sections[i] != null) && (sections[i].hasOnlyAir() == false)) { + int sy = c.getSectionYFromSectionIndex(i); + if (sy < ymin) + ymin = sy; + if ((sy + 16) > ymax) + ymax = sy + 16; + } + } + int x = cp.x << 4; + int z = cp.z << 4; + // If not empty AND not initial scan + if (ymax != Integer.MIN_VALUE) { + // Log.info(String.format("chunkkeyerate(unload)(%s,%d,%d,%d,%d,%d,%s)", + // fw.getName(), x, ymin, z, x+15, ymax, z+15)); + mapManager.touchVolume(fw.getName(), x, ymin, z, x + 15, ymax, z + 15, "chunkgenerate"); + } + } + removeKnownChunk(fw, cp); + } + } + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleChunkDataSave(ChunkDataEvent.Save event) { + if (!onchunkgenerate) + return; + + LevelAccessor w = event.getLevel(); + if (!(w instanceof ServerLevel)) + return; + ChunkAccess c = event.getChunk(); + if (c != null) { + NeoForgeWorld fw = getWorld((ServerLevel) w, false); + ChunkPos cp = c.getPos(); + if (fw != null) { + if (!checkIfKnownChunk(fw, cp)) { + int ymax = Integer.MIN_VALUE; + int ymin = Integer.MAX_VALUE; + LevelChunkSection[] sections = c.getSections(); + for (int i = 0; i < sections.length; i++) { + if ((sections[i] != null) && (sections[i].hasOnlyAir() == false)) { + int sy = c.getSectionYFromSectionIndex(i); + if (sy < ymin) + ymin = sy; + if ((sy + 16) > ymax) + ymax = sy + 16; + } + } + int x = cp.x << 4; + int z = cp.z << 4; + // If not empty AND not initial scan + if (ymax != Integer.MIN_VALUE) { + // Log.info(String.format("chunkkeyerate(save)(%s,%d,%d,%d,%d,%d,%s)", + // fw.getName(), x, ymin, z, x+15, ymax, z+15)); + mapManager.touchVolume(fw.getName(), x, ymin, z, x + 15, ymax, z + 15, "chunkgenerate"); + } + // If cooked, add to known + if ((c.getPersistedStatus() == ChunkStatus.FULL) && (c instanceof LevelChunk)) { + addKnownChunk(fw, cp); + } + } + } + } + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleBlockToolModificationEvent(BlockEvent.BlockToolModificationEvent event) { + handleBlockEvent(event); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleBreakEvent(BlockEvent.BreakEvent event) { + handleBlockEvent(event); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleEntityMultiPlaceEvent(BlockEvent.EntityMultiPlaceEvent event) { + handleBlockEvent(event); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleEntityPlaceEvent(BlockEvent.EntityPlaceEvent event) { + handleBlockEvent(event); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleFarmlandTrampleEvent(BlockEvent.FarmlandTrampleEvent event) { + handleBlockEvent(event); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleFluidPlaceBlockEvent(BlockEvent.FluidPlaceBlockEvent event) { + handleBlockEvent(event); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleNeighborNotifyEvent(BlockEvent.NeighborNotifyEvent event) { + handleBlockEvent(event); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handlePortalSpawnEvent(BlockEvent.PortalSpawnEvent event) { + handleBlockEvent(event); + } + + private void handleBlockEvent(BlockEvent event) { + if (!core_enabled) + return; + if (!onblockchange) + return; + BlockUpdateRec r = new BlockUpdateRec(); + r.w = event.getLevel(); + if (!(r.w instanceof ServerLevel)) + return; // band-aid to prevent errors in unsupported 'running in client' scenario + NeoForgeWorld fw = getWorld((ServerLevel) r.w, false); + if (fw == null) + return; + r.wid = fw.getName(); + BlockPos p = event.getPos(); + r.x = p.getX(); + r.y = p.getY(); + r.z = p.getZ(); + blockupdatequeue.add(r); + } + } + + private WorldTracker worldTracker = null; + private boolean onblockchange = false; + private boolean onchunkpopulate = false; + private boolean onchunkgenerate = false; + boolean onblockchange_with_id = false; + + private void registerEvents() { + // To trigger rendering. + onblockchange = core.isTrigger("blockupdate"); + onchunkpopulate = core.isTrigger("chunkpopulate"); + onchunkgenerate = core.isTrigger("chunkgenerate"); + onblockchange_with_id = core.isTrigger("blockupdate-with-id"); + if (onblockchange_with_id) + onblockchange = true; + if ((worldTracker == null) && (onblockchange || onchunkpopulate || onchunkgenerate)) { + worldTracker = new WorldTracker(); + NeoForge.EVENT_BUS.register(worldTracker); + } + // Prime the known full chunks + if (onchunkgenerate && (server.getAllLevels() != null)) { + for (ServerLevel world : server.getAllLevels()) { + NeoForgeWorld fw = getWorld(world); + if (fw == null) + continue; + Long2ObjectLinkedOpenHashMap chunks = world.getChunkSource().chunkMap.visibleChunkMap; + for (Entry k : chunks.long2ObjectEntrySet()) { + long key = k.getKey().longValue(); + ChunkHolder ch = k.getValue(); + ChunkAccess c = null; + try { + c = ch.getChunkToSend(); + } catch (Exception x) { + } + if (c == null) + continue; + ChunkStatus cs = c.getPersistedStatus(); + ChunkPos pos = ch.getPos(); + if (cs == ChunkStatus.FULL) { // Cooked? + // Add it as known + addKnownChunk(fw, pos); + } + } + } + } + } + + private NeoForgeWorld getWorldByName(String name) { + return worlds.get(name); + } + + NeoForgeWorld getWorld(ServerLevel w) { + return getWorld(w, true); + } + + private NeoForgeWorld getWorld(ServerLevel w, boolean add_if_not_found) { + if (last_world == w) { + return last_fworld; + } + String wname = NeoForgeWorld.getWorldName(w); + + for (NeoForgeWorld fw : worlds.values()) { + if (fw.getRawName().equals(wname)) { + last_world = w; + last_fworld = fw; + if (fw.isLoaded() == false) { + fw.setWorldLoaded(w); + } + fw.updateWorld(w); + return fw; + } + } + NeoForgeWorld fw = null; + if (add_if_not_found) { + /* Add to list if not found */ + fw = new NeoForgeWorld(w); + worlds.put(fw.getName(), fw); + } + last_world = w; + last_fworld = fw; + return fw; + } + + private void saveWorlds() { + File f = new File(core.getDataFolder(), NeoForgeWorld.SAVED_WORLDS_FILE); + 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("miny", fw.minY); + vals.put("sealevel", fw.sealevel); + vals.put("nether", fw.isNether()); + vals.put("the_end", ((NeoForgeWorld) fw).isTheEnd()); + vals.put("title", fw.getTitle()); + lst.add(vals); + } + cn.put("worlds", lst); + cn.put("useSaveFolderAsName", useSaveFolder); + cn.put("maxWorldHeight", NeoForgeWorld.getMaxWorldHeight()); + + cn.save(); + } + + private void loadWorlds() { + File f = new File(core.getDataFolder(), NeoForgeWorld.SAVED_WORLDS_FILE); + if (f.canRead() == false) { + useSaveFolder = true; + return; + } + ConfigurationNode cn = new ConfigurationNode(f); + cn.load(); + // If defined, use maxWorldHeight + NeoForgeWorld.setMaxWorldHeight(cn.getInteger("maxWorldHeight", 256)); + + // If setting defined, use it + if (cn.containsKey("useSaveFolderAsName")) { + useSaveFolder = cn.getBoolean("useSaveFolderAsName", useSaveFolder); + } + List> lst = cn.getMapList("worlds"); + if (lst == null) { + Log.warning(String.format("Discarding bad %s", NeoForgeWorld.SAVED_WORLDS_FILE)); + return; + } + + for (Map world : lst) { + try { + String name = (String) world.get("name"); + int height = (Integer) world.get("height"); + Integer miny = (Integer) world.get("miny"); + 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) { + NeoForgeWorld fw = new NeoForgeWorld(name, height, sealevel, nether, theend, title, + (miny != null) ? miny : 0); + fw.setWorldUnloaded(); + core.processWorldLoad(fw); + worlds.put(fw.getName(), fw); + } + } catch (Exception x) { + Log.warning(String.format("Unable to load saved worlds from %s", NeoForgeWorld.SAVED_WORLDS_FILE)); + 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, CommandSourceStack commandSourceStack, + String cmdline) { + String[] args = cmdline.split("\\s+"); + plugin.onCommand(commandSourceStack, 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); + } +} diff --git a/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/NBT.java b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/NBT.java new file mode 100644 index 000000000..de991ae66 --- /dev/null +++ b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/NBT.java @@ -0,0 +1,156 @@ +package org.dynmap.neoforge_1_21_4; + +import java.util.Set; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.util.SimpleBitStorage; + +import org.dynmap.common.chunk.GenericBitStorage; +import org.dynmap.common.chunk.GenericNBTCompound; +import org.dynmap.common.chunk.GenericNBTList; + +public class NBT { + + public static class NBTCompound implements GenericNBTCompound { + private final CompoundTag obj; + + public NBTCompound(CompoundTag t) { + this.obj = t; + } + + @Override + public Set getAllKeys() { + return obj.getAllKeys(); + } + + @Override + public boolean contains(String s) { + return obj.contains(s); + } + + @Override + public boolean contains(String s, int i) { + return obj.contains(s, i); + } + + @Override + public byte getByte(String s) { + return obj.getByte(s); + } + + @Override + public short getShort(String s) { + return obj.getShort(s); + } + + @Override + public int getInt(String s) { + return obj.getInt(s); + } + + @Override + public long getLong(String s) { + return obj.getLong(s); + } + + @Override + public float getFloat(String s) { + return obj.getFloat(s); + } + + @Override + public double getDouble(String s) { + return obj.getDouble(s); + } + + @Override + public String getString(String s) { + return obj.getString(s); + } + + @Override + public byte[] getByteArray(String s) { + return obj.getByteArray(s); + } + + @Override + public int[] getIntArray(String s) { + return obj.getIntArray(s); + } + + @Override + public long[] getLongArray(String s) { + return obj.getLongArray(s); + } + + @Override + public GenericNBTCompound getCompound(String s) { + return new NBTCompound(obj.getCompound(s)); + } + + @Override + public GenericNBTList getList(String s, int i) { + return new NBTList(obj.getList(s, i)); + } + + @Override + public boolean getBoolean(String s) { + return obj.getBoolean(s); + } + + @Override + public String getAsString(String s) { + return obj.get(s).getAsString(); + } + + @Override + public GenericBitStorage makeBitStorage(int bits, int count, long[] data) { + return new OurBitStorage(bits, count, data); + } + + public String toString() { + return obj.toString(); + } + } + + public static class NBTList implements GenericNBTList { + private final ListTag obj; + + public NBTList(ListTag t) { + obj = t; + } + + @Override + public int size() { + return obj.size(); + } + + @Override + public String getString(int idx) { + return obj.getString(idx); + } + + @Override + public GenericNBTCompound getCompound(int idx) { + return new NBTCompound(obj.getCompound(idx)); + } + + public String toString() { + return obj.toString(); + } + } + + public static class OurBitStorage implements GenericBitStorage { + private final SimpleBitStorage bs; + + public OurBitStorage(int bits, int count, long[] data) { + bs = new SimpleBitStorage(bits, count, data); + } + + @Override + public int get(int idx) { + return bs.get(idx); + } + } +} diff --git a/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/NeoForgeCommandSender.java b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/NeoForgeCommandSender.java new file mode 100644 index 000000000..59179398c --- /dev/null +++ b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/NeoForgeCommandSender.java @@ -0,0 +1,49 @@ +package org.dynmap.neoforge_1_21_4; + +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.Component; + +import org.dynmap.common.DynmapCommandSender; + +/** + * Handler for generic console command sender + */ +public class NeoForgeCommandSender implements DynmapCommandSender { + private CommandSourceStack sender; + + protected NeoForgeCommandSender() { + sender = null; + } + + public NeoForgeCommandSender(CommandSourceStack send) { + sender = send; + } + + @Override + public boolean hasPrivilege(String privid) { + return true; + } + + @Override + public void sendMessage(String msg) { + if (sender != null) { + Component ichatcomponent = Component.literal(msg); + sender.sendSuccess(() -> ichatcomponent, true); + } + } + + @Override + public boolean isConnected() { + return false; + } + + @Override + public boolean isOp() { + return true; + } + + @Override + public boolean hasPermissionNode(String node) { + return true; + } +} diff --git a/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/NeoForgeMapChunkCache.java b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/NeoForgeMapChunkCache.java new file mode 100644 index 000000000..701833608 --- /dev/null +++ b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/NeoForgeMapChunkCache.java @@ -0,0 +1,115 @@ +package org.dynmap.neoforge_1_21_4; + +import java.util.List; +import java.util.NoSuchElementException; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeSpecialEffects; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.chunk.storage.SerializableChunkData; + +import org.dynmap.DynmapChunk; +import org.dynmap.Log; +import org.dynmap.common.BiomeMap; +import org.dynmap.common.chunk.GenericChunk; +import org.dynmap.common.chunk.GenericChunkCache; +import org.dynmap.common.chunk.GenericMapChunkCache; + +/** + * Container for managing chunks - dependent upon using chunk snapshots, since + * rendering is off server thread + */ +public class NeoForgeMapChunkCache extends GenericMapChunkCache { + private ServerLevel w; + private ServerChunkCache cps; + + /** + * Construct empty cache + */ + public NeoForgeMapChunkCache(GenericChunkCache cc) { + super(cc); + } + + // Load generic chunk from existing and already loaded chunk + protected GenericChunk getLoadedChunk(DynmapChunk chunk) { + GenericChunk gc = null; + ChunkAccess ch = cps.getChunk(chunk.x, chunk.z, ChunkStatus.FULL, false); + if (ch != null) { + SerializableChunkData sc = SerializableChunkData.copyOf(w, cps.getChunk(chunk.x, chunk.z, false)); + CompoundTag nbt = sc.write(); + if (nbt != null) { + gc = parseChunkFromNBT(new NBT.NBTCompound(nbt)); + } + } + return gc; + } + + // Load generic chunk from unloaded chunk + protected GenericChunk loadChunk(DynmapChunk chunk) { + GenericChunk gc = null; + CompoundTag nbt = readChunk(chunk.x, chunk.z); + // If read was good + if (nbt != null) { + gc = parseChunkFromNBT(new NBT.NBTCompound(nbt)); + } + return gc; + } + + public void setChunks(NeoForgeWorld dw, List chunks) { + this.w = dw.getWorld(); + if (dw.isLoaded()) { + /* Check if world's provider is ServerChunkProvider */ + cps = this.w.getChunkSource(); + } + super.setChunks(dw, chunks); + } + + private CompoundTag readChunk(int x, int z) { + try { + CompoundTag rslt = cps.chunkMap.read(new ChunkPos(x, z)).join().get(); + if (rslt != null) { + CompoundTag lev = rslt; + if (lev.contains("Level")) { + lev = lev.getCompound("Level"); + } + // Don't load uncooked chunks + String stat = lev.getString("Status"); + ChunkStatus cs = ChunkStatus.byName(stat); + if ((stat == null) || + // Needs to be at least lighted + (!cs.isOrAfter(ChunkStatus.LIGHT))) { + rslt = null; + } + } + // Log.info(String.format("loadChunk(%d,%d)=%s", x, z, (rslt != null) ? + // rslt.toString() : "null")); + return rslt; + } catch (NoSuchElementException nsex) { + return null; + } catch (Exception exc) { + Log.severe(String.format("Error reading chunk: %s,%d,%d", dw.getName(), x, z), exc); + return null; + } + } + + @Override + public int getFoliageColor(BiomeMap bm, int[] colormap, int x, int z) { + return bm.getBiomeObject().map(Biome::getSpecialEffects) + .flatMap(BiomeSpecialEffects::getFoliageColorOverride) + .orElse(colormap[bm.biomeLookup()]); + } + + @Override + public int getGrassColor(BiomeMap bm, int[] colormap, int x, int z) { + BiomeSpecialEffects effects = bm.getBiomeObject().map(Biome::getSpecialEffects).orElse(null); + if (effects == null) + return colormap[bm.biomeLookup()]; + return effects.getGrassColorModifier().modifyColor(x, z, + effects.getGrassColorOverride().orElse(colormap[bm.biomeLookup()])); + } +} diff --git a/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/NeoForgePlayer.java b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/NeoForgePlayer.java new file mode 100644 index 000000000..835192a96 --- /dev/null +++ b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/NeoForgePlayer.java @@ -0,0 +1,280 @@ +package org.dynmap.neoforge_1_21_4; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.UUID; + +import net.minecraft.network.Connection; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.game.ClientboundSetSubtitleTextPacket; +import net.minecraft.network.protocol.game.ClientboundSetTitleTextPacket; +import net.minecraft.network.protocol.game.ClientboundSetTitlesAnimationPacket; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.world.entity.Pose; +import net.minecraft.world.phys.Vec3; + +import org.dynmap.DynmapLocation; +import org.dynmap.common.DynmapPlayer; +import org.dynmap.neoforge_1_21_4.DynmapPlugin.TexturesPayload; + +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; + +/** + * Player access abstraction class + */ +public class NeoForgePlayer extends NeoForgeCommandSender implements DynmapPlayer { + private DynmapPlugin plugin; + ServerPlayer player; + private final String skinurl; + private final UUID uuid; + + private static final Gson gson = new GsonBuilder().create(); + + public NeoForgePlayer(DynmapPlugin plugin, ServerPlayer player) { + this.plugin = plugin; + this.player = player; + String url = null; + if (player != null) { + uuid = player.getUUID(); + 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.value()), + 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.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; + } + Vec3 v = player.position(); + return toLoc(player.serverLevel(), v.x, v.y, v.z); + } + + private DynmapLocation toLoc(ServerLevel level, double x, double y, double z) { + return new DynmapLocation(plugin.getWorld(level).getName(), x, y, z); + } + + @Override + public String getWorld() { + if (player == null) { + return null; + } + + if (player.serverLevel() != null) { + return plugin.getWorld((ServerLevel) player.serverLevel()).getName(); + } + + return null; + } + + public static final Connection getNetworkManager(ServerGamePacketListenerImpl nh) { + return nh.getConnection(); + } + + @Override + public InetSocketAddress getAddress() { + if ((player != null) && (player instanceof ServerPlayer)) { + ServerGamePacketListenerImpl nsh = ((ServerPlayer) 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.getPose() == Pose.CROUCHING; + } + + 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.getArmorValue(); + } 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 plugin.hasPerm(player, privid); + return false; + } + + @Override + public boolean isOp() { + return plugin.isOp(player.getName().getString()); + } + + @Override + public void sendMessage(String msg) { + Component ichatcomponent = Component.literal(msg); + player.sendSystemMessage(ichatcomponent); + } + + @Override + public boolean isInvisible() { + if (player != null) { + return player.isInvisible(); + } + return false; + } + + @Override + public boolean isSpectator() { + if (player != null) { + return player.isSpectator(); + } + return false; + } + + @Override + public int getSortWeight() { + Integer wt = plugin.sortWeights.get(getName()); + if (wt != null) + return wt; + return 0; + } + + @Override + public void setSortWeight(int wt) { + if (wt == 0) { + plugin.sortWeights.remove(getName()); + } else { + plugin.sortWeights.put(getName(), wt); + } + } + + @Override + public boolean hasPermissionNode(String node) { + if (player != null) + return plugin.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 ServerPlayer) { + ServerPlayer mp = (ServerPlayer) player; + ClientboundSetTitlesAnimationPacket times = new ClientboundSetTitlesAnimationPacket(fadeInTicks, + stayTicks, fadeOutTicks); + mp.connection.send(times); + if (title != null) { + ClientboundSetTitleTextPacket titlepkt = new ClientboundSetTitleTextPacket( + Component.literal(title)); + mp.connection.send(titlepkt); + } + + if (subtitle != null) { + ClientboundSetSubtitleTextPacket subtitlepkt = new ClientboundSetSubtitleTextPacket( + Component.literal(subtitle)); + mp.connection.send(subtitlepkt); + } + } + } +} diff --git a/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/NeoForgeServer.java b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/NeoForgeServer.java new file mode 100644 index 000000000..db17c044d --- /dev/null +++ b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/NeoForgeServer.java @@ -0,0 +1,702 @@ +package org.dynmap.neoforge_1_21_4; + +import java.io.File; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.PriorityQueue; +import java.util.Set; +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.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.players.GameProfileCache; +import net.minecraft.server.players.UserBanList; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.ModContainer; +import net.neoforged.fml.ModList; +import net.neoforged.fml.loading.LoadingModList; +import net.neoforged.fml.loading.moddiscovery.ModFileInfo; +import net.neoforged.fml.loading.moddiscovery.ModInfo; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.ServerChatEvent; +import net.neoforged.neoforge.event.tick.ServerTickEvent; + +import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.dynmap.DynmapChunk; +import org.dynmap.DynmapCommonAPIListener; +import org.dynmap.DynmapWorld; +import org.dynmap.Log; +import org.dynmap.common.BiomeMap; +import org.dynmap.common.DynmapListenerManager.EventType; +import org.dynmap.common.DynmapPlayer; +import org.dynmap.common.DynmapServerInterface; +import org.dynmap.neoforge_1_21_4.DynmapPlugin.BlockUpdateRec; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.VisibilityLimit; + +import com.mojang.authlib.GameProfile; + +/** + * Server access abstraction class + */ +public class NeoForgeServer extends DynmapServerInterface { + private DynmapPlugin plugin; + private MinecraftServer server; + /* Server thread scheduler */ + private Object schedlock = new Object(); + private long cur_tick; + private long next_id; + private long cur_tick_starttime; + PriorityQueue runqueue = new PriorityQueue(); + private ChatHandler chathandler; + private ConcurrentLinkedQueue msgqueue = new ConcurrentLinkedQueue(); + private HashSet modsused = new HashSet(); + // TPS calculator + private double tps; + long lasttick; + long avgticklen; + // Per tick limit, in nsec + private long perTickLimit = (50000000); // 50 ms + + private static final Pattern patternControlCode = Pattern.compile("(?i)\\u00A7[0-9A-FK-OR]"); + + public NeoForgeServer(DynmapPlugin plugin, MinecraftServer server) { + this.plugin = plugin; + this.server = server; + } + + void onStart(long perTickLimit) { + this.perTickLimit = perTickLimit; + // Prep TPS + lasttick = System.nanoTime(); + tps = 20.0; + } + + private GameProfile getProfileByName(String player) { + GameProfileCache cache = server.getProfileCache(); + Optional val = cache.get(player); + return val.isPresent() ? val.get() : null; + } + + @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++) { + ServerPlayer p = playlist.get(i); + dplay[i] = plugin.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 (ServerPlayer p : players) { + if (p.getName().getString().equalsIgnoreCase(name)) { + return plugin.getOrAddPlayer(p); + } + } + + return null; + } + + @Override + public Set getIPBans() { + UserBanList bl = server.getPlayerList().getBans(); + Set ips = new HashSet(); + + for (String s : bl.getUserList()) { + 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.getLocalIp(); + if (sn == null) + sn = "Unknown Server"; + return sn; + } + + @Override + public boolean isPlayerBanned(String pid) { + UserBanList bl = server.getPlayerList().getBans(); + 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(); + NeoForge.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) { + Component component = Component.literal(msg); + server.getPlayerList().broadcastSystemMessage(component, false); + 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 (plugin.sscache != null) + return plugin.sscache.getHitRate(); + return 0.0; + } + + @Override + public void resetCacheStats() { + if (plugin.sscache != null) + plugin.sscache.resetStats(); + } + + @Override + public DynmapWorld getWorldByName(String wname) { + return 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.players.PlayerList scm = server.getPlayerList(); + if (scm == null) + return Collections.emptySet(); + UserBanList bl = scm.getBans(); + if (bl == null) + return Collections.emptySet(); + if (bl.isBanned(getProfileByName(player))) { + return Collections.emptySet(); + } + Set rslt = plugin.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.players.PlayerList scm = server.getPlayerList(); + if (scm == null) + return false; + UserBanList bl = scm.getBans(); + if (bl == null) + return false; + if (bl.isBanned(getProfileByName(player))) { + return false; + } + return plugin.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) { + NeoForgeMapChunkCache c = (NeoForgeMapChunkCache) 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 (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 NeoForgeMapChunkCache cc = c; + Future f = this.callSyncMethod(new Callable() { + public Boolean call() throws Exception { + // Update busy state on world + NeoForgeWorld fw = (NeoForgeWorld) cc.getWorld(); + // TODO + // setBusy(fw.getWorld()); + cc.getLoadedChunks(); + return true; + } + }, 0); + try { + f.get(); + } catch (CancellationException cx) { + return null; + } catch (InterruptedException 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().getPlayerCount(); + } + + @SubscribeEvent + public void tickEvent(ServerTickEvent.Post event) { + 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 (plugin.core != null) { + plugin.core.serverTick(tps); + } + + boolean done = false; + TaskRecord tr = null; + + while (!plugin.blockupdatequeue.isEmpty()) { + BlockUpdateRec r = plugin.blockupdatequeue.remove(); + BlockState bs = r.w.getBlockState(new BlockPos(r.x, r.y, r.z)); + int idx = Block.BLOCK_STATE_REGISTRY.getId(bs); + if ((idx >= 0) && (!org.dynmap.hdmap.HDBlockModels.isChangeIgnoredBlock(DynmapPlugin.stateByID[idx]))) { + if (plugin.onblockchange_with_id) + plugin.mapManager.touch(r.wid, r.x, r.y, r.z, "blockchange[" + idx + "]"); + else + plugin.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 = plugin.getOrAddPlayer(cm.sender); + else + dp = new NeoForgePlayer(plugin, null); + + plugin.core.listenerManager.processChatEvent(EventType.PLAYER_CHAT, dp, cm.message); + } + // Check for generated chunks + if ((cur_tick % 20) == 0) { + } + } + + @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.getLocalIp(); + } + + @Override + public File getModContainerFile(String name) { + ModFileInfo mfi = LoadingModList.get().getModFileById(name); // Try case sensitive lookup + if (mfi != null) { + try { + File f = mfi.getFile().getFilePath().toFile(); + return f; + } catch (UnsupportedOperationException ex) { + // TODO Implement proper jar in jar method for fetching data + /* + Log.info("Searching for: " + name); + for (IModInfo e : ModList.get().getMods()) { + Log.info("in: " + e.getModId().toString()); + Log.info("resource: " + ModList.get().getModFileById(e.getModId()).getFile() + .findResource(String.valueOf(mfi.getFile().getFilePath()))); + } + */ + Log.warning("jar in jar method found, skipping: " + ex.getMessage()); + } + } + return null; + } + + @Override + public List getModList() { + List mil = LoadingModList.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) { + // NeoForge removed ModContainer#getMod with no replacement + /* if (modid == null) + modid = "minecraft"; + + Optional mc = ModList.get().getModContainerById(modid); + Object mod = (mc.isPresent()) ? mc.get().getMod() : null; + if (mod != null) { + ClassLoader cl = mod.getClass().getClassLoader(); + if (cl == null) + cl = ClassLoader.getSystemClassLoader(); + InputStream is = cl.getResourceAsStream(rname); + if (is != null) { + return is; + } + } + List mcl = LoadingModList.get().getMods(); + for (ModInfo mci : mcl) { + mc = ModList.get().getModContainerById(mci.getModId()); + mod = (mc.isPresent()) ? mc.get().getMod() : null; + if (mod == null) + continue; + ClassLoader cl = mod.getClass().getClassLoader(); + if (cl == null) + cl = ClassLoader.getSystemClassLoader(); + InputStream is = cl.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 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; + ServerPlayer sender; + } + + public class ChatHandler { + @SubscribeEvent + public void handleChat(ServerChatEvent event) { + String msg = event.getMessage().getString(); + if (!msg.startsWith("/")) { + ChatMessage cm = new ChatMessage(); + cm.message = msg; + cm.sender = event.getPlayer(); + msgqueue.add(cm); + } + } + } + +} diff --git a/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/NeoForgeWorld.java b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/NeoForgeWorld.java new file mode 100644 index 000000000..5aa69df96 --- /dev/null +++ b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/NeoForgeWorld.java @@ -0,0 +1,244 @@ +package org.dynmap.neoforge_1_21_4; + +/** + * NeoForge specific implementation of DynmapWorld + */ +import java.util.List; + +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LightLayer; +import net.minecraft.world.level.ServerLevelAccessor; +import net.minecraft.world.level.border.WorldBorder; +import net.minecraft.world.level.levelgen.Heightmap; + +import org.dynmap.DynmapChunk; +import org.dynmap.DynmapLocation; +import org.dynmap.DynmapWorld; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.Polygon; + +public class NeoForgeWorld extends DynmapWorld { + public static final String SAVED_WORLDS_FILE = "neoforgeworlds.yml"; + private ServerLevelAccessor world; + private final boolean skylight; + private final boolean isnether; + private final boolean istheend; + private final String env; + private DynmapLocation spawnloc = new DynmapLocation(); + private static int maxWorldHeight = 320; // Maximum allows world height + + public static int getMaxWorldHeight() { + return maxWorldHeight; + } + + public static void setMaxWorldHeight(int h) { + maxWorldHeight = h; + } + + public static String getWorldName(ServerLevelAccessor w) { + ResourceKey rk = w.getLevel().dimension(); + String id = rk.location().getNamespace() + "_" + rk.location().getPath(); + if (id.equals("minecraft_overworld")) { // Overworld? + return w.getLevel().serverLevelData.getLevelName(); + } else if (id.equals("minecraft_the_end")) { + return "DIM1"; + } else if (id.equals("minecraft_the_nether")) { + return "DIM-1"; + } else { + return id; + } + } + + public void updateWorld(ServerLevelAccessor w) { + this.updateWorldHeights(w.getLevel().getHeight(), w.getLevel().dimensionType().minY(), + w.getLevel().getSeaLevel()); + } + + public NeoForgeWorld(ServerLevelAccessor w) { + this(getWorldName(w), + w.getLevel().getHeight(), + w.getLevel().getSeaLevel(), + w.getLevel().dimension() == Level.NETHER, + w.getLevel().dimension() == Level.END, + getWorldName(w), + w.getLevel().dimensionType().minY()); + setWorldLoaded(w); + } + + public NeoForgeWorld(String name, int height, int sealevel, boolean nether, boolean the_end, String deftitle, + int miny) { + super(name, (height > maxWorldHeight) ? maxWorldHeight : height, sealevel, miny); + world = null; + setTitle(deftitle); + isnether = nether; + istheend = the_end; + skylight = !(isnether || istheend); + + if (isnether) { + env = "nether"; + } else if (istheend) { + env = "the_end"; + } else { + env = "normal"; + } + // Log.info(getName() + ": skylight=" + skylight + ", height=" + + // this.worldheight + ", isnether=" + isnether + ", istheend=" + istheend); + } + + /* Test if world is nether */ + @Override + public boolean isNether() { + return isnether; + } + + public boolean isTheEnd() { + return istheend; + } + + /* Get world spawn location */ + @Override + public DynmapLocation getSpawnLocation() { + if (world != null) { + BlockPos p = world.getLevel().getSharedSpawnPos(); + spawnloc.x = p.getX(); + spawnloc.y = p.getY(); + spawnloc.z = p.getZ(); + spawnloc.world = this.getName(); + } + return spawnloc; + } + + /* Get world time */ + @Override + public long getTime() { + if (world != null) + return world.getLevel().getDayTime(); + else + return -1; + } + + /* World is storming */ + @Override + public boolean hasStorm() { + if (world != null) + return world.getLevel().isRaining(); + else + return false; + } + + /* World is thundering */ + @Override + public boolean isThundering() { + if (world != null) + return world.getLevel().isThundering(); + else + return false; + } + + /* World is loaded */ + @Override + public boolean isLoaded() { + return (world != null); + } + + /* Set world to unloaded */ + @Override + public void setWorldUnloaded() { + getSpawnLocation(); + world = null; + } + + /* Set world to loaded */ + public void setWorldLoaded(ServerLevelAccessor w) { + world = w; + this.sealevel = w.getLevel().getSeaLevel(); // Read actual current sealevel from world + // Update lighting table + for (int i = 0; i < 16; i++) { + // Algorithm based on LightmapTextureManager.getBrightness() + // We can't call that method because it's client-only. + // This means the code below can stop being correct if Mojang ever + // updates the curve; in that case we should reflect the changes. + float value = (float) i / 15.0f; + float brightness = value / (4.0f - 3.0f * value); + this.setBrightnessTableEntry(i, brightness); + // Log.info(getName() + ": light " + i + " = " + light); + } + } + + /* Get light level of block */ + @Override + public int getLightLevel(int x, int y, int z) { + if (world != null) + return world.getLevel().getLightEngine().getRawBrightness(new BlockPos(x, y, z), 0); + else + return -1; + } + + /* Get highest Y coord of given location */ + @Override + public int getHighestBlockYAt(int x, int z) { + if (world != null) { + return world.getLevel().getChunk(x >> 4, z >> 4).getHeight(Heightmap.Types.MOTION_BLOCKING, x & 15, z & 15); + } else + return -1; + } + + /* Test if sky light level is requestable */ + @Override + public boolean canGetSkyLightLevel() { + return skylight; + } + + /* Return sky light level */ + @Override + public int getSkyLightLevel(int x, int y, int z) { + if (world != null) { + return world.getLevel().getBrightness(LightLayer.SKY, new BlockPos(x, y, z)); + } else + return -1; + } + + /** + * Get world environment ID (lower case - normal, the_end, nether) + */ + @Override + public String getEnvironment() { + return env; + } + + /** + * Get map chunk cache for world + */ + @Override + public MapChunkCache getChunkCache(List chunks) { + if (world != null) { + NeoForgeMapChunkCache c = new NeoForgeMapChunkCache(DynmapPlugin.plugin.sscache); + c.setChunks(this, chunks); + return c; + } + return null; + } + + public ServerLevel getWorld() { + return world.getLevel(); + } + + @Override + public Polygon getWorldBorder() { + if (world != null) { + WorldBorder wb = world.getWorldBorder(); + if ((wb != null) && (wb.getSize() < 5.9E7)) { + Polygon p = new Polygon(); + p.addVertex(wb.getMinX(), wb.getMinZ()); + p.addVertex(wb.getMinX(), wb.getMaxZ()); + p.addVertex(wb.getMaxX(), wb.getMaxZ()); + p.addVertex(wb.getMaxX(), wb.getMinZ()); + return p; + } + } + return null; + } +} diff --git a/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/Proxy.java b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/Proxy.java new file mode 100644 index 000000000..e250787f9 --- /dev/null +++ b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/Proxy.java @@ -0,0 +1,24 @@ +package org.dynmap.neoforge_1_21_4; + +import net.minecraft.server.MinecraftServer; + +/** + * Server side proxy - methods for creating and cleaning up plugin + */ +public class Proxy { + public Proxy() { + } + + public DynmapPlugin startServer(MinecraftServer srv) { + DynmapPlugin plugin = DynmapPlugin.plugin; + if (plugin == null) { + plugin = new DynmapPlugin(srv); + plugin.onEnable(); + } + return plugin; + } + + public void stopServer(DynmapPlugin plugin) { + plugin.onDisable(); + } +} diff --git a/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/VersionCheck.java b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/VersionCheck.java new file mode 100644 index 000000000..22ffe8d4a --- /dev/null +++ b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/VersionCheck.java @@ -0,0 +1,100 @@ +package org.dynmap.neoforge_1_21_4; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; + +import org.dynmap.DynmapCore; +import org.dynmap.Log; + +public class VersionCheck { + private static final String VERSION_URL = "http://mikeprimm.com/dynmap/releases.php"; + + public static void runCheck(final DynmapCore core) { + new Thread(new Runnable() { + public void run() { + doCheck(core); + } + }).start(); + } + + private static int getReleaseVersion(String s) { + int index = s.lastIndexOf('-'); + if (index < 0) + index = s.lastIndexOf('.'); + if (index >= 0) + s = s.substring(0, index); + String[] split = s.split("\\."); + int v = 0; + try { + for (int i = 0; (i < split.length) && (i < 3); i++) { + v += Integer.parseInt(split[i]) << (8 * (2 - i)); + } + } catch (NumberFormatException nfx) { + } + return v; + } + + private static int getBuildNumber(String s) { + int index = s.lastIndexOf('-'); + if (index < 0) + index = s.lastIndexOf('.'); + if (index >= 0) + s = s.substring(index + 1); + try { + return Integer.parseInt(s); + } catch (NumberFormatException nfx) { + return 99999999; + } + } + + private static void doCheck(DynmapCore core) { + String pluginver = core.getDynmapPluginVersion(); + String platform = core.getDynmapPluginPlatform(); + String platver = core.getDynmapPluginPlatformVersion(); + if ((pluginver == null) || (platform == null) || (platver == null)) + return; + HttpURLConnection conn = null; + String loc = VERSION_URL; + int cur_ver = getReleaseVersion(pluginver); + int cur_bn = getBuildNumber(pluginver); + try { + while ((loc != null) && (!loc.isEmpty())) { + URL url = new URL(loc); + conn = (HttpURLConnection) url.openConnection(); + conn.setRequestProperty("User-Agent", "Dynmap (" + platform + "/" + platver + "/" + pluginver); + conn.connect(); + loc = conn.getHeaderField("Location"); + } + BufferedReader rdr = new BufferedReader(new InputStreamReader(conn.getInputStream())); + String line = null; + while ((line = rdr.readLine()) != null) { + String[] split = line.split(":"); + if (split.length < 4) + continue; + /* If our platform and version, or wildcard platform version */ + if (split[0].equals(platform) && (split[1].equals("*") || split[1].equals(platver))) { + int recommended_ver = getReleaseVersion(split[2]); + int recommended_bn = getBuildNumber(split[2]); + if ((recommended_ver > cur_ver) || ((recommended_ver == cur_ver) + && (recommended_bn > cur_bn))) { /* Newer recommended build */ + Log.info("Version obsolete: new recommended version " + split[2] + " is available."); + } else if (cur_ver > recommended_ver) { /* Running dev or prerelease? */ + int prerel_ver = getReleaseVersion(split[3]); + int prerel_bn = getBuildNumber(split[3]); + if ((prerel_ver > cur_ver) || ((prerel_ver == cur_ver) && (prerel_bn > cur_bn))) { + Log.info("Version obsolete: new prerelease version " + split[3] + " is available."); + } + } + } + } + } catch (Exception x) { + Log.info("Error checking for latest version"); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + } +} diff --git a/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/permissions/FilePermissions.java b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/permissions/FilePermissions.java new file mode 100644 index 000000000..e5939d985 --- /dev/null +++ b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/permissions/FilePermissions.java @@ -0,0 +1,104 @@ +package org.dynmap.neoforge_1_21_4.permissions; + +import java.io.File; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import net.minecraft.server.level.ServerPlayer; + +import org.dynmap.ConfigurationNode; +import org.dynmap.Log; +import org.dynmap.neoforge_1_21_4.DynmapPlugin; + +public class FilePermissions implements PermissionProvider { + private HashMap> perms; + private Set defperms; + + public static FilePermissions create() { + File f = new File("dynmap/permissions.yml"); + if (!f.exists()) + return null; + ConfigurationNode cfg = new ConfigurationNode(f); + cfg.load(); + + Log.info("Using permissions.yml for access control"); + + return new FilePermissions(cfg); + } + + private FilePermissions(ConfigurationNode cfg) { + perms = new HashMap>(); + for (String k : cfg.keySet()) { + List p = cfg.getStrings(k, null); + if (p != null) { + k = k.toLowerCase(); + HashSet pset = new HashSet(); + for (String perm : p) { + pset.add(perm.toLowerCase()); + } + perms.put(k, pset); + if (k.equals("defaultuser")) { + defperms = pset; + } + } + } + } + + private boolean hasPerm(String player, String perm) { + Set ps = perms.get(player); + if ((ps != null) && (ps.contains(perm))) { + return true; + } + if (defperms.contains(perm)) { + return true; + } + return false; + } + + @Override + public Set hasOfflinePermissions(String player, Set perms) { + player = player.toLowerCase(); + HashSet rslt = new HashSet(); + if (DynmapPlugin.plugin.isOp(player)) { + rslt.addAll(perms); + } else { + for (String p : perms) { + if (hasPerm(player, p)) { + rslt.add(p); + } + } + } + return rslt; + } + + @Override + public boolean hasOfflinePermission(String player, String perm) { + player = player.toLowerCase(); + if (DynmapPlugin.plugin.isOp(player)) { + return true; + } else { + return hasPerm(player, perm); + } + } + + @Override + public boolean has(ServerPlayer psender, String permission) { + if (psender != null) { + String n = psender.getName().getString().toLowerCase(); + return hasPerm(n, permission); + } + return true; + } + + @Override + public boolean hasPermissionNode(ServerPlayer psender, String permission) { + if (psender != null) { + String player = psender.getName().getString().toLowerCase(); + return DynmapPlugin.plugin.isOp(player); + } + return false; + } + +} diff --git a/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/permissions/OpPermissions.java b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/permissions/OpPermissions.java new file mode 100644 index 000000000..6b2443b78 --- /dev/null +++ b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/permissions/OpPermissions.java @@ -0,0 +1,53 @@ +package org.dynmap.neoforge_1_21_4.permissions; + +import java.util.HashSet; +import java.util.Set; + +import net.minecraft.server.level.ServerPlayer; + +import org.dynmap.Log; +import org.dynmap.neoforge_1_21_4.DynmapPlugin; + +public class OpPermissions implements PermissionProvider { + public HashSet usrCommands = new HashSet(); + + public OpPermissions(String[] usrCommands) { + for (String usrCommand : usrCommands) { + this.usrCommands.add(usrCommand); + } + Log.info("Using ops.txt for access control"); + } + + @Override + public Set hasOfflinePermissions(String player, Set perms) { + HashSet rslt = new HashSet(); + if (DynmapPlugin.plugin.isOp(player)) { + rslt.addAll(perms); + } + return rslt; + } + + @Override + public boolean hasOfflinePermission(String player, String perm) { + return DynmapPlugin.plugin.isOp(player); + } + + @Override + public boolean has(ServerPlayer psender, String permission) { + if (psender != null) { + if (usrCommands.contains(permission)) { + return true; + } + return DynmapPlugin.plugin.isOp(psender.getName().getString()); + } + return true; + } + + @Override + public boolean hasPermissionNode(ServerPlayer psender, String permission) { + if (psender != null) { + return DynmapPlugin.plugin.isOp(psender.getName().getString()); + } + return true; + } +} diff --git a/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/permissions/PermissionProvider.java b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/permissions/PermissionProvider.java new file mode 100644 index 000000000..f85597c65 --- /dev/null +++ b/neoforge-1.21.4/src/main/java/org/dynmap/neoforge_1_21_4/permissions/PermissionProvider.java @@ -0,0 +1,16 @@ +package org.dynmap.neoforge_1_21_4.permissions; + +import java.util.Set; + +import net.minecraft.server.level.ServerPlayer; + +public interface PermissionProvider { + boolean has(ServerPlayer sender, String permission); + + boolean hasPermissionNode(ServerPlayer sender, String permission); + + Set hasOfflinePermissions(String player, Set perms); + + boolean hasOfflinePermission(String player, String perm); + +} diff --git a/neoforge-1.21.4/src/main/resources/META-INF/accesstransformer.cfg b/neoforge-1.21.4/src/main/resources/META-INF/accesstransformer.cfg new file mode 100644 index 000000000..af53a2e93 --- /dev/null +++ b/neoforge-1.21.4/src/main/resources/META-INF/accesstransformer.cfg @@ -0,0 +1,4 @@ +public net.minecraft.world.level.biome.BiomeSpecialEffects$Builder waterColor +public net.minecraft.server.level.ServerLevel serverLevelData +public net.minecraft.server.level.ChunkMap visibleChunkMap +public net.minecraft.server.level.ChunkMap readChunk(Lnet/minecraft/world/level/ChunkPos;)Ljava/util/concurrent/CompletableFuture; diff --git a/neoforge-1.21.4/src/main/resources/META-INF/neoforge.mods.toml b/neoforge-1.21.4/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 000000000..d2b91b93e --- /dev/null +++ b/neoforge-1.21.4/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,27 @@ +modLoader="javafml" +loaderVersion="[2,)" +issueTrackerURL="https://github.com/webbukkit/dynmap/issues" +license="Apache Public License v2" +[[mods]] +modId="dynmap" +version="${version}" +displayName="Dynmap" +authors="mikeprimm" +description=''' +Dynamic, Google-maps style rendered maps for your Minecraft server +''' +[[accessTransformers]] +file="META-INF/accesstransformer.cfg" +[[dependencies.dynmap]] + modId="neoforge" + mandatory=true + versionRange="[21.1,)" + ordering="NONE" + # Side this dependency is applied on - BOTH, CLIENT or SERVER + side="SERVER" +[[dependencies.dynmap]] + modId="minecraft" + mandatory=true + versionRange="[1.21.4,)" + ordering="NONE" + side="SERVER" diff --git a/neoforge-1.21.4/src/main/resources/configuration.txt b/neoforge-1.21.4/src/main/resources/configuration.txt new file mode 100644 index 000000000..38174f817 --- /dev/null +++ b/neoforge-1.21.4/src/main/resources/configuration.txt @@ -0,0 +1,504 @@ +# All paths in this configuration file are relative to Dynmap's data-folder: minecraft_server/dynmap/ + +# All map templates are defined in the templates directory +# To use the HDMap very-low-res (2 ppb) map templates as world defaults, set value to vlowres +# The definitions of these templates are in normal-vlowres.txt, nether-vlowres.txt, and the_end-vlowres.txt +# To use the HDMap low-res (4 ppb) map templates as world defaults, set value to lowres +# The definitions of these templates are in normal-lowres.txt, nether-lowres.txt, and the_end-lowres.txt +# To use the HDMap hi-res (16 ppb) map templates (these can take a VERY long time for initial fullrender), set value to hires +# The definitions of these templates are in normal-hires.txt, nether-hires.txt, and the_end-hires.txt +# To use the HDMap low-res (4 ppb) map templates, with support for boosting resolution selectively to hi-res (16 ppb), set value to low_boost_hi +# The definitions of these templates are in normal-low_boost_hi.txt, nether-low_boost_hi.txt, and the_end-low_boost_hi.txt +# To use the HDMap hi-res (16 ppb) map templates, with support for boosting resolution selectively to vhi-res (32 ppb), set value to hi_boost_vhi +# The definitions of these templates are in normal-hi_boost_vhi.txt, nether-hi_boost_vhi.txt, and the_end-hi_boost_vhi.txt +# To use the HDMap hi-res (16 ppb) map templates, with support for boosting resolution selectively to xhi-res (64 ppb), set value to hi_boost_xhi +# The definitions of these templates are in normal-hi_boost_xhi.txt, nether-hi_boost_xhi.txt, and the_end-hi_boost_xhi.txt +deftemplatesuffix: hires + +# Set default tile scale (0 = 128px x 128x, 1 = 256px x 256px, 2 = 512px x 512px, 3 = 1024px x 1024px, 4 = 2048px x 2048px) - 0 is default +# Note: changing this value will result in all maps that use the default value being required to be fully rendered +#defaulttilescale: 0 + +# Map storage scheme: only uncommoent one 'type' value +# filetree: classic and default scheme: tree of files, with all map data under the directory indicated by 'tilespath' setting +# sqlite: single SQLite database file (this can get VERY BIG), located at 'dbfile' setting (default is file dynmap.db in data directory) +# mysql: MySQL database, at hostname:port in database, accessed via userid with password +# mariadb: MariaDB database, at hostname:port in database, accessed via userid with password +# postgres: PostgreSQL database, at hostname:port in database, accessed via userid with password +storage: + # Filetree storage (standard tree of image files for maps) + type: filetree + # SQLite db for map storage (uses dbfile as storage location) + #type: sqlite + #dbfile: dynmap.db + # MySQL DB for map storage (at 'hostname':'port' in database 'database' using user 'userid' password 'password' and table prefix 'prefix' + #type: mysql + #hostname: localhost + #port: 3306 + #database: dynmap + #userid: dynmap + #password: dynmap + #prefix: "" + # + # AWS S3 backet web site + #type: aws_s3 + #bucketname: "dynmap-bucket-name" + #region: us-east-1 + #aws_access_key_id: "" + #aws_secret_access_key: "" + #prefix: "" + #override_endpoint: "" + +components: + - class: org.dynmap.ClientConfigurationComponent + + # Remember to change the following class to org.dynmap.JsonFileClientUpdateComponent when using an external web server. + - class: org.dynmap.InternalClientUpdateComponent + sendhealth: true + sendposition: true + allowwebchat: true + webchat-interval: 5 + hidewebchatip: false + trustclientname: false + includehiddenplayers: false + # (optional) if true, color codes in player display names are used + use-name-colors: false + # (optional) if true, player login IDs will be used for web chat when their IPs match + use-player-login-ip: true + # (optional) if use-player-login-ip is true, setting this to true will cause chat messages not matching a known player IP to be ignored + require-player-login-ip: false + # (optional) block player login IDs that are banned from chatting + block-banned-player-chat: true + # Require login for web-to-server chat (requires login-enabled: true) + webchat-requires-login: false + # If set to true, users must have dynmap.webchat permission in order to chat + webchat-permissions: false + # Limit length of single chat messages + chatlengthlimit: 256 + # # Optional - make players hidden when they are inside/underground/in shadows (#=light level: 0=full shadow,15=sky) + # hideifshadow: 4 + # # Optional - make player hidden when they are under cover (#=sky light level,0=underground,15=open to sky) + # hideifundercover: 14 + # # (Optional) if true, players that are crouching/sneaking will be hidden + hideifsneaking: false + # optional, if true, players that are in spectator mode will be hidden + hideifspectator: false + # If true, player positions/status is protected (login with ID with dynmap.playermarkers.seeall permission required for info other than self) + protected-player-info: false + # If true, hide players with invisibility potion effects active + hide-if-invisiblity-potion: true + # If true, player names are not shown on map, chat, list + hidenames: false + #- class: org.dynmap.JsonFileClientUpdateComponent + # writeinterval: 1 + # sendhealth: true + # sendposition: true + # allowwebchat: true + # webchat-interval: 5 + # hidewebchatip: false + # includehiddenplayers: false + # use-name-colors: false + # use-player-login-ip: false + # require-player-login-ip: false + # block-banned-player-chat: true + # hideifshadow: 0 + # hideifundercover: 0 + # hideifsneaking: false + # # Require login for web-to-server chat (requires login-enabled: true) + # webchat-requires-login: false + # # If set to true, users must have dynmap.webchat permission in order to chat + # webchat-permissions: false + # # Limit length of single chat messages + # chatlengthlimit: 256 + # hide-if-invisiblity-potion: true + # hidenames: false + + - class: org.dynmap.SimpleWebChatComponent + allowchat: true + # If true, web UI users can supply name for chat using 'playername' URL parameter. 'trustclientname' must also be set true. + allowurlname: false + + # Note: this component is needed for the dmarker commands, and for the Marker API to be available to other plugins + - class: org.dynmap.MarkersComponent + type: markers + showlabel: false + enablesigns: false + # Default marker set for sign markers + default-sign-set: markers + # (optional) add spawn point markers to standard marker layer + showspawn: true + spawnicon: world + spawnlabel: "Spawn" + # (optional) layer for showing offline player's positions (for 'maxofflinetime' minutes after logoff) + showofflineplayers: false + offlinelabel: "Offline" + offlineicon: offlineuser + offlinehidebydefault: true + offlineminzoom: 0 + maxofflinetime: 30 + # (optional) layer for showing player's spawn beds + showspawnbeds: false + spawnbedlabel: "Spawn Beds" + spawnbedicon: bed + spawnbedhidebydefault: true + spawnbedminzoom: 0 + spawnbedformat: "%name%'s bed" + spawnbedremoveonplayerleave: true + # (optional) Show world border (vanilla 1.8+) + showworldborder: true + worldborderlabel: "Border" + + - class: org.dynmap.ClientComponent + type: chat + allowurlname: false + - class: org.dynmap.ClientComponent + type: chatballoon + focuschatballoons: false + - class: org.dynmap.ClientComponent + type: chatbox + showplayerfaces: true + messagettl: 5 + # Optional: set number of lines in scrollable message history: if set, messagettl is not used to age out messages + #scrollback: 100 + # Optional: set maximum number of lines visible for chatbox + #visiblelines: 10 + # Optional: send push button + sendbutton: false + - class: org.dynmap.ClientComponent + type: playermarkers + showplayerfaces: true + showplayerhealth: true + # If true, show player body too (only valid if showplayerfaces=true) + showplayerbody: false + # Option to make player faces small - don't use with showplayerhealth or largeplayerfaces + smallplayerfaces: false + # Option to make player faces larger - don't use with showplayerhealth or smallplayerfaces + largeplayerfaces: false + # Optional - make player faces layer hidden by default + hidebydefault: false + # Optional - ordering priority in layer menu (low goes before high - default is 0) + layerprio: 0 + # Optional - label for player marker layer (default is 'Players') + label: "Players" + + #- class: org.dynmap.ClientComponent + # type: digitalclock + - class: org.dynmap.ClientComponent + type: link + + - class: org.dynmap.ClientComponent + type: timeofdayclock + showdigitalclock: true + #showweather: true + # Mouse pointer world coordinate display + - class: org.dynmap.ClientComponent + type: coord + label: "Location" + hidey: false + show-mcr: false + show-chunk: false + + # Note: more than one logo component can be defined + #- class: org.dynmap.ClientComponent + # type: logo + # text: "Dynmap" + # #logourl: "images/block_surface.png" + # linkurl: "http://forums.bukkit.org/threads/dynmap.489/" + # # Valid positions: top-left, top-right, bottom-left, bottom-right + # position: bottom-right + + #- class: org.dynmap.ClientComponent + # type: inactive + # timeout: 1800 # in seconds (1800 seconds = 30 minutes) + # redirecturl: inactive.html + # #showmessage: 'You were inactive for too long.' + + #- class: org.dynmap.TestComponent + # stuff: "This is some configuration-value" + +# Treat hiddenplayers.txt as a whitelist for players to be shown on the map? (Default false) +display-whitelist: false + +# How often a tile gets rendered (in seconds). +renderinterval: 1 + +# How many tiles on update queue before accelerate render interval +renderacceleratethreshold: 60 + +# How often to render tiles when backlog is above renderacceleratethreshold +renderaccelerateinterval: 0.2 + +# How many update tiles to work on at once (if not defined, default is 1/2 the number of cores) +tiles-rendered-at-once: 2 + +# If true, use normal priority threads for rendering (versus low priority) - this can keep rendering +# from starving on busy Windows boxes (Linux JVMs pretty much ignore thread priority), but may result +# in more competition for CPU resources with other processes +usenormalthreadpriority: true + +# Save and restore pending tile renders - prevents their loss on server shutdown or /reload +saverestorepending: true + +# Save period for pending jobs (in seconds): periodic saving for crash recovery of jobs +save-pending-period: 900 + +# Zoom-out tile update period - how often to scan for and process tile updates into zoom-out tiles (in seconds) +zoomoutperiod: 30 + +# Control whether zoom out tiles are validated on startup (can be needed if zoomout processing is interrupted, but can be expensive on large maps) +initial-zoomout-validate: true + +# Default delay on processing of updated tiles, in seconds. This can reduce potentially expensive re-rendering +# of frequently updated tiles (such as due to machines, pistons, quarries or other automation). Values can +# also be set on individual worlds and individual maps. +tileupdatedelay: 30 + +# Tile hashing is used to minimize tile file updates when no changes have occurred - set to false to disable +enabletilehash: true + +# Optional - hide ores: render as normal stone (so that they aren't revealed by maps) +#hideores: true + +# Optional - enabled BetterGrass style rendering of grass and snow block sides +#better-grass: true + +# Optional - enable smooth lighting by default on all maps supporting it (can be set per map as lighting option) +smooth-lighting: true + +# Optional - use world provider lighting table (good for custom worlds with custom lighting curves, like nether) +# false=classic Dynmap lighting curve +use-brightness-table: true + +# Optional - render specific block names using the textures and models of another block name: can be used to hide/disguise specific +# blocks (e.g. make ores look like stone, hide chests) or to provide simple support for rendering unsupported custom blocks +block-alias: +# "minecraft:quartz_ore": "stone" +# "diamond_ore": "coal_ore" + +# Default image format for HDMaps (png, jpg, jpg-q75, jpg-q80, jpg-q85, jpg-q90, jpg-q95, jpg-q100, webp, webp-q75, webp-q80, webp-q85, webp-q90, webp-q95, webp-q100), +# Note: any webp format requires the presence of the 'webp command line tools' (cwebp, dwebp) (https://developers.google.com/speed/webp/download) +# +# Has no effect on maps with explicit format settings +image-format: jpg-q90 + +# If cwebp or dwebp are not on the PATH, use these settings to provide their full path. Do not use these settings if the tools are on the PATH +# For Windows, include .exe +# +#cwebpPath: /usr/bin/cwebp +#dwebpPath: /usr/bin/dwebp + +# use-generated-textures: if true, use generated textures (same as client); false is static water/lava textures +# correct-water-lighting: if true, use corrected water lighting (same as client); false is legacy water (darker) +# transparent-leaves: if true, leaves are transparent (lighting-wise): false is needed for some Spout versions that break lighting on leaf blocks +use-generated-textures: true +correct-water-lighting: true +transparent-leaves: true + +# ctm-support: if true, Connected Texture Mod (CTM) in texture packs is enabled (default) +ctm-support: true +# custom-colors-support: if true, Custom Colors in texture packs is enabled (default) +custom-colors-support: true + +# Control loading of player faces (if set to false, skins are never fetched) +#fetchskins: false + +# Control updating of player faces, once loaded (if faces are being managed by other apps or manually) +#refreshskins: false + +# Customize URL used for fetching player skins (%player% is macro for name, %uuid% for UUID) +skin-url: "http://skins.minecraft.net/MinecraftSkins/%player%.png" + +# Control behavior for new (1.0+) compass orientation (sunrise moved 90 degrees: east is now what used to be south) +# default is 'newrose' (preserve pre-1.0 maps, rotate rose) +# 'newnorth' is used to rotate maps and rose (requires fullrender of any HDMap map - same as 'newrose' for FlatMap or KzedMap) +compass-mode: newnorth + +# Triggers for automatic updates : blockupdate-with-id is debug for breaking down updates by ID:meta +# To disable, set just 'none' and comment/delete the rest +render-triggers: + - blockupdate + #- blockupdate-with-id + #- lightingupdate + - chunkpopulate + - chunkgenerate + #- none + +# Title for the web page - if not specified, defaults to the server's name (unless it is the default of 'Unknown Server') +#webpage-title: "My Awesome Server Map" + +# The path where the tile-files are placed. +tilespath: web/tiles + +# The path where the web-files are located. +webpath: web + +# If set to false, disable extraction of webpath content (good if using custom web UI or 3rd party web UI) +# Note: web interface is unsupported in this configuration - you're on your own +update-webpath-files: true + +# The path were the /dynmapexp command exports OBJ ZIP files +exportpath: export + +# The path where files can be imported for /dmarker commands +importpath: import + +# The network-interface the webserver will bind to (0.0.0.0 for all interfaces, 127.0.0.1 for only local access). +# If not set, uses same setting as server in server.properties (or 0.0.0.0 if not specified) +#webserver-bindaddress: 0.0.0.0 + +# The TCP-port the webserver will listen on. +webserver-port: 8123 + +# Maximum concurrent session on internal web server - limits resources used in Bukkit server +max-sessions: 30 + +# Disables Webserver portion of Dynmap (Advanced users only) +disable-webserver: false + +# Enable/disable having the web server allow symbolic links (true=compatible with existing code, false=more secure (default)) +allow-symlinks: true + +# Enable login support +login-enabled: false +# Require login to access website (requires login-enabled: true) +login-required: false + +# Period between tile renders for fullrender, in seconds (non-zero to pace fullrenders, lessen CPU load) +timesliceinterval: 0.0 + +# Maximum chunk loads per server tick (1/20th of a second) - reducing this below 90 will impact render performance, but also will reduce server thread load +maxchunkspertick: 200 + +# Progress report interval for fullrender/radiusrender, in tiles. Must be 100 or greater +progressloginterval: 100 + +# Parallel fullrender: if defined, number of concurrent threads used for fullrender or radiusrender +# Note: setting this will result in much more intensive CPU use, some additional memory use. Caution should be used when +# setting this to equal or exceed the number of physical cores on the system. +#parallelrendercnt: 4 + +# Interval the browser should poll for updates. +updaterate: 2000 + +# If nonzero, server will pause fullrender/radiusrender processing when 'fullrenderplayerlimit' or more users are logged in +fullrenderplayerlimit: 0 +# If nonzero, server will pause update render processing when 'updateplayerlimit' or more users are logged in +updateplayerlimit: 0 +# Target limit on server thread use - msec per tick +per-tick-time-limit: 50 +# If TPS of server is below this setting, update renders processing is paused +update-min-tps: 18.0 +# If TPS of server is below this setting, full/radius renders processing is paused +fullrender-min-tps: 18.0 +# If TPS of server is below this setting, zoom out processing is paused +zoomout-min-tps: 18.0 + +showplayerfacesinmenu: true + +# Control whether players that are hidden or not on current map are grayed out (true=yes) +grayplayerswhenhidden: true + +# Set sidebaropened: 'true' to pin menu sidebar opened permanently, 'pinned' to default the sidebar to pinned, but allow it to unpin +#sidebaropened: true + +# Customized HTTP response headers - add 'id: value' pairs to all HTTP response headers (internal web server only) +#http-response-headers: +# Access-Control-Allow-Origin: "my-domain.com" +# X-Custom-Header-Of-Mine: "MyHeaderValue" + +# Trusted proxies for web server - which proxy addresses are trusted to supply valid X-Forwarded-For fields +# This now supports both IP address, and subnet ranges (e.g. 192.168.1.0/24 or 202.24.0.0/14 ) +trusted-proxies: + - "127.0.0.1" + - "0:0:0:0:0:0:0:1" + +joinmessage: "%playername% joined" +quitmessage: "%playername% quit" +spammessage: "You may only chat once every %interval% seconds." +# format for messages from web: %playername% substitutes sender ID (typically IP), %message% includes text +webmsgformat: "&color;2[WEB] %playername%: &color;f%message%" + +# Control whether layer control is presented on the UI (default is true) +showlayercontrol: true + +# Enable checking for banned IPs via banned-ips.txt (internal web server only) +check-banned-ips: true + +# Default selection when map page is loaded +defaultzoom: 0 +defaultworld: world +defaultmap: flat +# (optional) Zoom level and map to switch to when following a player, if possible +#followzoom: 3 +#followmap: surface + +# If true, make persistent record of IP addresses used by player logins, to support web IP to player matching +persist-ids-by-ip: true + +# If true, map text to cyrillic +cyrillic-support: false + +# Messages to customize +msg: + maptypes: "Map Types" + players: "Players" + chatrequireslogin: "Chat Requires Login" + chatnotallowed: "You are not permitted to send chat messages" + hiddennamejoin: "Player joined" + hiddennamequit: "Player quit" + +# URL for client configuration (only need to be tailored for proxies or other non-standard configurations) +url: + # configuration URL + #configuration: "up/configuration" + # update URL + #update: "up/world/{world}/{timestamp}" + # sendmessage URL + #sendmessage: "up/sendmessage" + # login URL + #login: "up/login" + # register URL + #register: "up/register" + # tiles base URL + #tiles: "tiles/" + # markers base URL + #markers: "tiles/" + # Snapshot cache size, in chunks +snapshotcachesize: 500 +# Snapshot cache uses soft references (true), else weak references (false) +soft-ref-cache: true + +# Player enter/exit title messages for map markers +# +# Processing period - how often to check player positions vs markers - default is 1000ms (1 second) +#enterexitperiod: 1000 +# Title message fade in time, in ticks (0.05 second intervals) - default is 10 (1/2 second) +#titleFadeIn: 10 +# Title message stay time, in ticks (0.05 second intervals) - default is 70 (3.5 seconds) +#titleStay: 70 +# Title message fade out time, in ticks (0.05 seocnd intervals) - default is 20 (1 second) +#titleFadeOut: 20 +# Enter/exit messages use on screen titles (true - default), if false chat messages are sent instead +#enterexitUseTitle: true +# Set true if new enter messages should supercede pending exit messages (vs being queued in order), default false +#enterReplacesExits: true + +# Published public URL for Dynmap server (allows users to use 'dynmap url' command to get public URL usable to access server +# If not set, 'dynmap url' will not return anything. URL should be fully qualified (e.g. https://mc.westeroscraft.com/) +#publicURL: http://my.greatserver.com/dynmap + +# Send this message if the player does not have permission to use the command +noPermissionMsg: "You don't have permission to use this command!" + +# Set to true to enable verbose startup messages - can help with debugging map configuration problems +# Set to false for a much quieter startup log +verbose: false + +# Enables debugging. +#debuggers: +# - class: org.dynmap.debug.LogDebugger +# Debug: dump blocks missing render data +dump-missing-blocks: false + +# Log4J defense: string substituted for attempts to use macros in web chat +hackAttemptBlurb: "(IaM5uchA1337Haxr-Ban Me!)" diff --git a/neoforge-1.21.4/src/main/resources/pack.mcmeta b/neoforge-1.21.4/src/main/resources/pack.mcmeta new file mode 100644 index 000000000..64ff3854a --- /dev/null +++ b/neoforge-1.21.4/src/main/resources/pack.mcmeta @@ -0,0 +1,8 @@ +{ + "pack": { + "description": { + "text": "Dynmap resources" + }, + "pack_format": 22 + } +} diff --git a/neoforge-1.21.4/src/main/resources/permissions.yml.example b/neoforge-1.21.4/src/main/resources/permissions.yml.example new file mode 100644 index 000000000..a25f9adca --- /dev/null +++ b/neoforge-1.21.4/src/main/resources/permissions.yml.example @@ -0,0 +1,27 @@ +# +# Sample permissions.yml for dynmap - trivial, flat-file based permissions for dynmap features +# To use, copy this file to dynmap/permissions.yml, and edit appropriate. File is YAML format. +# +# All operators have full permissions to all functions. +# All users receive the permissions under the 'defaultuser' section +# Specific users can be given more permissions by defining a section with their name containing their permisssions +# All permissions correspond to those documented here (https://github.com/webbukkit/dynmap/wiki/Permissions), but +# do NOT have the 'dynmap.' prefix when used here (e.g. 'dynmap.fullrender' permission is just 'fullrender' here). +# +defaultuser: + - render + - show.self + - hide.self + - sendtoweb + - stats + - marker.list + - marker.listsets + - marker.icons + - webregister + - webchat + #- marker.sign + +#playername1: +# - fullrender +# - cancelrender +# - radiusrender diff --git a/settings.gradle b/settings.gradle index f97dab01a..0d1aa0a00 100644 --- a/settings.gradle +++ b/settings.gradle @@ -44,6 +44,7 @@ include ':fabric-1.17.1' include ':fabric-1.16.4' include ':fabric-1.15.2' include ':fabric-1.14.4' +include ':neoforge-1.21.4' include ':neoforge-1.21.1' include ':neoforge-1.20.6' include ':forge-1.21.3' @@ -91,6 +92,7 @@ project(':fabric-1.17.1').projectDir = "$rootDir/fabric-1.17.1" as File project(':fabric-1.16.4').projectDir = "$rootDir/fabric-1.16.4" as File project(':fabric-1.15.2').projectDir = "$rootDir/fabric-1.15.2" as File project(':fabric-1.14.4').projectDir = "$rootDir/fabric-1.14.4" as File +project(':neoforge-1.21.4').projectDir = "$rootDir/neoforge-1.21.4" as File project(':neoforge-1.21.1').projectDir = "$rootDir/neoforge-1.21.1" as File project(':neoforge-1.20.6').projectDir = "$rootDir/neoforge-1.20.6" as File project(':forge-1.21.3').projectDir = "$rootDir/forge-1.21.3" as File From 79fb2650ec9a9676acbb7c837f3476ffa9a6d7f9 Mon Sep 17 00:00:00 2001 From: minimusubi Date: Sat, 18 Jan 2025 02:22:06 -1000 Subject: [PATCH 08/10] feat(neoforge 1.20.6): add support --- neoforge-1.20.6/build.gradle | 22 +- .../org/dynmap/neoforge_1_20_6/DynmapMod.java | 234 +- .../dynmap/neoforge_1_20_6/DynmapPlugin.java | 2909 ++++++----------- .../dynmap/neoforge_1_20_6/ForgeWorld.java | 249 -- .../java/org/dynmap/neoforge_1_20_6/NBT.java | 40 +- .../NeoForgeCommandSender.java | 49 + ...kCache.java => NeoForgeMapChunkCache.java} | 31 +- .../neoforge_1_20_6/NeoForgePlayer.java | 280 ++ .../neoforge_1_20_6/NeoForgeServer.java | 702 ++++ .../dynmap/neoforge_1_20_6/NeoForgeWorld.java | 244 ++ .../org/dynmap/neoforge_1_20_6/Proxy.java | 20 +- .../dynmap/neoforge_1_20_6/VersionCheck.java | 173 +- .../permissions/FilePermissions.java | 173 +- .../permissions/OpPermissions.java | 84 +- .../permissions/PermissionProvider.java | 11 +- .../resources/META-INF/accesstransformer.cfg | 8 +- .../resources/META-INF/neoforge.mods.toml | 4 +- 17 files changed, 2708 insertions(+), 2525 deletions(-) delete mode 100644 neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/ForgeWorld.java create mode 100644 neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/NeoForgeCommandSender.java rename neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/{ForgeMapChunkCache.java => NeoForgeMapChunkCache.java} (89%) create mode 100644 neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/NeoForgePlayer.java create mode 100644 neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/NeoForgeServer.java create mode 100644 neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/NeoForgeWorld.java diff --git a/neoforge-1.20.6/build.gradle b/neoforge-1.20.6/build.gradle index b431abd9c..ad895bc8c 100644 --- a/neoforge-1.20.6/build.gradle +++ b/neoforge-1.20.6/build.gradle @@ -5,8 +5,8 @@ buildscript { } } plugins { - id 'eclipse' - id 'net.neoforged.gradle.userdev' version '7.0.178' + id 'eclipse' + id 'net.neoforged.gradle.userdev' version '7.0.178' } eclipse { @@ -15,6 +15,12 @@ eclipse { } } +minecraft { + accessTransformers { + file('src/main/resources/META-INF/accesstransformer.cfg') + } +} + sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(21) // Need this here so eclipse task generates correctly. java.toolchain.languageVersion = JavaLanguageVersion.of(21) @@ -23,12 +29,12 @@ println('Java: ' + System.getProperty('java.version') + ' JVM: ' + System.getPro ext.buildNumber = System.getenv().BUILD_NUMBER ?: "Dev" -project.archivesBaseName = "${project.archivesBaseName}-forge-1.20.6" +project.archivesBaseName = "${project.archivesBaseName}-neoforge-1.20.6" dependencies { implementation project(path: ":DynmapCore", configuration: "shadow") implementation project(path: ':DynmapCoreAPI') - implementation "net.neoforged:neoforge:20.6.62-beta" + implementation "net.neoforged:neoforge:20.6.124" } processResources @@ -36,9 +42,9 @@ processResources filesMatching('META-INF/neoforge.mods.toml') { // replace version and mcversion expand( - version: project.version + '-' + project.ext.buildNumber, - mcversion: "1.20.6" - ) + version: project.version + '-' + project.ext.buildNumber, + mcversion: "1.20.6" + ) } } @@ -49,7 +55,7 @@ shadowJar { exclude("META-INF/maven/**") exclude("META-INF/services/**") } - relocate('org.apache.commons.codec', 'org.dynmap.neoforge_1_20_6.commons.codec') + relocate('org.apache.commons.codec', 'org.dynmap.neoforge_1_20_6.commons.codec') archiveBaseName = "Dynmap" archiveClassifier = "neoforge-1.20.6" diff --git a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/DynmapMod.java b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/DynmapMod.java index e8deca096..cbc09be83 100644 --- a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/DynmapMod.java +++ b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/DynmapMod.java @@ -2,124 +2,136 @@ import java.io.File; -import org.apache.commons.lang3.tuple.Pair; -import org.dynmap.DynmapCommonAPI; -import org.dynmap.DynmapCommonAPIListener; -import org.dynmap.Log; -import org.dynmap.neoforge_1_20_6.DynmapPlugin.OurLog; - import net.minecraft.server.MinecraftServer; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.ModList; +import net.neoforged.fml.ModLoadingContext; import net.neoforged.fml.common.Mod; +import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; +import net.neoforged.fml.event.lifecycle.FMLLoadCompleteEvent; +import net.neoforged.fml.loading.FMLEnvironment; +import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.event.server.ServerAboutToStartEvent; import net.neoforged.neoforge.event.server.ServerStartedEvent; -import net.neoforged.neoforge.event.server.ServerStartingEvent; import net.neoforged.neoforge.event.server.ServerStoppingEvent; +import org.dynmap.DynmapCommonAPI; +import org.dynmap.DynmapCommonAPIListener; +import org.dynmap.Log; +import org.dynmap.neoforge_1_20_6.DynmapPlugin.OurLog; + @Mod("dynmap") -public class DynmapMod -{ - // The instance of your mod that Forge uses. - public static DynmapMod instance; - - // Says where the client and server 'proxy' code is loaded. - public static Proxy proxy = DistExecutor.runForDist(() -> ClientProxy::new, () -> Proxy::new); - - public static DynmapPlugin plugin; - public static File jarfile; - public static String ver; - public static boolean useforcedchunks; - - public class APICallback extends DynmapCommonAPIListener { - @Override - public void apiListenerAdded() { - if(plugin == null) { - plugin = proxy.startServer(server); - } - } - @Override - public void apiEnabled(DynmapCommonAPI api) { - } - } - - //TODO - //public class LoadingCallback implements net.minecraftforge.common.ForgeChunkManager.LoadingCallback { - // @Override - // public void ticketsLoaded(List tickets, World world) { - // if(tickets.size() > 0) { - // DynmapPlugin.setBusy(world, tickets.get(0)); - // for(int i = 1; i < tickets.size(); i++) { - // ForgeChunkManager.releaseTicket(tickets.get(i)); - // } - // } - // } - //} - - public DynmapMod() { - instance = this; - FMLJavaModLoadingContext.get().getModEventBus().addListener(this::setup); - FMLJavaModLoadingContext.get().getModEventBus().addListener(this::init); - - MinecraftForge.EVENT_BUS.register(this); - - ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, - ()->new IExtensionPoint.DisplayTest(()->IExtensionPoint.DisplayTest.IGNORESERVERONLY, (remote, isServer)-> true)); - - Log.setLogger(new OurLog()); - org.dynmap.modsupport.ModSupportImpl.init(); - } - - public void setup(final FMLCommonSetupEvent event) - { - //TOOO - jarfile = ModList.get().getModFileById("dynmap").getFile().getFilePath().toFile(); - - ver = ModList.get().getModContainerById("dynmap").get().getModInfo().getVersion().toString(); - - //// Load configuration file - use suggested (config/WesterosBlocks.cfg) - //Configuration cfg = new Configuration(event.getSuggestedConfigurationFile()); - //try { - // cfg.load(); - // - // useforcedchunks = cfg.get("Settings", "UseForcedChunks", true).getBoolean(true); - //} - //finally - //{ - // cfg.save(); - //} - } - - public void init(FMLLoadCompleteEvent event) - { - /* Set up for chunk loading notice from chunk manager */ - //TODO - //if(useforcedchunks) { - // ForgeChunkManager.setForcedChunkLoadingCallback(DynmapMod.instance, new LoadingCallback()); - //} - //else { - // Log.info("[Dynmap] World loading using forced chunks is disabled"); - //} - } - - private MinecraftServer server; - - @SubscribeEvent - public void onServerStarting(ServerAboutToStartEvent event) { - server = event.getServer(); - if(plugin == null) - plugin = proxy.startServer(server); +public class DynmapMod { + // The instance of your mod that NeoForge uses. + public static DynmapMod instance; + + // Says where the client and server 'proxy' code is loaded. + public static Proxy proxy; + + public static DynmapPlugin plugin; + public static File jarfile; + public static String ver; + public static boolean useforcedchunks; + + public class APICallback extends DynmapCommonAPIListener { + @Override + public void apiListenerAdded() { + if (plugin == null) { + plugin = proxy.startServer(server); + } + } + + @Override + public void apiEnabled(DynmapCommonAPI api) { + } + } + + // TODO + // public class LoadingCallback implements + // net.minecraftforge.common.ForgeChunkManager.LoadingCallback { + // @Override + // public void ticketsLoaded(List tickets, World world) { + // if (tickets.size() > 0) { + // DynmapPlugin.setBusy(world, tickets.get(0)); + // for (int i = 1; i < tickets.size(); i++) { + // ForgeChunkManager.releaseTicket(tickets.get(i)); + // } + // } + // } + // } + + public DynmapMod() { + instance = this; + + if (FMLEnvironment.dist == Dist.CLIENT) { + proxy = new ClientProxy(); + } else { + proxy = new Proxy(); + } + + ModLoadingContext.get().getActiveContainer().getEventBus().addListener(this::setup); + ModLoadingContext.get().getActiveContainer().getEventBus().addListener(this::init); + + NeoForge.EVENT_BUS.register(this); + + // NeoForge removed DisplayTest, with no current replacement. + // A replacement may arrive in a future networking rework + // ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, + // () -> new IExtensionPoint.DisplayTest(() -> IExtensionPoint.DisplayTest.IGNORESERVERONLY, + // (remote, isServer) -> true)); + + Log.setLogger(new OurLog()); + org.dynmap.modsupport.ModSupportImpl.init(); + } + + public void setup(final FMLCommonSetupEvent event) { + // TOOO + jarfile = ModList.get().getModFileById("dynmap").getFile().getFilePath().toFile(); + + ver = ModList.get().getModContainerById("dynmap").get().getModInfo().getVersion().toString(); + + // // Load configuration file - use suggested (config/WesterosBlocks.cfg) + // Configuration cfg = new Configuration(event.getSuggestedConfigurationFile()); + // try { + // cfg.load(); + + // useforcedchunks = cfg.get("Settings", "UseForcedChunks", + // // true).getBoolean(true); + // } finally { + // cfg.save(); + // } + } + + public void init(FMLLoadCompleteEvent event) { + /* Set up for chunk loading notice from chunk manager */ + // TODO + // if (useforcedchunks) { + // ForgeChunkManager.setForcedChunkLoadingCallback(DynmapMod.instance, new LoadingCallback()); + // } else { + // Log.info("[Dynmap] World loading using forced chunks is disabled"); + // } + } + + private MinecraftServer server; + + @SubscribeEvent + public void onServerStarting(ServerAboutToStartEvent event) { + server = event.getServer(); + if (plugin == null) + plugin = proxy.startServer(server); plugin.onStarting(server.getCommands().getDispatcher()); } - - @SubscribeEvent - public void onServerStarted(ServerStartedEvent event) { - DynmapCommonAPIListener.register(new APICallback()); - plugin.serverStarted(); - } - - @SubscribeEvent - public void serverStopping(ServerStoppingEvent event) - { - proxy.stopServer(plugin); - plugin = null; - } + + @SubscribeEvent + public void onServerStarted(ServerStartedEvent event) { + DynmapCommonAPIListener.register(new APICallback()); + plugin.serverStarted(); + } + + @SubscribeEvent + public void serverStopping(ServerStoppingEvent event) { + proxy.stopServer(plugin); + plugin = null; + } } diff --git a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/DynmapPlugin.java b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/DynmapPlugin.java index 9acca0cbc..4e1594c23 100644 --- a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/DynmapPlugin.java +++ b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/DynmapPlugin.java @@ -1,31 +1,16 @@ package org.dynmap.neoforge_1_20_6; import java.io.File; -import java.io.InputStream; -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.commands.CommandSource; import net.minecraft.commands.CommandSourceStack; @@ -35,87 +20,56 @@ import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; -import net.minecraft.network.Connection; -import net.minecraft.network.chat.Component; -import net.minecraft.network.protocol.game.ClientboundSetSubtitleTextPacket; -import net.minecraft.network.protocol.game.ClientboundSetTitleTextPacket; -import net.minecraft.network.protocol.game.ClientboundSetTitlesAnimationPacket; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; -import net.minecraft.server.network.ServerGamePacketListenerImpl; -import net.minecraft.server.players.GameProfileCache; -import net.minecraft.server.players.UserBanList; import net.minecraft.tags.BlockTags; -import net.minecraft.world.entity.Pose; import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.EmptyBlockGetter; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.LeavesBlock; import net.minecraft.world.level.block.LiquidBlock; -import net.minecraft.world.level.block.SoundType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; -import net.minecraft.world.phys.Vec3; -import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.event.ServerChatEvent; -import net.minecraftforge.event.TickEvent; -import net.minecraftforge.event.entity.player.PlayerEvent.PlayerChangedDimensionEvent; -import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedInEvent; -import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedOutEvent; -import net.minecraftforge.event.entity.player.PlayerEvent.PlayerRespawnEvent; -import net.minecraftforge.event.level.BlockEvent; -import net.minecraftforge.event.level.ChunkDataEvent; -import net.minecraftforge.event.level.ChunkEvent; -import net.minecraftforge.event.level.LevelEvent; -import net.minecraftforge.fml.ModList; -import net.minecraftforge.fml.ModContainer; -import net.minecraftforge.fml.loading.LoadingModList; -import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo; -import net.minecraftforge.fml.loading.moddiscovery.ModInfo; import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.neoforged.bus.api.EventPriority; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.entity.player.PlayerEvent.PlayerChangedDimensionEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent.PlayerLoggedInEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent.PlayerLoggedOutEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent.PlayerRespawnEvent; +import net.neoforged.neoforge.event.level.BlockEvent; +import net.neoforged.neoforge.event.level.ChunkDataEvent; +import net.neoforged.neoforge.event.level.ChunkEvent; +import net.neoforged.neoforge.event.level.LevelEvent; 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.common.DynmapPlayer; import org.dynmap.common.chunk.GenericChunkCache; -import org.dynmap.neoforge_1_20_6.DmapCommand; -import org.dynmap.neoforge_1_20_6.DmarkerCommand; -import org.dynmap.neoforge_1_20_6.DynmapCommand; import org.dynmap.neoforge_1_20_6.permissions.FilePermissions; import org.dynmap.neoforge_1_20_6.permissions.OpPermissions; import org.dynmap.neoforge_1_20_6.permissions.PermissionProvider; import org.dynmap.permissions.PermissionsHandler; import org.dynmap.renderer.DynmapBlockState; 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.RequiredArgumentBuilder; @@ -123,1919 +77,1062 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -import net.minecraftforge.eventbus.api.EventPriority; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraft.world.level.EmptyBlockGetter; +public class DynmapPlugin { + DynmapCore core; + PermissionProvider permissions; + private boolean core_enabled; + public GenericChunkCache sscache; + public PlayerList playerList; + MapManager mapManager; + private static net.minecraft.server.MinecraftServer server; + public static DynmapPlugin plugin; + HashMap sortWeights = new HashMap(); + // Drop world load ticket after 30 seconds + private long worldIdleTimeoutNS = 30 * 1000000000L; + private HashMap worlds = new HashMap(); + private LevelAccessor last_world; + private NeoForgeWorld last_fworld; + private Map players = new HashMap(); + // TODO private ForgeMetrics metrics; + private NeoForgeServer fserver; + private boolean tickregistered = false; + private boolean useSaveFolder = true; + + private static final String[] TRIGGER_DEFAULTS = { "blockupdate", "chunkpopulate", "chunkgenerate" }; + + public static class BlockUpdateRec { + LevelAccessor w; + String wid; + int x, y, z; + } + + ConcurrentLinkedQueue blockupdatequeue = new ConcurrentLinkedQueue(); + + public static DynmapBlockState[] stateByID; + + private Map knownloadedchunks = new HashMap(); + private boolean didInitialKnownChunks = false; + + private void addKnownChunk(NeoForgeWorld fw, ChunkPos pos) { + LongOpenHashSet cset = knownloadedchunks.get(fw.getName()); + if (cset == null) { + cset = new LongOpenHashSet(); + knownloadedchunks.put(fw.getName(), cset); + } + cset.add(pos.toLong()); + } -public class DynmapPlugin -{ - private DynmapCore core; - private PermissionProvider permissions; - private boolean core_enabled; - public GenericChunkCache sscache; - public PlayerList playerList; - private MapManager mapManager; - private static 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 LevelAccessor 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 useSaveFolder = true; - - 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 { - LevelAccessor w; - String wid; - int x, y, z; - } - ConcurrentLinkedQueue blockupdatequeue = new ConcurrentLinkedQueue(); - - public static DynmapBlockState[] stateByID; - - private Map knownloadedchunks = new HashMap(); - private boolean didInitialKnownChunks = false; - private void addKnownChunk(ForgeWorld fw, ChunkPos pos) { - LongOpenHashSet cset = knownloadedchunks.get(fw.getName()); - if (cset == null) { - cset = new LongOpenHashSet(); - knownloadedchunks.put(fw.getName(), cset); - } - cset.add(pos.toLong()); - } - private void removeKnownChunk(ForgeWorld fw, ChunkPos pos) { - LongOpenHashSet cset = knownloadedchunks.get(fw.getName()); - if (cset != null) { - cset.remove(pos.toLong()); - } - } - private boolean checkIfKnownChunk(ForgeWorld fw, ChunkPos pos) { - LongOpenHashSet cset = knownloadedchunks.get(fw.getName()); - if (cset != null) { - return cset.contains(pos.toLong()); - } - return false; - } - - private static Registry reg = null; - - private static Registry getBiomeReg() { - if (reg == null) { - reg = server.registryAccess().registryOrThrow(Registries.BIOME); - } - return reg; - } - - /** - * 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 - - IdMapper bsids = Block.BLOCK_STATE_REGISTRY; - - DynmapBlockState basebs = null; - Block baseb = null; - int baseidx = 0; - - Iterator iter = bsids.iterator(); - DynmapBlockState.Builder bld = new DynmapBlockState.Builder(); + private void removeKnownChunk(NeoForgeWorld fw, ChunkPos pos) { + LongOpenHashSet cset = knownloadedchunks.get(fw.getName()); + if (cset != null) { + cset.remove(pos.toLong()); + } + } + + private boolean checkIfKnownChunk(NeoForgeWorld fw, ChunkPos pos) { + LongOpenHashSet cset = knownloadedchunks.get(fw.getName()); + if (cset != null) { + return cset.contains(pos.toLong()); + } + return false; + } + + private static Registry reg = null; + + private static Registry getBiomeReg() { + if (reg == null) { + reg = server.registryAccess().registryOrThrow(Registries.BIOME); + } + return reg; + } + + /** + * 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 + + IdMapper bsids = Block.BLOCK_STATE_REGISTRY; + + DynmapBlockState basebs = null; + Block baseb = null; + int baseidx = 0; + + Iterator iter = bsids.iterator(); + DynmapBlockState.Builder bld = new DynmapBlockState.Builder(); while (iter.hasNext()) { BlockState bs = iter.next(); int idx = bsids.getId(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 = BuiltInRegistries.BLOCK.getKey(b); - - if (ui == null) { - continue; - } - String bn = ui.getNamespace() + ":" + ui.getPath(); - // Only do defined names, and not "air" - if (!bn.equals(DynmapBlockState.AIR_BLOCK)) { - String statename = ""; - for (net.minecraft.world.level.block.state.properties.Property p : bs.getProperties()) { - if (statename.length() > 0) { - statename += ","; - } - statename += p.getName() + "=" + bs.getValue(p).toString(); - } - int lightAtten = 15; - try { // Workaround for mods with broken block state logic... - lightAtten =bs.isSolidRender(EmptyBlockGetter.INSTANCE, BlockPos.ZERO) ? 15 : (bs.propagatesSkylightDown(EmptyBlockGetter.INSTANCE, BlockPos.ZERO) ? 0 : 1); - } catch (Exception x) { - Log.warning(String.format("Exception while checking lighting data for block state: %s[%s]", bn, statename)); - Log.verboseinfo("Exception: " + x.toString()); - } - //Log.info("statename=" + bn + "[" + statename + "], lightAtten=" + lightAtten); - // Fill in base attributes - bld.setBaseState(basebs).setStateIndex(idx - baseidx).setBlockName(bn).setStateName(statename).setLegacyBlockID(idx).setAttenuatesLight(lightAtten); - if (bs.getSoundType() != null) { bld.setMaterial(bs.getSoundType().toString()); } - if (bs.isSolid()) { bld.setSolid(); } - if (bs.isAir()) { bld.setAir(); } - if (bs.is(BlockTags.LOGS)) { bld.setLog(); } - if (bs.is(BlockTags.LEAVES)) { bld.setLeaves(); } + 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 = BuiltInRegistries.BLOCK.getKey(b); + + if (ui == null) { + continue; + } + String bn = ui.getNamespace() + ":" + ui.getPath(); + // Only do defined names, and not "air" + if (!bn.equals(DynmapBlockState.AIR_BLOCK)) { + String statename = ""; + for (net.minecraft.world.level.block.state.properties.Property p : bs.getProperties()) { + if (statename.length() > 0) { + statename += ","; + } + statename += p.getName() + "=" + bs.getValue(p).toString(); + } + int lightAtten = 15; + try { // Workaround for mods with broken block state logic... + lightAtten = bs.isSolidRender(EmptyBlockGetter.INSTANCE, BlockPos.ZERO) ? 15 + : (bs.propagatesSkylightDown(EmptyBlockGetter.INSTANCE, BlockPos.ZERO) ? 0 : 1); + } catch (Exception x) { + Log.warning(String.format("Exception while checking lighting data for block state: %s[%s]", bn, + statename)); + Log.verboseinfo("Exception: " + x.toString()); + } + // Log.info("statename=" + bn + "[" + statename + "], lightAtten=" + + // lightAtten); + // Fill in base attributes + bld.setBaseState(basebs).setStateIndex(idx - baseidx).setBlockName(bn).setStateName(statename) + .setLegacyBlockID(idx).setAttenuatesLight(lightAtten); + if (bs.getSoundType() != null) { + bld.setMaterial(bs.getSoundType().toString()); + } + if (bs.isSolid()) { + bld.setSolid(); + } + if (bs.isAir()) { + bld.setAir(); + } + if (bs.is(BlockTags.LOGS)) { + bld.setLog(); + } + if (bs.is(BlockTags.LEAVES)) { + bld.setLeaves(); + } if ((!bs.getFluidState().isEmpty()) && !(bs.getBlock() instanceof LiquidBlock)) { bld.setWaterlogged(); } - DynmapBlockState dbs = bld.build(); // Build state - stateByID[idx] = dbs; - if (basebs == null) { basebs = dbs; } - } - } - 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); - //} - - private static Biome[] biomelist = null; - - public static final Biome[] getBiomeList() { - if (biomelist == null) { - biomelist = new Biome[256]; - Iterator iter = getBiomeReg().iterator(); - while (iter.hasNext()) { - Biome b = iter.next(); - int bidx = getBiomeReg().getId(b); - if (bidx >= biomelist.length) { - biomelist = Arrays.copyOf(biomelist, bidx + biomelist.length); - } - biomelist[bidx] = b; - } - } - return biomelist; - } - //public static final NetworkManager getNetworkManager(ServerPlayNetHandler nh) { - // return nh.netManager; - //} - - private ForgePlayer getOrAddPlayer(ServerPlayer p) { - String name = p.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; - ServerPlayer sender; - } - private ConcurrentLinkedQueue msgqueue = new ConcurrentLinkedQueue(); - - public class ChatHandler { - @SubscribeEvent - public void handleChat(ServerChatEvent event) { - String msg = event.getMessage().getString(); - 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().getOps().getUserList(); - for (String op : ops) { - if (op.equalsIgnoreCase(player)) { - return true; - } - } - return (server.isSingleplayer() && player.equalsIgnoreCase(server.getSingleplayerProfile().getName())); - } - - private boolean hasPerm(ServerPlayer psender, String permission) { - PermissionsHandler ph = PermissionsHandler.getHandler(); - if ((psender != null) && (ph != null) && ph.hasPermission(psender.getName().getString(), permission)) { - return true; - } - return permissions.has(psender, permission); - } - - private boolean hasPermNode(ServerPlayer psender, String permission) { - PermissionsHandler ph = PermissionsHandler.getHandler(); - if ((psender != null) && (ph != null) && ph.hasPermissionNode(psender.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) { - GameProfileCache cache = server.getProfileCache(); - Optional val = cache.get(player); - return val.isPresent() ? val.get() : null; - } - - @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++) - { - ServerPlayer p = 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 (ServerPlayer p : players) - { - if (p.getName().getString().equalsIgnoreCase(name)) - { - return getOrAddPlayer(p); - } - } - - return null; - } - @Override - public Set getIPBans() - { - UserBanList bl = server.getPlayerList().getBans(); - Set ips = new HashSet(); - - for (String s : bl.getUserList()) { - 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.getLocalIp(); - if(sn == null) sn = "Unknown Server"; - return sn; - } - @Override - public boolean isPlayerBanned(String pid) - { - UserBanList bl = server.getPlayerList().getBans(); - 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) - { - Component component = Component.literal(msg); - server.getPlayerList().broadcastSystemMessage(component, false); - 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.players.PlayerList scm = server.getPlayerList(); - if (scm == null) return Collections.emptySet(); - UserBanList bl = scm.getBans(); - 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.players.PlayerList scm = server.getPlayerList(); - if (scm == null) return false; - UserBanList bl = scm.getBans(); - 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 (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 (InterruptedException 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().getPlayerCount(); - } - - @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(); - BlockState bs = r.w.getBlockState(new BlockPos(r.x, r.y, r.z)); - int idx = Block.BLOCK_STATE_REGISTRY.getId(bs); - if((idx >= 0) && (!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 generated chunks - if((cur_tick % 20) == 0) { - } + DynmapBlockState dbs = bld.build(); // Build state + stateByID[idx] = dbs; + if (basebs == null) { + basebs = dbs; + } + } + } + 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); + // } + + private static Biome[] biomelist = null; + + public static final Biome[] getBiomeList() { + if (biomelist == null) { + biomelist = new Biome[256]; + Iterator iter = getBiomeReg().iterator(); + while (iter.hasNext()) { + Biome b = iter.next(); + int bidx = getBiomeReg().getId(b); + if (bidx >= biomelist.length) { + biomelist = Arrays.copyOf(biomelist, bidx + biomelist.length); + } + biomelist[bidx] = b; + } + } + return biomelist; + } + + // public static final NetworkManager getNetworkManager(ServerPlayNetHandler nh) + // { + // return nh.netManager; + // } + + NeoForgePlayer getOrAddPlayer(ServerPlayer p) { + String name = p.getName().getString(); + NeoForgePlayer fp = players.get(name); + if (fp != null) { + fp.player = p; + } else { + fp = new NeoForgePlayer(this, p); + players.put(name, fp); + } + return fp; + } + + /** + * 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 boolean isModLoaded(String name) { - boolean loaded = ModList.get().isLoaded(name); - if (loaded) { - modsused.add(name); - } - return loaded; + 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 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.getLocalIp(); - } - @Override - public File getModContainerFile(String name) { - ModFileInfo mfi = LoadingModList.get().getModFileById(name); // Try case sensitive lookup - if (mfi != null) { - try { - File f = mfi.getFile().getFilePath().toFile(); - return f; - } - catch (UnsupportedOperationException ex) { - //TODO Implement proper jar in jar method for fetching data -/* - Log.info("Searching for: " + name); - for (IModInfo e: ModList.get().getMods()) { - Log.info("in: " + e.getModId().toString()); - Log.info("resource: "+ ModList.get().getModFileById(e.getModId()).getFile().findResource(String.valueOf(mfi.getFile().getFilePath()))); - } -*/ - Log.warning("jar in jar method found, skipping: " + ex.getMessage()); - } - } - return null; - } - @Override - public List getModList() { - List mil = LoadingModList.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) modid = "minecraft"; - - Optional mc = ModList.get().getModContainerById(modid); - Object mod = (mc.isPresent()) ? mc.get().getMod() : null; - if (mod != null) { - ClassLoader cl = mod.getClass().getClassLoader(); - if (cl == null) cl = ClassLoader.getSystemClassLoader(); - InputStream is = cl.getResourceAsStream(rname); - if (is != null) { - return is; - } - } - List mcl = LoadingModList.get().getMods(); - for (ModInfo mci : mcl) { - mc = ModList.get().getModContainerById(mci.getModId()); - mod = (mc.isPresent()) ? mc.get().getMod() : null; - if (mod == null) continue; - ClassLoader cl = mod.getClass().getClassLoader(); - if (cl == null) cl = ClassLoader.getSystemClassLoader(); - InputStream is = cl.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 ServerPlayer player; - private final String skinurl; - private final UUID uuid; - - - public ForgePlayer(ServerPlayer p) - { - player = p; - String url = null; - if (player != null) { - uuid = player.getUUID(); - 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.value()), 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.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; - } - Vec3 v = player.position(); - return toLoc(player.serverLevel(), v.x, v.y, v.z); - } - @Override - public String getWorld() - { - if (player == null) - { - return null; - } - - if (player.serverLevel() != null) - { - return DynmapPlugin.this.getWorld((ServerLevel)player.serverLevel()).getName(); - } - - return null; - } - public static final Connection getNetworkManager(ServerGamePacketListenerImpl nh) { - return nh.getConnection(); - } - - @Override - public InetSocketAddress getAddress() - { - if((player != null) && (player instanceof ServerPlayer)) { - ServerGamePacketListenerImpl nsh = ((ServerPlayer)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.getPose() == Pose.CROUCHING; - } - - 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.getArmorValue(); - } - 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.getName().getString()); - } - @Override - public void sendMessage(String msg) - { - Component ichatcomponent = Component.literal(msg); - player.sendSystemMessage(ichatcomponent); - } - @Override - public boolean isInvisible() { - if(player != null) { - return player.isInvisible(); - } - return false; - } - @Override - public boolean isSpectator() { - if(player != null) { - return player.isSpectator(); - } - 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 ServerPlayer) { - ServerPlayer mp = (ServerPlayer) player; - ClientboundSetTitlesAnimationPacket times = new ClientboundSetTitlesAnimationPacket(fadeInTicks, stayTicks, fadeOutTicks); - mp.connection.send(times); - if (title != null) { - ClientboundSetTitleTextPacket titlepkt = new ClientboundSetTitleTextPacket(Component.literal(title)); - mp.connection.send(titlepkt); - } - - if (subtitle != null) { - ClientboundSetSubtitleTextPacket subtitlepkt = new ClientboundSetSubtitleTextPacket(Component.literal(subtitle)); - mp.connection.send(subtitlepkt); - } - } - } - } - /* Handler for generic console command sender */ - public class ForgeCommandSender implements DynmapCommandSender - { - private CommandSourceStack sender; - - protected ForgeCommandSender() { - sender = null; - } - - public ForgeCommandSender(CommandSourceStack send) - { - sender = send; - } - - @Override - public boolean hasPrivilege(String privid) - { - return true; - } - - @Override - public void sendMessage(String msg) - { - if(sender != null) { - Component ichatcomponent = Component.literal(msg); - sender.sendSuccess(() -> ichatcomponent, true); - } - } - - @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) { - ResourceLocation regid = getBiomeReg().getKey(bb); - String id = regid.getPath(); - String rl = regid.toString(); - float tmp = bb.getBaseTemperature(), hum = bb.getModifiedClimateSettings().downfall(); - int watermult = bb.getWaterColor(); - Log.verboseinfo("biome[" + i + "]: hum=" + hum + ", tmp=" + tmp + ", mult=" + Integer.toHexString(watermult)); - - BiomeMap bmap = BiomeMap.NULL; - if (rl != null) { // If resource location, lookup by this - bmap = BiomeMap.byBiomeResourceLocation(rl); - } - else { - bmap = BiomeMap.byBiomeID(i); - } - if (bmap.isDefault() || (bmap == BiomeMap.NULL)) { - bmap = new BiomeMap((rl != null) ? BiomeMap.NO_INDEX : i, id, tmp, hum, rl); - 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)); - } - bmap.setBiomeObject(bb); - } - } - 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.toString(); - } - } - return lst; - } - - public void onEnable() - { - /* Get MC version */ - String mcver = server.getServerVersion(); - /* 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); - 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()); + public void warning(String s, Throwable t) { + log.warn(DM + s, t); + } + } + + public DynmapPlugin(MinecraftServer srv) { + plugin = this; + this.server = srv; + fserver = new NeoForgeServer(this, srv); + } + + public boolean isOp(String player) { + String[] ops = server.getPlayerList().getOps().getUserList(); + for (String op : ops) { + if (op.equalsIgnoreCase(player)) { + return true; + } + } + return (server.isSingleplayer() && player.equalsIgnoreCase(server.getSingleplayerProfile().getName())); + } + + boolean hasPerm(ServerPlayer psender, String permission) { + PermissionsHandler ph = PermissionsHandler.getHandler(); + if ((psender != null) && (ph != null) && ph.hasPermission(psender.getName().getString(), permission)) { + return true; + } + return permissions.has(psender, permission); + } + + boolean hasPermNode(ServerPlayer psender, String permission) { + PermissionsHandler ph = PermissionsHandler.getHandler(); + if ((psender != null) && (ph != null) && ph.hasPermissionNode(psender.getName().getString(), permission)) { + return true; + } + return permissions.hasPermissionNode(psender, permission); + } + + 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; + } + + 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); + } + + public class TexturesPayload { + public long timestamp; + public String profileId; + public String profileName; + public boolean isPublic; + public Map textures; + + } + + public class ProfileTexture { + public String url; + } + + 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) { + ResourceLocation regid = getBiomeReg().getKey(bb); + String id = regid.getPath(); + String rl = regid.toString(); + float tmp = bb.getBaseTemperature(), hum = bb.getModifiedClimateSettings().downfall(); + int watermult = bb.getWaterColor(); + Log.verboseinfo( + "biome[" + i + "]: hum=" + hum + ", tmp=" + tmp + ", mult=" + Integer.toHexString(watermult)); + + BiomeMap bmap = BiomeMap.NULL; + if (rl != null) { // If resource location, lookup by this + bmap = BiomeMap.byBiomeResourceLocation(rl); + } else { + bmap = BiomeMap.byBiomeID(i); + } + if (bmap.isDefault() || (bmap == BiomeMap.NULL)) { + bmap = new BiomeMap((rl != null) ? BiomeMap.NO_INDEX : i, id, tmp, hum, rl); + 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)); + } + bmap.setBiomeObject(bb); + } + } + 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.toString(); + } + } + return lst; + } + + public void onEnable() { + /* Get MC version */ + String mcver = server.getServerVersion(); + /* 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); + 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 GenericChunkCache(core.getSnapShotCacheSize(), core.useSoftRefInSnapShotCache()); - /* Get map manager from core */ - mapManager = core.getMapManager(); - - /* Load saved world definitions */ - loadWorlds(); - - /* Initialized the currently loaded worlds */ - for (ServerLevel world : server.getAllLevels()) { - ForgeWorld w = this.getWorld(world); - } - 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"); - - //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(CommandSourceStack commandSourceStack, String cmd, String[] args) - { - DynmapCommandSender dsender; - ServerPlayer psender; - try { - psender = commandSourceStack.getPlayerOrException(); - } catch (com.mojang.brigadier.exceptions.CommandSyntaxException x) { - psender = null; - } - - if (psender != null) - { - dsender = new ForgePlayer(psender); - } - else - { - dsender = new ForgeCommandSender(commandSourceStack); - } - 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(ServerLevel level, double x, double y, double z) - { - return new DynmapLocation(DynmapPlugin.this.getWorld(level).getName(), x, y, z); - } - - public class PlayerTracker { + } + + 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 + fserver.onStart(core.getMaxTickUseMS() * 1000000); + + /* Register tick handler */ + if (!tickregistered) { + NeoForge.EVENT_BUS.register(fserver); + tickregistered = true; + } + + playerList = core.playerList; + sscache = new GenericChunkCache(core.getSnapShotCacheSize(), core.useSoftRefInSnapShotCache()); + /* Get map manager from core */ + mapManager = core.getMapManager(); + + /* Load saved world definitions */ + loadWorlds(); + + /* Initialized the currently loaded worlds */ + for (ServerLevel world : server.getAllLevels()) { + NeoForgeWorld w = this.getWorld(world); + } + for (NeoForgeWorld 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"); + + // 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(CommandSourceStack commandSourceStack, String cmd, String[] args) { + DynmapCommandSender dsender; + ServerPlayer psender; + try { + psender = commandSourceStack.getPlayerOrException(); + } catch (com.mojang.brigadier.exceptions.CommandSyntaxException x) { + psender = null; + } + + if (psender != null) { + dsender = new NeoForgePlayer(this, psender); + } else { + dsender = new NeoForgeCommandSender(commandSourceStack); + } + 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); + } + } + + public class PlayerTracker { + @SubscribeEvent + public void onPlayerLogin(PlayerLoggedInEvent event) { + if (!core_enabled) + return; + final DynmapPlayer dp = getOrAddPlayer((ServerPlayer) event.getEntity()); + /* 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 onPlayerLogin(PlayerLoggedInEvent event) { - if(!core_enabled) return; - final DynmapPlayer dp = getOrAddPlayer((ServerPlayer)event.getEntity()); - /* 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((ServerPlayer)event.getEntity()); - final String name = event.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 + if (!core_enabled) + return; + final DynmapPlayer dp = getOrAddPlayer((ServerPlayer) event.getEntity()); + final String name = event.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((ServerPlayer)event.getEntity()); // Freshen player object reference + if (!core_enabled) + return; + getOrAddPlayer((ServerPlayer) event.getEntity()); // Freshen player object reference } - @SubscribeEvent + + @SubscribeEvent public void onPlayerRespawn(PlayerRespawnEvent event) { - if(!core_enabled) return; - getOrAddPlayer((ServerPlayer)event.getEntity()); // 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(priority=EventPriority.LOWEST) - public void handleWorldLoad(LevelEvent.Load event) { - if(!core_enabled) return; + if (!core_enabled) + return; + getOrAddPlayer((ServerPlayer) event.getEntity()); // Freshen player object reference + } + } + + private PlayerTracker playerTracker = null; + + private void registerPlayerLoginListener() { + if (playerTracker == null) { + playerTracker = new PlayerTracker(); + NeoForge.EVENT_BUS.register(playerTracker); + } + } + + public class WorldTracker { + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleWorldLoad(LevelEvent.Load event) { + if (!core_enabled) + return; LevelAccessor w = event.getLevel(); - if(!(w instanceof ServerLevel)) return; - final ForgeWorld fw = getWorld((ServerLevel)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(priority=EventPriority.LOWEST) - public void handleWorldUnload(LevelEvent.Unload event) { - if(!core_enabled) return; + if (!(w instanceof ServerLevel)) + return; + final NeoForgeWorld fw = getWorld((ServerLevel) 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(priority = EventPriority.LOWEST) + public void handleWorldUnload(LevelEvent.Unload event) { + if (!core_enabled) + return; LevelAccessor w = event.getLevel(); - if(!(w instanceof ServerLevel)) return; - final ForgeWorld fw = getWorld((ServerLevel)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(priority=EventPriority.LOWEST) - public void handleChunkLoad(ChunkEvent.Load event) { - if(!onchunkgenerate) return; + if (!(w instanceof ServerLevel)) + return; + final NeoForgeWorld fw = getWorld((ServerLevel) 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(priority = EventPriority.LOWEST) + public void handleChunkLoad(ChunkEvent.Load event) { + if (!onchunkgenerate) + return; LevelAccessor w = event.getLevel(); - if(!(w instanceof ServerLevel)) return; + if (!(w instanceof ServerLevel)) + return; ChunkAccess c = event.getChunk(); if ((c != null) && (c.getStatus() == ChunkStatus.FULL) && (c instanceof LevelChunk)) { - ForgeWorld fw = getWorld((ServerLevel)w, false); + NeoForgeWorld fw = getWorld((ServerLevel) w, false); if (fw != null) { addKnownChunk(fw, c.getPos()); } } - } - @SubscribeEvent(priority=EventPriority.LOWEST) - public void handleChunkUnload(ChunkEvent.Unload event) { - if(!onchunkgenerate) return; + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleChunkUnload(ChunkEvent.Unload event) { + if (!onchunkgenerate) + return; LevelAccessor w = event.getLevel(); - if(!(w instanceof ServerLevel)) return; + if (!(w instanceof ServerLevel)) + return; ChunkAccess c = event.getChunk(); if (c != null) { - ForgeWorld fw = getWorld((ServerLevel)w, false); + NeoForgeWorld fw = getWorld((ServerLevel) w, false); ChunkPos cp = c.getPos(); if (fw != null) { if (!checkIfKnownChunk(fw, cp)) { - int ymax = Integer.MIN_VALUE; - int ymin = Integer.MAX_VALUE; - LevelChunkSection[] sections = c.getSections(); - for(int i = 0; i < sections.length; i++) { - if((sections[i] != null) && (sections[i].hasOnlyAir() == false)) { - int sy = c.getSectionYFromSectionIndex(i); - if (sy < ymin) ymin = sy; - if ((sy+16) > ymax) ymax = sy + 16; - } - } - int x = cp.x << 4; - int z = cp.z << 4; - // If not empty AND not initial scan - if (ymax != Integer.MIN_VALUE) { - //Log.info(String.format("chunkkeyerate(unload)(%s,%d,%d,%d,%d,%d,%s)", fw.getName(), x, ymin, z, x+15, ymax, z+15)); - mapManager.touchVolume(fw.getName(), x, ymin, z, x+15, ymax, z+15, "chunkgenerate"); - } + int ymax = Integer.MIN_VALUE; + int ymin = Integer.MAX_VALUE; + LevelChunkSection[] sections = c.getSections(); + for (int i = 0; i < sections.length; i++) { + if ((sections[i] != null) && (sections[i].hasOnlyAir() == false)) { + int sy = c.getSectionYFromSectionIndex(i); + if (sy < ymin) + ymin = sy; + if ((sy + 16) > ymax) + ymax = sy + 16; + } + } + int x = cp.x << 4; + int z = cp.z << 4; + // If not empty AND not initial scan + if (ymax != Integer.MIN_VALUE) { + // Log.info(String.format("chunkkeyerate(unload)(%s,%d,%d,%d,%d,%d,%s)", + // fw.getName(), x, ymin, z, x+15, ymax, z+15)); + mapManager.touchVolume(fw.getName(), x, ymin, z, x + 15, ymax, z + 15, "chunkgenerate"); + } } removeKnownChunk(fw, cp); } } - } - @SubscribeEvent(priority=EventPriority.LOWEST) - public void handleChunkDataSave(ChunkDataEvent.Save event) { - if(!onchunkgenerate) return; + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleChunkDataSave(ChunkDataEvent.Save event) { + if (!onchunkgenerate) + return; LevelAccessor w = event.getLevel(); - if(!(w instanceof ServerLevel)) return; + if (!(w instanceof ServerLevel)) + return; ChunkAccess c = event.getChunk(); if (c != null) { - ForgeWorld fw = getWorld((ServerLevel)w, false); + NeoForgeWorld fw = getWorld((ServerLevel) w, false); ChunkPos cp = c.getPos(); if (fw != null) { if (!checkIfKnownChunk(fw, cp)) { - int ymax = Integer.MIN_VALUE; - int ymin = Integer.MAX_VALUE; - LevelChunkSection[] sections = c.getSections(); - for(int i = 0; i < sections.length; i++) { - if((sections[i] != null) && (sections[i].hasOnlyAir() == false)) { - int sy = c.getSectionYFromSectionIndex(i); - if (sy < ymin) ymin = sy; - if ((sy+16) > ymax) ymax = sy + 16; - } - } - int x = cp.x << 4; - int z = cp.z << 4; - // If not empty AND not initial scan - if (ymax != Integer.MIN_VALUE) { - //Log.info(String.format("chunkkeyerate(save)(%s,%d,%d,%d,%d,%d,%s)", fw.getName(), x, ymin, z, x+15, ymax, z+15)); - mapManager.touchVolume(fw.getName(), x, ymin, z, x+15, ymax, z+15, "chunkgenerate"); - } - // If cooked, add to known - if ((c.getStatus() == ChunkStatus.FULL) && (c instanceof LevelChunk)) { - addKnownChunk(fw, cp); - } + int ymax = Integer.MIN_VALUE; + int ymin = Integer.MAX_VALUE; + LevelChunkSection[] sections = c.getSections(); + for (int i = 0; i < sections.length; i++) { + if ((sections[i] != null) && (sections[i].hasOnlyAir() == false)) { + int sy = c.getSectionYFromSectionIndex(i); + if (sy < ymin) + ymin = sy; + if ((sy + 16) > ymax) + ymax = sy + 16; + } + } + int x = cp.x << 4; + int z = cp.z << 4; + // If not empty AND not initial scan + if (ymax != Integer.MIN_VALUE) { + // Log.info(String.format("chunkkeyerate(save)(%s,%d,%d,%d,%d,%d,%s)", + // fw.getName(), x, ymin, z, x+15, ymax, z+15)); + mapManager.touchVolume(fw.getName(), x, ymin, z, x + 15, ymax, z + 15, "chunkgenerate"); + } + // If cooked, add to known + if ((c.getStatus() == ChunkStatus.FULL) && (c instanceof LevelChunk)) { + addKnownChunk(fw, cp); + } } } } - } - @SubscribeEvent(priority=EventPriority.LOWEST) - public void handleBlockEvent(BlockEvent event) { - if(!core_enabled) return; - if(!onblockchange) return; - BlockUpdateRec r = new BlockUpdateRec(); - r.w = event.getLevel(); - if(!(r.w instanceof ServerLevel)) return; // band-aid to prevent errors in unsupported 'running in client' scenario - ForgeWorld fw = getWorld((ServerLevel)r.w, false); - if (fw == null) return; + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleBlockToolModificationEvent(BlockEvent.BlockToolModificationEvent event) { + handleBlockEvent(event); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleBreakEvent(BlockEvent.BreakEvent event) { + handleBlockEvent(event); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleEntityMultiPlaceEvent(BlockEvent.EntityMultiPlaceEvent event) { + handleBlockEvent(event); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleEntityPlaceEvent(BlockEvent.EntityPlaceEvent event) { + handleBlockEvent(event); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleFarmlandTrampleEvent(BlockEvent.FarmlandTrampleEvent event) { + handleBlockEvent(event); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleFluidPlaceBlockEvent(BlockEvent.FluidPlaceBlockEvent event) { + handleBlockEvent(event); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handleNeighborNotifyEvent(BlockEvent.NeighborNotifyEvent event) { + handleBlockEvent(event); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void handlePortalSpawnEvent(BlockEvent.PortalSpawnEvent event) { + handleBlockEvent(event); + } + + private void handleBlockEvent(BlockEvent event) { + if (!core_enabled) + return; + if (!onblockchange) + return; + BlockUpdateRec r = new BlockUpdateRec(); + r.w = event.getLevel(); + if (!(r.w instanceof ServerLevel)) + return; // band-aid to prevent errors in unsupported 'running in client' scenario + NeoForgeWorld fw = getWorld((ServerLevel) r.w, false); + if (fw == null) + return; r.wid = fw.getName(); BlockPos p = event.getPos(); r.x = p.getX(); r.y = p.getY(); r.z = p.getZ(); - blockupdatequeue.add(r); - } - } - private WorldTracker worldTracker = null; - private boolean onblockchange = false; - private boolean onchunkpopulate = false; - private boolean onchunkgenerate = false; - private boolean onblockchange_with_id = false; - - private void registerEvents() - { - // To trigger rendering. - onblockchange = core.isTrigger("blockupdate"); - onchunkpopulate = core.isTrigger("chunkpopulate"); - onchunkgenerate = core.isTrigger("chunkgenerate"); - onblockchange_with_id = core.isTrigger("blockupdate-with-id"); - if(onblockchange_with_id) - onblockchange = true; - if ((worldTracker == null) && (onblockchange || onchunkpopulate || onchunkgenerate)) { - worldTracker = new WorldTracker(); - MinecraftForge.EVENT_BUS.register(worldTracker); - } - // Prime the known full chunks - if (onchunkgenerate && (server.getAllLevels() != null)) { - for (ServerLevel world : server.getAllLevels()) { - ForgeWorld fw = getWorld(world); - if (fw == null) continue; - Long2ObjectLinkedOpenHashMap chunks = world.getChunkSource().chunkMap.visibleChunkMap; - for (Entry k : chunks.long2ObjectEntrySet()) { - long key = k.getKey().longValue(); - ChunkHolder ch = k.getValue(); - ChunkAccess c = null; - try { - c = ch.getLastAvailable(); - } catch (Exception x) { } - if (c == null) continue; - ChunkStatus cs = c.getStatus(); - ChunkPos pos = ch.getPos(); - if (cs == ChunkStatus.FULL) { // Cooked? - // Add it as known - addKnownChunk(fw, pos); - } - } - } - } - } - - private ForgeWorld getWorldByName(String name) { - return worlds.get(name); - } - - private ForgeWorld getWorld(ServerLevel w) { - return getWorld(w, true); - } - - private ForgeWorld getWorld(ServerLevel 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)) { + blockupdatequeue.add(r); + } + } + + private WorldTracker worldTracker = null; + private boolean onblockchange = false; + private boolean onchunkpopulate = false; + private boolean onchunkgenerate = false; + boolean onblockchange_with_id = false; + + private void registerEvents() { + // To trigger rendering. + onblockchange = core.isTrigger("blockupdate"); + onchunkpopulate = core.isTrigger("chunkpopulate"); + onchunkgenerate = core.isTrigger("chunkgenerate"); + onblockchange_with_id = core.isTrigger("blockupdate-with-id"); + if (onblockchange_with_id) + onblockchange = true; + if ((worldTracker == null) && (onblockchange || onchunkpopulate || onchunkgenerate)) { + worldTracker = new WorldTracker(); + NeoForge.EVENT_BUS.register(worldTracker); + } + // Prime the known full chunks + if (onchunkgenerate && (server.getAllLevels() != null)) { + for (ServerLevel world : server.getAllLevels()) { + NeoForgeWorld fw = getWorld(world); + if (fw == null) + continue; + Long2ObjectLinkedOpenHashMap chunks = world.getChunkSource().chunkMap.visibleChunkMap; + for (Entry k : chunks.long2ObjectEntrySet()) { + long key = k.getKey().longValue(); + ChunkHolder ch = k.getValue(); + ChunkAccess c = null; + try { + c = ch.getLastAvailable(); + } catch (Exception x) { + } + if (c == null) + continue; + ChunkStatus cs = c.getStatus(); + ChunkPos pos = ch.getPos(); + if (cs == ChunkStatus.FULL) { // Cooked? + // Add it as known + addKnownChunk(fw, pos); + } + } + } + } + } + + private NeoForgeWorld getWorldByName(String name) { + return worlds.get(name); + } + + NeoForgeWorld getWorld(ServerLevel w) { + return getWorld(w, true); + } + + private NeoForgeWorld getWorld(ServerLevel w, boolean add_if_not_found) { + if (last_world == w) { + return last_fworld; + } + String wname = NeoForgeWorld.getWorldName(w); + + for (NeoForgeWorld fw : worlds.values()) { + if (fw.getRawName().equals(wname)) { last_world = w; - last_fworld = fw; - if(fw.isLoaded() == false) { - fw.setWorldLoaded(w); - } - fw.updateWorld(w); - 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); - } + last_fworld = fw; + if (fw.isLoaded() == false) { + fw.setWorldLoaded(w); + } + fw.updateWorld(w); + return fw; + } + } + NeoForgeWorld fw = null; + if (add_if_not_found) { + /* Add to list if not found */ + fw = new NeoForgeWorld(w); + worlds.put(fw.getName(), fw); + } last_world = w; last_fworld = fw; - return fw; - } - - 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("miny", fw.minY); - 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("useSaveFolderAsName", useSaveFolder); - cn.put("maxWorldHeight", ForgeWorld.getMaxWorldHeight()); - - cn.save(); - } - private void loadWorlds() { - File f = new File(core.getDataFolder(), "forgeworlds.yml"); - if(f.canRead() == false) { - useSaveFolder = true; - return; - } - ConfigurationNode cn = new ConfigurationNode(f); - cn.load(); - // If defined, use maxWorldHeight - ForgeWorld.setMaxWorldHeight(cn.getInteger("maxWorldHeight", 256)); - - // If setting defined, use it - if (cn.containsKey("useSaveFolderAsName")) { - useSaveFolder = cn.getBoolean("useSaveFolderAsName", useSaveFolder); - } - 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"); - Integer miny = (Integer) world.get("miny"); - 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, (miny != null) ? miny : 0); - 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; - } + return fw; + } + + private void saveWorlds() { + File f = new File(core.getDataFolder(), NeoForgeWorld.SAVED_WORLDS_FILE); + 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("miny", fw.minY); + vals.put("sealevel", fw.sealevel); + vals.put("nether", fw.isNether()); + vals.put("the_end", ((NeoForgeWorld) fw).isTheEnd()); + vals.put("title", fw.getTitle()); + lst.add(vals); + } + cn.put("worlds", lst); + cn.put("useSaveFolderAsName", useSaveFolder); + cn.put("maxWorldHeight", NeoForgeWorld.getMaxWorldHeight()); + + cn.save(); + } + + private void loadWorlds() { + File f = new File(core.getDataFolder(), NeoForgeWorld.SAVED_WORLDS_FILE); + if (f.canRead() == false) { + useSaveFolder = true; + return; + } + ConfigurationNode cn = new ConfigurationNode(f); + cn.load(); + // If defined, use maxWorldHeight + NeoForgeWorld.setMaxWorldHeight(cn.getInteger("maxWorldHeight", 256)); + + // If setting defined, use it + if (cn.containsKey("useSaveFolderAsName")) { + useSaveFolder = cn.getBoolean("useSaveFolderAsName", useSaveFolder); + } + List> lst = cn.getMapList("worlds"); + if (lst == null) { + Log.warning(String.format("Discarding bad %s", NeoForgeWorld.SAVED_WORLDS_FILE)); + return; + } + + for (Map world : lst) { + try { + String name = (String) world.get("name"); + int height = (Integer) world.get("height"); + Integer miny = (Integer) world.get("miny"); + 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) { + NeoForgeWorld fw = new NeoForgeWorld(name, height, sealevel, nether, theend, title, + (miny != null) ? miny : 0); + fw.setWorldUnloaded(); + core.processWorldLoad(fw); + worlds.put(fw.getName(), fw); + } + } catch (Exception x) { + Log.warning(String.format("Unable to load saved worlds from %s", NeoForgeWorld.SAVED_WORLDS_FILE)); + 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, CommandSourceStack commandSourceStack, - String cmdline) { - String[] args = cmdline.split("\\s+"); - plugin.onCommand(commandSourceStack, 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 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, CommandSourceStack commandSourceStack, + String cmdline) { + String[] args = cmdline.split("\\s+"); + plugin.onCommand(commandSourceStack, 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); - } + DynmapCommand(DynmapPlugin p) { + super("dynmap", p); + } } + class DmapCommand extends DynmapCommandHandler { - DmapCommand(DynmapPlugin p) { - super("dmap", p); - } + DmapCommand(DynmapPlugin p) { + super("dmap", p); + } } + class DmarkerCommand extends DynmapCommandHandler { - DmarkerCommand(DynmapPlugin p) { - super("dmarker", p); - } + DmarkerCommand(DynmapPlugin p) { + super("dmarker", p); + } } + class DynmapExpCommand extends DynmapCommandHandler { - DynmapExpCommand(DynmapPlugin p) { - super("dynmapexp", p); - } + DynmapExpCommand(DynmapPlugin p) { + super("dynmapexp", p); + } } - diff --git a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/ForgeWorld.java b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/ForgeWorld.java deleted file mode 100644 index cd511574b..000000000 --- a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/ForgeWorld.java +++ /dev/null @@ -1,249 +0,0 @@ -package org.dynmap.neoforge_1_20_6; -/** - * Forge specific implementation of DynmapWorld - */ -import java.util.List; - -import net.minecraft.world.level.ServerLevelAccessor; -import net.minecraft.world.level.border.WorldBorder; -import net.minecraft.world.level.levelgen.Heightmap; -import net.minecraft.core.BlockPos; -import net.minecraft.resources.ResourceKey; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.LightLayer; - -import org.dynmap.DynmapChunk; -import org.dynmap.DynmapLocation; -import org.dynmap.DynmapWorld; -import org.dynmap.utils.MapChunkCache; -import org.dynmap.utils.Polygon; - -public class ForgeWorld extends DynmapWorld -{ - private ServerLevelAccessor world; - private final boolean skylight; - private final boolean isnether; - private final boolean istheend; - private final String env; - private DynmapLocation spawnloc = new DynmapLocation(); - private static int maxWorldHeight = 320; // Maximum allows world height - - public static int getMaxWorldHeight() { - return maxWorldHeight; - } - public static void setMaxWorldHeight(int h) { - maxWorldHeight = h; - } - - public static String getWorldName(ServerLevelAccessor w) { - ResourceKey rk = w.getLevel().dimension(); - String id = rk.location().getNamespace() + "_" + rk.location().getPath(); - if (id.equals("minecraft_overworld")) { // Overworld? - return w.getLevel().serverLevelData.getLevelName(); - } - else if (id.equals("minecraft_the_end")) { - return "DIM1"; - } - else if (id.equals("minecraft_the_nether")) { - return "DIM-1"; - } - else { - return id; - } - } - - public void updateWorld(ServerLevelAccessor w) { - this.updateWorldHeights(w.getLevel().getHeight(), w.getLevel().dimensionType().minY(), w.getLevel().getSeaLevel()); - } - - public ForgeWorld(ServerLevelAccessor w) - { - this(getWorldName(w), - w.getLevel().getHeight(), - w.getLevel().getSeaLevel(), - w.getLevel().dimension() == Level.NETHER, - w.getLevel().dimension() == Level.END, - getWorldName(w), - w.getLevel().dimensionType().minY()); - setWorldLoaded(w); - } - public ForgeWorld(String name, int height, int sealevel, boolean nether, boolean the_end, String deftitle, int miny) - { - super(name, (height > maxWorldHeight)?maxWorldHeight:height, sealevel, miny); - world = null; - setTitle(deftitle); - isnether = nether; - istheend = the_end; - skylight = !(isnether || istheend); - - if (isnether) - { - env = "nether"; - } - else if (istheend) - { - env = "the_end"; - } - else - { - env = "normal"; - } - //Log.info(getName() + ": skylight=" + skylight + ", height=" + this.worldheight + ", isnether=" + isnether + ", istheend=" + istheend); - } - /* Test if world is nether */ - @Override - public boolean isNether() - { - return isnether; - } - public boolean isTheEnd() - { - return istheend; - } - /* Get world spawn location */ - @Override - public DynmapLocation getSpawnLocation() - { - if(world != null) { - BlockPos p = world.getLevel().getSharedSpawnPos(); - spawnloc.x = p.getX(); - spawnloc.y = p.getY(); - spawnloc.z = p.getZ(); - spawnloc.world = this.getName(); - } - return spawnloc; - } - /* Get world time */ - @Override - public long getTime() - { - if(world != null) - return world.getLevel().getDayTime(); - else - return -1; - } - /* World is storming */ - @Override - public boolean hasStorm() - { - if(world != null) - return world.getLevel().isRaining(); - else - return false; - } - /* World is thundering */ - @Override - public boolean isThundering() - { - if(world != null) - return world.getLevel().isThundering(); - else - return false; - } - /* World is loaded */ - @Override - public boolean isLoaded() - { - return (world != null); - } - /* Set world to unloaded */ - @Override - public void setWorldUnloaded() - { - getSpawnLocation(); - world = null; - } - /* Set world to loaded */ - public void setWorldLoaded(ServerLevelAccessor w) { - world = w; - this.sealevel = w.getLevel().getSeaLevel(); // Read actual current sealevel from world - // Update lighting table - for (int i = 0; i < 16; i++) { - // Algorithm based on LightmapTextureManager.getBrightness() - // We can't call that method because it's client-only. - // This means the code below can stop being correct if Mojang ever - // updates the curve; in that case we should reflect the changes. - float value = (float) i / 15.0f; - float brightness = value / (4.0f - 3.0f * value); - this.setBrightnessTableEntry(i, brightness); - //Log.info(getName() + ": light " + i + " = " + light); - } - } - /* Get light level of block */ - @Override - public int getLightLevel(int x, int y, int z) - { - if(world != null) - return world.getLevel().getLightEngine().getRawBrightness(new BlockPos(x, y, z), 0); - else - return -1; - } - /* Get highest Y coord of given location */ - @Override - public int getHighestBlockYAt(int x, int z) - { - if(world != null) { - return world.getLevel().getChunk(x >> 4, z >> 4).getHeight(Heightmap.Types.MOTION_BLOCKING, x & 15, z & 15); - } - else - return -1; - } - /* Test if sky light level is requestable */ - @Override - public boolean canGetSkyLightLevel() - { - return skylight; - } - /* Return sky light level */ - @Override - public int getSkyLightLevel(int x, int y, int z) - { - if(world != null) { - return world.getLevel().getBrightness(LightLayer.SKY, new BlockPos(x, y, z)); - } - else - return -1; - } - /** - * Get world environment ID (lower case - normal, the_end, nether) - */ - @Override - public String getEnvironment() - { - return env; - } - /** - * Get map chunk cache for world - */ - @Override - public MapChunkCache getChunkCache(List chunks) - { - if (world != null) { - ForgeMapChunkCache c = new ForgeMapChunkCache(DynmapPlugin.plugin.sscache); - c.setChunks(this, chunks); - return c; - } - return null; - } - - public ServerLevel getWorld() - { - return world.getLevel(); - } - @Override - public Polygon getWorldBorder() { - if (world != null) { - WorldBorder wb = world.getWorldBorder(); - if ((wb != null) && (wb.getSize() < 5.9E7)) { - Polygon p = new Polygon(); - p.addVertex(wb.getMinX(), wb.getMinZ()); - p.addVertex(wb.getMinX(), wb.getMaxZ()); - p.addVertex(wb.getMaxX(), wb.getMaxZ()); - p.addVertex(wb.getMaxX(), wb.getMinZ()); - return p; - } - } - return null; - } -} diff --git a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/NBT.java b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/NBT.java index 93675bc8a..5bcb90cf5 100644 --- a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/NBT.java +++ b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/NBT.java @@ -1,123 +1,153 @@ package org.dynmap.neoforge_1_20_6; -import org.dynmap.common.chunk.GenericBitStorage; -import org.dynmap.common.chunk.GenericNBTCompound; -import org.dynmap.common.chunk.GenericNBTList; - import java.util.Set; + import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.util.SimpleBitStorage; +import org.dynmap.common.chunk.GenericBitStorage; +import org.dynmap.common.chunk.GenericNBTCompound; +import org.dynmap.common.chunk.GenericNBTList; + public class NBT { public static class NBTCompound implements GenericNBTCompound { private final CompoundTag obj; + public NBTCompound(CompoundTag t) { this.obj = t; } + @Override public Set getAllKeys() { return obj.getAllKeys(); } + @Override public boolean contains(String s) { return obj.contains(s); } + @Override public boolean contains(String s, int i) { return obj.contains(s, i); } + @Override public byte getByte(String s) { return obj.getByte(s); } + @Override public short getShort(String s) { return obj.getShort(s); } + @Override public int getInt(String s) { return obj.getInt(s); } + @Override public long getLong(String s) { return obj.getLong(s); } + @Override public float getFloat(String s) { return obj.getFloat(s); } + @Override public double getDouble(String s) { return obj.getDouble(s); } + @Override public String getString(String s) { return obj.getString(s); } + @Override public byte[] getByteArray(String s) { return obj.getByteArray(s); } + @Override public int[] getIntArray(String s) { return obj.getIntArray(s); } + @Override public long[] getLongArray(String s) { return obj.getLongArray(s); } + @Override public GenericNBTCompound getCompound(String s) { return new NBTCompound(obj.getCompound(s)); } + @Override public GenericNBTList getList(String s, int i) { return new NBTList(obj.getList(s, i)); } + @Override public boolean getBoolean(String s) { return obj.getBoolean(s); } + @Override public String getAsString(String s) { return obj.get(s).getAsString(); } + @Override public GenericBitStorage makeBitStorage(int bits, int count, long[] data) { return new OurBitStorage(bits, count, data); - } + } + public String toString() { return obj.toString(); } } + public static class NBTList implements GenericNBTList { private final ListTag obj; + public NBTList(ListTag t) { obj = t; } + @Override public int size() { return obj.size(); } + @Override public String getString(int idx) { return obj.getString(idx); } + @Override public GenericNBTCompound getCompound(int idx) { return new NBTCompound(obj.getCompound(idx)); } + public String toString() { return obj.toString(); } } + public static class OurBitStorage implements GenericBitStorage { private final SimpleBitStorage bs; + public OurBitStorage(int bits, int count, long[] data) { bs = new SimpleBitStorage(bits, count, data); } + @Override public int get(int idx) { return bs.get(idx); diff --git a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/NeoForgeCommandSender.java b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/NeoForgeCommandSender.java new file mode 100644 index 000000000..70a358b70 --- /dev/null +++ b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/NeoForgeCommandSender.java @@ -0,0 +1,49 @@ +package org.dynmap.neoforge_1_20_6; + +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.Component; + +import org.dynmap.common.DynmapCommandSender; + +/** + * Handler for generic console command sender + */ +public class NeoForgeCommandSender implements DynmapCommandSender { + private CommandSourceStack sender; + + protected NeoForgeCommandSender() { + sender = null; + } + + public NeoForgeCommandSender(CommandSourceStack send) { + sender = send; + } + + @Override + public boolean hasPrivilege(String privid) { + return true; + } + + @Override + public void sendMessage(String msg) { + if (sender != null) { + Component ichatcomponent = Component.literal(msg); + sender.sendSuccess(() -> ichatcomponent, true); + } + } + + @Override + public boolean isConnected() { + return false; + } + + @Override + public boolean isOp() { + return true; + } + + @Override + public boolean hasPermissionNode(String node) { + return true; + } +} diff --git a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/ForgeMapChunkCache.java b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/NeoForgeMapChunkCache.java similarity index 89% rename from neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/ForgeMapChunkCache.java rename to neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/NeoForgeMapChunkCache.java index 62daf05a6..460530160 100644 --- a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/ForgeMapChunkCache.java +++ b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/NeoForgeMapChunkCache.java @@ -3,8 +3,16 @@ import java.util.List; import java.util.NoSuchElementException; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.BiomeSpecialEffects; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.chunk.storage.ChunkSerializer; + import org.dynmap.DynmapChunk; import org.dynmap.Log; import org.dynmap.common.BiomeMap; @@ -12,25 +20,18 @@ import org.dynmap.common.chunk.GenericChunkCache; import org.dynmap.common.chunk.GenericMapChunkCache; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.server.level.ServerChunkCache; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.ChunkPos; -import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.chunk.storage.ChunkSerializer; -import net.minecraft.world.level.chunk.status.ChunkStatus; - /** * Container for managing chunks - dependent upon using chunk snapshots, since * rendering is off server thread */ -public class ForgeMapChunkCache extends GenericMapChunkCache { +public class NeoForgeMapChunkCache extends GenericMapChunkCache { private ServerLevel w; private ServerChunkCache cps; + /** * Construct empty cache */ - public ForgeMapChunkCache(GenericChunkCache cc) { + public NeoForgeMapChunkCache(GenericChunkCache cc) { super(cc); } @@ -46,6 +47,7 @@ protected GenericChunk getLoadedChunk(DynmapChunk chunk) { } return gc; } + // Load generic chunk from unloaded chunk protected GenericChunk loadChunk(DynmapChunk chunk) { GenericChunk gc = null; @@ -57,7 +59,7 @@ protected GenericChunk loadChunk(DynmapChunk chunk) { return gc; } - public void setChunks(ForgeWorld dw, List chunks) { + public void setChunks(NeoForgeWorld dw, List chunks) { this.w = dw.getWorld(); if (dw.isLoaded()) { /* Check if world's provider is ServerChunkProvider */ @@ -93,6 +95,7 @@ private CompoundTag readChunk(int x, int z) { return null; } } + @Override public int getFoliageColor(BiomeMap bm, int[] colormap, int x, int z) { return bm.getBiomeObject().map(Biome::getSpecialEffects) @@ -103,7 +106,9 @@ public int getFoliageColor(BiomeMap bm, int[] colormap, int x, int z) { @Override public int getGrassColor(BiomeMap bm, int[] colormap, int x, int z) { BiomeSpecialEffects effects = bm.getBiomeObject().map(Biome::getSpecialEffects).orElse(null); - if (effects == null) return colormap[bm.biomeLookup()]; - return effects.getGrassColorModifier().modifyColor(x, z, effects.getGrassColorOverride().orElse(colormap[bm.biomeLookup()])); + if (effects == null) + return colormap[bm.biomeLookup()]; + return effects.getGrassColorModifier().modifyColor(x, z, + effects.getGrassColorOverride().orElse(colormap[bm.biomeLookup()])); } } diff --git a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/NeoForgePlayer.java b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/NeoForgePlayer.java new file mode 100644 index 000000000..429ce8493 --- /dev/null +++ b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/NeoForgePlayer.java @@ -0,0 +1,280 @@ +package org.dynmap.neoforge_1_20_6; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.UUID; + +import net.minecraft.network.Connection; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.game.ClientboundSetSubtitleTextPacket; +import net.minecraft.network.protocol.game.ClientboundSetTitleTextPacket; +import net.minecraft.network.protocol.game.ClientboundSetTitlesAnimationPacket; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.world.entity.Pose; +import net.minecraft.world.phys.Vec3; + +import org.dynmap.DynmapLocation; +import org.dynmap.common.DynmapPlayer; +import org.dynmap.neoforge_1_20_6.DynmapPlugin.TexturesPayload; + +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; + +/** + * Player access abstraction class + */ +public class NeoForgePlayer extends NeoForgeCommandSender implements DynmapPlayer { + private DynmapPlugin plugin; + ServerPlayer player; + private final String skinurl; + private final UUID uuid; + + private static final Gson gson = new GsonBuilder().create(); + + public NeoForgePlayer(DynmapPlugin plugin, ServerPlayer player) { + this.plugin = plugin; + this.player = player; + String url = null; + if (player != null) { + uuid = player.getUUID(); + 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.value()), + 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.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; + } + Vec3 v = player.position(); + return toLoc(player.serverLevel(), v.x, v.y, v.z); + } + + private DynmapLocation toLoc(ServerLevel level, double x, double y, double z) { + return new DynmapLocation(plugin.getWorld(level).getName(), x, y, z); + } + + @Override + public String getWorld() { + if (player == null) { + return null; + } + + if (player.serverLevel() != null) { + return plugin.getWorld((ServerLevel) player.serverLevel()).getName(); + } + + return null; + } + + public static final Connection getNetworkManager(ServerGamePacketListenerImpl nh) { + return nh.getConnection(); + } + + @Override + public InetSocketAddress getAddress() { + if ((player != null) && (player instanceof ServerPlayer)) { + ServerGamePacketListenerImpl nsh = ((ServerPlayer) 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.getPose() == Pose.CROUCHING; + } + + 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.getArmorValue(); + } 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 plugin.hasPerm(player, privid); + return false; + } + + @Override + public boolean isOp() { + return plugin.isOp(player.getName().getString()); + } + + @Override + public void sendMessage(String msg) { + Component ichatcomponent = Component.literal(msg); + player.sendSystemMessage(ichatcomponent); + } + + @Override + public boolean isInvisible() { + if (player != null) { + return player.isInvisible(); + } + return false; + } + + @Override + public boolean isSpectator() { + if (player != null) { + return player.isSpectator(); + } + return false; + } + + @Override + public int getSortWeight() { + Integer wt = plugin.sortWeights.get(getName()); + if (wt != null) + return wt; + return 0; + } + + @Override + public void setSortWeight(int wt) { + if (wt == 0) { + plugin.sortWeights.remove(getName()); + } else { + plugin.sortWeights.put(getName(), wt); + } + } + + @Override + public boolean hasPermissionNode(String node) { + if (player != null) + return plugin.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 ServerPlayer) { + ServerPlayer mp = (ServerPlayer) player; + ClientboundSetTitlesAnimationPacket times = new ClientboundSetTitlesAnimationPacket(fadeInTicks, + stayTicks, fadeOutTicks); + mp.connection.send(times); + if (title != null) { + ClientboundSetTitleTextPacket titlepkt = new ClientboundSetTitleTextPacket( + Component.literal(title)); + mp.connection.send(titlepkt); + } + + if (subtitle != null) { + ClientboundSetSubtitleTextPacket subtitlepkt = new ClientboundSetSubtitleTextPacket( + Component.literal(subtitle)); + mp.connection.send(subtitlepkt); + } + } + } +} diff --git a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/NeoForgeServer.java b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/NeoForgeServer.java new file mode 100644 index 000000000..9030a2e5d --- /dev/null +++ b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/NeoForgeServer.java @@ -0,0 +1,702 @@ +package org.dynmap.neoforge_1_20_6; + +import java.io.File; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.PriorityQueue; +import java.util.Set; +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.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.players.GameProfileCache; +import net.minecraft.server.players.UserBanList; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.ModContainer; +import net.neoforged.fml.ModList; +import net.neoforged.fml.loading.LoadingModList; +import net.neoforged.fml.loading.moddiscovery.ModFileInfo; +import net.neoforged.fml.loading.moddiscovery.ModInfo; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.ServerChatEvent; +import net.neoforged.neoforge.event.tick.ServerTickEvent; + +import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.dynmap.DynmapChunk; +import org.dynmap.DynmapCommonAPIListener; +import org.dynmap.DynmapWorld; +import org.dynmap.Log; +import org.dynmap.common.BiomeMap; +import org.dynmap.common.DynmapListenerManager.EventType; +import org.dynmap.common.DynmapPlayer; +import org.dynmap.common.DynmapServerInterface; +import org.dynmap.neoforge_1_20_6.DynmapPlugin.BlockUpdateRec; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.VisibilityLimit; + +import com.mojang.authlib.GameProfile; + +/** + * Server access abstraction class + */ +public class NeoForgeServer extends DynmapServerInterface { + private DynmapPlugin plugin; + private MinecraftServer server; + /* Server thread scheduler */ + private Object schedlock = new Object(); + private long cur_tick; + private long next_id; + private long cur_tick_starttime; + PriorityQueue runqueue = new PriorityQueue(); + private ChatHandler chathandler; + private ConcurrentLinkedQueue msgqueue = new ConcurrentLinkedQueue(); + private HashSet modsused = new HashSet(); + // TPS calculator + private double tps; + long lasttick; + long avgticklen; + // Per tick limit, in nsec + private long perTickLimit = (50000000); // 50 ms + + private static final Pattern patternControlCode = Pattern.compile("(?i)\\u00A7[0-9A-FK-OR]"); + + public NeoForgeServer(DynmapPlugin plugin, MinecraftServer server) { + this.plugin = plugin; + this.server = server; + } + + void onStart(long perTickLimit) { + this.perTickLimit = perTickLimit; + // Prep TPS + lasttick = System.nanoTime(); + tps = 20.0; + } + + private GameProfile getProfileByName(String player) { + GameProfileCache cache = server.getProfileCache(); + Optional val = cache.get(player); + return val.isPresent() ? val.get() : null; + } + + @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++) { + ServerPlayer p = playlist.get(i); + dplay[i] = plugin.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 (ServerPlayer p : players) { + if (p.getName().getString().equalsIgnoreCase(name)) { + return plugin.getOrAddPlayer(p); + } + } + + return null; + } + + @Override + public Set getIPBans() { + UserBanList bl = server.getPlayerList().getBans(); + Set ips = new HashSet(); + + for (String s : bl.getUserList()) { + 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.getLocalIp(); + if (sn == null) + sn = "Unknown Server"; + return sn; + } + + @Override + public boolean isPlayerBanned(String pid) { + UserBanList bl = server.getPlayerList().getBans(); + 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(); + NeoForge.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) { + Component component = Component.literal(msg); + server.getPlayerList().broadcastSystemMessage(component, false); + 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 (plugin.sscache != null) + return plugin.sscache.getHitRate(); + return 0.0; + } + + @Override + public void resetCacheStats() { + if (plugin.sscache != null) + plugin.sscache.resetStats(); + } + + @Override + public DynmapWorld getWorldByName(String wname) { + return 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.players.PlayerList scm = server.getPlayerList(); + if (scm == null) + return Collections.emptySet(); + UserBanList bl = scm.getBans(); + if (bl == null) + return Collections.emptySet(); + if (bl.isBanned(getProfileByName(player))) { + return Collections.emptySet(); + } + Set rslt = plugin.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.players.PlayerList scm = server.getPlayerList(); + if (scm == null) + return false; + UserBanList bl = scm.getBans(); + if (bl == null) + return false; + if (bl.isBanned(getProfileByName(player))) { + return false; + } + return plugin.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) { + NeoForgeMapChunkCache c = (NeoForgeMapChunkCache) 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 (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 NeoForgeMapChunkCache cc = c; + Future f = this.callSyncMethod(new Callable() { + public Boolean call() throws Exception { + // Update busy state on world + NeoForgeWorld fw = (NeoForgeWorld) cc.getWorld(); + // TODO + // setBusy(fw.getWorld()); + cc.getLoadedChunks(); + return true; + } + }, 0); + try { + f.get(); + } catch (CancellationException cx) { + return null; + } catch (InterruptedException 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().getPlayerCount(); + } + + @SubscribeEvent + public void tickEvent(ServerTickEvent.Post event) { + 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 (plugin.core != null) { + plugin.core.serverTick(tps); + } + + boolean done = false; + TaskRecord tr = null; + + while (!plugin.blockupdatequeue.isEmpty()) { + BlockUpdateRec r = plugin.blockupdatequeue.remove(); + BlockState bs = r.w.getBlockState(new BlockPos(r.x, r.y, r.z)); + int idx = Block.BLOCK_STATE_REGISTRY.getId(bs); + if ((idx >= 0) && (!org.dynmap.hdmap.HDBlockModels.isChangeIgnoredBlock(DynmapPlugin.stateByID[idx]))) { + if (plugin.onblockchange_with_id) + plugin.mapManager.touch(r.wid, r.x, r.y, r.z, "blockchange[" + idx + "]"); + else + plugin.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 = plugin.getOrAddPlayer(cm.sender); + else + dp = new NeoForgePlayer(plugin, null); + + plugin.core.listenerManager.processChatEvent(EventType.PLAYER_CHAT, dp, cm.message); + } + // Check for generated chunks + if ((cur_tick % 20) == 0) { + } + } + + @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.getLocalIp(); + } + + @Override + public File getModContainerFile(String name) { + ModFileInfo mfi = LoadingModList.get().getModFileById(name); // Try case sensitive lookup + if (mfi != null) { + try { + File f = mfi.getFile().getFilePath().toFile(); + return f; + } catch (UnsupportedOperationException ex) { + // TODO Implement proper jar in jar method for fetching data + /* + Log.info("Searching for: " + name); + for (IModInfo e : ModList.get().getMods()) { + Log.info("in: " + e.getModId().toString()); + Log.info("resource: " + ModList.get().getModFileById(e.getModId()).getFile() + .findResource(String.valueOf(mfi.getFile().getFilePath()))); + } + */ + Log.warning("jar in jar method found, skipping: " + ex.getMessage()); + } + } + return null; + } + + @Override + public List getModList() { + List mil = LoadingModList.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) { + // NeoForge removed ModContainer#getMod with no replacement + /* if (modid == null) + modid = "minecraft"; + + Optional mc = ModList.get().getModContainerById(modid); + Object mod = (mc.isPresent()) ? mc.get().getMod() : null; + if (mod != null) { + ClassLoader cl = mod.getClass().getClassLoader(); + if (cl == null) + cl = ClassLoader.getSystemClassLoader(); + InputStream is = cl.getResourceAsStream(rname); + if (is != null) { + return is; + } + } + List mcl = LoadingModList.get().getMods(); + for (ModInfo mci : mcl) { + mc = ModList.get().getModContainerById(mci.getModId()); + mod = (mc.isPresent()) ? mc.get().getMod() : null; + if (mod == null) + continue; + ClassLoader cl = mod.getClass().getClassLoader(); + if (cl == null) + cl = ClassLoader.getSystemClassLoader(); + InputStream is = cl.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 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; + ServerPlayer sender; + } + + public class ChatHandler { + @SubscribeEvent + public void handleChat(ServerChatEvent event) { + String msg = event.getMessage().getString(); + if (!msg.startsWith("/")) { + ChatMessage cm = new ChatMessage(); + cm.message = msg; + cm.sender = event.getPlayer(); + msgqueue.add(cm); + } + } + } + +} diff --git a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/NeoForgeWorld.java b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/NeoForgeWorld.java new file mode 100644 index 000000000..6fca1d585 --- /dev/null +++ b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/NeoForgeWorld.java @@ -0,0 +1,244 @@ +package org.dynmap.neoforge_1_20_6; + +/** + * NeoForge specific implementation of DynmapWorld + */ +import java.util.List; + +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LightLayer; +import net.minecraft.world.level.ServerLevelAccessor; +import net.minecraft.world.level.border.WorldBorder; +import net.minecraft.world.level.levelgen.Heightmap; + +import org.dynmap.DynmapChunk; +import org.dynmap.DynmapLocation; +import org.dynmap.DynmapWorld; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.Polygon; + +public class NeoForgeWorld extends DynmapWorld { + public static final String SAVED_WORLDS_FILE = "neoforgeworlds.yml"; + private ServerLevelAccessor world; + private final boolean skylight; + private final boolean isnether; + private final boolean istheend; + private final String env; + private DynmapLocation spawnloc = new DynmapLocation(); + private static int maxWorldHeight = 320; // Maximum allows world height + + public static int getMaxWorldHeight() { + return maxWorldHeight; + } + + public static void setMaxWorldHeight(int h) { + maxWorldHeight = h; + } + + public static String getWorldName(ServerLevelAccessor w) { + ResourceKey rk = w.getLevel().dimension(); + String id = rk.location().getNamespace() + "_" + rk.location().getPath(); + if (id.equals("minecraft_overworld")) { // Overworld? + return w.getLevel().serverLevelData.getLevelName(); + } else if (id.equals("minecraft_the_end")) { + return "DIM1"; + } else if (id.equals("minecraft_the_nether")) { + return "DIM-1"; + } else { + return id; + } + } + + public void updateWorld(ServerLevelAccessor w) { + this.updateWorldHeights(w.getLevel().getHeight(), w.getLevel().dimensionType().minY(), + w.getLevel().getSeaLevel()); + } + + public NeoForgeWorld(ServerLevelAccessor w) { + this(getWorldName(w), + w.getLevel().getHeight(), + w.getLevel().getSeaLevel(), + w.getLevel().dimension() == Level.NETHER, + w.getLevel().dimension() == Level.END, + getWorldName(w), + w.getLevel().dimensionType().minY()); + setWorldLoaded(w); + } + + public NeoForgeWorld(String name, int height, int sealevel, boolean nether, boolean the_end, String deftitle, + int miny) { + super(name, (height > maxWorldHeight) ? maxWorldHeight : height, sealevel, miny); + world = null; + setTitle(deftitle); + isnether = nether; + istheend = the_end; + skylight = !(isnether || istheend); + + if (isnether) { + env = "nether"; + } else if (istheend) { + env = "the_end"; + } else { + env = "normal"; + } + // Log.info(getName() + ": skylight=" + skylight + ", height=" + + // this.worldheight + ", isnether=" + isnether + ", istheend=" + istheend); + } + + /* Test if world is nether */ + @Override + public boolean isNether() { + return isnether; + } + + public boolean isTheEnd() { + return istheend; + } + + /* Get world spawn location */ + @Override + public DynmapLocation getSpawnLocation() { + if (world != null) { + BlockPos p = world.getLevel().getSharedSpawnPos(); + spawnloc.x = p.getX(); + spawnloc.y = p.getY(); + spawnloc.z = p.getZ(); + spawnloc.world = this.getName(); + } + return spawnloc; + } + + /* Get world time */ + @Override + public long getTime() { + if (world != null) + return world.getLevel().getDayTime(); + else + return -1; + } + + /* World is storming */ + @Override + public boolean hasStorm() { + if (world != null) + return world.getLevel().isRaining(); + else + return false; + } + + /* World is thundering */ + @Override + public boolean isThundering() { + if (world != null) + return world.getLevel().isThundering(); + else + return false; + } + + /* World is loaded */ + @Override + public boolean isLoaded() { + return (world != null); + } + + /* Set world to unloaded */ + @Override + public void setWorldUnloaded() { + getSpawnLocation(); + world = null; + } + + /* Set world to loaded */ + public void setWorldLoaded(ServerLevelAccessor w) { + world = w; + this.sealevel = w.getLevel().getSeaLevel(); // Read actual current sealevel from world + // Update lighting table + for (int i = 0; i < 16; i++) { + // Algorithm based on LightmapTextureManager.getBrightness() + // We can't call that method because it's client-only. + // This means the code below can stop being correct if Mojang ever + // updates the curve; in that case we should reflect the changes. + float value = (float) i / 15.0f; + float brightness = value / (4.0f - 3.0f * value); + this.setBrightnessTableEntry(i, brightness); + // Log.info(getName() + ": light " + i + " = " + light); + } + } + + /* Get light level of block */ + @Override + public int getLightLevel(int x, int y, int z) { + if (world != null) + return world.getLevel().getLightEngine().getRawBrightness(new BlockPos(x, y, z), 0); + else + return -1; + } + + /* Get highest Y coord of given location */ + @Override + public int getHighestBlockYAt(int x, int z) { + if (world != null) { + return world.getLevel().getChunk(x >> 4, z >> 4).getHeight(Heightmap.Types.MOTION_BLOCKING, x & 15, z & 15); + } else + return -1; + } + + /* Test if sky light level is requestable */ + @Override + public boolean canGetSkyLightLevel() { + return skylight; + } + + /* Return sky light level */ + @Override + public int getSkyLightLevel(int x, int y, int z) { + if (world != null) { + return world.getLevel().getBrightness(LightLayer.SKY, new BlockPos(x, y, z)); + } else + return -1; + } + + /** + * Get world environment ID (lower case - normal, the_end, nether) + */ + @Override + public String getEnvironment() { + return env; + } + + /** + * Get map chunk cache for world + */ + @Override + public MapChunkCache getChunkCache(List chunks) { + if (world != null) { + NeoForgeMapChunkCache c = new NeoForgeMapChunkCache(DynmapPlugin.plugin.sscache); + c.setChunks(this, chunks); + return c; + } + return null; + } + + public ServerLevel getWorld() { + return world.getLevel(); + } + + @Override + public Polygon getWorldBorder() { + if (world != null) { + WorldBorder wb = world.getWorldBorder(); + if ((wb != null) && (wb.getSize() < 5.9E7)) { + Polygon p = new Polygon(); + p.addVertex(wb.getMinX(), wb.getMinZ()); + p.addVertex(wb.getMinX(), wb.getMaxZ()); + p.addVertex(wb.getMaxX(), wb.getMaxZ()); + p.addVertex(wb.getMaxX(), wb.getMinZ()); + return p; + } + } + return null; + } +} diff --git a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/Proxy.java b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/Proxy.java index 85a01235a..aa07972b4 100644 --- a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/Proxy.java +++ b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/Proxy.java @@ -5,19 +5,19 @@ /** * Server side proxy - methods for creating and cleaning up plugin */ -public class Proxy -{ - public Proxy() - { - } +public class Proxy { + public Proxy() { + } + public DynmapPlugin startServer(MinecraftServer srv) { - DynmapPlugin plugin = DynmapPlugin.plugin; - if (plugin == null) { - plugin = new DynmapPlugin(srv); - plugin.onEnable(); - } + DynmapPlugin plugin = DynmapPlugin.plugin; + if (plugin == null) { + plugin = new DynmapPlugin(srv); + plugin.onEnable(); + } return plugin; } + public void stopServer(DynmapPlugin plugin) { plugin.onDisable(); } diff --git a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/VersionCheck.java b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/VersionCheck.java index 3840bdb8b..d3b3519d5 100644 --- a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/VersionCheck.java +++ b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/VersionCheck.java @@ -9,89 +9,92 @@ import org.dynmap.Log; public class VersionCheck { - private static final String VERSION_URL = "http://mikeprimm.com/dynmap/releases.php"; - public static void runCheck(final DynmapCore core) { - new Thread(new Runnable() { - public void run() { - doCheck(core); - } - }).start(); - } - - private static int getReleaseVersion(String s) { - int index = s.lastIndexOf('-'); - if(index < 0) - index = s.lastIndexOf('.'); - if(index >= 0) - s = s.substring(0, index); - String[] split = s.split("\\."); - int v = 0; - try { - for(int i = 0; (i < split.length) && (i < 3); i++) { - v += Integer.parseInt(split[i]) << (8 * (2 - i)); - } - } catch (NumberFormatException nfx) {} - return v; - } - - private static int getBuildNumber(String s) { - int index = s.lastIndexOf('-'); - if(index < 0) - index = s.lastIndexOf('.'); - if(index >= 0) - s = s.substring(index+1); - try { - return Integer.parseInt(s); - } catch (NumberFormatException nfx) { - return 99999999; - } - } - - private static void doCheck(DynmapCore core) { - String pluginver = core.getDynmapPluginVersion(); - String platform = core.getDynmapPluginPlatform(); - String platver = core.getDynmapPluginPlatformVersion(); - if((pluginver == null) || (platform == null) || (platver == null)) - return; - HttpURLConnection conn = null; - String loc = VERSION_URL; - int cur_ver = getReleaseVersion(pluginver); - int cur_bn = getBuildNumber(pluginver); - try { - while((loc != null) && (!loc.isEmpty())) { - URL url = new URL(loc); - conn = (HttpURLConnection) url.openConnection(); - conn.setRequestProperty("User-Agent", "Dynmap (" + platform + "/" + platver + "/" + pluginver); - conn.connect(); - loc = conn.getHeaderField("Location"); - } - BufferedReader rdr = new BufferedReader(new InputStreamReader(conn.getInputStream())); - String line = null; - while((line = rdr.readLine()) != null) { - String[] split = line.split(":"); - if(split.length < 4) continue; - /* If our platform and version, or wildcard platform version */ - if(split[0].equals(platform) && (split[1].equals("*") || split[1].equals(platver))) { - int recommended_ver = getReleaseVersion(split[2]); - int recommended_bn = getBuildNumber(split[2]); - if((recommended_ver > cur_ver) || ((recommended_ver == cur_ver) && (recommended_bn > cur_bn))) { /* Newer recommended build */ - Log.info("Version obsolete: new recommended version " + split[2] + " is available."); - } - else if(cur_ver > recommended_ver) { /* Running dev or prerelease? */ - int prerel_ver = getReleaseVersion(split[3]); - int prerel_bn = getBuildNumber(split[3]); - if((prerel_ver > cur_ver) || ((prerel_ver == cur_ver) && (prerel_bn > cur_bn))) { - Log.info("Version obsolete: new prerelease version " + split[3] + " is available."); - } - } - } - } - } catch (Exception x) { - Log.info("Error checking for latest version"); - } finally { - if(conn != null) { - conn.disconnect(); - } - } - } + private static final String VERSION_URL = "http://mikeprimm.com/dynmap/releases.php"; + + public static void runCheck(final DynmapCore core) { + new Thread(new Runnable() { + public void run() { + doCheck(core); + } + }).start(); + } + + private static int getReleaseVersion(String s) { + int index = s.lastIndexOf('-'); + if (index < 0) + index = s.lastIndexOf('.'); + if (index >= 0) + s = s.substring(0, index); + String[] split = s.split("\\."); + int v = 0; + try { + for (int i = 0; (i < split.length) && (i < 3); i++) { + v += Integer.parseInt(split[i]) << (8 * (2 - i)); + } + } catch (NumberFormatException nfx) { + } + return v; + } + + private static int getBuildNumber(String s) { + int index = s.lastIndexOf('-'); + if (index < 0) + index = s.lastIndexOf('.'); + if (index >= 0) + s = s.substring(index + 1); + try { + return Integer.parseInt(s); + } catch (NumberFormatException nfx) { + return 99999999; + } + } + + private static void doCheck(DynmapCore core) { + String pluginver = core.getDynmapPluginVersion(); + String platform = core.getDynmapPluginPlatform(); + String platver = core.getDynmapPluginPlatformVersion(); + if ((pluginver == null) || (platform == null) || (platver == null)) + return; + HttpURLConnection conn = null; + String loc = VERSION_URL; + int cur_ver = getReleaseVersion(pluginver); + int cur_bn = getBuildNumber(pluginver); + try { + while ((loc != null) && (!loc.isEmpty())) { + URL url = new URL(loc); + conn = (HttpURLConnection) url.openConnection(); + conn.setRequestProperty("User-Agent", "Dynmap (" + platform + "/" + platver + "/" + pluginver); + conn.connect(); + loc = conn.getHeaderField("Location"); + } + BufferedReader rdr = new BufferedReader(new InputStreamReader(conn.getInputStream())); + String line = null; + while ((line = rdr.readLine()) != null) { + String[] split = line.split(":"); + if (split.length < 4) + continue; + /* If our platform and version, or wildcard platform version */ + if (split[0].equals(platform) && (split[1].equals("*") || split[1].equals(platver))) { + int recommended_ver = getReleaseVersion(split[2]); + int recommended_bn = getBuildNumber(split[2]); + if ((recommended_ver > cur_ver) || ((recommended_ver == cur_ver) + && (recommended_bn > cur_bn))) { /* Newer recommended build */ + Log.info("Version obsolete: new recommended version " + split[2] + " is available."); + } else if (cur_ver > recommended_ver) { /* Running dev or prerelease? */ + int prerel_ver = getReleaseVersion(split[3]); + int prerel_bn = getBuildNumber(split[3]); + if ((prerel_ver > cur_ver) || ((prerel_ver == cur_ver) && (prerel_bn > cur_bn))) { + Log.info("Version obsolete: new prerelease version " + split[3] + " is available."); + } + } + } + } + } catch (Exception x) { + Log.info("Error checking for latest version"); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + } } diff --git a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/permissions/FilePermissions.java b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/permissions/FilePermissions.java index e02a554b5..96f4eb29e 100644 --- a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/permissions/FilePermissions.java +++ b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/permissions/FilePermissions.java @@ -6,98 +6,99 @@ import java.util.List; import java.util.Set; +import net.minecraft.server.level.ServerPlayer; + import org.dynmap.ConfigurationNode; import org.dynmap.Log; import org.dynmap.neoforge_1_20_6.DynmapPlugin; -import net.minecraft.server.level.ServerPlayer; - public class FilePermissions implements PermissionProvider { - private HashMap> perms; - private Set defperms; - - public static FilePermissions create() { - File f = new File("dynmap/permissions.yml"); - if(!f.exists()) - return null; - ConfigurationNode cfg = new ConfigurationNode(f); - cfg.load(); - - Log.info("Using permissions.yml for access control"); - - return new FilePermissions(cfg); - } - - private FilePermissions(ConfigurationNode cfg) { - perms = new HashMap>(); - for(String k : cfg.keySet()) { - List p = cfg.getStrings(k, null); - if(p != null) { - k = k.toLowerCase(); - HashSet pset = new HashSet(); - for(String perm : p) { - pset.add(perm.toLowerCase()); - } - perms.put(k, pset); - if(k.equals("defaultuser")) { - defperms = pset; - } - } - } - } + private HashMap> perms; + private Set defperms; + + public static FilePermissions create() { + File f = new File("dynmap/permissions.yml"); + if (!f.exists()) + return null; + ConfigurationNode cfg = new ConfigurationNode(f); + cfg.load(); + + Log.info("Using permissions.yml for access control"); + + return new FilePermissions(cfg); + } + + private FilePermissions(ConfigurationNode cfg) { + perms = new HashMap>(); + for (String k : cfg.keySet()) { + List p = cfg.getStrings(k, null); + if (p != null) { + k = k.toLowerCase(); + HashSet pset = new HashSet(); + for (String perm : p) { + pset.add(perm.toLowerCase()); + } + perms.put(k, pset); + if (k.equals("defaultuser")) { + defperms = pset; + } + } + } + } + + private boolean hasPerm(String player, String perm) { + Set ps = perms.get(player); + if ((ps != null) && (ps.contains(perm))) { + return true; + } + if (defperms.contains(perm)) { + return true; + } + return false; + } + + @Override + public Set hasOfflinePermissions(String player, Set perms) { + player = player.toLowerCase(); + HashSet rslt = new HashSet(); + if (DynmapPlugin.plugin.isOp(player)) { + rslt.addAll(perms); + } else { + for (String p : perms) { + if (hasPerm(player, p)) { + rslt.add(p); + } + } + } + return rslt; + } + + @Override + public boolean hasOfflinePermission(String player, String perm) { + player = player.toLowerCase(); + if (DynmapPlugin.plugin.isOp(player)) { + return true; + } else { + return hasPerm(player, perm); + } + } - private boolean hasPerm(String player, String perm) { - Set ps = perms.get(player); - if((ps != null) && (ps.contains(perm))) { - return true; - } - if(defperms.contains(perm)) { - return true; - } - return false; - } - @Override - public Set hasOfflinePermissions(String player, Set perms) { - player = player.toLowerCase(); - HashSet rslt = new HashSet(); - if(DynmapPlugin.plugin.isOp(player)) { - rslt.addAll(perms); - } - else { - for(String p : perms) { - if(hasPerm(player, p)) { - rslt.add(p); - } - } - } - return rslt; - } - @Override - public boolean hasOfflinePermission(String player, String perm) { - player = player.toLowerCase(); - if(DynmapPlugin.plugin.isOp(player)) { - return true; - } - else { - return hasPerm(player, perm); - } - } + @Override + public boolean has(ServerPlayer psender, String permission) { + if (psender != null) { + String n = psender.getName().getString().toLowerCase(); + return hasPerm(n, permission); + } + return true; + } - @Override - public boolean has(ServerPlayer psender, String permission) { - if(psender != null) { - String n = psender.getName().getString().toLowerCase(); - return hasPerm(n, permission); - } - return true; - } - @Override - public boolean hasPermissionNode(ServerPlayer psender, String permission) { - if(psender != null) { - String player = psender.getName().getString().toLowerCase(); - return DynmapPlugin.plugin.isOp(player); - } - return false; - } + @Override + public boolean hasPermissionNode(ServerPlayer psender, String permission) { + if (psender != null) { + String player = psender.getName().getString().toLowerCase(); + return DynmapPlugin.plugin.isOp(player); + } + return false; + } } diff --git a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/permissions/OpPermissions.java b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/permissions/OpPermissions.java index 8d7e74cd6..f54175beb 100644 --- a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/permissions/OpPermissions.java +++ b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/permissions/OpPermissions.java @@ -3,49 +3,51 @@ import java.util.HashSet; import java.util.Set; +import net.minecraft.server.level.ServerPlayer; + import org.dynmap.Log; import org.dynmap.neoforge_1_20_6.DynmapPlugin; -import net.minecraft.server.level.ServerPlayer; - public class OpPermissions implements PermissionProvider { - public HashSet usrCommands = new HashSet(); - - public OpPermissions(String[] usrCommands) { - for (String usrCommand : usrCommands) { - this.usrCommands.add(usrCommand); - } - Log.info("Using ops.txt for access control"); - } - - @Override - public Set hasOfflinePermissions(String player, Set perms) { - HashSet rslt = new HashSet(); - if(DynmapPlugin.plugin.isOp(player)) { - rslt.addAll(perms); - } - return rslt; - } - @Override - public boolean hasOfflinePermission(String player, String perm) { - return DynmapPlugin.plugin.isOp(player); - } - - @Override - public boolean has(ServerPlayer psender, String permission) { - if(psender != null) { - if(usrCommands.contains(permission)) { - return true; - } - return DynmapPlugin.plugin.isOp(psender.getName().getString()); - } - return true; - } - @Override - public boolean hasPermissionNode(ServerPlayer psender, String permission) { - if(psender != null) { - return DynmapPlugin.plugin.isOp(psender.getName().getString()); - } - return true; - } + public HashSet usrCommands = new HashSet(); + + public OpPermissions(String[] usrCommands) { + for (String usrCommand : usrCommands) { + this.usrCommands.add(usrCommand); + } + Log.info("Using ops.txt for access control"); + } + + @Override + public Set hasOfflinePermissions(String player, Set perms) { + HashSet rslt = new HashSet(); + if (DynmapPlugin.plugin.isOp(player)) { + rslt.addAll(perms); + } + return rslt; + } + + @Override + public boolean hasOfflinePermission(String player, String perm) { + return DynmapPlugin.plugin.isOp(player); + } + + @Override + public boolean has(ServerPlayer psender, String permission) { + if (psender != null) { + if (usrCommands.contains(permission)) { + return true; + } + return DynmapPlugin.plugin.isOp(psender.getName().getString()); + } + return true; + } + + @Override + public boolean hasPermissionNode(ServerPlayer psender, String permission) { + if (psender != null) { + return DynmapPlugin.plugin.isOp(psender.getName().getString()); + } + return true; + } } diff --git a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/permissions/PermissionProvider.java b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/permissions/PermissionProvider.java index 183baae70..825436d61 100644 --- a/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/permissions/PermissionProvider.java +++ b/neoforge-1.20.6/src/main/java/org/dynmap/neoforge_1_20_6/permissions/PermissionProvider.java @@ -5,11 +5,12 @@ import net.minecraft.server.level.ServerPlayer; public interface PermissionProvider { - boolean has(ServerPlayer sender, String permission); - boolean hasPermissionNode(ServerPlayer sender, String permission); - - Set hasOfflinePermissions(String player, Set perms); + boolean has(ServerPlayer sender, String permission); - boolean hasOfflinePermission(String player, String perm); + boolean hasPermissionNode(ServerPlayer sender, String permission); + + Set hasOfflinePermissions(String player, Set perms); + + boolean hasOfflinePermission(String player, String perm); } diff --git a/neoforge-1.20.6/src/main/resources/META-INF/accesstransformer.cfg b/neoforge-1.20.6/src/main/resources/META-INF/accesstransformer.cfg index 9f6e7211e..af53a2e93 100644 --- a/neoforge-1.20.6/src/main/resources/META-INF/accesstransformer.cfg +++ b/neoforge-1.20.6/src/main/resources/META-INF/accesstransformer.cfg @@ -1,4 +1,4 @@ -public net.minecraft.world.level.biome.BiomeSpecialEffects$Builder f_48006_ # waterColor -public net.minecraft.server.level.ServerLevel f_8549_ # serverLevelData -public net.minecraft.server.level.ChunkMap f_140130_ # visibleChunkMap -public net.minecraft.server.level.ChunkMap m_214963_(Lnet/minecraft/world/level/ChunkPos;)Ljava/util/concurrent/CompletableFuture; # readChunk( +public net.minecraft.world.level.biome.BiomeSpecialEffects$Builder waterColor +public net.minecraft.server.level.ServerLevel serverLevelData +public net.minecraft.server.level.ChunkMap visibleChunkMap +public net.minecraft.server.level.ChunkMap readChunk(Lnet/minecraft/world/level/ChunkPos;)Ljava/util/concurrent/CompletableFuture; diff --git a/neoforge-1.20.6/src/main/resources/META-INF/neoforge.mods.toml b/neoforge-1.20.6/src/main/resources/META-INF/neoforge.mods.toml index 06fd2abea..489739a11 100644 --- a/neoforge-1.20.6/src/main/resources/META-INF/neoforge.mods.toml +++ b/neoforge-1.20.6/src/main/resources/META-INF/neoforge.mods.toml @@ -15,13 +15,13 @@ file="META-INF/accesstransformer.cfg" [[dependencies.dynmap]] modId="neoforge" mandatory=true - versionRange="[20.6,)" + versionRange="[21.1,)" ordering="NONE" # Side this dependency is applied on - BOTH, CLIENT or SERVER side="SERVER" [[dependencies.dynmap]] modId="minecraft" mandatory=true - versionRange="[1.20.6,1.21)" + versionRange="[1.20.6,)" ordering="NONE" side="SERVER" From dc5efb504f128d8efc126361526c57fe16918526 Mon Sep 17 00:00:00 2001 From: minimusubi Date: Mon, 20 Jan 2025 15:39:28 -1000 Subject: [PATCH 09/10] chore: update readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 640ac88b5..bdc6c3637 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ * [Where to go for questions and discussions](#where-to-go-for-questions-and-discussions) * [Where to go to make donations](#where-to-go-to-make-donations) # How to build -Dynmap 3.x+ uses Gradle v8.7 for building support for all platforms, with all resulting artifacts produced in the /targets directory. Due to Minecraft 1.18.x+ requirements, the developer's +Dynmap 3.x+ uses Gradle v8.12 for building support for all platforms, with all resulting artifacts produced in the /targets directory. Due to Minecraft 1.18.x+ requirements, the developer's default JDK must be a JDK 21 version - older versions will still be compiled to run on the default JDK for those platforms (JDK 8 for 1.16 and earlier, JDK 16 for 1.17.x, JDK 17 for 1.18 to 1.20.4, JDK 21 for 1.20.5+), and common libraries are built using JDK 8. @@ -48,6 +48,7 @@ The following target platforms are supported, and you can find them at the links | ------------ | ------- | ---------- | ------ | | Spigot/PaperMC | ≤1.21.4 | `Dynmap--spigot.jar` | [SpigotMC](https://www.spigotmc.org/resources/dynmap%C2%AE.274/) | | Spigot/PaperMC | ≤1.21.4 | `Dynmap--spigot.jar` | [Modrinth](https://modrinth.com/plugin/dynmap/versions?l=paper&l=spigot) | +| NeoForge | 1.20.6 - 1.21.4 | `Dynmap--neoforge-.jar` | [Curseforge](https://www.curseforge.com/minecraft/mc-mods/dynmapforge) | | Forge | 1.12.2 - 1.20.6 | `Dynmap--forge-.jar` | [Curseforge](https://www.curseforge.com/minecraft/mc-mods/dynmapforge) | | Fabric | 1.14.4 - 1.21.4 | `Dynmap--fabric-.jar` | [Curseforge](https://www.curseforge.com/minecraft/mc-mods/dynmapforge) | From 23728ba2b7cd026c06726b3bda1b60f30b057b5a Mon Sep 17 00:00:00 2001 From: minimusubi Date: Tue, 21 Jan 2025 01:55:12 -1000 Subject: [PATCH 10/10] fix(neoforge): incorrect version requirements --- .../src/main/resources/META-INF/neoforge.mods.toml | 4 ++-- .../src/main/resources/META-INF/neoforge.mods.toml | 4 ++-- .../src/main/resources/META-INF/neoforge.mods.toml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/neoforge-1.20.6/src/main/resources/META-INF/neoforge.mods.toml b/neoforge-1.20.6/src/main/resources/META-INF/neoforge.mods.toml index 489739a11..0a520530f 100644 --- a/neoforge-1.20.6/src/main/resources/META-INF/neoforge.mods.toml +++ b/neoforge-1.20.6/src/main/resources/META-INF/neoforge.mods.toml @@ -15,13 +15,13 @@ file="META-INF/accesstransformer.cfg" [[dependencies.dynmap]] modId="neoforge" mandatory=true - versionRange="[21.1,)" + versionRange="[20.6,21)" ordering="NONE" # Side this dependency is applied on - BOTH, CLIENT or SERVER side="SERVER" [[dependencies.dynmap]] modId="minecraft" mandatory=true - versionRange="[1.20.6,)" + versionRange="[1.20.6,1.21)" ordering="NONE" side="SERVER" diff --git a/neoforge-1.21.1/src/main/resources/META-INF/neoforge.mods.toml b/neoforge-1.21.1/src/main/resources/META-INF/neoforge.mods.toml index b8bb55f32..a47298e5d 100644 --- a/neoforge-1.21.1/src/main/resources/META-INF/neoforge.mods.toml +++ b/neoforge-1.21.1/src/main/resources/META-INF/neoforge.mods.toml @@ -15,13 +15,13 @@ file="META-INF/accesstransformer.cfg" [[dependencies.dynmap]] modId="neoforge" mandatory=true - versionRange="[21.1,)" + versionRange="[21.1,21.2)" ordering="NONE" # Side this dependency is applied on - BOTH, CLIENT or SERVER side="SERVER" [[dependencies.dynmap]] modId="minecraft" mandatory=true - versionRange="[1.21.1,)" + versionRange="[1.21.1,1.21.2)" ordering="NONE" side="SERVER" diff --git a/neoforge-1.21.4/src/main/resources/META-INF/neoforge.mods.toml b/neoforge-1.21.4/src/main/resources/META-INF/neoforge.mods.toml index d2b91b93e..38e7316a3 100644 --- a/neoforge-1.21.4/src/main/resources/META-INF/neoforge.mods.toml +++ b/neoforge-1.21.4/src/main/resources/META-INF/neoforge.mods.toml @@ -15,7 +15,7 @@ file="META-INF/accesstransformer.cfg" [[dependencies.dynmap]] modId="neoforge" mandatory=true - versionRange="[21.1,)" + versionRange="[21.4,)" ordering="NONE" # Side this dependency is applied on - BOTH, CLIENT or SERVER side="SERVER"