#!/usr/bin/env python3 """Which .xpr package holds each effect the datasheets bind. Effect names are matched EXACTLY, not as substrings: Base.xpr contains eff_f0002_barnhaze, so a substring test for "eff_f0002" reports a false hit for a resource that is not there. Names are pulled as maximal [A-Za-z0-9_] runs containing "eff_", which yields whole resource names -- 268 of ptc_pack's 727 carry an EF_IDX_ prefix, so a pattern anchored at "eff_" would truncate them and silently merge distinct names. A bound name is looked up bare AND under that prefix. Regenerates docs/re/data/effect-homes.txt. """ import glob, os, re, subprocess, sys, collections sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from unit_substructures import pak_entries import unitgroup as U XPR = '/work/sylph_extract/hidden/resource3d' FX = re.compile(r'(FxModel|EffectName|Effect_|ShellModel|CoverModel)') def width(n): m = re.match(r'eff_[a-z](\d+)', n) return len(m.group(1)) if m else 0 def main(): if not os.path.isdir(XPR): print('disc not mounted; nothing to do'); return home = collections.defaultdict(set) for path in sorted(glob.glob(os.path.join(XPR, '*.xpr'))): out = subprocess.run(['grep', '-o', '-a', '[A-Za-z0-9_]*eff_[A-Za-z0-9_]*', path], capture_output=True, text=True, errors='replace').stdout for name in set(out.split()): home[name].add(os.path.basename(path)) bound = set() for pk in sorted(glob.glob('/work/sylph_extract/**/*.pak', recursive=True)): for _h, b in pak_entries(pk): if b[:4] != b'IDXD': continue try: recs = U.parse(b) except Exception: continue for r in recs: for _t, f, v in r['fields']: if f and FX.search(f) and isinstance(v, str) and v.startswith('eff_'): bound.add(v) # a name may be stored bare or under the EF_IDX_ index prefix def where(n): return home.get(n, set()) | home.get('EF_IDX_' + n, set()) res = sorted(n for n in bound if where(n)) un = sorted(n for n in bound if not where(n)) print('effect names bound by a datasheet field : %d' % len(bound)) print('resolved to an .xpr package (exact name): %d' % len(res)) print('unlocated : %d' % len(un)) print() per = collections.Counter() for n in res: for f in where(n): per[f] += 1 print('resolved, by package:') for f, k in sorted(per.items(), key=lambda kv: (-kv[1], kv[0])): print(' %-26s %d' % (f, k)) print() print('by digit-width:') for k in (3, 4, 0): s = [n for n in bound if width(n) == k] if s: print(' %d-digit: %d resolved of %d' % (k, len([n for n in s if where(n)]), len(s))) print() print('the %d unlocated, in full:' % len(un)) for n in un: print(' %s' % n) if __name__ == '__main__': main()