feat(inventory-go): weapon_types multi-select filter by skill (melee/missile/war/void)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-15 08:41:43 +02:00
parent 29d400f86a
commit b4bee24773
2 changed files with 111 additions and 9 deletions

View file

@ -191,7 +191,7 @@ func (s *Server) runSearch(ctx context.Context, q url.Values) (map[string]any, e
case qBool(q, "jewelry_only"):
conds = append(conds, "object_class = 4")
case qBool(q, "weapon_only"):
conds = append(conds, weaponTypeClause(q.Get("weapon_type")))
conds = append(conds, weaponOnlyClause(q))
case qBool(q, "clothing_only"):
conds = append(conds, "(object_class = 3 AND name NOT ILIKE '%cloak%' AND name NOT ILIKE '%robe%' AND name NOT ILIKE '%pallium%' AND name NOT ILIKE '%armet%' AND (name ILIKE '%shirt%' OR name ILIKE '%pants%' OR name ILIKE '%breeches%' OR name ILIKE '%baggy%' OR name ILIKE '%tunic%'))")
}
@ -543,24 +543,36 @@ func underwearCTEWhere(q map[string][]string) string {
}
func weaponTypeClause(wt string) string {
exists := func(skill int) string {
meleeSkill := func(skill int) string {
return fmt.Sprintf("(object_class = 1 AND EXISTS (SELECT 1 FROM item_raw_data wrd WHERE wrd.item_id = db_item_id AND (wrd.int_values->>'218103840')::int = %d))", skill)
}
missileMastery := func(mastery int) string {
return fmt.Sprintf("(object_class = 9 AND EXISTS (SELECT 1 FROM item_raw_data wrd WHERE wrd.item_id = db_item_id AND (wrd.int_values->>'218103840')::int = 47 AND (wrd.int_values->>'353')::int = %d))", mastery)
}
casterSkill := func(skill int) string {
return fmt.Sprintf("(object_class = 31 AND EXISTS (SELECT 1 FROM item_raw_data wrd WHERE wrd.item_id = db_item_id AND (wrd.int_values->>'159')::int = %d))", skill)
}
switch strings.ToLower(wt) {
case "heavy":
return exists(44)
return meleeSkill(44)
case "light":
return exists(45)
return meleeSkill(45)
case "finesse":
return exists(46)
return meleeSkill(46)
case "two_handed":
return exists(41)
return meleeSkill(41)
case "missile":
return "(object_class = 9 AND EXISTS (SELECT 1 FROM item_raw_data wrd WHERE wrd.item_id = db_item_id AND (wrd.int_values->>'218103840')::int = 47))"
case "bow":
return "(object_class = 9 AND name ILIKE '%bow%' AND name NOT ILIKE '%crossbow%')"
return missileMastery(8)
case "crossbow":
return "(object_class = 9 AND name ILIKE '%crossbow%')"
return missileMastery(9)
case "thrown":
return "(object_class = 9 AND (name ILIKE '%atlatl%' OR name ILIKE '%throwing%' OR name ILIKE '%javelin%' OR name ILIKE '%shuriken%' OR name ILIKE '%dart%' OR name ILIKE '%slingshot%'))"
return missileMastery(10)
case "war":
return casterSkill(34)
case "void":
return casterSkill(43)
case "caster":
return "object_class = 31"
default:
@ -568,6 +580,34 @@ func weaponTypeClause(wt string) string {
}
}
// weaponOnlyClause builds the WHERE fragment for weapon_only searches: an OR of
// the per-type clauses named in weapon_types (CSV) plus the legacy single
// weapon_type. Empty selection = all weapons. Types are deduped.
func weaponOnlyClause(q url.Values) string {
var types []string
types = append(types, splitNonEmpty(q.Get("weapon_types"))...)
if wt := strings.TrimSpace(q.Get("weapon_type")); wt != "" {
types = append(types, wt)
}
seen := map[string]bool{}
var clauses []string
for _, t := range types {
key := strings.ToLower(strings.TrimSpace(t))
if key == "" || seen[key] {
continue
}
seen[key] = true
clauses = append(clauses, weaponTypeClause(key))
}
if len(clauses) == 0 {
return "object_class IN (1, 9, 31)"
}
if len(clauses) == 1 {
return clauses[0]
}
return "(" + strings.Join(clauses, " OR ") + ")"
}
func slotNameClause(name string, ab *argBuilder) string {
switch strings.ToLower(name) {
case "ring":