package localmods.mtkviewpoint; import java.util.*; import me.zed_0xff.zombie_buddy.Exposer.LuaClass; import se.krka.kahlua.vm.*; import viewpoint.core.Frame; import viewpoint.render.SceneData; import zombie.Lua.Event; import zombie.Lua.LuaEventManager; /** Reads MTK's real projectiles on the game thread; publishes immutable render snapshots. */ @LuaClass(name = "MTKViewpointProjectiles") public final class ProjectileBridge { static final int LIMIT = 256; static final double LEVEL = 2.4494895935058594; private static KahluaTable physics, visuals; private static final Map> frames = Collections.synchronizedMap(new WeakHashMap<>()); private ProjectileBridge() {} public static LuaClosure legacyRenderer() { var events = new ArrayList(); LuaEventManager.getEvents(events, new HashMap<>()); LuaClosure found = null; for (var event : events) { if (!"OnPreUIDraw".equals(event.name)) continue; for (var callback : event.callbacks) { var constants = Arrays.asList(callback.prototype.constants); if (constants.containsAll(List.of("renderline","VisualProjectilePC","currentDistance","startHeight","isCovered"))) { if (found != null) return null; // ambiguous: leave the dependency untouched found = callback; } } } return found; } public static boolean bind(Object removeById, KahluaTable visualList) { // In installed MTK 2.0.0 removeByID closes over exactly one value: // projectilesByID. Read that reference; never replace or modify it. if (!(removeById instanceof LuaClosure fn) || fn.prototype.numParams != 2 || fn.upvalues.length != 1 || !Arrays.asList(fn.prototype.constants).containsAll(List.of("id","list","table","remove")) || fn.upvalues[0] == null || !(fn.upvalues[0].getValue() instanceof KahluaTable table) || visualList == null) return false; physics = table; visuals = visualList; frames.clear(); return true; } public static void capture(Frame frame) { if (frame == null) return; if (physics == null || visuals == null) { frames.remove(frame.scene); return; } var result = new ArrayList(); for (int i = 1, count = Math.min(visuals.len(), LIMIT); i <= count; i++) { if (!(visuals.rawget(i) instanceof KahluaTable visual)) continue; if (!(physics.rawget(visual.rawget("id")) instanceof KahluaTable shot)) continue; double distance = num(shot, "distance"), travelled = num(shot, "currentDistance"); double x = num(shot,"startX"), y = num(shot,"startY"), z = num(shot,"startHeight"); double ex = num(shot,"endX"), ey = num(shot,"endY"), ez = num(shot,"endHeight"); if (!finite(distance,travelled,x,y,z,ex,ey,ez) || distance <= 0 || travelled <= 0 || travelled >= distance) continue; double scale = num(visual,"scale"); if (!Double.isFinite(scale)) scale = 0.1; double length = Math.max(0.45, Math.min(2.5, distance * Math.max(0.1,scale) * 0.08)); double head = Math.min(1,travelled/distance), tail = Math.max(0,(travelled-length)/distance); double hx=x+(ex-x)*head, hy=y+(ey-y)*head, hz=z+(ez-z)*head; double tx=x+(ex-x)*tail, ty=y+(ey-y)*tail, tz=z+(ez-z)*tail; float[] h=local(frame.scene,hx,hy,hz), t=local(frame.scene,tx,ty,tz); KahluaTable color = visual.rawget("color") instanceof KahluaTable c ? c : null; result.add(new TracerMesh.Segment(h[0],h[1],h[2],t[0],t[1],t[2], color(color,1,1),color(color,2,0.72f),color(color,3,0.14f),1)); } frames.put(frame.scene,List.copyOf(result)); } static List segments(SceneData scene) { return frames.getOrDefault(scene,List.of()); } static float[] local(SceneData scene, double x, double y, double z) { double lift=scene.relief == null ? 0 : scene.relief.scene(x,y); return new float[]{(float)(-x-scene.originX),(float)(z*LEVEL+lift-scene.originY),(float)(-y-scene.originZ)}; } private static double num(KahluaTable t,String key) { return t.rawget(key) instanceof Number n ? n.doubleValue() : Double.NaN; } private static float color(KahluaTable t,int key,float fallback) { return t != null && t.rawget(key) instanceof Number n && Float.isFinite(n.floatValue()) ? Math.max(0,Math.min(1,n.floatValue())) : fallback; } private static boolean finite(double... values) { for (double v:values) if (!Double.isFinite(v)) return false; return true; } }