match vanilla arrow/trident orientation incl. arrow impact shake, add projectile test scenes
This commit is contained in:
1 parent
addebffba1
commit
fbea9dfe08
3 files changed
+154
-3
No files matched your search
@@ -134,6 +134,9 @@ public final class CemBaker implements EntityBaker<EntityState> {
|
||||
place = Affine.translation(s.x(), s.y(), s.z())
|
||||
.mul(Affine.scale(extra))
|
||||
.mul(Affine.rotY(yawSign * Math.toRadians(s.bodyYaw())));
|
||||
// Projectiles (verified against ArrowRenderer/ThrownTridentRenderer 26.3): T0 = rotY(-90)[·rotZ(90)],
|
||||
// pitchAxis = -X, so rotY(yaw)·rotAxis(-X, pitch)·T0 == vanilla rotY(yRot-90)·rotZ(xRot[+90]) with the
|
||||
// raw projectile rotation (yaw atan2(dx,dz), pitch positive up); origin = entity position, no offset.
|
||||
if (info != null && info.pitchAxis() != null && s.pitch() != 0) {
|
||||
double[] a = info.pitchAxis();
|
||||
place = place.mul(Affine.rotAxis(a[0], a[1], a[2], Math.toRadians(s.pitch())));
|
||||
|
||||
+33
-1
@@ -19,6 +19,7 @@ import java.util.function.DoubleSupplier;
|
||||
import org.bukkit.Keyed;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.AbstractArrow;
|
||||
import org.bukkit.entity.AbstractHorse;
|
||||
import org.bukkit.entity.Ageable;
|
||||
import org.bukkit.entity.ArmorStand;
|
||||
@@ -47,6 +48,7 @@ import org.bukkit.entity.Shulker;
|
||||
import org.bukkit.entity.Slime;
|
||||
import org.bukkit.entity.Snowman;
|
||||
import org.bukkit.entity.TraderLlama;
|
||||
import org.bukkit.entity.Trident;
|
||||
import org.bukkit.entity.TropicalFish;
|
||||
import org.bukkit.entity.Villager;
|
||||
import org.bukkit.entity.Wolf;
|
||||
@@ -304,7 +306,7 @@ public final class EntitySnapshotBuilder {
|
||||
bodyEquip,
|
||||
equipment,
|
||||
invisible,
|
||||
loc.getPitch(),
|
||||
loc.getPitch() + arrowShake(e),
|
||||
bodyDye,
|
||||
tint2,
|
||||
e.isInsideVehicle(),
|
||||
@@ -406,6 +408,36 @@ public final class EntitySnapshotBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/** Per NMS entity class: its public {@code shakeTime} field (AbstractArrow), or null. */
|
||||
private static final ClassValue<java.lang.reflect.Field> SHAKE_FIELD = new ClassValue<>() {
|
||||
@Override
|
||||
protected java.lang.reflect.Field computeValue(Class<?> c) {
|
||||
try {
|
||||
return c.getField("shakeTime");
|
||||
} catch (NoSuchFieldException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Vanilla impact wobble of a freshly stuck arrow (ArrowModel#setupAnim, partialTick 0): the model root turns
|
||||
* by {@code -sin(3·shake)·shake} degrees about its z axis — the same axis as the pitch, so it is added to it.
|
||||
* {@code shakeTime} (7 → 0 ticks after hitting a block) has no API; read from the server entity. Tridents
|
||||
* ignore it (ThrownTridentRenderer has no shake).
|
||||
*/
|
||||
private static float arrowShake(Entity e) {
|
||||
if (!(e instanceof AbstractArrow) || e instanceof Trident) return 0;
|
||||
try {
|
||||
Object handle = e.getClass().getMethod("getHandle").invoke(e);
|
||||
java.lang.reflect.Field f = SHAKE_FIELD.get(handle.getClass());
|
||||
int shake = f == null ? 0 : f.getInt(handle);
|
||||
return shake <= 0 ? 0 : (float) (-Math.sin(shake * 3.0) * shake);
|
||||
} catch (ReflectiveOperationException | RuntimeException ex) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/** Per entity class: its {@code getVariant()} / {@code getWeatheringState()} getter, or null. */
|
||||
private static final ClassValue<java.lang.reflect.Method> VARIANT_GETTER = new ClassValue<>() {
|
||||
@Override
|
||||
|
||||
@@ -65,6 +65,21 @@ public class PPTest extends JavaPlugin {
|
||||
scenes.put("chests_e", new Scene("chests_e", this::chests, 16, 5, 3, 4.5, 0, 3));
|
||||
scenes.put("chests_w", new Scene("chests_w", this::chests, -7, 5, 3, 4.5, 0, 3));
|
||||
scenes.put("translucent", new Scene("translucent", this::translucent, 4.5, 2.8, -5.5, 4.5, 0.8, 2));
|
||||
// Projectile orientation (vanilla ArrowRenderer/ThrownTridentRenderer): flying stars around gold pillars
|
||||
// (tips point radially outward, rings level / +35 deg / -35 deg) and arrows/tridents stuck in stone pillars.
|
||||
scenes.put("proj_fly_top", new Scene("proj_fly_top", this::projFly, 6.5, 13, 2.2, 6.5, 0, 2));
|
||||
scenes.put("proj_fly_side", new Scene("proj_fly_side", this::projFly, 6.5, 2.6, -8, 6.5, 2.4, 2));
|
||||
scenes.put("proj_fly_obl", new Scene("proj_fly_obl", this::projFly, -2, 6, -6, 6.5, 2, 2));
|
||||
scenes.put("proj_stuck_nw", new Scene("proj_stuck_nw", this::projStuck, -3, 6, -6, 8, 1.5, 2));
|
||||
scenes.put("proj_stuck_se", new Scene("proj_stuck_se", this::projStuck, 19, 6, 10, 8, 1.5, 2));
|
||||
scenes.put("proj_stuck_ne", new Scene("proj_stuck_ne", this::projStuck, 19, 6, -6, 8, 1.5, 2));
|
||||
scenes.put("proj_fly_gold", new Scene("proj_fly_gold", this::projFly, 2.6, 8.5, 2.5, 2.5, 0, 2.5));
|
||||
scenes.put("proj_fly_gold_side", new Scene("proj_fly_gold_side", this::projFly, 2.5, 2.6, -3.5, 2.5, 2.4, 2.5));
|
||||
scenes.put("proj_stuck_close_a", new Scene("proj_stuck_close_a", this::projStuck, -2.5, 2.4, -2.5, 2.5, 1.6, 1));
|
||||
scenes.put("proj_stuck_close_t", new Scene("proj_stuck_close_t", this::projStuck, 18.5, 2.6, -3, 13.5, 1.6, 1));
|
||||
// Rendered 4 ticks after the shots: freshly stuck arrows still wobble (vanilla shakeTime -> pitch offset).
|
||||
scenes.put("proj_shake", new Scene("proj_shake", this::projStuck, -2.5, 2.4, -2.5, 2.5, 1.6, 1));
|
||||
scenes.put("proj_stuck_sw", new Scene("proj_stuck_sw", this::projStuck, -3, 6, 10, 8, 1.5, 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,7 +109,7 @@ public class PPTest extends JavaPlugin {
|
||||
t.printStackTrace();
|
||||
}
|
||||
// Let one tick pass (passengers/leashes settle), then render.
|
||||
Bukkit.getScheduler().runTaskLater(this, () -> render(sc, o), 10);
|
||||
Bukkit.getScheduler().runTaskLater(this, () -> render(sc, o), sc.name.equals("proj_shake") ? 4 : sc.name.startsWith("proj_") ? 30 : 10);
|
||||
}
|
||||
|
||||
private void render(Scene sc, Location o) {
|
||||
@@ -102,7 +117,7 @@ public class PPTest extends JavaPlugin {
|
||||
Location eye = o.clone().add(sc.camX, sc.camY, sc.camZ);
|
||||
Vector look = o.clone().add(sc.lookX, sc.lookY, sc.lookZ).toVector().subtract(eye.toVector());
|
||||
eye.setDirection(look);
|
||||
RenderJob job = r.prepare(eye, new Resolution(480, 320), null);
|
||||
RenderJob job = r.prepare(eye, sc.name.startsWith("proj_") ? new Resolution(960, 640) : new Resolution(480, 320), null);
|
||||
if (sc.name.equals("dbg")) {
|
||||
try {
|
||||
java.lang.reflect.Field f = DefaultScreenRenderer.class.getDeclaredField("objectBaker");
|
||||
@@ -124,6 +139,30 @@ public class PPTest extends JavaPlugin {
|
||||
getLogger().warning("dbg: " + t);
|
||||
}
|
||||
}
|
||||
if (sc.name.startsWith("proj_"))
|
||||
for (Entity e : w.getNearbyEntities(o.clone().add(8, 3, 2), 12, 8, 12))
|
||||
if (e instanceof Projectile pr) {
|
||||
Vector v = pr.getVelocity();
|
||||
getLogger().info(String.format("proj %s yaw=%.1f pitch=%.1f velYaw=%.1f velPitch=%.1f ground=%s",
|
||||
e.getType(), e.getLocation().getYaw(), e.getLocation().getPitch(),
|
||||
Math.toDegrees(Math.atan2(v.getX(), v.getZ())),
|
||||
Math.toDegrees(Math.atan2(v.getY(), Math.hypot(v.getX(), v.getZ()))),
|
||||
pr instanceof AbstractArrow a && a.isInBlock()));
|
||||
}
|
||||
if (sc.name.equals("proj_shake"))
|
||||
for (var st : job.entities())
|
||||
if (st.typeKey().contains("arrow"))
|
||||
getLogger().info(String.format("state %s pitch=%.2f", st.typeKey(), st.pitch()));
|
||||
if (sc.name.equals("proj_shake"))
|
||||
for (Entity e : w.getNearbyEntities(o.clone().add(8, 3, 2), 12, 8, 12))
|
||||
if (e instanceof AbstractArrow a && !(e instanceof Trident))
|
||||
try {
|
||||
Object h = e.getClass().getMethod("getHandle").invoke(e);
|
||||
getLogger().info(String.format("shake %s pitch=%.2f shakeTime=%d", e.getType(), e.getLocation().getPitch(),
|
||||
h.getClass().getField("shakeTime").getInt(h)));
|
||||
} catch (ReflectiveOperationException ex) {
|
||||
getLogger().warning("shake: " + ex);
|
||||
}
|
||||
getLogger().info("scene " + sc.name + ": " + job.entities().size() + " entities, " + job.objects().size()
|
||||
+ " objects, " + job.blockEntities().size() + " block entities");
|
||||
Bukkit.getScheduler().runTaskAsynchronously(this, () -> {
|
||||
@@ -573,4 +612,81 @@ public class PPTest extends JavaPlugin {
|
||||
t.text(Component.text("Default background"));
|
||||
t.setBillboard(Display.Billboard.CENTER);
|
||||
}
|
||||
|
||||
// --- projectiles -----------------------------------------------------------------------------
|
||||
|
||||
/** Spawns a projectile at p moving along dir, rotated like vanilla Projectile#shoot (yaw atan2(x,z), pitch up). */
|
||||
private Projectile projectile(Location p, Vector dir, double speed, boolean gravity, Class<? extends Projectile> type) {
|
||||
Vector d = dir.clone().normalize();
|
||||
Location l = p.clone();
|
||||
l.setYaw((float) Math.toDegrees(Math.atan2(d.getX(), d.getZ())));
|
||||
l.setPitch((float) Math.toDegrees(Math.atan2(d.getY(), Math.hypot(d.getX(), d.getZ()))));
|
||||
Projectile pr = w.spawn(l, type, e -> {
|
||||
e.setPersistent(false);
|
||||
e.setGravity(gravity);
|
||||
e.setVelocity(d.clone().multiply(speed));
|
||||
if (e instanceof AbstractArrow a) a.setPickupStatus(AbstractArrow.PickupStatus.DISALLOWED);
|
||||
});
|
||||
pr.setRotation(l.getYaw(), l.getPitch()); // spawning resets it to 0 (it would only lerp over ~20 ticks)
|
||||
return pr;
|
||||
}
|
||||
|
||||
private static Vector dir(double yawDeg, double pitchDeg) {
|
||||
double y = Math.toRadians(yawDeg), p = Math.toRadians(pitchDeg);
|
||||
return new Vector(Math.sin(y) * Math.cos(p), Math.sin(p), Math.cos(y) * Math.cos(p));
|
||||
}
|
||||
|
||||
/** Hovering (almost still, no gravity) arrows, spectral arrows and tridents pointing radially away from a pillar. */
|
||||
private void projFly(Location o) {
|
||||
for (int y = 0; y <= 4; y++) {
|
||||
o.clone().add(2, y, 2).getBlock().setType(Material.GOLD_BLOCK, false);
|
||||
o.clone().add(11, y, 2).getBlock().setType(Material.DIAMOND_BLOCK, false);
|
||||
}
|
||||
double[][] rings = {{2.5, 0}, {4.3, 35}, {0.9, -35}}; // height, pitch
|
||||
for (double[] ring : rings)
|
||||
for (int i = 0; i < 8; i++) {
|
||||
double yaw = i * 45;
|
||||
Vector d = dir(yaw, ring[1]);
|
||||
Vector h = dir(yaw, 0);
|
||||
Location a = o.clone().add(2.5 + h.getX() * 1.3, ring[0], 2.5 + h.getZ() * 1.3);
|
||||
projectile(a, d, 1e-4, false, (Class<? extends AbstractArrow>) (i % 2 == 0 ? Arrow.class : SpectralArrow.class));
|
||||
Location t = o.clone().add(11.5 + h.getX() * 2.1, ring[0], 2.5 + h.getZ() * 2.1);
|
||||
projectile(t, d, 1e-4, false, Trident.class);
|
||||
}
|
||||
}
|
||||
|
||||
/** Arrows and tridents shot into stone pillars from all four sides (rising, level+sideways, falling) and the floor. */
|
||||
private void projStuck(Location o) {
|
||||
for (int x = 0; x < 3; x++)
|
||||
for (int z = 0; z < 3; z++)
|
||||
for (int y = 0; y < 4; y++) {
|
||||
o.clone().add(1 + x, y, 1 + z).getBlock().setType(Material.STONE, false);
|
||||
o.clone().add(12 + x, y, 1 + z).getBlock().setType(Material.STONE, false);
|
||||
}
|
||||
double[][] pillars = {{2.5, 2.5}, {13.5, 2.5}}; // centres; arrows left, tridents right
|
||||
for (int pi = 0; pi < 2; pi++) {
|
||||
boolean trident = pi == 1;
|
||||
double cx = pillars[pi][0], cz = pillars[pi][1];
|
||||
for (int side = 0; side < 4; side++) {
|
||||
double out = side * 90; // yaw (projectile convention) pointing away from the pillar
|
||||
Vector n = dir(out, 0);
|
||||
Vector t = new Vector(n.getZ(), 0, -n.getX());
|
||||
double dist = 1.5 + 3.5;
|
||||
// rising from low, level but 20 deg sideways, falling from high
|
||||
double[][] shots = {{-1, 0.6, 25, 0}, {0, 1.6, 0, 20}, {1, 3.4, -25, 0}};
|
||||
for (double[] sh : shots) {
|
||||
Location p = o.clone().add(cx + n.getX() * dist + t.getX() * sh[0], sh[1], cz + n.getZ() * dist + t.getZ() * sh[0]);
|
||||
Vector d = dir(out + 180 + sh[3], sh[2]);
|
||||
if (trident) projectile(p, d, 2.0, true, Trident.class);
|
||||
else projectile(p, d, 1.5, true, (Class<? extends AbstractArrow>) (side % 2 == 0 ? Arrow.class : SpectralArrow.class));
|
||||
}
|
||||
// steep shot into the floor, diagonally away from the pillar
|
||||
Vector diag = dir(out + 45, 0);
|
||||
Location p = o.clone().add(cx + diag.getX() * 2.6, 3, cz + diag.getZ() * 2.6);
|
||||
Vector d = dir(out + 45, trident ? -70 : -60);
|
||||
if (trident) projectile(p, d, 2.0, true, Trident.class);
|
||||
else projectile(p, d, 1.5, true, Arrow.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user