Skip to content

Commit 17267c3

Browse files
committed
Loot Table
1 parent 44c7ff2 commit 17267c3

File tree

22 files changed

+1177
-48
lines changed

22 files changed

+1177
-48
lines changed

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,14 @@ I also added the ability to import waypoints from [Wurst7-CevAPI](https://github
160160
### Highlight Timeout Setting
161161
Can now change the default 5 minute render timeout with ```/sm:config EspTimeoutMinutes```
162162

163+
### Loot Table Browser
164+
165+
Can now visually search and browse through the loot table for the given map area. Click the 'Loot Table' button on the seed map to open the browser. You can also make ESP highlights or waypoints to the chests or simply copy the coordinates. Enchantments will be highlighted with colors and icons.
166+
167+
![LootTable](https://i.imgur.com/lnT5LsP.png)
168+
163169
### Export Loot Table
164-
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]```.
170+
Can now export the entire loot table for the map you're viewing (or any other dimension) via the command ```/sm:exportloot <radius> [dimension] [structures/all]```.
165171

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package dev.xpple.simplewaypoints.api;
2+
3+
import com.mojang.brigadier.exceptions.CommandSyntaxException;
4+
import dev.xpple.simplewaypoints.impl.SimpleWaypointsImpl;
5+
import java.util.Map;
6+
import java.util.Set;
7+
import net.minecraft.client.Minecraft;
8+
import net.minecraft.core.BlockPos;
9+
import net.minecraft.resources.ResourceKey;
10+
import net.minecraft.world.level.Level;
11+
12+
public interface SimpleWaypointsAPI {
13+
static SimpleWaypointsAPI getInstance() {
14+
return SimpleWaypointsImpl.INSTANCE;
15+
}
16+
17+
void registerCommandAlias(String alias);
18+
19+
Set<String> getCommandAliases();
20+
21+
String getWorldIdentifier(Minecraft minecraft);
22+
23+
Map<String, Map<String, Waypoint>> getAllWaypoints();
24+
25+
Map<String, Waypoint> getWorldWaypoints(String worldIdentifier);
26+
27+
int addWaypoint(String worldIdentifier, ResourceKey<Level> dimension, String name, BlockPos pos) throws CommandSyntaxException;
28+
29+
int addWaypoint(String worldIdentifier, ResourceKey<Level> dimension, String name, BlockPos pos, int color) throws CommandSyntaxException;
30+
31+
int removeWaypoint(String worldIdentifier, String name) throws CommandSyntaxException;
32+
33+
int renameWaypoint(String worldIdentifier, String name, String newName) throws CommandSyntaxException;
34+
35+
int editWaypoint(String worldIdentifier, String name, BlockPos pos) throws CommandSyntaxException;
36+
37+
int editWaypoint(String worldIdentifier, String name, ResourceKey<Level> dimension, BlockPos pos) throws CommandSyntaxException;
38+
39+
int setWaypointVisibility(String worldIdentifier, String name, boolean visible) throws CommandSyntaxException;
40+
41+
int setWaypointColor(String worldIdentifier, String name, int color) throws CommandSyntaxException;
42+
}

src/main/java/dev/xpple/seedmapper/render/RenderManager.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,13 @@ public static void clear() {
8282
HIGHLIGHTS.clear();
8383
}
8484

85+
public static void clearHighlight(BlockPos pos) {
86+
if (pos == null) {
87+
return;
88+
}
89+
HIGHLIGHTS.removeIf(highlight -> highlight.pos().equals(pos));
90+
}
91+
8592
public static void registerEvents() {
8693
WorldRenderEvents.END_EXTRACTION.register(RenderManager::extractLines);
8794
WorldRenderEvents.END_MAIN.register(RenderManager::renderLines);

src/main/java/dev/xpple/seedmapper/seedmap/LootTableScreen.java

Lines changed: 877 additions & 0 deletions
Large diffs are not rendered by default.

src/main/java/dev/xpple/seedmapper/seedmap/SeedMapScreen.java

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -919,7 +919,7 @@ private void createExportButton() {
919919
.bounds(xaeroButtonX, buttonY, buttonWidth, buttonHeight)
920920
.build();
921921
int exportLootButtonX = xaeroButtonX - buttonWidth - buttonSpacing;
922-
Button exportLootButton = Button.builder(Component.literal("Export Loot"), button -> this.exportVisibleLoot())
922+
Button exportLootButton = Button.builder(Component.literal("Loot Table"), button -> this.openLootTableScreen())
923923
.bounds(exportLootButtonX, buttonY, buttonWidth, buttonHeight)
924924
.build();
925925
this.addRenderableWidget(xaeroButton);
@@ -3721,6 +3721,47 @@ private void enqueueTilesSpiralIncremental(Object2ObjectMap<TilePos, java.util.L
37213721
}
37223722
}
37233723

3724+
private void openLootTableScreen() {
3725+
LocalPlayer player = this.minecraft.player;
3726+
if (player == null) {
3727+
return;
3728+
}
3729+
List<ExportEntry> exportEntries = this.collectVisibleExportEntries();
3730+
if (exportEntries.isEmpty()) {
3731+
player.displayClientMessage(Component.literal("No structures to export."), false);
3732+
return;
3733+
}
3734+
List<LootExportHelper.Target> targets = exportEntries.stream()
3735+
.filter(entry -> LocateCommand.LOOT_SUPPORTED_STRUCTURES.contains(entry.feature().getStructureId()))
3736+
.map(entry -> new LootExportHelper.Target(entry.feature().getStructureId(), entry.pos()))
3737+
.toList();
3738+
if (targets.isEmpty()) {
3739+
player.displayClientMessage(Component.literal("No lootable structures in view."), false);
3740+
return;
3741+
}
3742+
List<LootExportHelper.LootEntry> entries;
3743+
try {
3744+
entries = LootExportHelper.collectLootEntries(
3745+
this.minecraft,
3746+
this.biomeGenerator,
3747+
this.seed,
3748+
this.version,
3749+
this.dimension,
3750+
BIOME_SCALE,
3751+
targets
3752+
);
3753+
} catch (Exception e) {
3754+
LOGGER.error("Failed to collect loot", e);
3755+
player.displayClientMessage(Component.literal("Failed to collect loot: " + e.getMessage()), false);
3756+
return;
3757+
}
3758+
if (entries.isEmpty()) {
3759+
player.displayClientMessage(Component.literal("No lootable structures in view."), false);
3760+
return;
3761+
}
3762+
this.minecraft.setScreen(new LootTableScreen(this, this.minecraft, DIM_ID_TO_MC.get(this.dimension), player.blockPosition(), entries));
3763+
}
3764+
37243765
private void enqueueTilesSpiral(Object2ObjectMap<TilePos, java.util.List<CustomStructureMarker>> dimensionCache,
37253766
TilePos centerTile, int horTileRadius, int verTileRadius) {
37263767
int maxRadius = Math.max(horTileRadius, verTileRadius);

src/main/java/dev/xpple/seedmapper/util/LootExportHelper.java

Lines changed: 77 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,68 @@ private LootExportHelper() {
4444
}
4545

4646
public static Result exportLoot(Minecraft minecraft, MemorySegment biomeGenerator, long seed, int version, int dimension, int biomeScale, String dimensionName, int centerX, int centerZ, int radius, List<Target> targets) throws IOException {
47-
if (targets.isEmpty()) {
47+
List<LootEntry> entries = collectLootEntries(minecraft, biomeGenerator, seed, version, dimension, biomeScale, targets);
48+
if (entries.isEmpty()) {
4849
return new Result(null, 0);
4950
}
5051

5152
JsonArray structuresArray = new JsonArray();
53+
for (LootEntry entry : entries) {
54+
JsonObject entryObj = new JsonObject();
55+
entryObj.addProperty("id", entry.id());
56+
entryObj.addProperty("type", entry.type());
57+
entryObj.addProperty("x", entry.pos().getX());
58+
entryObj.addProperty("y", entry.pos().getY());
59+
entryObj.addProperty("z", entry.pos().getZ());
60+
JsonArray items = new JsonArray();
61+
for (LootItem item : entry.items()) {
62+
JsonObject itemObj = new JsonObject();
63+
itemObj.addProperty("slot", item.slot());
64+
itemObj.addProperty("count", item.count());
65+
itemObj.addProperty("itemId", item.itemId());
66+
itemObj.addProperty("id", item.itemId());
67+
itemObj.addProperty("displayName", item.displayName());
68+
itemObj.addProperty("nbt", item.nbt());
69+
JsonArray enchantments = new JsonArray();
70+
JsonArray enchantmentLevels = new JsonArray();
71+
for (String enchantment : item.enchantments()) {
72+
enchantments.add(enchantment);
73+
}
74+
for (Integer level : item.enchantmentLevels()) {
75+
enchantmentLevels.add(level);
76+
}
77+
itemObj.add("enchantments", enchantments);
78+
itemObj.add("enchantmentLevels", enchantmentLevels);
79+
items.add(itemObj);
80+
}
81+
entryObj.add("items", items);
82+
structuresArray.add(entryObj);
83+
}
84+
85+
JsonObject root = new JsonObject();
86+
root.addProperty("seed", seed);
87+
root.addProperty("dimension", dimensionName == null ? Integer.toString(dimension) : dimensionName);
88+
root.addProperty("center_x", centerX);
89+
root.addProperty("center_z", centerZ);
90+
root.addProperty("radius", radius);
91+
root.addProperty("minecraftVersion", SharedConstants.getCurrentVersion().name());
92+
root.add("structures", structuresArray);
93+
94+
Path lootDir = minecraft.gameDirectory.toPath().resolve("SeedMapper").resolve("loot");
95+
Files.createDirectories(lootDir);
96+
String serverId = resolveServerId(minecraft);
97+
String timestamp = EXPORT_TIMESTAMP.format(LocalDateTime.now());
98+
Path exportFile = lootDir.resolve("%s_%s-%s.json".formatted(serverId, Long.toString(seed), timestamp));
99+
Files.writeString(exportFile, GSON.toJson(root), StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
100+
return new Result(exportFile, structuresArray.size());
101+
}
102+
103+
public static List<LootEntry> collectLootEntries(Minecraft minecraft, MemorySegment biomeGenerator, long seed, int version, int dimension, int biomeScale, List<Target> targets) {
104+
if (targets.isEmpty()) {
105+
return List.of();
106+
}
107+
108+
List<LootEntry> entries = new java.util.ArrayList<>();
52109

53110
for (Target target : targets) {
54111
try (Arena arena = Arena.ofConfined()) {
@@ -94,29 +151,24 @@ public static Result exportLoot(Minecraft minecraft, MemorySegment biomeGenerato
94151
Cubiomes.set_loot_seed(lootTableContext, lootSeed);
95152
Cubiomes.generate_loot(lootTableContext);
96153
int lootCount = LootTableContext.generated_item_count(lootTableContext);
97-
JsonArray items = new JsonArray();
154+
List<LootItem> items = new java.util.ArrayList<>(lootCount);
98155
for (int lootIdx = 0; lootIdx < lootCount; lootIdx++) {
99156
MemorySegment itemStack = ItemStack.asSlice(LootTableContext.generated_items(lootTableContext), lootIdx);
100157
int itemId = Cubiomes.get_global_item_id(lootTableContext, ItemStack.item(itemStack));
101158
String itemName = Cubiomes.global_id2item_name(itemId, version).getString(0);
102-
JsonObject itemObj = new JsonObject();
103-
itemObj.addProperty("slot", lootIdx);
104-
itemObj.addProperty("count", ItemStack.count(itemStack));
105-
itemObj.addProperty("itemId", itemName);
106-
itemObj.addProperty("id", itemName);
159+
int count = ItemStack.count(itemStack);
107160

161+
String displayName = itemName;
162+
String nbt = itemName;
108163
Item mcItem = ItemAndEnchantmentsPredicateArgument.ITEM_ID_TO_MC.get(itemId);
109164
if (mcItem != null) {
110-
net.minecraft.world.item.ItemStack mcStack = new net.minecraft.world.item.ItemStack(mcItem, ItemStack.count(itemStack));
111-
itemObj.addProperty("displayName", mcStack.getHoverName().getString());
112-
itemObj.addProperty("nbt", mcStack.toString());
113-
} else {
114-
itemObj.addProperty("displayName", itemName);
115-
itemObj.addProperty("nbt", itemName);
165+
net.minecraft.world.item.ItemStack mcStack = new net.minecraft.world.item.ItemStack(mcItem, count);
166+
displayName = mcStack.getHoverName().getString();
167+
nbt = mcStack.toString();
116168
}
117169

118-
JsonArray enchantments = new JsonArray();
119-
JsonArray enchantmentLevels = new JsonArray();
170+
List<String> enchantments = new java.util.ArrayList<>();
171+
List<Integer> enchantmentLevels = new java.util.ArrayList<>();
120172
MemorySegment enchantmentsInternal = ItemStack.enchantments(itemStack);
121173
int enchantmentCount = ItemStack.enchantment_count(itemStack);
122174
for (int enchantmentIdx = 0; enchantmentIdx < enchantmentCount; enchantmentIdx++) {
@@ -129,21 +181,13 @@ public static Result exportLoot(Minecraft minecraft, MemorySegment biomeGenerato
129181
enchantments.add(enchantmentName);
130182
enchantmentLevels.add(EnchantInstance.level(enchantInstance));
131183
}
132-
itemObj.add("enchantments", enchantments);
133-
itemObj.add("enchantmentLevels", enchantmentLevels);
134-
items.add(itemObj);
184+
items.add(new LootItem(lootIdx, count, itemName, displayName, nbt, enchantments, enchantmentLevels));
135185
}
136186

137187
MemorySegment chestPos = Pos.asSlice(chestPoses, chestIdx);
138-
JsonObject entry = new JsonObject();
139188
String structName = Cubiomes.struct2str(structure).getString(0);
140-
entry.addProperty("id", structName + "-" + pieceName + "-" + chestIdx);
141-
entry.addProperty("type", structName);
142-
entry.addProperty("x", Pos.x(chestPos));
143-
entry.addProperty("y", 0);
144-
entry.addProperty("z", Pos.z(chestPos));
145-
entry.add("items", items);
146-
structuresArray.add(entry);
189+
BlockPos entryPos = new BlockPos(Pos.x(chestPos), 0, Pos.z(chestPos));
190+
entries.add(new LootEntry(structName + "-" + pieceName + "-" + chestIdx, structName, pieceName, entryPos, items));
147191
} finally {
148192
CubiomesCompat.freeLootTablePools(lootTableContext);
149193
}
@@ -152,26 +196,7 @@ public static Result exportLoot(Minecraft minecraft, MemorySegment biomeGenerato
152196
}
153197
}
154198

155-
if (structuresArray.isEmpty()) {
156-
return new Result(null, 0);
157-
}
158-
159-
JsonObject root = new JsonObject();
160-
root.addProperty("seed", seed);
161-
root.addProperty("dimension", dimensionName == null ? Integer.toString(dimension) : dimensionName);
162-
root.addProperty("center_x", centerX);
163-
root.addProperty("center_z", centerZ);
164-
root.addProperty("radius", radius);
165-
root.addProperty("minecraftVersion", SharedConstants.getCurrentVersion().name());
166-
root.add("structures", structuresArray);
167-
168-
Path lootDir = minecraft.gameDirectory.toPath().resolve("SeedMapper").resolve("loot");
169-
Files.createDirectories(lootDir);
170-
String serverId = resolveServerId(minecraft);
171-
String timestamp = EXPORT_TIMESTAMP.format(LocalDateTime.now());
172-
Path exportFile = lootDir.resolve("%s_%s-%s.json".formatted(serverId, Long.toString(seed), timestamp));
173-
Files.writeString(exportFile, GSON.toJson(root), StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
174-
return new Result(exportFile, structuresArray.size());
199+
return entries;
175200
}
176201

177202
public record Target(int structureId, BlockPos pos) {
@@ -180,6 +205,12 @@ public record Target(int structureId, BlockPos pos) {
180205
public record Result(Path path, int entryCount) {
181206
}
182207

208+
public record LootEntry(String id, String type, String pieceName, BlockPos pos, List<LootItem> items) {
209+
}
210+
211+
public record LootItem(int slot, int count, String itemId, String displayName, String nbt, List<String> enchantments, List<Integer> enchantmentLevels) {
212+
}
213+
183214
private static String resolveServerId(Minecraft minecraft) {
184215
String serverId = "local";
185216
try {
6 KB
Binary file not shown.

0 commit comments

Comments
 (0)