Files
2026-10-27 17:16:21 -04:00

98 lines
4.7 KiB
Java

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<SceneData, List<TracerMesh.Segment>> frames =
Collections.synchronizedMap(new WeakHashMap<>());
private ProjectileBridge() {}
public static LuaClosure legacyRenderer() {
var events = new ArrayList<Event>();
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<TracerMesh.Segment>();
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<TracerMesh.Segment> 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;
}
}