Skip to content

Commit f8a2d0d

Browse files
committed
Improved Loot Search
1 parent 32e991b commit f8a2d0d

5 files changed

Lines changed: 276 additions & 156 deletions

File tree

src/main/java/net/wurstclient/chestsearch/ChestDatabase.java

Lines changed: 92 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.ArrayList;
2020
import java.util.Iterator;
2121
import java.util.List;
22+
import java.util.Locale;
2223
import java.util.stream.Collectors;
2324

2425
public class ChestDatabase
@@ -180,60 +181,115 @@ public synchronized void removeAt(String serverIp, String dimension, int x,
180181

181182
public synchronized List<ChestEntry> search(String query)
182183
{
183-
String q = query.toLowerCase();
184+
String q = query == null ? "" : query.toLowerCase(Locale.ROOT).trim();
185+
String[] tokens = tokenizeQuery(q);
184186
List<ChestEntry> res = new ArrayList<>();
185187
for(ChestEntry e : entries)
186188
{
187189
boolean matched = false;
188-
if(e.serverIp != null && e.serverIp.toLowerCase().contains(q))
190+
if(e.serverIp != null && containsQueryTokens(
191+
e.serverIp.toLowerCase(Locale.ROOT), q, tokens))
189192
matched = true;
190-
if(e.dimension != null && e.dimension.toLowerCase().contains(q))
193+
if(e.dimension != null && containsQueryTokens(
194+
e.dimension.toLowerCase(Locale.ROOT), q, tokens))
191195
matched = true;
192-
for(ChestEntry.ItemEntry item : e.items)
196+
if(!matched && e.items != null)
193197
{
194-
if(item.itemId != null && item.itemId.toLowerCase().contains(q))
195-
matched = true;
196-
if(item.displayName != null
197-
&& item.displayName.toLowerCase().contains(q))
198-
matched = true;
199-
if(!matched && item.nbt != null
200-
&& item.nbt.toString().toLowerCase().contains(q))
201-
matched = true;
202-
// Also match extracted enchantment and potion ids collected
203-
// by the recorder so searches like "sharpness" or "speed"
204-
// will find chests containing those effects.
205-
if(!matched && item.enchantments != null)
198+
for(ChestEntry.ItemEntry item : e.items)
206199
{
207-
for(String en : item.enchantments)
200+
if(itemMatchesQuery(item, q, tokens))
208201
{
209-
if(en != null && en.toLowerCase().contains(q))
210-
{
211-
matched = true;
212-
break;
213-
}
202+
matched = true;
203+
break;
214204
}
215205
}
216-
if(!matched && item.potionEffects != null)
217-
{
218-
for(String pe : item.potionEffects)
219-
{
220-
if(pe != null && pe.toLowerCase().contains(q))
221-
{
222-
matched = true;
223-
break;
224-
}
225-
}
226-
}
227-
if(!matched && item.primaryPotion != null
228-
&& item.primaryPotion.toLowerCase().contains(q))
229-
matched = true;
230206
}
231207
if(matched)
232208
res.add(e);
233209
}
234210
return res;
235211
}
236212

213+
private static boolean itemMatchesQuery(ChestEntry.ItemEntry item, String q,
214+
String[] tokens)
215+
{
216+
if(item == null)
217+
return false;
218+
if(q.isEmpty())
219+
return true;
220+
221+
StringBuilder sb = new StringBuilder(256);
222+
appendSearchPart(sb, item.itemId);
223+
appendSearchPart(sb, item.displayName);
224+
if(item.nbt != null)
225+
appendSearchPart(sb, item.nbt.toString());
226+
if(item.enchantments != null)
227+
for(String en : item.enchantments)
228+
appendSearchPart(sb, en);
229+
if(item.potionEffects != null)
230+
for(String pe : item.potionEffects)
231+
appendSearchPart(sb, pe);
232+
appendSearchPart(sb, item.primaryPotion);
233+
234+
return containsQueryTokens(sb.toString(), q, tokens);
235+
}
236+
237+
private static void appendSearchPart(StringBuilder sb, String part)
238+
{
239+
if(part == null || part.isBlank())
240+
return;
241+
if(sb.length() > 0)
242+
sb.append(' ');
243+
sb.append(part.toLowerCase(Locale.ROOT));
244+
}
245+
246+
private static String[] tokenizeQuery(String query)
247+
{
248+
if(query == null || query.isBlank())
249+
return new String[0];
250+
String normalized = normalizeForTokenSearch(query);
251+
if(normalized.isEmpty())
252+
return new String[0];
253+
return normalized.split(" ");
254+
}
255+
256+
private static boolean containsQueryTokens(String haystack, String rawQuery,
257+
String[] tokens)
258+
{
259+
if(rawQuery == null || rawQuery.isEmpty())
260+
return true;
261+
if(haystack == null || haystack.isEmpty())
262+
return false;
263+
264+
String lowerHaystack = haystack.toLowerCase(Locale.ROOT);
265+
if(lowerHaystack.contains(rawQuery))
266+
return true;
267+
268+
if(tokens == null || tokens.length == 0)
269+
return false;
270+
271+
String normalizedHaystack = normalizeForTokenSearch(lowerHaystack);
272+
for(String token : tokens)
273+
{
274+
if(token == null || token.isEmpty())
275+
continue;
276+
if(!lowerHaystack.contains(token)
277+
&& !normalizedHaystack.contains(token))
278+
{
279+
return false;
280+
}
281+
}
282+
return true;
283+
}
284+
285+
private static String normalizeForTokenSearch(String value)
286+
{
287+
if(value == null || value.isBlank())
288+
return "";
289+
return value.toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9]+", " ")
290+
.trim();
291+
}
292+
237293
private boolean equalsPos(ChestEntry a, ChestEntry b)
238294
{
239295
if(a == null || b == null)

src/main/java/net/wurstclient/clickgui/screens/ChestSearchScreen.java

Lines changed: 80 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1445,6 +1445,7 @@ private java.util.List<ChestEntry.ItemEntry> collectMatches(
14451445
ChestEntry entry, String query)
14461446
{
14471447
String q = (query == null ? "" : query.trim()).toLowerCase(Locale.ROOT);
1448+
String[] tokens = tokenizeQuery(q);
14481449
if(q.equals(lastMatchQuery))
14491450
{
14501451
java.util.List<ChestEntry.ItemEntry> cached = matchCache.get(entry);
@@ -1465,52 +1466,7 @@ private java.util.List<ChestEntry.ItemEntry> collectMatches(
14651466
{
14661467
if(it == null)
14671468
continue;
1468-
boolean matched = false;
1469-
if(q.isEmpty())
1470-
matched = true;
1471-
if(!matched && it.itemId != null
1472-
&& it.itemId.toLowerCase(Locale.ROOT).contains(q))
1473-
matched = true;
1474-
if(!matched && it.displayName != null
1475-
&& it.displayName.toLowerCase(Locale.ROOT).contains(q))
1476-
matched = true;
1477-
// Also search NBT (full ItemStack data) so enchantments on
1478-
// books/gear
1479-
// are searchable if full NBT was recorded.
1480-
if(!matched && it.nbt != null)
1481-
{
1482-
String n = it.nbt.toString().toLowerCase(Locale.ROOT);
1483-
if(n.contains(q))
1484-
matched = true;
1485-
}
1486-
// Also match extracted enchantment/potion ids collected by the
1487-
// recorder so queries like "sharpness" or "speed" match items.
1488-
if(!matched && it.enchantments != null)
1489-
{
1490-
for(String en : it.enchantments)
1491-
{
1492-
if(en != null && en.toLowerCase(Locale.ROOT).contains(q))
1493-
{
1494-
matched = true;
1495-
break;
1496-
}
1497-
}
1498-
}
1499-
if(!matched && it.potionEffects != null)
1500-
{
1501-
for(String pe : it.potionEffects)
1502-
{
1503-
if(pe != null && pe.toLowerCase(Locale.ROOT).contains(q))
1504-
{
1505-
matched = true;
1506-
break;
1507-
}
1508-
}
1509-
}
1510-
if(!matched && it.primaryPotion != null
1511-
&& it.primaryPotion.toLowerCase(Locale.ROOT).contains(q))
1512-
matched = true;
1513-
if(matched)
1469+
if(itemMatchesQuery(it, q, tokens))
15141470
matches.add(it);
15151471
}
15161472
java.util.List<ChestEntry.ItemEntry> immutable = java.util.Collections
@@ -1520,6 +1476,84 @@ private java.util.List<ChestEntry.ItemEntry> collectMatches(
15201476
return immutable;
15211477
}
15221478

1479+
private static boolean itemMatchesQuery(ChestEntry.ItemEntry item, String q,
1480+
String[] tokens)
1481+
{
1482+
if(item == null)
1483+
return false;
1484+
if(q.isEmpty())
1485+
return true;
1486+
1487+
StringBuilder sb = new StringBuilder(256);
1488+
appendSearchPart(sb, item.itemId);
1489+
appendSearchPart(sb, item.displayName);
1490+
if(item.nbt != null)
1491+
appendSearchPart(sb, item.nbt.toString());
1492+
if(item.enchantments != null)
1493+
for(String en : item.enchantments)
1494+
appendSearchPart(sb, en);
1495+
if(item.potionEffects != null)
1496+
for(String pe : item.potionEffects)
1497+
appendSearchPart(sb, pe);
1498+
appendSearchPart(sb, item.primaryPotion);
1499+
1500+
return containsQueryTokens(sb.toString(), q, tokens);
1501+
}
1502+
1503+
private static void appendSearchPart(StringBuilder sb, String part)
1504+
{
1505+
if(part == null || part.isBlank())
1506+
return;
1507+
if(sb.length() > 0)
1508+
sb.append(' ');
1509+
sb.append(part.toLowerCase(Locale.ROOT));
1510+
}
1511+
1512+
private static String[] tokenizeQuery(String query)
1513+
{
1514+
if(query == null || query.isBlank())
1515+
return new String[0];
1516+
String normalized = normalizeForTokenSearch(query);
1517+
if(normalized.isEmpty())
1518+
return new String[0];
1519+
return normalized.split(" ");
1520+
}
1521+
1522+
private static boolean containsQueryTokens(String haystack, String rawQuery,
1523+
String[] tokens)
1524+
{
1525+
if(rawQuery == null || rawQuery.isEmpty())
1526+
return true;
1527+
if(haystack == null || haystack.isEmpty())
1528+
return false;
1529+
1530+
String lowerHaystack = haystack.toLowerCase(Locale.ROOT);
1531+
if(lowerHaystack.contains(rawQuery))
1532+
return true;
1533+
1534+
if(tokens == null || tokens.length == 0)
1535+
return false;
1536+
1537+
String normalizedHaystack = normalizeForTokenSearch(lowerHaystack);
1538+
for(String token : tokens)
1539+
{
1540+
if(token == null || token.isEmpty())
1541+
continue;
1542+
if(!lowerHaystack.contains(token)
1543+
&& !normalizedHaystack.contains(token))
1544+
return false;
1545+
}
1546+
return true;
1547+
}
1548+
1549+
private static String normalizeForTokenSearch(String value)
1550+
{
1551+
if(value == null || value.isBlank())
1552+
return "";
1553+
return value.toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9]+", " ")
1554+
.trim();
1555+
}
1556+
15231557
/*
15241558
* Find a canonical chest entry for given entry by matching contents.
15251559
*

src/main/java/net/wurstclient/clickgui/screens/WaypointsScreen.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -267,12 +267,12 @@ protected void init()
267267
minecraft.setScreen(this);
268268
}).bounds(x + 205, rowY, 55, 20).build());
269269

270-
Button copyBtn = addRenderableWidget(
271-
Button.builder(Component.literal("Copy"), b -> {
272-
String s = w.getPos().getX() + " " + w.getPos().getY()
273-
+ " " + w.getPos().getZ();
274-
minecraft.keyboardHandler.setClipboard(s);
275-
}).bounds(x + 265, rowY, 35, 20).build());
270+
Button copyBtn = addRenderableWidget(
271+
Button.builder(Component.literal("Copy"), b -> {
272+
String s = w.getPos().getX() + " " + w.getPos().getY() + " "
273+
+ w.getPos().getZ();
274+
minecraft.keyboardHandler.setClipboard(s);
275+
}).bounds(x + 265, rowY, 35, 20).build());
276276

277277
RowWidgets rw = new RowWidgets();
278278
rw.w = w;

src/main/java/net/wurstclient/hacks/LootSearchHack.java

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
*/
88
package net.wurstclient.hacks;
99

10-
import java.io.File;
11-
import java.nio.file.Path;
12-
import net.minecraft.network.chat.Component;
13-
import net.wurstclient.Category;
14-
import net.wurstclient.WurstClient;
15-
import net.wurstclient.hack.Hack;
10+
import java.io.File;
11+
import java.nio.file.Path;
12+
import net.minecraft.network.chat.Component;
13+
import net.wurstclient.Category;
14+
import net.wurstclient.WurstClient;
15+
import net.wurstclient.hack.Hack;
1616
import net.wurstclient.lootsearch.LootChestManager;
1717
import net.wurstclient.lootsearch.LootSearchUtil;
1818
import net.wurstclient.settings.FileSetting;
@@ -39,13 +39,13 @@ public final class LootSearchHack extends Hack
3939
"Literal path to a loot export JSON file. If set, this path is used instead of auto-detection.",
4040
"");
4141

42-
public LootSearchHack()
43-
{
44-
super("LootSearch");
45-
setCategory(Category.ITEMS);
46-
addSetting(lootJsonPicker);
47-
addSetting(literalJsonPath);
48-
}
42+
public LootSearchHack()
43+
{
44+
super("LootSearch");
45+
setCategory(Category.ITEMS);
46+
addSetting(lootJsonPicker);
47+
addSetting(literalJsonPath);
48+
}
4949

5050
@Override
5151
protected void onEnable()

0 commit comments

Comments
 (0)