Skip to content

Commit ab86ccf

Browse files
committed
Datapack Support
1 parent 1ec7391 commit ab86ccf

File tree

9 files changed

+2319
-2
lines changed

9 files changed

+2319
-2
lines changed

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,41 @@ You can also override per dimension:
3131

3232
Per-dimension values take priority when set; use 0 to fall back to the global border.
3333

34+
### Datapack Structures Import
35+
Import a datapack URL so its custom structures appear on the SeedMap. Datapack structures render as solid colored squares (one color per structure) and show up in the datapack toggle list so you can enable/disable them like vanilla features. You can also right‑click a datapack structure and mark it complete/incomplete (a green tick is drawn on the icon).
36+
37+
Datapack structures load differently to vanilla: vanilla features are built-in, while datapack structures are parsed from the provided pack and then cached. They load in the background and appear progressively as tiles are processed.
38+
39+
Version mismatch handling: if the server version doesn't match the client, SeedMapper avoids loading registries that frequently fail across versions (notably enchantments and enchantment providers). This lets the worldgen data load even when other datapack content is incompatible.
40+
41+
Caching scope: datapack structures are cached for the current game session. Switching dimensions or servers keeps the cached structures available, but restarting the game will reload them.
42+
43+
Import a datapack:
44+
- ```/sm:datapack import <url>```
45+
46+
Save the last imported URL for the current server (by IP):
47+
- ```/sm:datapack save```
48+
49+
Load the saved URL for the current server:
50+
- ```/sm:datapack load```
51+
52+
Read the current datapack URL (last imported or saved for this server):
53+
- ```/sm:datapack read```
54+
55+
Change the datapack structure color scheme (applies immediately):
56+
- ```/sm:datapack colorscheme 1``` - current scheme
57+
- ```/sm:datapack colorscheme 2``` - secondary scheme
58+
- ```/sm:datapack colorscheme 3``` - third scheme
59+
60+
Enable or disable auto-loading on join:
61+
- ```/sm:datapack autoload true```
62+
- ```/sm:datapack autoload false```
63+
64+
Autoload uses the saved URL for the server you are joining but will prefer the cached datapack copy if it exists. The URL and cache is keyed by the server address.
65+
66+
67+
![Datapack](https://i.imgur.com/8coATaJ.png)
68+
3469
### Double Click Recenter
3570
Double clicking anywhere on the map will recenter the map to the players location.
3671

src/main/java/dev/xpple/seedmapper/SeedMapper.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import dev.xpple.seedmapper.command.commands.SeedMapCommand;
2020
import dev.xpple.seedmapper.command.commands.SourceCommand;
2121
import dev.xpple.seedmapper.command.commands.StopTaskCommand;
22+
import dev.xpple.seedmapper.command.commands.DatapackImportCommand;
2223
import dev.xpple.seedmapper.config.Configs;
2324
import dev.xpple.seedmapper.config.DurationAdapter;
2425
import dev.xpple.seedmapper.config.MapFeatureAdapter;
@@ -114,6 +115,7 @@ private static void registerCommands(CommandDispatcher<FabricClientCommandSource
114115
DiscordCommand.register(dispatcher);
115116
SampleCommand.register(dispatcher);
116117
ExportLootCommand.register(dispatcher);
118+
DatapackImportCommand.register(dispatcher);
117119
// ESP config command
118120
dev.xpple.seedmapper.command.commands.EspConfigCommand.register(dispatcher);
119121
}
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package dev.xpple.seedmapper.command.commands;
2+
3+
import com.github.cubiomes.Cubiomes;
4+
import com.mojang.brigadier.Command;
5+
import com.mojang.brigadier.StringReader;
6+
import com.mojang.brigadier.arguments.BoolArgumentType;
7+
import com.mojang.brigadier.arguments.IntegerArgumentType;
8+
import com.mojang.brigadier.arguments.StringArgumentType;
9+
import com.mojang.brigadier.context.CommandContext;
10+
import com.mojang.brigadier.exceptions.CommandSyntaxException;
11+
import dev.xpple.seedmapper.command.CustomClientCommandSource;
12+
import dev.xpple.seedmapper.config.Configs;
13+
import dev.xpple.seedmapper.datapack.DatapackStructureManager;
14+
import dev.xpple.seedmapper.util.SeedIdentifier;
15+
import dev.xpple.seedmapper.util.WorldIdentifier;
16+
import dev.xpple.seedmapper.command.arguments.DimensionArgument;
17+
import dev.xpple.seedmapper.seedmap.SeedMapScreen;
18+
import dev.xpple.seedmapper.seedmap.SeedMapMinimapManager;
19+
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
20+
import net.minecraft.client.Minecraft;
21+
import net.minecraft.network.chat.Component;
22+
import net.minecraft.server.permissions.PermissionSet;
23+
24+
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument;
25+
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
26+
27+
public class DatapackImportCommand {
28+
private static final String COMMAND = "sm:datapack";
29+
30+
public static void register(com.mojang.brigadier.CommandDispatcher<FabricClientCommandSource> dispatcher) {
31+
dispatcher.register(literal(COMMAND)
32+
.then(literal("import")
33+
.then(argument("url", StringArgumentType.greedyString())
34+
.executes(DatapackImportCommand::execute)))
35+
.then(literal("autoload")
36+
.then(argument("enabled", BoolArgumentType.bool())
37+
.executes(DatapackImportCommand::toggleAutoload)))
38+
.then(literal("save")
39+
.executes(DatapackImportCommand::save))
40+
.then(literal("load")
41+
.executes(DatapackImportCommand::load))
42+
.then(literal("read")
43+
.executes(DatapackImportCommand::read))
44+
.then(literal("colorscheme")
45+
.then(argument("scheme", IntegerArgumentType.integer(1, 3))
46+
.executes(DatapackImportCommand::setColorScheme))));
47+
}
48+
49+
@SuppressWarnings("unused")
50+
private static int execute(CommandContext<FabricClientCommandSource> context) {
51+
CustomClientCommandSource source = CustomClientCommandSource.of(context.getSource());
52+
String url = StringArgumentType.getString(context, "url").trim();
53+
return importUrl(source, url);
54+
}
55+
56+
private static int toggleAutoload(CommandContext<FabricClientCommandSource> context) {
57+
CustomClientCommandSource source = CustomClientCommandSource.of(context.getSource());
58+
boolean enabled = BoolArgumentType.getBool(context, "enabled");
59+
Configs.DatapackAutoload = enabled;
60+
Configs.save();
61+
source.sendFeedback(Component.translatable("seedMap.datapackImport.autoloadSet", enabled));
62+
return Command.SINGLE_SUCCESS;
63+
}
64+
65+
private static int save(CommandContext<FabricClientCommandSource> context) {
66+
CustomClientCommandSource source = CustomClientCommandSource.of(context.getSource());
67+
String url = DatapackStructureManager.getLastImportedUrl();
68+
if (url == null || url.isBlank()) {
69+
source.sendError(Component.translatable("seedMap.datapackImport.save.noImported"));
70+
return Command.SINGLE_SUCCESS;
71+
}
72+
java.nio.file.Path cachePath = DatapackStructureManager.getLastImportedCachePath();
73+
if (!Configs.saveDatapackForCurrentServer(url, cachePath)) {
74+
source.sendError(Component.translatable("seedMap.datapackImport.save.noServer"));
75+
return Command.SINGLE_SUCCESS;
76+
}
77+
source.sendFeedback(Component.translatable("seedMap.datapackImport.save.success"));
78+
return Command.SINGLE_SUCCESS;
79+
}
80+
81+
private static int load(CommandContext<FabricClientCommandSource> context) {
82+
CustomClientCommandSource source = CustomClientCommandSource.of(context.getSource());
83+
String url = Configs.getSavedDatapackUrlForCurrentServer();
84+
java.nio.file.Path cachePath = Configs.getSavedDatapackCachePathForCurrentServer();
85+
if ((url == null || url.isBlank()) && cachePath == null) {
86+
source.sendError(Component.translatable("seedMap.datapackImport.load.none"));
87+
return Command.SINGLE_SUCCESS;
88+
}
89+
return importSavedWithFallback(source, url, cachePath);
90+
}
91+
92+
private static int read(CommandContext<FabricClientCommandSource> context) {
93+
CustomClientCommandSource source = CustomClientCommandSource.of(context.getSource());
94+
String url = DatapackStructureManager.getLastImportedUrl();
95+
if (url == null || url.isBlank()) {
96+
url = Configs.getSavedDatapackUrlForCurrentServer();
97+
}
98+
if (url == null || url.isBlank()) {
99+
source.sendError(Component.translatable("seedMap.datapackImport.read.none"));
100+
return Command.SINGLE_SUCCESS;
101+
}
102+
source.sendFeedback(Component.translatable("seedMap.datapackImport.read.current", url));
103+
return Command.SINGLE_SUCCESS;
104+
}
105+
106+
private static int setColorScheme(CommandContext<FabricClientCommandSource> context) {
107+
CustomClientCommandSource source = CustomClientCommandSource.of(context.getSource());
108+
int scheme = IntegerArgumentType.getInteger(context, "scheme");
109+
Configs.DatapackColorScheme = scheme;
110+
Configs.save();
111+
DatapackStructureManager.clearColorSchemeCache();
112+
source.sendFeedback(Component.translatable("seedMap.datapackImport.colorschemeSet", scheme));
113+
try {
114+
int generatorFlags = source.getGeneratorFlags();
115+
SeedMapScreen.reopenIfOpen(generatorFlags);
116+
SeedMapMinimapManager.refreshIfOpenWithGeneratorFlags(generatorFlags);
117+
} catch (CommandSyntaxException ignored) {
118+
SeedMapScreen.reopenIfOpen(0);
119+
SeedMapMinimapManager.refreshIfOpenWithGeneratorFlags(0);
120+
}
121+
return Command.SINGLE_SUCCESS;
122+
}
123+
124+
public static int importUrl(CustomClientCommandSource source, String url) {
125+
if (url.isBlank()) {
126+
source.sendError(Component.translatable("seedMap.datapackImport.status.invalidUrl"));
127+
return Command.SINGLE_SUCCESS;
128+
}
129+
130+
SeedIdentifier seed = Configs.Seed;
131+
if (seed == null) {
132+
source.sendError(Component.translatable("seedMap.datapackImport.noSeed"));
133+
return Command.SINGLE_SUCCESS;
134+
}
135+
136+
int dimension;
137+
try {
138+
String dimensionPath = source.getWorld().dimension().identifier().getPath();
139+
dimension = DimensionArgument.dimension().parse(new StringReader(dimensionPath));
140+
} catch (CommandSyntaxException e) {
141+
source.sendError(Component.translatable("seedMap.datapackImport.dimensionError"));
142+
return Command.SINGLE_SUCCESS;
143+
}
144+
145+
WorldIdentifier identifier = new WorldIdentifier(seed.seed(), dimension, seed.version(), seed.generatorFlags());
146+
source.sendFeedback(Component.translatable("seedMap.datapackImport.started"));
147+
DatapackStructureManager.importDatapack(identifier, url, source::sendFeedback, source::sendError);
148+
return Command.SINGLE_SUCCESS;
149+
}
150+
151+
private static int importSavedWithFallback(CustomClientCommandSource source, String url, java.nio.file.Path cachePath) {
152+
SeedIdentifier seed = Configs.Seed;
153+
if (seed == null) {
154+
source.sendError(Component.translatable("seedMap.datapackImport.noSeed"));
155+
return Command.SINGLE_SUCCESS;
156+
}
157+
158+
int dimension;
159+
try {
160+
String dimensionPath = source.getWorld().dimension().identifier().getPath();
161+
dimension = DimensionArgument.dimension().parse(new StringReader(dimensionPath));
162+
} catch (CommandSyntaxException e) {
163+
source.sendError(Component.translatable("seedMap.datapackImport.dimensionError"));
164+
return Command.SINGLE_SUCCESS;
165+
}
166+
167+
WorldIdentifier identifier = new WorldIdentifier(seed.seed(), dimension, seed.version(), seed.generatorFlags());
168+
source.sendFeedback(Component.translatable("seedMap.datapackImport.started"));
169+
DatapackStructureManager.importDatapackWithFallback(identifier, cachePath, url, source::sendFeedback, source::sendError);
170+
return Command.SINGLE_SUCCESS;
171+
}
172+
173+
public static void importUrlForCurrentServer(String url) {
174+
Minecraft minecraft = Minecraft.getInstance();
175+
if (minecraft == null || minecraft.player == null || minecraft.level == null) {
176+
return;
177+
}
178+
CustomClientCommandSource source = new CustomClientCommandSource(
179+
minecraft.getConnection(),
180+
minecraft,
181+
minecraft.player,
182+
minecraft.player.position(),
183+
minecraft.player.getRotationVector(),
184+
minecraft.level,
185+
PermissionSet.NO_PERMISSIONS,
186+
new java.util.HashMap<>()
187+
);
188+
java.nio.file.Path cachePath = Configs.getSavedDatapackCachePathForCurrentServer();
189+
importSavedWithFallback(source, url, cachePath);
190+
}
191+
}

src/main/java/dev/xpple/seedmapper/config/Configs.java

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,22 @@ private static Component displaySavedSeeds() {
7171
@Config
7272
public static boolean AutoApplySeedCrackerSeed = true;
7373

74+
@Config
75+
public static boolean DatapackAutoload = false;
76+
77+
@Config
78+
public static Map<String, String> DatapackSavedUrls = new HashMap<>();
79+
80+
@Config
81+
public static Map<String, String> DatapackSavedCachePaths = new HashMap<>();
82+
83+
@Config(setter = @Config.Setter("setDatapackColorScheme"))
84+
public static int DatapackColorScheme = 1;
85+
86+
private static void setDatapackColorScheme(int scheme) {
87+
DatapackColorScheme = Math.clamp(scheme, 1, 3);
88+
}
89+
7490
public static String getCurrentServerKey() {
7591
Minecraft minecraft = Minecraft.getInstance();
7692
if (minecraft == null || minecraft.getConnection() == null || minecraft.getConnection().getConnection() == null) {
@@ -80,6 +96,62 @@ public static String getCurrentServerKey() {
8096
return remoteAddress != null ? remoteAddress.toString() : null;
8197
}
8298

99+
public static String getSavedDatapackUrlForCurrentServer() {
100+
String key = getCurrentServerKey();
101+
if (key == null) {
102+
return null;
103+
}
104+
String url = DatapackSavedUrls.get(key);
105+
return (url == null || url.isBlank()) ? null : url;
106+
}
107+
108+
public static java.nio.file.Path getSavedDatapackCachePathForCurrentServer() {
109+
String key = getCurrentServerKey();
110+
if (key == null) {
111+
return null;
112+
}
113+
String raw = DatapackSavedCachePaths.get(key);
114+
if (raw == null || raw.isBlank()) {
115+
return null;
116+
}
117+
return java.nio.file.Path.of(raw);
118+
}
119+
120+
public static boolean saveDatapackUrlForCurrentServer(String url) {
121+
String key = getCurrentServerKey();
122+
if (key == null || url == null || url.isBlank()) {
123+
return false;
124+
}
125+
String trimmed = url.trim();
126+
DatapackSavedUrls.put(key, trimmed);
127+
save();
128+
return true;
129+
}
130+
131+
public static boolean saveDatapackForCurrentServer(String url, java.nio.file.Path cachePath) {
132+
String key = getCurrentServerKey();
133+
if (key == null || url == null || url.isBlank()) {
134+
return false;
135+
}
136+
String trimmed = url.trim();
137+
DatapackSavedUrls.put(key, trimmed);
138+
if (cachePath != null) {
139+
DatapackSavedCachePaths.put(key, cachePath.toString());
140+
}
141+
save();
142+
return true;
143+
}
144+
145+
public static void removeDatapackUrlForCurrentServer() {
146+
String key = getCurrentServerKey();
147+
if (key == null) {
148+
return;
149+
}
150+
DatapackSavedUrls.remove(key);
151+
DatapackSavedCachePaths.remove(key);
152+
save();
153+
}
154+
83155
public static void applySeedForCurrentServer(long seed, boolean storeAsSavedSeed) {
84156
SeedIdentifier identifier = new SeedIdentifier(seed);
85157
String key = getCurrentServerKey();
@@ -261,6 +333,13 @@ private static Component getPlayerDirectionArrowComment() {
261333
return Component.translatable("config.showPlayerDirectionArrow.comment");
262334
}
263335

336+
@Config(comment = "getShowDatapackStructuresComment")
337+
public static boolean ShowDatapackStructures = true;
338+
339+
private static Component getShowDatapackStructuresComment() {
340+
return Component.translatable("config.showDatapackStructures.comment");
341+
}
342+
264343
@Config(comment = "getManualWaypointCompassOverlayComment")
265344
public static boolean ManualWaypointCompassOverlay = false;
266345

@@ -330,6 +409,45 @@ public static void setSeedMapCompletedStructures(String worldIdentifier, java.ut
330409
SeedMapCompletedStructures.put(worldIdentifier, String.join(",", entries));
331410
}
332411

412+
@Config
413+
public static Map<String, String> DatapackStructureDisabled = new HashMap<>();
414+
415+
public static java.util.Set<String> getDatapackStructureDisabled(String worldIdentifier) {
416+
if (worldIdentifier == null || worldIdentifier.isBlank()) {
417+
return new java.util.HashSet<>();
418+
}
419+
String raw = DatapackStructureDisabled.get(worldIdentifier);
420+
if (raw == null || raw.isBlank()) {
421+
return new java.util.HashSet<>();
422+
}
423+
java.util.Set<String> entries = new java.util.HashSet<>();
424+
for (String part : raw.split(",")) {
425+
if (!part.isBlank()) {
426+
entries.add(part.trim());
427+
}
428+
}
429+
return entries;
430+
}
431+
432+
public static void setDatapackStructureDisabled(String worldIdentifier, java.util.Set<String> entries) {
433+
if (worldIdentifier == null || worldIdentifier.isBlank()) {
434+
return;
435+
}
436+
if (entries == null || entries.isEmpty()) {
437+
DatapackStructureDisabled.remove(worldIdentifier);
438+
return;
439+
}
440+
DatapackStructureDisabled.put(worldIdentifier, String.join(",", entries));
441+
}
442+
443+
public static boolean isDatapackStructureEnabled(String worldIdentifier, String structureId) {
444+
if (structureId == null || structureId.isBlank()) {
445+
return true;
446+
}
447+
java.util.Set<String> disabled = getDatapackStructureDisabled(worldIdentifier);
448+
return !disabled.contains(structureId);
449+
}
450+
333451
public static void applyWaypointCompassOverlaySetting() {
334452
try {
335453
dev.xpple.simplewaypoints.config.Configs.waypointMarkerRenderLimit = 0;

0 commit comments

Comments
 (0)