import pickle,json,collections,re,itertools D=pickle.load(open("data.pkl","rb")) mods=set(D["mods"])|{"minecraft","c","neoforge"} files=D["files"] # ---------- resource-level overrides ---------- byres=collections.defaultdict(list) for jar,path,j in files: byres[path].append((jar,j)) legacy=collections.Counter() for path,l in byres.items(): p=path.split("/") if p[2]=="recipes" or (p[2]=="tags" and p[3] in("items","blocks")): legacy[l[0][0]]+=1 # ---------- tags ---------- def is_tag_path(p): return p[2]=="tags" and p[3]=="item" tagdefs=collections.defaultdict(list) # tag -> [(jar, values, replace)] for path,l in byres.items(): p=path.split("/") if not is_tag_path(p): continue tag=p[1]+":"+"/".join(p[4:])[:-5] for jar,j in l: if not isinstance(j,dict): continue tagdefs[tag].append((jar,j.get("values",[]),j.get("replace",False))) items=set() for k in D["lang"]: _,ns,pa=k.split("."); items.add(ns+":"+pa) items|=D["models"] def ns(x): return x.split(":")[0] if ":" in x else "minecraft" def full(x): return x if ":" in x else "minecraft:"+x tagcache={}; tagissues=[] def resolve(tag,stack=()): if tag in tagcache: return tagcache[tag] if tag in stack: return set() out=set() for jar,vals,rep in tagdefs.get(tag,[]): if rep: out=set() for v in vals: req=True if isinstance(v,dict): req=v.get("required",True); v=v.get("id") if not isinstance(v,str): continue if v.startswith("#"): t=full(v[1:]) if req and t not in tagdefs: tagissues.append((tag,jar,v,"missing-tag")) out|=resolve(t,stack+(tag,)) else: v=full(v) if v not in items: if req: tagissues.append((tag,jar,v,"missing-item" if ns(v) in mods else "missing-mod-item")) continue out.add(v) tagcache[tag]=out; return out for t in list(tagdefs): resolve(t) # ---------- conditions ---------- unknown_cond=collections.Counter() def cond(c): if isinstance(c,list): return all(cond(x) for x in c) t=c.get("type","") if t in("neoforge:mod_loaded","forge:mod_loaded"): return c.get("modid") in mods if t in("neoforge:not","forge:not"): return not cond(c["value"]) if t in("neoforge:and","forge:and"): return all(cond(x) for x in c["values"]) if t in("neoforge:or","forge:or"): return any(cond(x) for x in c["values"]) if t in("neoforge:item_exists","forge:item_exists"): return full(c["item"]) in items if t in("neoforge:tag_empty","forge:tag_empty"): return not resolve(full(c["tag"])) if t in("neoforge:true","forge:true"): return True if t in("neoforge:false","forge:false"): return False unknown_cond[t]+=1; return True # ---------- ingredients ---------- def ing(x): """-> frozenset of items, or None if unknown type""" if x is None: return frozenset() if isinstance(x,str): return frozenset(resolve(full(x[1:]))) if x.startswith("#") else frozenset([full(x)]) if isinstance(x,list): s=set() for y in x: r=ing(y) if r is None: return None s|=r return frozenset(s) if isinstance(x,dict): if "item" in x and isinstance(x["item"],str): return frozenset([full(x["item"])]) if "tag" in x: return frozenset(resolve(full(x["tag"]))) if "id" in x and "count" in x and isinstance(x["id"],str): return frozenset([full(x["id"])]) if "ingredient" in x: return ing(x["ingredient"]) t=x.get("type","") if t in("neoforge:compound",): return ing(x.get("children")) if t=="neoforge:components": return ing(x.get("items")) if "items" in x: return ing(x["items"]) if t=="neoforge:difference": b=ing(x.get("base")); s=ing(x.get("subtracted")) return None if b is None or s is None else b-s if t=="neoforge:intersection": ch=[ing(c) for c in x.get("children",[])] if None in ch or not ch: return None return frozenset(set.intersection(*map(set,ch))) return None def result(j): r=j.get("result") if r is None: return None if isinstance(r,str): return (full(r),1) if isinstance(r,dict): i=r.get("id") or r.get("item") if isinstance(i,dict): i=i.get("id") or i.get("item") return (full(i) if isinstance(i,str) else str(i), r.get("count",1)) if isinstance(r,list): return tuple(sorted(result({"result":(e.get("item",e) if isinstance(e,dict) else e)}) + ((e.get("chance",1),) if isinstance(e,dict) else ()) for e in r)) return str(r) # ---------- active recipes (last jar wins per path; note collisions) ---------- active={}; overrides=[]; missingrefs=[] for path,l in byres.items(): p=path.split("/") if p[2]!="recipe" or not path.endswith(".json"): continue rid=p[1]+":"+"/".join(p[3:])[:-5] live=[] for jar,j in l: if not isinstance(j,dict): continue c=j.get("neoforge:conditions") if c is not None and not cond(c): continue live.append((jar,j)) if len(live)>1: uniq={json.dumps(j,sort_keys=True) for _,j in live} if len(uniq)>1: overrides.append((rid,[jr for jr,_ in live])) if live: active[rid]=live[-1] # cross-mod overrides of another namespace's recipes (single providing jar from other mod) owner={} for m,l in D["mods"].items(): for x in l: owner.setdefault(x["jar"].split("!")[0],set()).add(m) foreign=collections.defaultdict(list) for rid,(jar,j) in active.items(): n=ns(rid) if n not in owner.get(jar,set()) and jar not in("base_minecraft.jar",) and n not in("c",): foreign[(jar,n)].append(rid) # ---------- unknown refs in active recipes ---------- def refs(o,acc): if isinstance(o,dict): for k,v in o.items(): if k in("item","id","tag") and isinstance(v,str) and k!="type": acc.append((k,v)) else: refs(v,acc) elif isinstance(o,list): for v in o: refs(v,acc) return acc for rid,(jar,j) in active.items(): if j.get("type","").startswith(("neoforge:","forge:")) and "conditional" in j.get("type",""): continue for k,v in refs({kk:vv for kk,vv in j.items() if kk not in("neoforge:conditions",)},[]): if k=="tag": continue if ":" in v and ns(v) not in mods and not v.startswith("#"): missingrefs.append((rid,jar,v)); break pickle.dump(dict(active=active,tagcache=tagcache,items=items),open("active.pkl","wb")) print("== legacy (pre-1.21 'recipes/' or 'tags/items/') files, inert ==") for k,v in legacy.most_common(): print(f" {k}: {v}") print("== recipe path collisions with differing content (load order decides) ==",len(overrides)) for r,js in overrides: print(" ",r,js) print("== jars overriding another namespace's recipes ==") for (jar,n),l in sorted(foreign.items(),key=lambda x:-len(x[1])): print(f" {jar} -> {n}: {len(l)} e.g. {l[:3]}") print("== active recipes referencing items from absent mods (will fail to load) ==",len(missingrefs)) for x in missingrefs[:80]: print(" ",x) print("== tag issues (required entry missing => whole tag fails to load) ==") seen=set() for t,jar,v,k in tagissues: if (t,v) in seen: continue seen.add((t,v)); print(" ",k,t,"<-",v,"from",jar) print("== unknown condition types (assumed true) ==",dict(unknown_cond)) print("active recipes",len(active))