initial port to fabric 1.21.5

This port was inspired by the #4198 port for spigot 1.21.5 for the
NBT updates. Like that it also does not implement the new blocks.

Only light testing has been done, but it seems to work just as under
1.21.4 so far.

Note that gradle had to be updated to 8.12.
This commit is contained in:
Peter Svensson 2025-04-04 18:21:07 +02:00
parent 10e6739811
commit 802f62bc6a
47 changed files with 3977 additions and 1 deletions

32
fabric-1.21.5/.gitignore vendored Normal file
View file

@ -0,0 +1,32 @@
# gradle
.gradle/
build/
out/
classes/
# eclipse
*.launch
# idea
.idea/
*.iml
*.ipr
*.iws
# vscode
.settings/
.vscode/
bin/
.classpath
.project
# fabric
run/
# other
*.log

View file

@ -0,0 +1,73 @@
plugins {
id 'fabric-loom' version '1.10-SNAPSHOT'
}
archivesBaseName = "Dynmap"
version = parent.version
group = parent.group
eclipse {
project {
name = "Dynmap(Fabric-1.21.5)"
}
}
sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(21) // Need this here so eclipse task generates correctly.
configurations {
shadow
implementation.extendsFrom(shadow)
}
repositories {
mavenCentral()
maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
}
dependencies {
minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
compileOnly group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2'
shadow project(path: ':DynmapCore', configuration: 'shadow')
modCompileOnly "me.lucko:fabric-permissions-api:0.1-SNAPSHOT"
compileOnly 'net.luckperms:api:5.4'
}
loom {
accessWidenerPath = file("src/main/resources/dynmap.accesswidener")
}
processResources {
filesMatching('fabric.mod.json') {
expand "version": project.version
}
}
java {
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
// if it is present.
// If you remove this line, sources will not be generated.
withSourcesJar()
}
jar {
from "LICENSE"
from {
configurations.shadow.collect { it.toString().contains("guava") ? null : it.isDirectory() ? it : zipTree(it) }
}
}
remapJar {
archiveFileName = "${archivesBaseName}-${project.version}-fabric-${project.minecraft_version}.jar"
destinationDirectory = file '../target'
}
remapJar.doLast {
task ->
ant.checksum file: task.archivePath
}

View file

@ -0,0 +1,4 @@
minecraft_version=1.21.5
yarn_mappings=1.21.5+build.1
loader_version=0.16.12
fabric_version=0.119.6+1.21.5

View file

@ -0,0 +1,50 @@
package org.dynmap.fabric_1_21_5;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import org.dynmap.DynmapCore;
import org.dynmap.Log;
import java.io.File;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
public class DynmapMod implements ModInitializer {
private static final String MODID = "dynmap";
private static final ModContainer MOD_CONTAINER = FabricLoader.getInstance().getModContainer(MODID)
.orElseThrow(() -> new RuntimeException("Failed to get mod container: " + MODID));
// The instance of your mod that Fabric uses.
public static DynmapMod instance;
public static DynmapPlugin plugin;
public static File jarfile;
public static String ver;
public static boolean useforcedchunks;
@Override
public void onInitialize() {
instance = this;
Path path = MOD_CONTAINER.getRootPath();
try {
jarfile = new File(DynmapCore.class.getProtectionDomain().getCodeSource().getLocation().toURI());
} catch (URISyntaxException e) {
Log.severe("Unable to get DynmapCore jar path", e);
}
if (path.getFileSystem().provider().getScheme().equals("jar")) {
path = Paths.get(path.getFileSystem().toString());
jarfile = path.toFile();
}
ver = MOD_CONTAINER.getMetadata().getVersion().getFriendlyString();
Log.setLogger(new FabricLogger());
org.dynmap.modsupport.ModSupportImpl.init();
// Initialize the plugin, we will enable it fully when the server starts.
plugin = new DynmapPlugin();
}
}

View file

@ -0,0 +1,796 @@
package org.dynmap.fabric_1_21_5;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.FluidBlock;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.Item;
import net.minecraft.registry.tag.BlockTags;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier;
import net.minecraft.util.collection.IdList;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.EmptyBlockView;
import net.minecraft.world.World;
import net.minecraft.world.WorldAccess;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.ChunkSection;
import org.dynmap.*;
import org.dynmap.common.BiomeMap;
import org.dynmap.common.DynmapCommandSender;
import org.dynmap.common.DynmapListenerManager;
import org.dynmap.common.DynmapPlayer;
import org.dynmap.common.chunk.GenericChunkCache;
import org.dynmap.fabric_1_21_5.command.DmapCommand;
import org.dynmap.fabric_1_21_5.command.DmarkerCommand;
import org.dynmap.fabric_1_21_5.command.DynmapCommand;
import org.dynmap.fabric_1_21_5.command.DynmapExpCommand;
import org.dynmap.fabric_1_21_5.event.BlockEvents;
import org.dynmap.fabric_1_21_5.event.CustomServerChunkEvents;
import org.dynmap.fabric_1_21_5.event.CustomServerLifecycleEvents;
import org.dynmap.fabric_1_21_5.event.PlayerEvents;
import org.dynmap.fabric_1_21_5.mixin.BiomeEffectsAccessor;
import org.dynmap.fabric_1_21_5.permissions.*;
import org.dynmap.permissions.PermissionsHandler;
import org.dynmap.renderer.DynmapBlockState;
import java.io.File;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.regex.Pattern;
public class DynmapPlugin {
// FIXME: Fix package-private fields after splitting is done
DynmapCore core;
private PermissionProvider permissions;
private boolean core_enabled;
public GenericChunkCache sscache;
public PlayerList playerList;
MapManager mapManager;
/**
* Server is set when running and unset at shutdown.
*/
private net.minecraft.server.MinecraftServer server;
public static DynmapPlugin plugin;
ChatHandler chathandler;
private HashMap<String, Integer> sortWeights = new HashMap<String, Integer>();
private HashMap<String, FabricWorld> worlds = new HashMap<String, FabricWorld>();
private WorldAccess last_world;
private FabricWorld last_fworld;
private Map<String, FabricPlayer> players = new HashMap<String, FabricPlayer>();
private FabricServer fserver;
private boolean tickregistered = false;
// TPS calculator
double tps;
long lasttick;
long avgticklen;
// Per tick limit, in nsec
long perTickLimit = (50000000); // 50 ms
private boolean useSaveFolder = true;
private static final String[] TRIGGER_DEFAULTS = {"blockupdate", "chunkpopulate", "chunkgenerate"};
static final Pattern patternControlCode = Pattern.compile("(?i)\\u00A7[0-9A-FK-OR]");
DynmapPlugin() {
plugin = this;
// Fabric events persist between server instances
ServerLifecycleEvents.SERVER_STARTING.register(this::serverStart);
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> registerCommands(dispatcher));
CustomServerLifecycleEvents.SERVER_STARTED_PRE_WORLD_LOAD.register(this::serverStarted);
ServerLifecycleEvents.SERVER_STOPPING.register(this::serverStop);
}
int getSortWeight(String name) {
return sortWeights.getOrDefault(name, 0);
}
void setSortWeight(String name, int wt) {
sortWeights.put(name, wt);
}
void dropSortWeight(String name) {
sortWeights.remove(name);
}
public static class BlockUpdateRec {
WorldAccess w;
String wid;
int x, y, z;
}
ConcurrentLinkedQueue<BlockUpdateRec> blockupdatequeue = new ConcurrentLinkedQueue<BlockUpdateRec>();
public static DynmapBlockState[] stateByID;
/**
* Initialize block states (org.dynmap.blockstate.DynmapBlockState)
*/
public void initializeBlockStates() {
stateByID = new DynmapBlockState[512 * 32]; // Simple map - scale as needed
Arrays.fill(stateByID, DynmapBlockState.AIR); // Default to air
IdList<BlockState> bsids = Block.STATE_IDS;
DynmapBlockState basebs = null;
Block baseb = null;
int baseidx = 0;
Iterator<BlockState> iter = bsids.iterator();
DynmapBlockState.Builder bld = new DynmapBlockState.Builder();
while (iter.hasNext()) {
BlockState bs = iter.next();
int idx = bsids.getRawId(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;
}
Identifier ui = Registries.BLOCK.getId(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.state.property.Property<?> p : bs.getProperties()) {
if (statename.length() > 0) {
statename += ",";
}
statename += p.getName() + "=" + bs.get(p).toString();
}
int lightAtten = bs.isOpaqueFullCube() ? 15 : (bs.isTransparent() ? 0 : 1);
//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.getSoundGroup() != null) { bld.setMaterial(bs.getSoundGroup().toString()); }
if (bs.isSolid()) { bld.setSolid(); }
if (bs.isAir()) { bld.setAir(); }
if (bs.isIn(BlockTags.LOGS)) { bld.setLog(); }
if (bs.isIn(BlockTags.LEAVES)) { bld.setLeaves(); }
if ((!bs.getFluidState().isEmpty()) && !(bs.getBlock() instanceof FluidBlock)) {
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.byRawId(id);
}
FabricPlayer getOrAddPlayer(ServerPlayerEntity player) {
String name = player.getName().getString();
FabricPlayer fp = players.get(name);
if (fp != null) {
fp.player = player;
} else {
fp = new FabricPlayer(this, player);
players.put(name, fp);
}
return fp;
}
static class ChatMessage {
String message;
ServerPlayerEntity sender;
}
ConcurrentLinkedQueue<ChatMessage> msgqueue = new ConcurrentLinkedQueue<ChatMessage>();
public static class ChatHandler {
private final DynmapPlugin plugin;
ChatHandler(DynmapPlugin plugin) {
this.plugin = plugin;
}
public void handleChat(ServerPlayerEntity player, String message) {
if (!message.startsWith("/")) {
ChatMessage cm = new ChatMessage();
cm.message = message;
cm.sender = player;
plugin.msgqueue.add(cm);
}
}
}
public FabricServer getFabricServer() {
return fserver;
}
private void serverStart(MinecraftServer server) {
// Set the server so we don't NPE during setup
this.server = server;
this.fserver = new FabricServer(this, server);
this.onEnable();
}
private void serverStarted(MinecraftServer server) {
this.onStart();
if (core != null) {
core.serverStarted();
}
}
private void serverStop(MinecraftServer server) {
this.onDisable();
this.server = null;
}
public boolean isOp(String player) {
String[] ops = server.getPlayerManager().getOpList().getNames();
for (String op : ops) {
if (op.equalsIgnoreCase(player)) {
return true;
}
}
// TODO: Consider whether cheats are enabled for integrated server
return server.isSingleplayer() && server.isHost(server.getPlayerManager().getPlayer(player).getGameProfile());
}
boolean hasPerm(PlayerEntity psender, String permission) {
PermissionsHandler ph = PermissionsHandler.getHandler();
if ((ph != null) && (psender != null) && ph.hasPermission(psender.getName().getString(), permission)) {
return true;
}
return permissions.has(psender, permission);
}
boolean hasPermNode(PlayerEntity psender, String permission) {
PermissionsHandler ph = PermissionsHandler.getHandler();
if ((ph != null) && (psender != null) && ph.hasPermissionNode(psender.getName().getString(), permission)) {
return true;
}
return permissions.hasPermissionNode(psender, permission);
}
Set<String> hasOfflinePermissions(String player, Set<String> perms) {
Set<String> rslt = null;
PermissionsHandler ph = PermissionsHandler.getHandler();
if (ph != null) {
rslt = ph.hasOfflinePermissions(player, perms);
}
Set<String> rslt2 = hasOfflinePermissions(player, perms);
if ((rslt != null) && (rslt2 != null)) {
Set<String> newrslt = new HashSet<String>(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);
}
void setChatHandler(ChatHandler chatHandler) {
plugin.chathandler = chatHandler;
}
public class TexturesPayload {
public long timestamp;
public String profileId;
public String profileName;
public boolean isPublic;
public Map<String, ProfileTexture> textures;
}
public class ProfileTexture {
public String url;
}
public void loadExtraBiomes(String mcver) {
int cnt = 0;
BiomeMap.loadWellKnownByVersion(mcver);
Registry<Biome> biomeRegistry = getFabricServer().getBiomeRegistry();
Biome[] list = getFabricServer().getBiomeList(biomeRegistry);
for (int i = 0; i < list.length; i++) {
Biome bb = list[i];
if (bb != null) {
String id = biomeRegistry.getId(bb).getPath();
String rl = biomeRegistry.getId(bb).toString();
float tmp = bb.getTemperature(), hum = bb.weather.downfall();
int watermult = ((BiomeEffectsAccessor) bb.getEffects()).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() {
Registry<Biome> biomeRegistry = getFabricServer().getBiomeRegistry();
Biome[] list = getFabricServer().getBiomeList(biomeRegistry);
String[] lst = new String[list.length];
for (int i = 0; i < list.length; i++) {
Biome bb = list[i];
if (bb != null) {
lst[i] = biomeRegistry.getId(bb).getPath();
}
}
return lst;
}
public void onEnable() {
/* Get MC version */
String mcver = server.getVersion();
/* Load extra biomes */
loadExtraBiomes(mcver);
/* Set up player login/quit event handler */
registerPlayerLoginListener();
/* Initialize permissions handler */
if (FabricLoader.getInstance().isModLoaded("luckperms")) {
Log.info("Using luckperms for access control");
permissions = new LuckPermissions();
}
else if (FabricLoader.getInstance().isModLoaded("fabric-permissions-api-v0")) {
Log.info("Using fabric-permissions-api for access control");
permissions = new FabricPermissions();
} else {
/* 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()) {
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 DynmapCommand dynmapCmd;
private DmapCommand dmapCmd;
private DmarkerCommand dmarkerCmd;
private DynmapExpCommand dynmapexpCmd;
public void registerCommands(CommandDispatcher<ServerCommandSource> cd) {
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) {
ServerTickEvents.END_SERVER_TICK.register(server -> fserver.tickEvent(server));
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();
for (FabricWorld 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(DynmapListenerManager.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.clearTaskQueue();
/* Disable core */
core.disableCore();
core_enabled = false;
if (sscache != null) {
sscache.cleanup();
sscache = null;
}
Log.info("Disabled");
}
// TODO: Clean a bit
public void handleCommand(ServerCommandSource commandSource, String cmd, String[] args) throws CommandSyntaxException {
DynmapCommandSender dsender;
ServerPlayerEntity psender = null;
// getPlayer throws a CommandSyntaxException, so getEntity and instanceof for safety
if (commandSource.getEntity() instanceof ServerPlayerEntity) {
psender = commandSource.getPlayerOrThrow();
}
if (psender != null) {
// FIXME: New Player? Why not query the current player list.
dsender = new FabricPlayer(this, psender);
} else {
dsender = new FabricCommandSender(commandSource);
}
core.processCommand(dsender, cmd, cmd, args);
}
public class PlayerTracker {
public void onPlayerLogin(ServerPlayerEntity player) {
if (!core_enabled) return;
final DynmapPlayer dp = getOrAddPlayer(player);
/* This event can be called from off server thread, so push processing there */
core.getServer().scheduleServerTask(new Runnable() {
public void run() {
core.listenerManager.processPlayerEvent(DynmapListenerManager.EventType.PLAYER_JOIN, dp);
}
}, 2);
}
public void onPlayerLogout(ServerPlayerEntity player) {
if (!core_enabled) return;
final DynmapPlayer dp = getOrAddPlayer(player);
final String name = player.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(DynmapListenerManager.EventType.PLAYER_QUIT, dp);
players.remove(name);
}
}, 0);
}
public void onPlayerChangedDimension(ServerPlayerEntity player) {
if (!core_enabled) return;
getOrAddPlayer(player); // Freshen player object reference
}
public void onPlayerRespawn(ServerPlayerEntity player) {
if (!core_enabled) return;
getOrAddPlayer(player); // Freshen player object reference
}
}
private PlayerTracker playerTracker = null;
private void registerPlayerLoginListener() {
if (playerTracker == null) {
playerTracker = new PlayerTracker();
PlayerEvents.PLAYER_LOGGED_IN.register(player -> playerTracker.onPlayerLogin(player));
PlayerEvents.PLAYER_LOGGED_OUT.register(player -> playerTracker.onPlayerLogout(player));
PlayerEvents.PLAYER_CHANGED_DIMENSION.register(player -> playerTracker.onPlayerChangedDimension(player));
PlayerEvents.PLAYER_RESPAWN.register(player -> playerTracker.onPlayerRespawn(player));
}
}
public class WorldTracker {
public void handleWorldLoad(MinecraftServer server, ServerWorld world) {
if (!core_enabled) return;
final FabricWorld fw = getWorld(world);
// 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(DynmapListenerManager.EventType.WORLD_LOAD, fw);
}
}, 0);
}
public void handleWorldUnload(MinecraftServer server, ServerWorld world) {
if (!core_enabled) return;
final FabricWorld fw = getWorld(world);
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(DynmapListenerManager.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;
}
}
public void handleChunkGenerate(ServerWorld world, Chunk chunk) {
if (!onchunkgenerate) return;
FabricWorld fw = getWorld(world, false);
ChunkPos chunkPos = chunk.getPos();
int ymax = Integer.MIN_VALUE;
int ymin = Integer.MAX_VALUE;
ChunkSection[] sections = chunk.getSectionArray();
for (int i = 0; i < sections.length; i++) {
if ((sections[i] != null) && (!sections[i].isEmpty())) {
int sy = chunk.getBottomY() + i * ChunkSection.field_31407 /* Mojmap: SECTION_HEIGHT */;
if (sy < ymin) ymin = sy;
if ((sy+16) > ymax) ymax = sy + 16;
}
}
if (ymax != Integer.MIN_VALUE) {
mapManager.touchVolume(fw.getName(),
chunkPos.getStartX(), ymin, chunkPos.getStartZ(),
chunkPos.getEndX(), ymax, chunkPos.getEndZ(),
"chunkgenerate");
//Log.info("New generated chunk detected at %s[%s]".formatted(fw.getName(), chunkPos.getStartPos()));
}
}
public void handleBlockEvent(World world, BlockPos pos) {
if (!core_enabled) return;
if (!onblockchange) return;
if (!(world instanceof ServerWorld)) return;
BlockUpdateRec r = new BlockUpdateRec();
r.w = world;
FabricWorld fw = getWorld(world, false);
if (fw == null) return;
r.wid = fw.getName();
r.x = pos.getX();
r.y = pos.getY();
r.z = pos.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)
worldTracker = new WorldTracker();
if (onchunkpopulate || onchunkgenerate) {
CustomServerChunkEvents.CHUNK_GENERATE.register((world, chunk) -> worldTracker.handleChunkGenerate(world, chunk));
}
if (onblockchange) {
BlockEvents.BLOCK_EVENT.register((world, pos) -> worldTracker.handleBlockEvent(world, pos));
}
ServerWorldEvents.LOAD.register((server, world) -> worldTracker.handleWorldLoad(server, world));
ServerWorldEvents.UNLOAD.register((server, world) -> worldTracker.handleWorldUnload(server, world));
}
FabricWorld getWorldByName(String name) {
return worlds.get(name);
}
FabricWorld getWorld(World w) {
return getWorld(w, true);
}
private FabricWorld getWorld(World w, boolean add_if_not_found) {
if (last_world == w) {
return last_fworld;
}
String wname = FabricWorld.getWorldName(this, w);
for (FabricWorld fw : worlds.values()) {
if (fw.getRawName().equals(wname)) {
last_world = w;
last_fworld = fw;
if (!fw.isLoaded()) {
fw.setWorldLoaded(w);
}
fw.updateWorld(w);
return fw;
}
}
FabricWorld fw = null;
if (add_if_not_found) {
/* Add to list if not found */
fw = new FabricWorld(this, w);
worlds.put(fw.getName(), fw);
}
last_world = w;
last_fworld = fw;
return fw;
}
private void saveWorlds() {
File f = new File(core.getDataFolder(), FabricWorld.SAVED_WORLDS_FILE);
ConfigurationNode cn = new ConfigurationNode(f);
ArrayList<HashMap<String, Object>> lst = new ArrayList<HashMap<String, Object>>();
for (DynmapWorld fw : core.mapManager.getWorlds()) {
HashMap<String, Object> vals = new HashMap<String, Object>();
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", ((FabricWorld) fw).isTheEnd());
vals.put("title", fw.getTitle());
lst.add(vals);
}
cn.put("worlds", lst);
cn.put("useSaveFolderAsName", useSaveFolder);
cn.put("maxWorldHeight", FabricWorld.getMaxWorldHeight());
cn.save();
}
private void loadWorlds() {
File f = new File(core.getDataFolder(), FabricWorld.SAVED_WORLDS_FILE);
if (f.canRead() == false) {
useSaveFolder = true;
return;
}
ConfigurationNode cn = new ConfigurationNode(f);
cn.load();
// If defined, use maxWorldHeight
FabricWorld.setMaxWorldHeight(cn.getInteger("maxWorldHeight", 256));
// If setting defined, use it
if (cn.containsKey("useSaveFolderAsName")) {
useSaveFolder = cn.getBoolean("useSaveFolderAsName", useSaveFolder);
}
List<Map<String, Object>> lst = cn.getMapList("worlds");
if (lst == null) {
Log.warning(String.format("Discarding bad %s", FabricWorld.SAVED_WORLDS_FILE));
return;
}
for (Map<String, Object> 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) {
FabricWorld fw = new FabricWorld(this, 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", FabricWorld.SAVED_WORLDS_FILE));
return;
}
}
}
}

View file

@ -0,0 +1,13 @@
package org.dynmap.fabric_1_21_5;
import net.minecraft.server.world.ServerWorld;
import org.dynmap.DynmapLocation;
public final class FabricAdapter {
public static DynmapLocation toDynmapLocation(DynmapPlugin plugin, ServerWorld world, double x, double y, double z) {
return new DynmapLocation(plugin.getWorld(world).getName(), x, y, z);
}
private FabricAdapter() {
}
}

View file

@ -0,0 +1,45 @@
package org.dynmap.fabric_1_21_5;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.Text;
import org.dynmap.common.DynmapCommandSender;
/* Handler for generic console command sender */
public class FabricCommandSender implements DynmapCommandSender {
private ServerCommandSource sender;
protected FabricCommandSender() {
sender = null;
}
public FabricCommandSender(ServerCommandSource send) {
sender = send;
}
@Override
public boolean hasPrivilege(String privid) {
return true;
}
@Override
public void sendMessage(String msg) {
if (sender != null) {
sender.sendFeedback(() -> Text.literal(msg), false);
}
}
@Override
public boolean isConnected() {
return false;
}
@Override
public boolean isOp() {
return true;
}
@Override
public boolean hasPermissionNode(String node) {
return true;
}
}

View file

@ -0,0 +1,49 @@
package org.dynmap.fabric_1_21_5;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dynmap.utils.DynmapLogger;
public class FabricLogger implements DynmapLogger {
Logger log;
public static final String DM = "[Dynmap] ";
FabricLogger() {
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);
}
}

View file

@ -0,0 +1,117 @@
package org.dynmap.fabric_1_21_5;
import net.minecraft.nbt.*;
import net.minecraft.server.world.ServerChunkLoadingManager;
import net.minecraft.server.world.ServerChunkManager;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.collection.PackedIntegerArray;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.WordPackedArray;
import net.minecraft.world.World;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.BiomeEffects;
import net.minecraft.world.chunk.ChunkManager;
import net.minecraft.world.chunk.ChunkStatus;
import net.minecraft.world.chunk.SerializedChunk;
import org.dynmap.DynmapChunk;
import org.dynmap.DynmapCore;
import org.dynmap.DynmapWorld;
import org.dynmap.Log;
import org.dynmap.common.BiomeMap;
import org.dynmap.common.chunk.GenericChunk;
import org.dynmap.common.chunk.GenericChunkSection;
import org.dynmap.common.chunk.GenericMapChunkCache;
import org.dynmap.hdmap.HDBlockModels;
import org.dynmap.renderer.DynmapBlockState;
import org.dynmap.renderer.RenderPatchFactory;
import org.dynmap.utils.*;
import java.lang.reflect.Field;
import java.util.*;
/**
* Container for managing chunks - dependent upon using chunk snapshots, since rendering is off server thread
*/
public class FabricMapChunkCache extends GenericMapChunkCache {
private World w;
private ServerChunkManager cps;
/**
* Construct empty cache
*/
public FabricMapChunkCache(DynmapPlugin plugin) {
super(plugin.sscache);
}
public void setChunks(FabricWorld dw, List<DynmapChunk> chunks) {
this.w = dw.getWorld();
if (dw.isLoaded()) {
/* Check if world's provider is ServerChunkManager */
ChunkManager cp = this.w.getChunkManager();
if (cp instanceof ServerChunkManager) {
cps = (ServerChunkManager) cp;
} else {
Log.severe("Error: world " + dw.getName() + " has unsupported chunk provider");
}
}
super.setChunks(dw, chunks);
}
// Load generic chunk from existing and already loaded chunk
protected GenericChunk getLoadedChunk(DynmapChunk chunk) {
GenericChunk gc = null;
if (cps.isChunkLoaded(chunk.x, chunk.z)) {
NbtCompound nbt = null;
try {
SerializedChunk sc = SerializedChunk.fromChunk((ServerWorld) w, cps.getWorldChunk(chunk.x, chunk.z, false));
nbt = sc.serialize();
} catch (NullPointerException e) {
// TODO: find out why this is happening and why it only seems to happen since 1.16.2
Log.severe("ChunkSerializer.serialize threw a NullPointerException", e);
}
if (nbt != null) {
gc = parseChunkFromNBT(new NBT.NBTCompound(nbt));
}
}
return gc;
}
private NbtCompound readChunk(int x, int z) {
try {
ServerChunkLoadingManager acl = cps.chunkLoadingManager;
ChunkPos coord = new ChunkPos(x, z);
// Async chunk reading is synchronized here. Perhaps we can do async and improve performance?
return acl.getNbt(coord).join().orElse(null);
} catch (Exception exc) {
Log.severe(String.format("Error reading chunk: %s,%d,%d", dw.getName(), x, z), exc);
return null;
}
}
// Load generic chunk from unloaded chunk
protected GenericChunk loadChunk(DynmapChunk chunk) {
GenericChunk gc = null;
NbtCompound nbt = readChunk(chunk.x, chunk.z);
// If read was good
if (nbt != null) {
gc = parseChunkFromNBT(new NBT.NBTCompound(nbt));
}
return gc;
}
@Override
public int getFoliageColor(BiomeMap bm, int[] colormap, int x, int z) {
return bm.<Biome>getBiomeObject().map(Biome::getEffects).flatMap(BiomeEffects::getFoliageColor).orElse(colormap[bm.biomeLookup()]);
}
@Override
public int getGrassColor(BiomeMap bm, int[] colormap, int x, int z) {
BiomeEffects effects = bm.<Biome>getBiomeObject().map(Biome::getEffects).orElse(null);
if (effects == null) return colormap[bm.biomeLookup()];
return effects.getGrassColorModifier().getModifiedGrassColor(x, z, effects.getGrassColor().orElse(colormap[bm.biomeLookup()]));
}
}

View file

@ -0,0 +1,260 @@
package org.dynmap.fabric_1_21_5;
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.packet.s2c.play.SubtitleS2CPacket;
import net.minecraft.network.packet.s2c.play.TitleFadeS2CPacket;
import net.minecraft.network.packet.s2c.play.TitleS2CPacket;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.util.Util;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import org.dynmap.DynmapLocation;
import org.dynmap.common.DynmapPlayer;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.UUID;
/**
* Player access abstraction class
*/
public class FabricPlayer extends FabricCommandSender implements DynmapPlayer {
private static final Gson GSON = new GsonBuilder().create();
private final DynmapPlugin plugin;
// FIXME: Proper setter
ServerPlayerEntity player;
private final String skinurl;
private final UUID uuid;
public FabricPlayer(DynmapPlugin plugin, ServerPlayerEntity player) {
this.plugin = plugin;
this.player = player;
String url = null;
if (this.player != null) {
uuid = this.player.getUuid();
GameProfile prof = this.player.getGameProfile();
if (prof != null) {
Property textureProperty = Iterables.getFirst(prof.getProperties().get("textures"), null);
if (textureProperty != null) {
DynmapPlugin.TexturesPayload result = null;
try {
String json = new String(Base64.getDecoder().decode(textureProperty.value()), StandardCharsets.UTF_8);
result = GSON.fromJson(json, DynmapPlugin.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;
}
Vec3d pos = player.getPos();
return FabricAdapter.toDynmapLocation(plugin, player.getServerWorld(), pos.getX(), pos.getY(), pos.getZ());
}
@Override
public String getWorld() {
if (player == null) {
return null;
}
World world = player.getWorld();
if (world != null) {
return plugin.getWorld(world).getName();
}
return null;
}
@Override
public InetSocketAddress getAddress() {
if (player != null) {
ServerPlayNetworkHandler networkHandler = player.networkHandler;
if (networkHandler != null) {
SocketAddress sa = networkHandler.getConnectionAddress();
if (sa instanceof InetSocketAddress) {
return (InetSocketAddress) sa;
}
}
}
return null;
}
@Override
public boolean isSneaking() {
if (player != null) {
return player.isSneaking();
}
return false;
}
@Override
public double getHealth() {
if (player != null) {
double h = player.getHealth();
if (h > 20) h = 20;
return h; // Scale to 20 range
} else {
return 0;
}
}
@Override
public int getArmorPoints() {
if (player != null) {
return player.getArmor();
} 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) {
Text ichatcomponent = Text.literal(msg);
player.sendMessage(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() {
return plugin.getSortWeight(getName());
}
@Override
public void setSortWeight(int wt) {
if (wt == 0) {
plugin.dropSortWeight(getName());
} else {
plugin.setSortWeight(getName(), wt);
}
}
@Override
public boolean hasPermissionNode(String node) {
return player != null && plugin.hasPermNode(player, node);
}
@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 != null) {
ServerPlayerEntity player = this.player;
TitleFadeS2CPacket times = new TitleFadeS2CPacket(fadeInTicks, stayTicks, fadeOutTicks);
player.networkHandler.sendPacket(times);
if (title != null) {
TitleS2CPacket titlepkt = new TitleS2CPacket(Text.literal(title));
player.networkHandler.sendPacket(titlepkt);
}
if (subtitle != null) {
SubtitleS2CPacket subtitlepkt = new SubtitleS2CPacket(Text.literal(subtitle));
player.networkHandler.sendPacket(subtitlepkt);
}
}
}
}

View file

@ -0,0 +1,609 @@
package org.dynmap.fabric_1_21_5;
import com.mojang.authlib.GameProfile;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.minecraft.block.AbstractSignBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.network.message.MessageType;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.server.BannedIpList;
import net.minecraft.server.BannedPlayerList;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.PlayerManager;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.util.UserCache;
import net.minecraft.util.Util;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.biome.Biome;
import org.dynmap.DynmapChunk;
import org.dynmap.DynmapWorld;
import org.dynmap.Log;
import org.dynmap.DynmapCommonAPIListener;
import org.dynmap.common.BiomeMap;
import org.dynmap.common.DynmapListenerManager;
import org.dynmap.common.DynmapPlayer;
import org.dynmap.common.DynmapServerInterface;
import org.dynmap.fabric_1_21_5.event.BlockEvents;
import org.dynmap.fabric_1_21_5.event.ServerChatEvents;
import org.dynmap.utils.MapChunkCache;
import org.dynmap.utils.VisibilityLimit;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
/**
* Server access abstraction class
*/
public class FabricServer extends DynmapServerInterface {
/* Server thread scheduler */
private final Object schedlock = new Object();
private final DynmapPlugin plugin;
private final MinecraftServer server;
private final Registry<Biome> biomeRegistry;
private long cur_tick;
private long next_id;
private long cur_tick_starttime;
private PriorityQueue<TaskRecord> runqueue = new PriorityQueue<TaskRecord>();
public FabricServer(DynmapPlugin plugin, MinecraftServer server) {
this.plugin = plugin;
this.server = server;
this.biomeRegistry = server.getRegistryManager().getOrThrow(RegistryKeys.BIOME);
}
private Optional<GameProfile> getProfileByName(String player) {
UserCache cache = server.getUserCache();
return cache.findByName(player);
}
public final Registry<Biome> getBiomeRegistry() {
return biomeRegistry;
}
private Biome[] biomelist = null;
public final Biome[] getBiomeList(Registry<Biome> biomeRegistry) {
if (biomelist == null) {
biomelist = new Biome[256];
Iterator<Biome> iter = biomeRegistry.iterator();
while (iter.hasNext()) {
Biome b = iter.next();
int bidx = biomeRegistry.getRawId(b);
if (bidx >= biomelist.length) {
biomelist = Arrays.copyOf(biomelist, bidx + biomelist.length);
}
biomelist[bidx] = b;
}
}
return biomelist;
}
@Override
public int getBlockIDAt(String wname, int x, int y, int z) {
return -1;
}
@SuppressWarnings("deprecation") /* Not much I can do... fix this if it breaks. */
@Override
public int isSignAt(String wname, int x, int y, int z) {
World world = plugin.getWorldByName(wname).getWorld();
BlockPos pos = new BlockPos(x, y, z);
if (!world.isChunkLoaded(pos))
return -1;
Block block = world.getBlockState(pos).getBlock();
return (block instanceof AbstractSignBlock ? 1 : 0);
}
@Override
public void scheduleServerTask(Runnable run, long delay) {
/* Add task record to queue */
synchronized (schedlock) {
TaskRecord tr = new TaskRecord(cur_tick + delay, next_id++, new FutureTask<Object>(run, null));
runqueue.add(tr);
}
}
@Override
public DynmapPlayer[] getOnlinePlayers() {
if (server.getPlayerManager() == null) return new DynmapPlayer[0];
List<ServerPlayerEntity> players = server.getPlayerManager().getPlayerList();
int playerCount = players.size();
DynmapPlayer[] dplay = new DynmapPlayer[players.size()];
for (int i = 0; i < playerCount; i++) {
ServerPlayerEntity player = players.get(i);
dplay[i] = plugin.getOrAddPlayer(player);
}
return dplay;
}
@Override
public void reload() {
plugin.onDisable();
plugin.onEnable();
plugin.onStart();
}
@Override
public DynmapPlayer getPlayer(String name) {
List<ServerPlayerEntity> players = server.getPlayerManager().getPlayerList();
for (ServerPlayerEntity player : players) {
if (player.getName().getString().equalsIgnoreCase(name)) {
return plugin.getOrAddPlayer(player);
}
}
return null;
}
@Override
public Set<String> getIPBans() {
BannedIpList bl = server.getPlayerManager().getIpBanList();
Set<String> ips = new HashSet<String>();
for (String s : bl.getNames()) {
ips.add(s);
}
return ips;
}
@Override
public <T> Future<T> callSyncMethod(Callable<T> task) {
return callSyncMethod(task, 0);
}
public <T> Future<T> callSyncMethod(Callable<T> task, long delay) {
FutureTask<T> ft = new FutureTask<T>(task);
/* Add task record to queue */
synchronized (schedlock) {
TaskRecord tr = new TaskRecord(cur_tick + delay, next_id++, ft);
runqueue.add(tr);
}
return ft;
}
void clearTaskQueue() {
this.runqueue.clear();
}
@Override
public String getServerName() {
String sn;
if (server.isSingleplayer())
sn = "Integrated";
else
sn = server.getServerIp();
if (sn == null) sn = "Unknown Server";
return sn;
}
@Override
public boolean isPlayerBanned(String pid) {
PlayerManager scm = server.getPlayerManager();
BannedPlayerList bl = scm.getUserBanList();
try {
return bl.contains(getProfileByName(pid).get());
} catch (NoSuchElementException e) {
/* If this profile doesn't exist, default to "banned" for good measure. */
return true;
}
}
@Override
public String stripChatColor(String s) {
return DynmapPlugin.patternControlCode.matcher(s).replaceAll("");
}
private Set<DynmapListenerManager.EventType> registered = new HashSet<DynmapListenerManager.EventType>();
@Override
public boolean requestEventNotification(DynmapListenerManager.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 (plugin.chathandler == null) {
plugin.setChatHandler(new DynmapPlugin.ChatHandler(plugin));
ServerChatEvents.EVENT.register((player, message) -> plugin.chathandler.handleChat(player, message));
}
break;
case BLOCK_BREAK:
/* Already handled by BlockEvents logic */
break;
case SIGN_CHANGE:
BlockEvents.SIGN_CHANGE_EVENT.register((world, pos, lines, player, front) -> {
plugin.core.processSignChange("fabric", FabricWorld.getWorldName(plugin, world),
pos.getX(), pos.getY(), pos.getZ(), lines, player.getName().getString());
});
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) {
Text component = Text.literal(msg);
server.getPlayerManager().broadcast(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 plugin.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<String> checkPlayerPermissions(String player, Set<String> perms) {
if (isPlayerBanned(player)) {
return Collections.emptySet();
}
Set<String> rslt = plugin.hasOfflinePermissions(player, perms);
if (rslt == null) {
rslt = new HashSet<String>();
if (plugin.isOp(player)) {
rslt.addAll(perms);
}
}
return rslt;
}
@Override
public boolean checkPlayerPermission(String player, String perm) {
if (isPlayerBanned(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<DynmapChunk> chunks,
boolean blockdata, boolean highesty, boolean biome, boolean rawbiome) {
FabricMapChunkCache c = (FabricMapChunkCache) w.getChunkCache(chunks);
if (c == null) {
return null;
}
if (w.visibility_limits != null) {
for (VisibilityLimit limit : w.visibility_limits) {
c.setVisibleRange(limit);
}
c.setHiddenFillStyle(w.hiddenchunkstyle);
}
if (w.hidden_limits != null) {
for (VisibilityLimit limit : w.hidden_limits) {
c.setHiddenRange(limit);
}
c.setHiddenFillStyle(w.hiddenchunkstyle);
}
if (!c.setChunkDataTypes(blockdata, biome, highesty, rawbiome)) {
Log.severe("CraftBukkit build does not support biome APIs");
}
if (chunks.size() == 0) /* No chunks to get? */ {
c.loadChunks(0);
return c;
}
//Now handle any chunks in server thread that are already loaded (on server thread)
final FabricMapChunkCache cc = c;
Future<Boolean> f = this.callSyncMethod(new Callable<Boolean>() {
public Boolean call() throws Exception {
// Update busy state on world
//FabricWorld fw = (FabricWorld) 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()) {
return null;
}
// Now, do rest of chunk reading from calling thread
c.readChunks(chunks.size());
return c;
}
@Override
public int getMaxPlayers() {
return server.getMaxPlayerCount();
}
@Override
public int getCurrentPlayers() {
return server.getPlayerManager().getCurrentPlayerCount();
}
public void tickEvent(MinecraftServer server) {
cur_tick_starttime = System.nanoTime();
long elapsed = cur_tick_starttime - plugin.lasttick;
plugin.lasttick = cur_tick_starttime;
plugin.avgticklen = ((plugin.avgticklen * 99) / 100) + (elapsed / 100);
plugin.tps = (double) 1E9 / (double) plugin.avgticklen;
// Tick core
if (plugin.core != null) {
plugin.core.serverTick(plugin.tps);
}
boolean done = false;
TaskRecord tr = null;
while (!plugin.blockupdatequeue.isEmpty()) {
DynmapPlugin.BlockUpdateRec r = plugin.blockupdatequeue.remove();
BlockState bs = r.w.getBlockState(new BlockPos(r.x, r.y, r.z));
int idx = Block.STATE_IDS.getRawId(bs);
if (!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.getTickToRun() > cur_tick) || ((now - cur_tick_starttime) > plugin.perTickLimit)) {
done = true;
} else {
tr = runqueue.poll();
}
}
while (!done) {
tr.run();
synchronized (schedlock) {
tr = runqueue.peek();
now = System.nanoTime();
/* Nothing due to run */
if ((tr == null) || (tr.getTickToRun() > cur_tick) || ((now - cur_tick_starttime) > plugin.perTickLimit)) {
done = true;
} else {
tr = runqueue.poll();
}
}
}
while (!plugin.msgqueue.isEmpty()) {
DynmapPlugin.ChatMessage cm = plugin.msgqueue.poll();
DynmapPlayer dp = null;
if (cm.sender != null)
dp = plugin.getOrAddPlayer(cm.sender);
else
dp = new FabricPlayer(plugin, null);
plugin.core.listenerManager.processChatEvent(DynmapListenerManager.EventType.PLAYER_CHAT, dp, cm.message);
}
// Check for generated chunks
if ((cur_tick % 20) == 0) {
}
}
private Optional<ModContainer> getModContainerById(String id) {
return FabricLoader.getInstance().getModContainer(id);
}
@Override
public boolean isModLoaded(String name) {
return FabricLoader.getInstance().getModContainer(name).isPresent();
}
@Override
public String getModVersion(String name) {
Optional<ModContainer> mod = getModContainerById(name); // Try case sensitive lookup
return mod.map(modContainer -> modContainer.getMetadata().getVersion().getFriendlyString()).orElse(null);
}
@Override
public double getServerTPS() {
return plugin.tps;
}
@Override
public String getServerIP() {
if (server.isSingleplayer())
return "0.0.0.0";
else
return server.getServerIp();
}
@Override
public File getModContainerFile(String name) {
Optional<ModContainer> container = getModContainerById(name); // Try case sensitive lookup
if (container.isPresent()) {
Path path = container.get().getRootPath();
if (path.getFileSystem().provider().getScheme().equals("jar")) {
path = Paths.get(path.getFileSystem().toString());
}
return path.toFile();
}
return null;
}
@Override
public List<String> getModList() {
return FabricLoader.getInstance()
.getAllMods()
.stream()
.map(container -> container.getMetadata().getId())
.collect(Collectors.toList());
}
@Override
public Map<Integer, String> getBlockIDMap() {
Map<Integer, String> map = new HashMap<Integer, String>();
return map;
}
@Override
public InputStream openResource(String modid, String rname) {
if (modid == null) modid = "minecraft";
if ("minecraft".equals(modid)) {
return MinecraftServer.class.getClassLoader().getResourceAsStream(rname);
} else {
if (rname.startsWith("/") || rname.startsWith("\\")) {
rname = rname.substring(1);
}
final String finalModid = modid;
final String finalRname = rname;
return getModContainerById(modid).map(container -> {
try {
return Files.newInputStream(container.getPath(finalRname));
} catch (IOException e) {
Log.severe("Failed to load resource of mod :" + finalModid, e);
return null;
}
}).orElse(null);
}
}
/**
* Get block unique ID map (module:blockid)
*/
@Override
public Map<String, Integer> getBlockUniqueIDMap() {
HashMap<String, Integer> map = new HashMap<String, Integer>();
return map;
}
/**
* Get item unique ID map (module:itemid)
*/
@Override
public Map<String, Integer> getItemUniqueIDMap() {
HashMap<String, Integer> map = new HashMap<String, Integer>();
return map;
}
}

View file

@ -0,0 +1,237 @@
package org.dynmap.fabric_1_21_5;
import net.minecraft.registry.RegistryKey;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.Heightmap;
import net.minecraft.world.LightType;
import net.minecraft.world.World;
import net.minecraft.world.border.WorldBorder;
import org.dynmap.DynmapChunk;
import org.dynmap.DynmapLocation;
import org.dynmap.DynmapWorld;
import org.dynmap.utils.MapChunkCache;
import org.dynmap.utils.Polygon;
import java.util.List;
public class FabricWorld extends DynmapWorld {
// TODO: Store this relative to World saves for integrated server
public static final String SAVED_WORLDS_FILE = "fabricworlds.yml";
private final DynmapPlugin plugin;
private World 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(DynmapPlugin plugin, World w) {
RegistryKey<World> rk = w.getRegistryKey();
if (rk == World.OVERWORLD) { // Overworld?
return w.getServer().getSaveProperties().getLevelName();
} else if (rk == World.END) {
return "DIM1";
} else if (rk == World.NETHER) {
return "DIM-1";
} else {
return rk.getValue().getNamespace() + "_" + rk.getValue().getPath();
}
}
public void updateWorld(World w) {
this.updateWorldHeights(w.getHeight(), w.getBottomY(), w.getSeaLevel());
}
public FabricWorld(DynmapPlugin plugin, World w) {
this(plugin, getWorldName(plugin, w), w.getHeight(),
w.getSeaLevel(),
w.getRegistryKey() == World.NETHER,
w.getRegistryKey() == World.END,
w.getRegistryKey().getValue().getPath(),
w.getBottomY());
setWorldLoaded(w);
}
public FabricWorld(DynmapPlugin plugin, String name, int height, int sealevel, boolean nether, boolean the_end, String deftitle, int miny) {
super(name, (height > maxWorldHeight) ? maxWorldHeight : height, sealevel, miny);
this.plugin = plugin;
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";
}
}
/* 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 spawnPos = world.getLevelProperties().getSpawnPos();
spawnloc.x = spawnPos.getX();
spawnloc.y = spawnPos.getY();
spawnloc.z = spawnPos.getZ();
spawnloc.world = this.getName();
}
return spawnloc;
}
/* Get world time */
@Override
public long getTime() {
if (world != null)
return world.getTimeOfDay();
else
return -1;
}
/* World is storming */
@Override
public boolean hasStorm() {
if (world != null)
return world.isRaining();
else
return false;
}
/* World is thundering */
@Override
public boolean isThundering() {
if (world != null)
return world.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(World w) {
world = w;
this.sealevel = w.getSeaLevel(); // Read actual current sealevel from world
// Update lighting table
for (int lightLevel = 0; lightLevel < 16; lightLevel++) {
// 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) lightLevel / 15.0f;
float brightness = value / (4.0f - 3.0f * value);
this.setBrightnessTableEntry(lightLevel, MathHelper.lerp(w.getDimension().ambientLight(), brightness, 1.0F));
}
}
/* Get light level of block */
@Override
public int getLightLevel(int x, int y, int z) {
if (world != null)
return world.getLightLevel(new BlockPos(x, y, z));
else
return -1;
}
/* Get highest Y coord of given location */
@Override
public int getHighestBlockYAt(int x, int z) {
if (world != null) {
return world.getChunk(x >> 4, z >> 4).getHeightmap(Heightmap.Type.MOTION_BLOCKING).get(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.getLightLevel(LightType.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<DynmapChunk> chunks) {
if (world != null) {
FabricMapChunkCache c = new FabricMapChunkCache(plugin);
c.setChunks(this, chunks);
return c;
}
return null;
}
public World getWorld() {
return world;
}
@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.getBoundWest(), wb.getBoundNorth());
p.addVertex(wb.getBoundWest(), wb.getBoundSouth());
p.addVertex(wb.getBoundEast(), wb.getBoundSouth());
p.addVertex(wb.getBoundEast(), wb.getBoundNorth());
return p;
}
}
return null;
}
}

View file

@ -0,0 +1,138 @@
package org.dynmap.fabric_1_21_5;
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.NbtCompound;
import net.minecraft.nbt.NbtElement;
import net.minecraft.nbt.NbtList;
import net.minecraft.util.collection.PackedIntegerArray;
public class NBT {
public static class NBTCompound implements GenericNBTCompound {
private final NbtCompound obj;
public NBTCompound(NbtCompound t) {
this.obj = t;
}
@Override
public Set<String> getAllKeys() {
return obj.getKeys();
}
@Override
public boolean contains(String s) {
return obj.contains(s);
}
@Override
public boolean contains(String s, int i) {
// Like contains, but with an extra constraint on type
NbtElement base = obj.get(s);
if (base == null)
return false;
int type = base.getType();
if (type == i)
return true;
else if (i != TAG_ANY_NUMERIC)
return false;
return type == TAG_BYTE || type == TAG_SHORT || type == TAG_INT || type == TAG_LONG || type == TAG_FLOAT
|| type == TAG_DOUBLE;
}
@Override
public byte getByte(String s) {
return obj.getByte(s, (byte)0);
}
@Override
public short getShort(String s) {
return obj.getShort(s, (short)0);
}
@Override
public int getInt(String s) {
return obj.getInt(s, (int)0);
}
@Override
public long getLong(String s) {
return obj.getLong(s, (long)0);
}
@Override
public float getFloat(String s) {
return obj.getFloat(s, (float)0);
}
@Override
public double getDouble(String s) {
return obj.getDouble(s, (double)0);
}
@Override
public String getString(String s) {
return obj.getString(s, "");
}
@Override
public byte[] getByteArray(String s) {
return obj.getByteArray(s).orElseGet(() -> new byte[0]);
}
@Override
public int[] getIntArray(String s) {
return obj.getIntArray(s).orElseGet(() -> new int[0]);
}
@Override
public long[] getLongArray(String s) {
return obj.getLongArray(s).orElseGet(() -> new long[0]);
}
@Override
public GenericNBTCompound getCompound(String s) {
return new NBTCompound(obj.getCompoundOrEmpty(s));
}
@Override
public GenericNBTList getList(String s, int i) {
// i argument used to be used to constrain list type, but nbt lists no longer have types as of 1.21.5
return new NBTList(obj.getListOrEmpty(s));
}
@Override
public boolean getBoolean(String s) {
return obj.getBoolean(s, false);
}
@Override
public String getAsString(String s) {
return obj.get(s).asString().orElse("");
}
@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 NbtList obj;
public NBTList(NbtList 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.getCompoundOrEmpty(idx));
}
public String toString() {
return obj.toString();
}
}
public static class OurBitStorage implements GenericBitStorage {
private final PackedIntegerArray bs;
public OurBitStorage(int bits, int count, long[] data) {
bs = new PackedIntegerArray(bits, count, data);
}
@Override
public int get(int idx) {
return bs.get(idx);
}
}
}

View file

@ -0,0 +1,38 @@
package org.dynmap.fabric_1_21_5;
import java.util.concurrent.FutureTask;
class TaskRecord implements Comparable<TaskRecord> {
TaskRecord(long ticktorun, long id, FutureTask<?> future) {
this.ticktorun = ticktorun;
this.id = id;
this.future = future;
}
private final long ticktorun;
private final long id;
private final FutureTask<?> future;
void run() {
this.future.run();
}
long getTickToRun() {
return this.ticktorun;
}
@Override
public int compareTo(TaskRecord o) {
if (this.ticktorun < o.ticktorun) {
return -1;
} else if (this.ticktorun > o.ticktorun) {
return 1;
} else if (this.id < o.id) {
return -1;
} else if (this.id > o.id) {
return 1;
} else {
return 0;
}
}
}

View file

@ -0,0 +1,98 @@
package org.dynmap.fabric_1_21_5;
import org.dynmap.DynmapCore;
import org.dynmap.Log;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
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();
}
}
}
}

View file

@ -0,0 +1,5 @@
package org.dynmap.fabric_1_21_5.access;
public interface ProtoChunkAccessor {
boolean getTouchedByWorldGen();
}

View file

@ -0,0 +1,9 @@
package org.dynmap.fabric_1_21_5.command;
import org.dynmap.fabric_1_21_5.DynmapPlugin;
public class DmapCommand extends DynmapCommandExecutor {
public DmapCommand(DynmapPlugin p) {
super("dmap", p);
}
}

View file

@ -0,0 +1,9 @@
package org.dynmap.fabric_1_21_5.command;
import org.dynmap.fabric_1_21_5.DynmapPlugin;
public class DmarkerCommand extends DynmapCommandExecutor {
public DmarkerCommand(DynmapPlugin p) {
super("dmarker", p);
}
}

View file

@ -0,0 +1,9 @@
package org.dynmap.fabric_1_21_5.command;
import org.dynmap.fabric_1_21_5.DynmapPlugin;
public class DynmapCommand extends DynmapCommandExecutor {
public DynmapCommand(DynmapPlugin p) {
super("dynmap", p);
}
}

View file

@ -0,0 +1,64 @@
package org.dynmap.fabric_1_21_5.command;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.tree.ArgumentCommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.mojang.brigadier.tree.RootCommandNode;
import net.minecraft.server.command.ServerCommandSource;
import java.util.Arrays;
import org.dynmap.fabric_1_21_5.DynmapPlugin;
import static com.mojang.brigadier.arguments.StringArgumentType.greedyString;
import static net.minecraft.server.command.CommandManager.argument;
import static net.minecraft.server.command.CommandManager.literal;
public class DynmapCommandExecutor implements Command<ServerCommandSource> {
private final String cmd;
private final DynmapPlugin plugin;
DynmapCommandExecutor(String cmd, DynmapPlugin plugin) {
this.cmd = cmd;
this.plugin = plugin;
}
public void register(CommandDispatcher<ServerCommandSource> dispatcher) {
final RootCommandNode<ServerCommandSource> root = dispatcher.getRoot();
final LiteralCommandNode<ServerCommandSource> command = literal(this.cmd)
.executes(this)
.build();
final ArgumentCommandNode<ServerCommandSource, String> args = argument("args", greedyString())
.executes(this)
.build();
// So this becomes "cmd" [args]
command.addChild(args);
// Add command to the command dispatcher via root node.
root.addChild(command);
}
@Override
public int run(CommandContext<ServerCommandSource> context) throws CommandSyntaxException {
// Commands in brigadier may be proxied in Minecraft via a syntax like `/execute ... ... run dmap [args]`
// Dynmap will fail to parse this properly, so we find the starting position of the actual command being parsed after any forks or redirects.
// The start position of the range specifies where the actual command dynmap has registered starts
int start = context.getRange().getStart();
String dynmapInput = context.getInput().substring(start);
String[] args = dynmapInput.split("\\s+");
plugin.handleCommand(context.getSource(), cmd, Arrays.copyOfRange(args, 1, args.length));
return 1;
}
// @Override // TODO: Usage?
public String getUsage(ServerCommandSource commandSource) {
return "Run /" + cmd + " help for details on using command";
}
}

View file

@ -0,0 +1,9 @@
package org.dynmap.fabric_1_21_5.command;
import org.dynmap.fabric_1_21_5.DynmapPlugin;
public class DynmapExpCommand extends DynmapCommandExecutor {
public DynmapExpCommand(DynmapPlugin p) {
super("dynmapexp", p);
}
}

View file

@ -0,0 +1,39 @@
package org.dynmap.fabric_1_21_5.event;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
public class BlockEvents {
private BlockEvents() {
}
public static Event<BlockCallback> BLOCK_EVENT = EventFactory.createArrayBacked(BlockCallback.class,
(listeners) -> (world, pos) -> {
for (BlockCallback callback : listeners) {
callback.onBlockEvent(world, pos);
}
}
);
public static Event<SignChangeCallback> SIGN_CHANGE_EVENT = EventFactory.createArrayBacked(SignChangeCallback.class,
(listeners) -> (world, pos, lines, player, front) -> {
for (SignChangeCallback callback : listeners) {
callback.onSignChange(world, pos, lines, player, front);
}
}
);
@FunctionalInterface
public interface BlockCallback {
void onBlockEvent(World world, BlockPos pos);
}
@FunctionalInterface
public interface SignChangeCallback {
void onSignChange(ServerWorld world, BlockPos pos, String[] lines, ServerPlayerEntity player, boolean front);
}
}

View file

@ -0,0 +1,21 @@
package org.dynmap.fabric_1_21_5.event;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.world.chunk.Chunk;
public class CustomServerChunkEvents {
public static Event<ChunkGenerate> CHUNK_GENERATE = EventFactory.createArrayBacked(ChunkGenerate.class,
(listeners) -> (world, chunk) -> {
for (ChunkGenerate callback : listeners) {
callback.onChunkGenerate(world, chunk);
}
}
);
@FunctionalInterface
public interface ChunkGenerate {
void onChunkGenerate(ServerWorld world, Chunk chunk);
}
}

View file

@ -0,0 +1,14 @@
package org.dynmap.fabric_1_21_5.event;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
public class CustomServerLifecycleEvents {
public static final Event<ServerLifecycleEvents.ServerStarted> SERVER_STARTED_PRE_WORLD_LOAD =
EventFactory.createArrayBacked(ServerLifecycleEvents.ServerStarted.class, (callbacks) -> (server) -> {
for (ServerLifecycleEvents.ServerStarted callback : callbacks) {
callback.onServerStarted(server);
}
});
}

View file

@ -0,0 +1,62 @@
package org.dynmap.fabric_1_21_5.event;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.server.network.ServerPlayerEntity;
public class PlayerEvents {
private PlayerEvents() {
}
public static Event<PlayerLoggedIn> PLAYER_LOGGED_IN = EventFactory.createArrayBacked(PlayerLoggedIn.class,
(listeners) -> (player) -> {
for (PlayerLoggedIn callback : listeners) {
callback.onPlayerLoggedIn(player);
}
}
);
public static Event<PlayerLoggedOut> PLAYER_LOGGED_OUT = EventFactory.createArrayBacked(PlayerLoggedOut.class,
(listeners) -> (player) -> {
for (PlayerLoggedOut callback : listeners) {
callback.onPlayerLoggedOut(player);
}
}
);
public static Event<PlayerChangedDimension> PLAYER_CHANGED_DIMENSION = EventFactory.createArrayBacked(PlayerChangedDimension.class,
(listeners) -> (player) -> {
for (PlayerChangedDimension callback : listeners) {
callback.onPlayerChangedDimension(player);
}
}
);
public static Event<PlayerRespawn> PLAYER_RESPAWN = EventFactory.createArrayBacked(PlayerRespawn.class,
(listeners) -> (player) -> {
for (PlayerRespawn callback : listeners) {
callback.onPlayerRespawn(player);
}
}
);
@FunctionalInterface
public interface PlayerLoggedIn {
void onPlayerLoggedIn(ServerPlayerEntity player);
}
@FunctionalInterface
public interface PlayerLoggedOut {
void onPlayerLoggedOut(ServerPlayerEntity player);
}
@FunctionalInterface
public interface PlayerChangedDimension {
void onPlayerChangedDimension(ServerPlayerEntity player);
}
@FunctionalInterface
public interface PlayerRespawn {
void onPlayerRespawn(ServerPlayerEntity player);
}
}

View file

@ -0,0 +1,23 @@
package org.dynmap.fabric_1_21_5.event;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.server.network.ServerPlayerEntity;
public class ServerChatEvents {
private ServerChatEvents() {
}
public static Event<ServerChatCallback> EVENT = EventFactory.createArrayBacked(ServerChatCallback.class,
(listeners) -> (player, message) -> {
for (ServerChatCallback callback : listeners) {
callback.onChatMessage(player, message);
}
}
);
@FunctionalInterface
public interface ServerChatCallback {
void onChatMessage(ServerPlayerEntity player, String message);
}
}

View file

@ -0,0 +1,11 @@
package org.dynmap.fabric_1_21_5.mixin;
import net.minecraft.world.biome.BiomeEffects;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(BiomeEffects.class)
public interface BiomeEffectsAccessor {
@Accessor
int getWaterColor();
}

View file

@ -0,0 +1,27 @@
package org.dynmap.fabric_1_21_5.mixin;
import net.minecraft.world.chunk.ChunkGenerating;
import net.minecraft.world.chunk.ChunkGenerationContext;
import net.minecraft.world.chunk.AbstractChunkHolder;
import net.minecraft.world.chunk.Chunk;
import org.dynmap.fabric_1_21_5.access.ProtoChunkAccessor;
import org.dynmap.fabric_1_21_5.event.CustomServerChunkEvents;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(value = ChunkGenerating.class, priority = 666 /* fire before Fabric API CHUNK_LOAD event */)
public abstract class ChunkGeneratingMixin {
@Inject(
/* Same place as fabric-lifecycle-events-v1 event CHUNK_LOAD (we will fire before it) */
method = "method_60553",
at = @At("TAIL")
)
private static void onChunkGenerate(Chunk chunk, ChunkGenerationContext chunkGenerationContext, AbstractChunkHolder chunkHolder, CallbackInfoReturnable<Chunk> callbackInfoReturnable) {
if (((ProtoChunkAccessor)chunk).getTouchedByWorldGen()) {
CustomServerChunkEvents.CHUNK_GENERATE.invoker().onChunkGenerate(chunkGenerationContext.world(), callbackInfoReturnable.getReturnValue());
}
}
}

View file

@ -0,0 +1,17 @@
package org.dynmap.fabric_1_21_5.mixin;
import net.minecraft.server.MinecraftServer;
import org.dynmap.fabric_1_21_5.event.CustomServerLifecycleEvents;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(MinecraftServer.class)
public class MinecraftServerMixin {
@Inject(method = "loadWorld", at = @At("HEAD"))
protected void loadWorld(CallbackInfo info) {
CustomServerLifecycleEvents.SERVER_STARTED_PRE_WORLD_LOAD.invoker().onServerStarted((MinecraftServer) (Object) this);
}
}

View file

@ -0,0 +1,32 @@
package org.dynmap.fabric_1_21_5.mixin;
import net.minecraft.entity.Entity;
import net.minecraft.network.ClientConnection;
import net.minecraft.server.PlayerManager;
import net.minecraft.server.network.ConnectedClientData;
import net.minecraft.server.network.ServerPlayerEntity;
import org.dynmap.fabric_1_21_5.event.PlayerEvents;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(PlayerManager.class)
public class PlayerManagerMixin {
@Inject(method = "onPlayerConnect", at = @At("TAIL"))
public void onPlayerConnect(ClientConnection connection, ServerPlayerEntity player, ConnectedClientData ccd, CallbackInfo info) {
PlayerEvents.PLAYER_LOGGED_IN.invoker().onPlayerLoggedIn(player);
}
@Inject(method = "remove", at = @At("HEAD"))
public void remove(ServerPlayerEntity player, CallbackInfo info) {
PlayerEvents.PLAYER_LOGGED_OUT.invoker().onPlayerLoggedOut(player);
}
@Inject(method = "respawnPlayer", at = @At("RETURN"))
public void respawnPlayer(ServerPlayerEntity player, boolean alive, Entity.RemovalReason removalReason, CallbackInfoReturnable<ServerPlayerEntity> info) {
PlayerEvents.PLAYER_RESPAWN.invoker().onPlayerRespawn(info.getReturnValue());
}
}

View file

@ -0,0 +1,31 @@
package org.dynmap.fabric_1_21_5.mixin;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.chunk.ProtoChunk;
import org.dynmap.fabric_1_21_5.access.ProtoChunkAccessor;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ProtoChunk.class)
public class ProtoChunkMixin implements ProtoChunkAccessor {
private boolean touchedByWorldGen = false;
@Inject(
method = "setBlockState",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/chunk/ChunkSection;setBlockState(IIILnet/minecraft/block/BlockState;)Lnet/minecraft/block/BlockState;"
)
)
public void setBlockState(BlockPos pos, BlockState state, int flags, CallbackInfoReturnable<BlockState> info) {
touchedByWorldGen = true;
}
public boolean getTouchedByWorldGen() {
return touchedByWorldGen;
}
}

View file

@ -0,0 +1,74 @@
package org.dynmap.fabric_1_21_5.mixin;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.SignBlockEntity;
import net.minecraft.network.message.FilterMask;
import net.minecraft.network.message.SignedMessage;
import net.minecraft.network.packet.c2s.play.UpdateSignC2SPacket;
import net.minecraft.server.filter.FilteredMessage;
import net.minecraft.server.filter.TextStream;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.Text;
import net.minecraft.util.math.BlockPos;
import java.util.Arrays;
import java.util.List;
import org.dynmap.fabric_1_21_5.event.BlockEvents;
import org.dynmap.fabric_1_21_5.event.ServerChatEvents;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
@Mixin(ServerPlayNetworkHandler.class)
public abstract class ServerPlayNetworkHandlerMixin {
@Shadow
public ServerPlayerEntity player;
@Inject(
method = "handleDecoratedMessage",
at = @At(
value = "HEAD"
)
)
public void onGameMessage(SignedMessage signedMessage, CallbackInfo ci) {
ServerChatEvents.EVENT.invoker().onChatMessage(player, signedMessage.getContent().getString());
}
@Inject(
method = "onSignUpdate",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/block/entity/SignBlockEntity;tryChangeText(Lnet/minecraft/entity/player/PlayerEntity;ZLjava/util/List;)V",
shift = At.Shift.BEFORE
),
locals = LocalCapture.CAPTURE_FAILHARD,
cancellable = true
)
public void onSignUpdate(UpdateSignC2SPacket packet, List<FilteredMessage> signText, CallbackInfo ci,
ServerWorld serverWorld, BlockPos blockPos, BlockEntity blockEntity, SignBlockEntity signBlockEntity)
{
// Pull the raw text from the input.
String[] rawTexts = new String[4];
for (int i=0; i<signText.size(); i++)
rawTexts[i] = signText.get(i).raw();
// Fire the event.
BlockEvents.SIGN_CHANGE_EVENT.invoker().onSignChange(serverWorld, blockPos, rawTexts, player, packet.isFront());
// Rebuild the signText list with the new values.
List<FilteredMessage> newSignText = Arrays.stream(rawTexts).map((raw) -> new FilteredMessage(raw, FilterMask.PASS_THROUGH)).toList();
// Execute the setting of the texts with the edited values.
signBlockEntity.tryChangeText(this.player, packet.isFront(), newSignText);
// Cancel the original tryChangeText() since we're calling it ourselves above.
ci.cancel();
}
}

View file

@ -0,0 +1,32 @@
package org.dynmap.fabric_1_21_5.mixin;
import net.minecraft.entity.Entity;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.world.TeleportTarget;
import org.dynmap.fabric_1_21_5.event.PlayerEvents;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ServerPlayerEntity.class)
public class ServerPlayerEntityMixin {
// @Inject(method = "teleport(Lnet/minecraft/server/world/ServerWorld;DDDFF)V", at = @At("RETURN"))
// public void teleport(ServerWorld targetWorld, double x, double y, double z, float yaw, float pitch, CallbackInfo info) {
// ServerPlayerEntity player = (ServerPlayerEntity) (Object) this;
// if (targetWorld != player.getServerWorld()) {
// PlayerEvents.PLAYER_CHANGED_DIMENSION.invoker().onPlayerChangedDimension(player);
// }
// }
@Inject(method = "teleportTo(Lnet/minecraft/world/TeleportTarget;)Lnet/minecraft/entity/Entity;", at = @At("TAIL"))
public void teleportTo(TeleportTarget teleportTarget, CallbackInfoReturnable<Entity> info) {
ServerPlayerEntity player = (ServerPlayerEntity) (Object) this;
if (player.getRemovalReason() == null) {
PlayerEvents.PLAYER_CHANGED_DIMENSION.invoker().onPlayerChangedDimension(player);
}
}
}

View file

@ -0,0 +1,26 @@
package org.dynmap.fabric_1_21_5.mixin;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.chunk.WorldChunk;
import org.dynmap.fabric_1_21_5.event.BlockEvents;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(WorldChunk.class)
public abstract class WorldChunkMixin {
@Shadow
public abstract World getWorld();
@Inject(method = "setBlockState", at = @At("RETURN"))
public void setBlockState(BlockPos pos, BlockState state, int flags, CallbackInfoReturnable<BlockState> info) {
if (info.getReturnValue() != null) {
BlockEvents.BLOCK_EVENT.invoker().onBlockEvent(this.getWorld(), pos);
}
}
}

View file

@ -0,0 +1,47 @@
package org.dynmap.fabric_1_21_5.permissions;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.minecraft.entity.player.PlayerEntity;
import org.dynmap.Log;
import org.dynmap.fabric_1_21_5.DynmapPlugin;
import org.dynmap.json.simple.parser.JSONParser;
import java.util.Set;
import java.util.stream.Collectors;
public class FabricPermissions implements PermissionProvider {
private String permissionKey(String perm) {
return "dynmap." + perm;
}
@Override
public Set<String> hasOfflinePermissions(String player, Set<String> perms) {
return perms.stream()
.filter(perm -> hasOfflinePermission(player, perm))
.collect(Collectors.toSet());
}
@Override
public boolean hasOfflinePermission(String player, String perm) {
return DynmapPlugin.plugin.isOp(player.toLowerCase());
}
@Override
public boolean has(PlayerEntity player, String permission) {
if (player == null) return false;
String name = player.getName().getString().toLowerCase();
if (DynmapPlugin.plugin.isOp(name)) return true;
return Permissions.check(player, permissionKey(permission));
}
@Override
public boolean hasPermissionNode(PlayerEntity player, String permission) {
if (player != null) {
String name = player.getName().getString().toLowerCase();
return DynmapPlugin.plugin.isOp(name);
}
return false;
}
}

View file

@ -0,0 +1,103 @@
package org.dynmap.fabric_1_21_5.permissions;
import net.minecraft.entity.player.PlayerEntity;
import org.dynmap.ConfigurationNode;
import org.dynmap.Log;
import org.dynmap.fabric_1_21_5.DynmapPlugin;
import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class FilePermissions implements PermissionProvider {
private HashMap<String, Set<String>> perms;
private Set<String> 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<String, Set<String>>();
for (String k : cfg.keySet()) {
List<String> p = cfg.getStrings(k, null);
if (p != null) {
k = k.toLowerCase();
HashSet<String> pset = new HashSet<String>();
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<String> ps = perms.get(player);
if ((ps != null) && (ps.contains(perm))) {
return true;
}
if (defperms.contains(perm)) {
return true;
}
return false;
}
@Override
public Set<String> hasOfflinePermissions(String player, Set<String> perms) {
player = player.toLowerCase();
HashSet<String> rslt = new HashSet<String>();
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(PlayerEntity psender, String permission) {
if (psender != null) {
String n = psender.getName().getString().toLowerCase();
return hasPerm(n, permission);
}
return true;
}
@Override
public boolean hasPermissionNode(PlayerEntity psender, String permission) {
if (psender != null) {
String player = psender.getName().getString().toLowerCase();
return DynmapPlugin.plugin.isOp(player);
}
return false;
}
}

View file

@ -0,0 +1,102 @@
package org.dynmap.fabric_1_21_5.permissions;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.cacheddata.CachedPermissionData;
import net.luckperms.api.model.user.User;
import net.luckperms.api.util.Tristate;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.MinecraftServer;
import org.dynmap.Log;
import org.dynmap.fabric_1_21_5.DynmapPlugin;
import org.dynmap.json.simple.JSONArray;
import org.dynmap.json.simple.JSONObject;
import org.dynmap.json.simple.parser.JSONParser;
import org.dynmap.json.simple.parser.ParseException;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
public class LuckPermissions implements PermissionProvider {
private final JSONParser parser = new JSONParser();
private LuckPerms api = null;
private Optional<LuckPerms> getApi() {
if (api != null) return Optional.of(api);
try {
api = LuckPermsProvider.get();
return Optional.of(api);
} catch (Exception ex) {
Log.warning("Trying to access LuckPerms before it has loaded");
return Optional.empty();
}
}
private Optional<UUID> cachedUUID(String username) {
try {
BufferedReader reader = new BufferedReader(new FileReader("usercache.json"));
JSONArray cache = (JSONArray) parser.parse(reader);
for (Object it : cache) {
JSONObject user = (JSONObject) it;
if (user.get("name").toString().equalsIgnoreCase(username)) {
String uuid = user.get("uuid").toString();
return Optional.of(UUID.fromString(uuid));
}
}
reader.close();
} catch (IOException | ParseException ex) {
Log.warning("Unable to read usercache.json");
}
return Optional.empty();
}
private String permissionKey(String perm) {
return "dynmap." + perm;
}
@Override
public Set<String> hasOfflinePermissions(String player, Set<String> perms) {
return perms.stream()
.filter(perm -> hasOfflinePermission(player, perm))
.collect(Collectors.toSet());
}
@Override
public boolean hasOfflinePermission(String player, String perm) {
if (DynmapPlugin.plugin.isOp(player.toLowerCase())) return true;
Optional<LuckPerms> api = getApi();
Optional<UUID> uuid = cachedUUID(player);
if (!uuid.isPresent() || !api.isPresent()) return false;
User user = api.get().getUserManager().loadUser(uuid.get()).join();
CachedPermissionData permissions = user.getCachedData().getPermissionData();
Tristate state = permissions.checkPermission(permissionKey(perm));
return state.asBoolean();
}
@Override
public boolean has(PlayerEntity player, String permission) {
if (player == null) return false;
String name = player.getName().getString().toLowerCase();
if (DynmapPlugin.plugin.isOp(name)) return true;
return Permissions.check(player, permissionKey(permission));
}
@Override
public boolean hasPermissionNode(PlayerEntity player, String permission) {
if (player != null) {
String name = player.getName().getString().toLowerCase();
return DynmapPlugin.plugin.isOp(name);
}
return false;
}
}

View file

@ -0,0 +1,52 @@
package org.dynmap.fabric_1_21_5.permissions;
import net.minecraft.entity.player.PlayerEntity;
import org.dynmap.Log;
import org.dynmap.fabric_1_21_5.DynmapPlugin;
import java.util.HashSet;
import java.util.Set;
public class OpPermissions implements PermissionProvider {
public HashSet<String> usrCommands = new HashSet<String>();
public OpPermissions(String[] usrCommands) {
for (String usrCommand : usrCommands) {
this.usrCommands.add(usrCommand);
}
Log.info("Using ops.txt for access control");
}
@Override
public Set<String> hasOfflinePermissions(String player, Set<String> perms) {
HashSet<String> rslt = new HashSet<String>();
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(PlayerEntity 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(PlayerEntity psender, String permission) {
if (psender != null) {
return DynmapPlugin.plugin.isOp(psender.getName().getString());
}
return true;
}
}

View file

@ -0,0 +1,16 @@
package org.dynmap.fabric_1_21_5.permissions;
import net.minecraft.entity.player.PlayerEntity;
import java.util.Set;
public interface PermissionProvider {
boolean has(PlayerEntity sender, String permission);
boolean hasPermissionNode(PlayerEntity sender, String permission);
Set<String> hasOfflinePermissions(String player, Set<String> perms);
boolean hasOfflinePermission(String player, String perm);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View file

@ -0,0 +1,498 @@
# 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-access-key-id>"
#aws_secret_access_key: "<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"
# (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, webp-l),
# 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
- 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
# 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!)"

View file

@ -0,0 +1,3 @@
accessWidener v1 named
accessible class net/minecraft/world/biome/Biome$Weather
accessible field net/minecraft/world/biome/Biome weather Lnet/minecraft/world/biome/Biome$Weather;

View file

@ -0,0 +1,19 @@
{
"required": true,
"minVersion": "0.8",
"package": "org.dynmap.fabric_1_21_5.mixin",
"compatibilityLevel": "JAVA_17",
"mixins": [
"BiomeEffectsAccessor",
"ChunkGeneratingMixin",
"MinecraftServerMixin",
"PlayerManagerMixin",
"ProtoChunkMixin",
"ServerPlayerEntityMixin",
"ServerPlayNetworkHandlerMixin",
"WorldChunkMixin"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -0,0 +1,34 @@
{
"schemaVersion": 1,
"id": "dynmap",
"version": "${version}",
"name": "Dynmap",
"description": "Dynamic, Google-maps style rendered maps for your Minecraft server",
"authors": [
"mikeprimm",
"LolHens",
"i509VCB"
],
"contact": {
"homepage": "https://github.com/webbukkit/dynmap",
"sources": "https://github.com/webbukkit/dynmap"
},
"license": "Apache-2.0",
"icon": "assets/dynmap/icon.png",
"environment": "*",
"entrypoints": {
"main": [
"org.dynmap.fabric_1_21_5.DynmapMod"
]
},
"mixins": [
"dynmap.mixins.json"
],
"accessWidener" : "dynmap.accesswidener",
"depends": {
"fabricloader": ">=0.16.9",
"fabric": ">=0.108.0",
"minecraft": ["1.21.5"]
}
}

View file

@ -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

View file

@ -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

View file

@ -35,6 +35,7 @@ include ':DynmapCore'
include ':DynmapCoreAPI'
include ':fabric-1.21.1'
include ':fabric-1.21.3'
include ':fabric-1.21.5'
include ':fabric-1.21'
include ':fabric-1.20.6'
include ':fabric-1.19.4'
@ -80,6 +81,7 @@ project(':DynmapCore').projectDir = "$rootDir/DynmapCore" as File
project(':DynmapCoreAPI').projectDir = "$rootDir/DynmapCoreAPI" as File
project(':fabric-1.21.1').projectDir = "$rootDir/fabric-1.21.1" as File
project(':fabric-1.21.3').projectDir = "$rootDir/fabric-1.21.3" as File
project(':fabric-1.21.5').projectDir = "$rootDir/fabric-1.21.5" as File
project(':fabric-1.21').projectDir = "$rootDir/fabric-1.21" as File
project(':fabric-1.20.6').projectDir = "$rootDir/fabric-1.20.6" as File
project(':fabric-1.19.4').projectDir = "$rootDir/fabric-1.19.4" as File