Skip to content

Commit a210f9c

Browse files
committed
Export Loot Command
1 parent 238fc61 commit a210f9c

File tree

5 files changed

+416
-102
lines changed

5 files changed

+416
-102
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,11 @@ Can now finally remove SeedMapper waypoints with via a right click context menu.
9191
Can now change the default 5 minute render timeout with ```/sm:config EspTimeoutMinutes```
9292

9393
### Export Loot Table
94-
Can now export the entire loot table for the map you're viewing by clicking ```Export Loot``` or via commands such as ```/sm:exportLoot <radius> [dimension] [structures/all]```.
94+
Can now export the entire loot table for the map you're viewing by clicking ```Export Loot``` or via commands such as ```/sm:exportloot <radius> [dimension] [structures/all]```.
9595

9696
Exported data will be located in ```SeedMapper/loot/<Server IP>_<Seed>-<Date/Time>.json```
9797

9898
### Auto Apply SeedCracker Seed
9999
If the server you're in already has a seed in the database it will be auto applied and saved.
100100

101-
You can enable/disable this with ```/sm:config AutoApplySeedCrackerSeed true/false```
101+
You can enable/disable this with ```/sm:config AutoApplySeedCrackerSeed true/false```

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import dev.xpple.seedmapper.command.commands.HighlightCommand;
1313
import dev.xpple.seedmapper.command.commands.LocateCommand;
1414
import dev.xpple.seedmapper.command.commands.MinimapCommand;
15+
import dev.xpple.seedmapper.command.commands.ExportLootCommand;
1516
import dev.xpple.seedmapper.command.commands.SampleCommand;
1617
import dev.xpple.seedmapper.command.commands.SeedMapCommand;
1718
import dev.xpple.seedmapper.command.commands.SourceCommand;
@@ -113,6 +114,7 @@ private static void registerCommands(CommandDispatcher<FabricClientCommandSource
113114
WorldPresetCommand.register(dispatcher);
114115
DiscordCommand.register(dispatcher);
115116
SampleCommand.register(dispatcher);
117+
ExportLootCommand.register(dispatcher);
116118
// ESP config command
117119
dev.xpple.seedmapper.command.commands.EspConfigCommand.register(dispatcher);
118120
}
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
package dev.xpple.seedmapper.command.commands;
2+
3+
import com.github.cubiomes.Cubiomes;
4+
import com.github.cubiomes.Generator;
5+
import com.github.cubiomes.Pos;
6+
import com.github.cubiomes.StructureConfig;
7+
import com.github.cubiomes.SurfaceNoise;
8+
import com.mojang.brigadier.CommandDispatcher;
9+
import com.mojang.brigadier.arguments.IntegerArgumentType;
10+
import com.mojang.brigadier.arguments.StringArgumentType;
11+
import com.mojang.brigadier.exceptions.CommandSyntaxException;
12+
import dev.xpple.seedmapper.command.CommandExceptions;
13+
import dev.xpple.seedmapper.command.CustomClientCommandSource;
14+
import dev.xpple.seedmapper.command.arguments.DimensionArgument;
15+
import dev.xpple.seedmapper.feature.StructureChecks;
16+
import dev.xpple.seedmapper.util.LootExportHelper;
17+
import dev.xpple.seedmapper.world.WorldPresetManager;
18+
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
19+
import net.minecraft.core.BlockPos;
20+
import net.minecraft.network.chat.Component;
21+
import net.minecraft.util.Mth;
22+
23+
import java.io.IOException;
24+
import java.lang.foreign.Arena;
25+
import java.lang.foreign.MemorySegment;
26+
import java.util.ArrayList;
27+
import java.util.Arrays;
28+
import java.util.HashSet;
29+
import java.util.List;
30+
import java.util.Set;
31+
import java.util.stream.Collectors;
32+
33+
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument;
34+
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
35+
36+
public final class ExportLootCommand {
37+
38+
private ExportLootCommand() {
39+
}
40+
41+
public static void register(CommandDispatcher<FabricClientCommandSource> dispatcher) {
42+
var root = literal("sm:exportloot");
43+
var radiusArg = argument("radius", IntegerArgumentType.integer(1))
44+
.executes(ctx -> exportLoot(
45+
CustomClientCommandSource.of(ctx.getSource()),
46+
IntegerArgumentType.getInteger(ctx, "radius"),
47+
null,
48+
""
49+
));
50+
var dimensionArg = argument("dimension", DimensionArgument.dimension())
51+
.executes(ctx -> exportLoot(
52+
CustomClientCommandSource.of(ctx.getSource()),
53+
IntegerArgumentType.getInteger(ctx, "radius"),
54+
DimensionArgument.getDimension(ctx, "dimension"),
55+
""
56+
));
57+
var structuresArg = argument("structures", StringArgumentType.greedyString())
58+
.executes(ctx -> exportLoot(
59+
CustomClientCommandSource.of(ctx.getSource()),
60+
IntegerArgumentType.getInteger(ctx, "radius"),
61+
DimensionArgument.getDimension(ctx, "dimension"),
62+
StringArgumentType.getString(ctx, "structures")
63+
));
64+
65+
dimensionArg = dimensionArg.then(structuresArg);
66+
radiusArg = radiusArg.then(dimensionArg);
67+
dispatcher.register(root.then(radiusArg));
68+
}
69+
70+
private static int exportLoot(CustomClientCommandSource source, int radius, Integer dimensionArg, String structuresFilter) throws CommandSyntaxException {
71+
int version = source.getVersion();
72+
if (version <= Cubiomes.MC_1_12()) {
73+
throw CommandExceptions.LOOT_NOT_SUPPORTED_EXCEPTION.create();
74+
}
75+
int dimension = dimensionArg == null ? source.getDimension() : dimensionArg;
76+
long seed = source.getSeed().getSecond();
77+
int centerX = Mth.floor(source.getPosition().x());
78+
int centerZ = Mth.floor(source.getPosition().z());
79+
80+
Set<Integer> filterStructures = parseStructureFilter(structuresFilter, version);
81+
82+
try (Arena arena = Arena.ofConfined()) {
83+
MemorySegment generator = Generator.allocate(arena);
84+
Cubiomes.setupGenerator(generator, version, WorldPresetManager.activePreset().generatorFlags());
85+
Cubiomes.applySeed(generator, dimension, seed);
86+
87+
MemorySegment surfaceNoise = SurfaceNoise.allocate(arena);
88+
Cubiomes.initSurfaceNoise(surfaceNoise, dimension, seed);
89+
90+
List<MemorySegment> structureConfigs = new ArrayList<>();
91+
for (int structure = 0; structure < Cubiomes.FEATURE_NUM(); structure++) {
92+
MemorySegment config = StructureConfig.allocate(arena);
93+
if (Cubiomes.getStructureConfig(structure, version, config) == 0) {
94+
continue;
95+
}
96+
if (StructureConfig.dim(config) != dimension) {
97+
continue;
98+
}
99+
if (filterStructures != null && !filterStructures.contains((int) StructureConfig.structType(config))) {
100+
continue;
101+
}
102+
structureConfigs.add(config);
103+
}
104+
105+
if (structureConfigs.isEmpty()) {
106+
source.sendFeedback(Component.literal("No lootable structures available for this dimension."));
107+
return 1;
108+
}
109+
110+
List<LootExportHelper.Target> targets = new ArrayList<>();
111+
MemorySegment structurePos = Pos.allocate(arena);
112+
long radiusSq = (long) radius * radius;
113+
114+
for (MemorySegment config : structureConfigs) {
115+
int structType = (int) StructureConfig.structType(config);
116+
int regionBlockSize = StructureConfig.regionSize(config) << 4;
117+
int minRegionX = (centerX - radius) / regionBlockSize;
118+
int maxRegionX = (centerX + radius) / regionBlockSize;
119+
int minRegionZ = (centerZ - radius) / regionBlockSize;
120+
int maxRegionZ = (centerZ + radius) / regionBlockSize;
121+
StructureChecks.GenerationCheck generationCheck = StructureChecks.getGenerationCheck(structType);
122+
123+
for (int regionX = minRegionX; regionX <= maxRegionX; regionX++) {
124+
for (int regionZ = minRegionZ; regionZ <= maxRegionZ; regionZ++) {
125+
if (!generationCheck.check(generator, surfaceNoise, regionX, regionZ, structurePos)) {
126+
continue;
127+
}
128+
int posX = Pos.x(structurePos);
129+
int posZ = Pos.z(structurePos);
130+
long dx = posX - centerX;
131+
long dz = posZ - centerZ;
132+
if (dx * dx + dz * dz > radiusSq) {
133+
continue;
134+
}
135+
targets.add(new LootExportHelper.Target(structType, new BlockPos(posX, 0, posZ)));
136+
}
137+
}
138+
}
139+
140+
if (targets.isEmpty()) {
141+
source.sendFeedback(Component.literal("No structures with loot found within %d blocks.".formatted(radius)));
142+
return 1;
143+
}
144+
145+
try {
146+
LootExportHelper.Result result = LootExportHelper.exportLoot(
147+
source.getClient(),
148+
generator,
149+
seed,
150+
version,
151+
dimension,
152+
4,
153+
dimensionName(dimension),
154+
centerX,
155+
centerZ,
156+
radius,
157+
targets
158+
);
159+
if (result.path() == null) {
160+
source.sendFeedback(Component.literal("No lootable chests found within %d blocks.".formatted(radius)));
161+
} else {
162+
source.sendFeedback(Component.literal("Exported %d loot entries to %s".formatted(result.entryCount(), result.path().toAbsolutePath())));
163+
}
164+
} catch (IOException e) {
165+
source.sendError(Component.literal("Failed to export loot: " + e.getMessage()));
166+
}
167+
}
168+
169+
return 1;
170+
}
171+
172+
private static Set<Integer> parseStructureFilter(String filter, int version) {
173+
if (filter == null) {
174+
return null;
175+
}
176+
String trimmed = filter.trim();
177+
if (trimmed.isEmpty() || "all".equalsIgnoreCase(trimmed)) {
178+
return null;
179+
}
180+
Set<String> wanted = Arrays.stream(trimmed.split("\\s+"))
181+
.map(String::trim)
182+
.filter(s -> !s.isEmpty())
183+
.map(String::toLowerCase)
184+
.collect(Collectors.toSet());
185+
if (wanted.isEmpty()) {
186+
return null;
187+
}
188+
Set<Integer> matches = new HashSet<>();
189+
try (Arena arena = Arena.ofConfined()) {
190+
MemorySegment config = StructureConfig.allocate(arena);
191+
for (int structure = 0; structure < Cubiomes.FEATURE_NUM(); structure++) {
192+
if (Cubiomes.getStructureConfig(structure, version, config) == 0) {
193+
continue;
194+
}
195+
String name = Cubiomes.struct2str(StructureConfig.structType(config)).getString(0);
196+
if (wanted.contains(name.toLowerCase())) {
197+
matches.add((int) StructureConfig.structType(config));
198+
}
199+
}
200+
}
201+
return matches.isEmpty() ? null : matches;
202+
}
203+
204+
private static String dimensionName(int dimension) {
205+
if (dimension == Cubiomes.DIM_OVERWORLD()) {
206+
return "minecraft:overworld";
207+
}
208+
if (dimension == Cubiomes.DIM_NETHER()) {
209+
return "minecraft:the_nether";
210+
}
211+
if (dimension == Cubiomes.DIM_END()) {
212+
return "minecraft:the_end";
213+
}
214+
return Integer.toString(dimension);
215+
}
216+
}

0 commit comments

Comments
 (0)