102 lines
7.7 KiB
Java
102 lines
7.7 KiB
Java
package dev.modzuozhi;
|
|
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.registries.BuiltInRegistries;
|
|
import net.minecraft.resources.ResourceLocation;
|
|
import net.minecraft.server.MinecraftServer;
|
|
import net.neoforged.neoforge.network.handling.ServerPayloadContext;
|
|
|
|
/** Isolated real packet, plot block entity and native constraint lifecycle fixture. */
|
|
public final class SimulatedHandleRegression {
|
|
public static void run(MinecraftServer server, ServerPayloadContext ctx) throws Exception {
|
|
if (!net.neoforged.fml.ModList.get().isLoaded("simulated")) return;
|
|
var level=server.overworld();
|
|
var handleId=ResourceLocation.parse("simulated:iron_handle");
|
|
ModZuozhi.LOGGER.info("Handle fixture block: {}",handleId);
|
|
var block=BuiltInRegistries.BLOCK.get(handleId);
|
|
var packet=Class.forName("dev.simulated_team.simulated.network.packets.UpdatePlayerUsingHandlePacket");
|
|
var ctor=packet.getConstructor(float.class,boolean.class,BlockPos.class);
|
|
var veilType=Class.forName("foundry.veil.api.network.handler.ServerPacketContext");
|
|
var veil=Class.forName("foundry.veil.forge.network.NeoForgeServerPacketContext")
|
|
.getConstructor(net.neoforged.neoforge.network.handling.IPayloadContext.class).newInstance(ctx);
|
|
var handle=packet.getMethod("handle",veilType);
|
|
var containerType=Class.forName("dev.ryanhcode.sable.api.sublevel.SubLevelContainer");
|
|
var subType=Class.forName("dev.ryanhcode.sable.sublevel.SubLevel");
|
|
var serverSubType=Class.forName("dev.ryanhcode.sable.sublevel.ServerSubLevel");
|
|
var poseType=Class.forName("dev.ryanhcode.sable.companion.math.Pose3d");
|
|
var container=call(containerType,null,"getContainer",Class.forName("dev.ryanhcode.sable.api.sublevel.ServerSubLevelContainer"),
|
|
new Class<?>[]{net.minecraft.server.level.ServerLevel.class},level);
|
|
var sub=call(containerType,container,"allocateNewSubLevel",subType,new Class<?>[]{poseType},poseType.getConstructor().newInstance());
|
|
var plot=subType.getMethod("getPlot").invoke(sub);
|
|
var plotType=Class.forName("dev.ryanhcode.sable.sublevel.plot.LevelPlot");
|
|
var pos=((BlockPos)plotType.getMethod("getCenterBlock").invoke(plot)).atY(115);
|
|
plotType.getMethod("newEmptyChunk",net.minecraft.world.level.ChunkPos.class).invoke(plot,new net.minecraft.world.level.ChunkPos(pos));
|
|
level.setBlockAndUpdate(pos,block.defaultBlockState());
|
|
var be=level.getBlockEntity(pos);
|
|
PayloadRegression.check(be!=null && be.getClass().getName().endsWith("HandleBlockEntity"),"real handle plot fixture");
|
|
var players=be.getClass().getDeclaredField("players");players.setAccessible(true);
|
|
var systemType=Class.forName("dev.ryanhcode.sable.sublevel.system.SubLevelPhysicsSystem");
|
|
var system=systemType.getMethod("get",net.minecraft.world.level.Level.class).invoke(null,level);
|
|
var body=systemType.getMethod("getPhysicsHandle",serverSubType).invoke(system,sub);
|
|
var bodyType=Class.forName("dev.ryanhcode.sable.api.physics.handle.RigidBodyHandle");
|
|
var jointType=Class.forName("dev.ryanhcode.sable.api.physics.constraint.PhysicsConstraintHandle");
|
|
Object joint=null;
|
|
var helper=Class.forName("dev.ryanhcode.sable.Sable").getField("HELPER").get(null);
|
|
var global=(net.minecraft.world.phys.Vec3)call(helper.getClass(),helper,"projectOutOfSubLevel",net.minecraft.world.phys.Vec3.class,
|
|
new Class<?>[]{net.minecraft.world.level.Level.class,net.minecraft.world.phys.Vec3.class},level,net.minecraft.world.phys.Vec3.atCenterOf(pos));
|
|
var player=ctx.player();player.setPos(global.x,global.y-1,global.z);player.getAbilities().flying=true;
|
|
level.addNewPlayer(player);
|
|
var release=new java.util.concurrent.CountDownLatch(1);
|
|
try {
|
|
for(boolean remove:new boolean[]{false,false,true}) {
|
|
var request=ctor.newInstance(4.0f,remove,pos);
|
|
if(remove) {
|
|
var entered=new java.util.concurrent.CountDownLatch(1);
|
|
var manager=dev.modzuozhi.core.dimthread.DimThreadCore.MANAGER;
|
|
manager.getLoop(level).execute(() -> {
|
|
server.execute(release::countDown);entered.countDown();
|
|
try {PayloadRegression.check(release.await(5,java.util.concurrent.TimeUnit.SECONDS),"native release callback pump");}
|
|
catch(InterruptedException e){throw new RuntimeException(e);}
|
|
});
|
|
manager.setPaused(server,false);
|
|
PayloadRegression.check(entered.await(5,java.util.concurrent.TimeUnit.SECONDS),"native release worker start");
|
|
// The old unprotected path clears player holding state but leaves the joint.
|
|
handle.invoke(request,veil);
|
|
PayloadRegression.check((boolean)jointType.getMethod("isValid").invoke(joint),"unprotected release did not reproduce stranded joint");
|
|
ModZuozhi.LOGGER.info("SIMULATED NATIVE RED: unprotected release left joint valid while worker owned plot");
|
|
}
|
|
ctx.enqueueWork(() -> {
|
|
try {handle.invoke(request,veil);} catch(ReflectiveOperationException e) {throw new RuntimeException(e);}
|
|
}).join();
|
|
var map=(java.util.Map<?,?>)players.get(be);
|
|
PayloadRegression.check(remove ? map.isEmpty() : map.containsKey(ctx.player().getUUID()),"real handle grab/release state");
|
|
if(!remove) {
|
|
be.getClass().getMethod("sable$physicsTick",serverSubType,bodyType,double.class).invoke(be,sub,body,0.05d);
|
|
var holding=map.get(player.getUUID());
|
|
var jointField=holding.getClass().getDeclaredField("constraintHandle");jointField.setAccessible(true);
|
|
joint=jointField.get(holding);
|
|
PayloadRegression.check(joint!=null && (boolean)jointType.getMethod("isValid").invoke(joint),"native handle joint created");
|
|
} else {
|
|
PayloadRegression.check(joint!=null && !(boolean)jointType.getMethod("isValid").invoke(joint),"native joint survived release packet");
|
|
dev.modzuozhi.core.dimthread.DimThreadCore.MANAGER.setPaused(server,true);
|
|
}
|
|
}
|
|
ModZuozhi.LOGGER.info("SIMULATED HANDLE PACKET PASS: real packet, BE and native joint invalidated on release");
|
|
} finally {
|
|
release.countDown();
|
|
dev.modzuozhi.core.dimthread.DimThreadCore.MANAGER.setPaused(server,true);
|
|
level.setBlockAndUpdate(pos,net.minecraft.world.level.block.Blocks.AIR.defaultBlockState());
|
|
level.removePlayerImmediately(player,net.minecraft.world.entity.Entity.RemovalReason.DISCARDED);
|
|
var reasonType=Class.forName("dev.ryanhcode.sable.sublevel.storage.SubLevelRemovalReason");
|
|
call(containerType,container,"removeSubLevel",void.class,new Class<?>[]{subType,reasonType},sub,reasonType.getField("REMOVED").get(null));
|
|
}
|
|
}
|
|
static Object call(Class<?> owner,Object target,String name,Class<?> result,Class<?>[] types,Object...args) throws Exception {
|
|
var signature=java.lang.invoke.MethodType.methodType(result,types);
|
|
var lookup=java.lang.invoke.MethodHandles.publicLookup();
|
|
var method=target==null?lookup.findStatic(owner,name,signature):lookup.findVirtual(owner,name,signature);
|
|
var values=new java.util.ArrayList<Object>();if(target!=null)values.add(target);java.util.Collections.addAll(values,args);
|
|
try{return method.invokeWithArguments(values);}catch(Throwable failure){throw new RuntimeException(failure);}
|
|
}
|
|
}
|