christmas chest textures (24-26 december, chests and trapped chests, local_time item models, pixelpics.testDate test property), chest items from the item definition texture, shield banner patterns, item displays turned 180° like vanilla, dropped item lift from the ground model bounds, baby held-item offset, map decorations on framed vanilla maps (banners, frames, markers; none on photo maps), inverted block elements in items and displays (spawner, vault inner faces), deterministic test scenes, items/xmas/marker test scenes
This commit is contained in:
1 parent
9e48e35680
commit
9093c1ccfb
17 files changed
+681
-74
No files matched your search
@@ -198,6 +198,160 @@ public class PPTest extends JavaPlugin {
|
||||
scenes.put("be_contents_close", new Scene("be_contents_close", this::beContents, 1.2, 1.8, 2.2, 1.5, 0.6, 0));
|
||||
scenes.put("be_contents_beam", new Scene("be_contents_beam", this::beContents, 4, 6, 26, 10, 9, 6));
|
||||
scenes.put("be_contents_portal", new Scene("be_contents_portal", this::beContents, 11.5, 3.6, -1.8, 11.5, 0, 1.2));
|
||||
// Items in detail (decompiled 26.3 special renderers): shields (plain, base colour, banner patterns), chests,
|
||||
// spawner/vault held by armor stands (+ a baby zombie: baby hand offset), item displays (fixed), item frames
|
||||
// (rotation 0/1) and dropped (ground: lift = 0.1625 - model min y). items_inverted: spawner, trial spawner and
|
||||
// vault as block, block display, item display and dropped item (inverted inner faces). xmas_chests: chests,
|
||||
// double/trapped/ender/copper chests + chest items; render it once normally and once with the server
|
||||
// started with -Dpixelpics.testDate=2026-12-25 (christmas textures for chests and trapped chests).
|
||||
// frames_markers: map decorations on framed maps (banner markers via toggleBanner, frame/target markers,
|
||||
// an invisible-on-frames player marker; rotations 0/1/3), a PixelPics photo map and an undecorated map.
|
||||
scenes.put("items_detail", new Scene("items_detail", this::itemsDetail, 4.5, 3.2, 13, 4.5, 0.9, 2));
|
||||
scenes.put("items_detail_close", new Scene("items_detail_close", this::itemsDetail, 2.2, 1.7, 7.4, 2.2, 0.9, 2));
|
||||
scenes.put("items_detail_side", new Scene("items_detail_side", this::itemsDetail, -4.5, 2.4, 4.5, 5, 0.9, 3));
|
||||
scenes.put("items_inverted", new Scene("items_inverted", this::itemsInverted, 3, 2.4, 7, 3, 0.6, 1.5));
|
||||
scenes.put("xmas_chests", new Scene("xmas_chests", this::xmasChests, 6, 3.6, 9.5, 6, 0.5, 1.5));
|
||||
scenes.put("frames_markers", new Scene("frames_markers", this::framesMarkers, 2.5, 2.4, 4.2, 2.5, 2.4, 0));
|
||||
}
|
||||
|
||||
private static ItemStack shield(DyeColor base, Object... patterns) {
|
||||
ItemStack s = it(Material.SHIELD);
|
||||
org.bukkit.inventory.meta.ShieldMeta m = (org.bukkit.inventory.meta.ShieldMeta) s.getItemMeta();
|
||||
m.setBaseColor(base);
|
||||
for (int i = 0; i + 1 < patterns.length; i += 2)
|
||||
m.addPattern(new org.bukkit.block.banner.Pattern((DyeColor) patterns[i], (org.bukkit.block.banner.PatternType) patterns[i + 1]));
|
||||
s.setItemMeta(m);
|
||||
return s;
|
||||
}
|
||||
|
||||
private void itemsDetail(Location o) {
|
||||
for (int x = -1; x <= 11; x++) for (int y = 0; y <= 3; y++) put(o, x, y, -1, "stone");
|
||||
ItemStack[] items = {it(Material.SHIELD), shield(DyeColor.RED),
|
||||
shield(DyeColor.BLUE, DyeColor.YELLOW, org.bukkit.block.banner.PatternType.CREEPER,
|
||||
DyeColor.WHITE, org.bukkit.block.banner.PatternType.BORDER),
|
||||
it(Material.CHEST), it(Material.TRAPPED_CHEST), it(Material.ENDER_CHEST), it(Material.COPPER_CHEST),
|
||||
it(Material.SPAWNER), it(Material.VAULT), it(Material.RED_BANNER)};
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
frame(o, i, 1, 0, BlockFace.SOUTH, false, items[i], 0, true);
|
||||
frame(o, i, 2, 0, BlockFace.SOUTH, false, items[i], 1, true);
|
||||
ArmorStand as = spawn(o, i + 0.5, 0, 2.5, 0, ArmorStand.class);
|
||||
as.setArms(true);
|
||||
as.getEquipment().setItemInMainHand(items[i]);
|
||||
if (i < 3) as.getEquipment().setItemInOffHand(items[i]);
|
||||
ItemDisplay d = spawn(o, i + 0.5, 0.6, 4.5, 0, ItemDisplay.class);
|
||||
d.setItemStack(items[i]);
|
||||
d.setItemDisplayTransform(ItemDisplay.ItemDisplayTransform.FIXED);
|
||||
d.setTransformation(new Transformation(new Vector3f(), new Quaternionf(), new Vector3f(0.6f), new Quaternionf()));
|
||||
drop(o, i + 0.5, 0, 6.5, items[i], i * 20);
|
||||
}
|
||||
Zombie baby = spawn(o, 10.5, 0, 2.5, 0, Zombie.class);
|
||||
baby.setBaby();
|
||||
baby.getEquipment().setItemInMainHand(it(Material.CHEST));
|
||||
baby.getEquipment().setItemInOffHand(items[2]);
|
||||
Zombie z = spawn(o, 11.5, 0, 4, 0, Zombie.class);
|
||||
z.setAdult();
|
||||
z.getEquipment().setItemInMainHand(it(Material.TRAPPED_CHEST));
|
||||
z.getEquipment().setItemInOffHand(items[2]);
|
||||
}
|
||||
|
||||
private void itemsInverted(Location o) {
|
||||
String[] blocks = {"spawner", "trial_spawner", "vault[facing=south]"};
|
||||
Material[] mats = {Material.SPAWNER, Material.TRIAL_SPAWNER, Material.VAULT};
|
||||
for (int i = 0; i < blocks.length; i++) {
|
||||
put(o, i * 2, 0, 0, blocks[i]);
|
||||
BlockDisplay b = spawn(o, i * 2, 0, 2, 0, BlockDisplay.class);
|
||||
b.setBlock(Bukkit.createBlockData("minecraft:" + blocks[i]));
|
||||
ItemDisplay d = spawn(o, i * 2 + 0.5, 0.5, 4.5, 0, ItemDisplay.class);
|
||||
d.setItemStack(it(mats[i]));
|
||||
drop(o, i * 2 + 0.5, 0, 6, it(mats[i]), 15);
|
||||
}
|
||||
}
|
||||
|
||||
private void xmasChests(Location o) {
|
||||
put(o, 0, 0, 0, "chest[facing=south]");
|
||||
put(o, 3, 0, 0, "chest[facing=south,type=left]");
|
||||
put(o, 2, 0, 0, "chest[facing=south,type=right]");
|
||||
put(o, 5, 0, 0, "trapped_chest[facing=south]");
|
||||
put(o, 8, 0, 0, "trapped_chest[facing=south,type=left]");
|
||||
put(o, 7, 0, 0, "trapped_chest[facing=south,type=right]");
|
||||
put(o, 10, 0, 0, "ender_chest[facing=south]");
|
||||
put(o, 12, 0, 0, "copper_chest[facing=south]");
|
||||
Material[] mats = {Material.CHEST, Material.TRAPPED_CHEST};
|
||||
for (int i = 0; i < 2; i++) {
|
||||
ItemDisplay d = spawn(o, 0.5 + i * 1.5, 0.6, 3.5, 0, ItemDisplay.class);
|
||||
d.setItemStack(it(mats[i]));
|
||||
d.setItemDisplayTransform(ItemDisplay.ItemDisplayTransform.FIXED);
|
||||
ArmorStand as = spawn(o, 4 + i * 1.5, 0, 3.5, 0, ArmorStand.class);
|
||||
as.setArms(true);
|
||||
as.getEquipment().setItemInMainHand(it(mats[i]));
|
||||
drop(o, 7.5 + i, 0, 3.5, it(mats[i]), 30);
|
||||
put(o, 10 + i, 0, 3, "stone");
|
||||
frame(o, 10 + i, 1, 3, BlockFace.UP, false, it(mats[i]), 0, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void framesMarkers(Location o) {
|
||||
for (int x = -2; x <= 7; x++) for (int y = 0; y <= 4; y++) put(o, x, y, -1, "stone");
|
||||
ItemStack marked = vanillaMap();
|
||||
org.bukkit.map.MapView v = ((org.bukkit.inventory.meta.MapMeta) marked.getItemMeta()).getMapView();
|
||||
v.setScale(org.bukkit.map.MapView.Scale.CLOSEST);
|
||||
v.setCenterX(o.getBlockX());
|
||||
v.setCenterZ(o.getBlockZ());
|
||||
put(o, -5, 0, -10, "red_banner[rotation=0]");
|
||||
put(o, 16, 0, -11, "lime_banner[rotation=4]");
|
||||
mapBanner(v, o.clone().add(-5, 0, -10));
|
||||
mapBanner(v, o.clone().add(16, 0, -11));
|
||||
ItemFrame f0 = frame(o, 0, 2, 0, BlockFace.SOUTH, false, marked, 0, true);
|
||||
frame(o, 1, 2, 0, BlockFace.SOUTH, false, marked, 1, true);
|
||||
frame(o, 2, 2, 0, BlockFace.SOUTH, false, synthMap(), 0, true);
|
||||
frame(o, 3, 2, 0, BlockFace.SOUTH, false, vanillaMap(), 0, true);
|
||||
frame(o, 4, 2, 0, BlockFace.SOUTH, true, marked, 3, true);
|
||||
// Frame markers like MapItemSavedData.tickCarriedBy (key frame-<id>, frame block pos, rot = 2D facing · 90):
|
||||
// the real frame, plus fictitious ones turned east/north; a red X and a player marker (not on frames).
|
||||
mapDecoration(v, "FRAME", "frame-" + f0.getEntityId(), o.getBlockX(), o.getBlockZ(), 0);
|
||||
mapDecoration(v, "FRAME", "frame-pptest-1", o.getBlockX() + 30, o.getBlockZ() - 25, 270);
|
||||
mapDecoration(v, "FRAME", "frame-pptest-2", o.getBlockX() - 28, o.getBlockZ() + 30, 180);
|
||||
mapDecoration(v, "RED_X", "pptest-x", o.getBlockX() + 40, o.getBlockZ() + 40, 180);
|
||||
mapDecoration(v, "PLAYER", "pptest-player", o.getBlockX() + 10, o.getBlockZ() + 10, 0);
|
||||
}
|
||||
|
||||
/** MapItemSavedData.toggleBanner(level, pos) on the map's server data (reflective: no API). */
|
||||
private void mapBanner(org.bukkit.map.MapView v, Location banner) {
|
||||
try {
|
||||
Object data = mapData(v);
|
||||
Object level = w.getClass().getMethod("getHandle").invoke(w);
|
||||
Class<?> bp = Class.forName("net.minecraft.core.BlockPos", true, data.getClass().getClassLoader());
|
||||
Object pos = bp.getConstructor(int.class, int.class, int.class)
|
||||
.newInstance(banner.getBlockX(), banner.getBlockY(), banner.getBlockZ());
|
||||
for (java.lang.reflect.Method m : data.getClass().getMethods())
|
||||
if (m.getName().equals("toggleBanner") && m.getParameterCount() == 2)
|
||||
getLogger().info("toggleBanner " + banner.toVector() + " -> " + m.invoke(data, level, pos));
|
||||
} catch (ReflectiveOperationException e) {
|
||||
getLogger().warning("mapBanner: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
/** MapItemSavedData.addDecoration(MapDecorationTypes.<type>, level, key, x, z, yRot, null) (private, reflective). */
|
||||
private void mapDecoration(org.bukkit.map.MapView v, String type, String key, double x, double z, double rot) {
|
||||
try {
|
||||
Object data = mapData(v);
|
||||
Object level = w.getClass().getMethod("getHandle").invoke(w);
|
||||
Object holder = Class.forName("net.minecraft.world.level.saveddata.maps.MapDecorationTypes", true,
|
||||
data.getClass().getClassLoader()).getField(type).get(null);
|
||||
for (java.lang.reflect.Method m : data.getClass().getDeclaredMethods())
|
||||
if (m.getName().equals("addDecoration") && m.getParameterCount() == 7) {
|
||||
m.setAccessible(true);
|
||||
m.invoke(data, holder, level, key, x, z, rot, null);
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
getLogger().warning("mapDecoration: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Object mapData(org.bukkit.map.MapView v) throws ReflectiveOperationException {
|
||||
java.lang.reflect.Field wm = v.getClass().getDeclaredField("worldMap");
|
||||
wm.setAccessible(true);
|
||||
return wm.get(v);
|
||||
}
|
||||
|
||||
private void beContents(Location o) {
|
||||
@@ -212,6 +366,7 @@ public class PPTest extends JavaPlugin {
|
||||
c.setItem(i, it(food[i]));
|
||||
c.setCookTimeTotal(i, 1_000_000); // else the lit campfire pops them out at once (total 0)
|
||||
}
|
||||
c.stopCooking(); // keep the food raw and in place while the scene waits
|
||||
c.update(true, false);
|
||||
}
|
||||
if (o.clone().add(4, 0, 0).getBlock().getState() instanceof org.bukkit.block.Campfire c) {
|
||||
@@ -561,7 +716,9 @@ public class PPTest extends JavaPlugin {
|
||||
put(o, 8, 0, 1, "water");
|
||||
Mannequin m3 = rfSpawn(o, 8.5, 0.1, 0.5, 180, Mannequin.class);
|
||||
m3.setPose(Pose.SWIMMING, true);
|
||||
m3.setGravity(false); // else it floats up in the water while the scene waits (non-deterministic)
|
||||
Mannequin m4 = rfSpawn(o, 10, 1.0, 0, 180, Mannequin.class);
|
||||
m4.setGravity(false); // keeps gliding at y 1 instead of falling
|
||||
m4.getEquipment().setChestplate(it(Material.ELYTRA));
|
||||
m4.setPose(Pose.FALL_FLYING, true);
|
||||
Mannequin m5 = rfSpawn(o, 12, 0, 0, 180, Mannequin.class);
|
||||
@@ -649,7 +806,30 @@ public class PPTest extends JavaPlugin {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the scene's chunks and waits until they are entity-ticking before building: entities spawned into a
|
||||
* chunk that only becomes entity-ticking a few (variable) ticks later start moving late (projectile scenes were
|
||||
* not reproducible).
|
||||
*/
|
||||
private void run(Scene sc, Location o) {
|
||||
loadArea(o);
|
||||
whenEntityTicking(sc, o, 0);
|
||||
}
|
||||
|
||||
private void whenEntityTicking(Scene sc, Location o, int waited) {
|
||||
boolean ready = true;
|
||||
for (int cx = -1; cx <= 2; cx++)
|
||||
for (int cz = -2; cz <= 2; cz++)
|
||||
ready &= w.getChunkAt((o.getBlockX() >> 4) + cx, (o.getBlockZ() >> 4) + cz).getLoadLevel() == Chunk.LoadLevel.ENTITY_TICKING;
|
||||
if (!ready && waited < 200) {
|
||||
Bukkit.getScheduler().runTaskLater(this, () -> whenEntityTicking(sc, o, waited + 1), 1);
|
||||
return;
|
||||
}
|
||||
if (!ready) getLogger().warning("scene " + sc.name + ": chunks not entity-ticking after 200 ticks");
|
||||
build(sc, o);
|
||||
}
|
||||
|
||||
private void build(Scene sc, Location o) {
|
||||
try {
|
||||
prepareArea(o);
|
||||
sc.build.accept(o.clone());
|
||||
@@ -666,7 +846,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, sc.name.endsWith("_sq") ? new Resolution(384, 384) : sc.name.startsWith("proj_") || sc.name.startsWith("rf_") || sc.name.startsWith("pose_") || sc.name.startsWith("frames") || sc.name.startsWith("geo_") ? new Resolution(960, 640) : new Resolution(480, 320), null);
|
||||
RenderJob job = r.prepare(eye, sc.name.endsWith("_sq") ? new Resolution(384, 384) : sc.name.startsWith("proj_") || sc.name.startsWith("rf_") || sc.name.startsWith("pose_") || sc.name.startsWith("frames") || sc.name.startsWith("geo_") || sc.name.startsWith("items_") || sc.name.startsWith("xmas_") ? new Resolution(960, 640) : new Resolution(480, 320), null);
|
||||
if (sc.name.equals("dbg")) {
|
||||
try {
|
||||
java.lang.reflect.Field f = DefaultScreenRenderer.class.getDeclaredField("objectBaker");
|
||||
@@ -730,43 +910,74 @@ public class PPTest extends JavaPlugin {
|
||||
});
|
||||
}
|
||||
|
||||
/** Clears the area (entities + blocks) and lays a 14x14 stone floor at the origin's y-1. */
|
||||
private void prepareArea(Location o) {
|
||||
/** Loads the scene's chunks and keeps them loaded (plugin chunk tickets). */
|
||||
private void loadArea(Location o) {
|
||||
for (int cx = -1; cx <= 2; cx++)
|
||||
for (int cz = -2; cz <= 2; cz++) {
|
||||
Chunk c = w.getChunkAt((o.getBlockX() >> 4) + cx, (o.getBlockZ() >> 4) + cz);
|
||||
c.load(true);
|
||||
c.addPluginChunkTicket(this);
|
||||
}
|
||||
for (Entity e : w.getNearbyEntities(o.clone().add(6, 5, 2), 20, 20, 20)) if (!(e instanceof Player)) e.remove();
|
||||
}
|
||||
|
||||
/** Clears the area (blocks + entities) and lays a 24x24 stone floor at the origin's y-1. */
|
||||
private void prepareArea(Location o) {
|
||||
// Blocks first: removing a campfire/shelf/container drops its items, which the entity sweep then removes
|
||||
// (before, the previous run's cooked campfire food lay around at random).
|
||||
for (int x = -6; x < 18; x++)
|
||||
for (int z = -12; z < 12; z++)
|
||||
for (int y = -1; y < 12; y++)
|
||||
w.getBlockAt(o.getBlockX() + x, o.getBlockY() + y, o.getBlockZ() + z)
|
||||
.setType(y == -1 ? Material.SMOOTH_STONE : Material.AIR, false);
|
||||
for (Entity e : w.getNearbyEntities(o.clone().add(6, 5, 2), 20, 20, 20)) if (!(e instanceof Player)) e.remove();
|
||||
}
|
||||
|
||||
// --- helpers ---------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Deterministic spawn (pixel-diffable between plugin builds): randomizeData=false (no finalizeSpawn — no random
|
||||
* variants, sizes, babies/jockeys or equipment) and the body turned to {@code yaw} (spawned mobs otherwise keep a
|
||||
* random body yaw). Scenes set every variant/equipment they show explicitly.
|
||||
*/
|
||||
private <T extends Entity> T spawn(Location o, double x, double y, double z, float yaw, Class<T> type) {
|
||||
Location l = o.clone().add(x, y, z);
|
||||
l.setYaw(yaw);
|
||||
T e = w.spawn(l, type, en -> {
|
||||
if (en instanceof Mob m) m.setAI(false);
|
||||
en.setPersistent(false);
|
||||
});
|
||||
if (e instanceof LivingEntity le) le.setRemoveWhenFarAway(false);
|
||||
T e = rfSpawn(o, x, y, z, yaw, type);
|
||||
if (e instanceof LivingEntity le) le.setBodyYaw(yaw);
|
||||
return e;
|
||||
}
|
||||
|
||||
private Entity spawn(Location o, double x, double y, double z, float yaw, EntityType type) {
|
||||
Location l = o.clone().add(x, y, z);
|
||||
l.setYaw(yaw);
|
||||
Entity e = w.spawnEntity(l, type);
|
||||
if (e instanceof Mob m) m.setAI(false);
|
||||
Entity e = rfSpawn(o, x, y, z, yaw, type);
|
||||
e.setPersistent(false);
|
||||
if (e instanceof LivingEntity le) {
|
||||
le.setRemoveWhenFarAway(false);
|
||||
le.setBodyYaw(yaw);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Respawns until the entity id is even: a full boat turns an animal passenger by 90° or 270° depending on the
|
||||
* id's parity (AbstractBoat.positionRider), and ids depend on the server's history.
|
||||
*/
|
||||
private <T extends Entity> T evenId(java.util.function.Supplier<T> spawner) {
|
||||
T e = spawner.get();
|
||||
while (e.getEntityId() % 2 != 0) {
|
||||
e.remove();
|
||||
e = spawner.get();
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
/** A dropped item at rest: no velocity/gravity, fixed yaw (PixelPics freezes the item spin from the yaw). */
|
||||
private Item drop(Location o, double x, double y, double z, ItemStack stack, float yaw) {
|
||||
return w.dropItem(o.clone().add(x, y, z), stack, i -> {
|
||||
i.setVelocity(new Vector());
|
||||
i.setGravity(false);
|
||||
i.setRotation(yaw, 0);
|
||||
i.setPersistent(false);
|
||||
});
|
||||
}
|
||||
|
||||
private static ItemStack it(Material m) {
|
||||
return new ItemStack(m);
|
||||
}
|
||||
@@ -839,7 +1050,7 @@ public class PPTest extends JavaPlugin {
|
||||
mc.addPassenger(spawn(o, 7, 1, 0, 180, Villager.class));
|
||||
Boat b = (Boat) spawn(o, 9.5, 0, 0, 180, EntityType.OAK_BOAT);
|
||||
b.addPassenger(spawn(o, 9.5, 1, 0, 180, Zombie.class));
|
||||
b.addPassenger(spawn(o, 9.5, 1, 0, 180, Pig.class));
|
||||
b.addPassenger(evenId(() -> spawn(o, 9.5, 1, 0, 180, Pig.class)));
|
||||
Camel c = spawn(o, 2, 0, 3.5, 90, Camel.class);
|
||||
c.getInventory().setSaddle(it(Material.SADDLE));
|
||||
c.addPassenger(spawn(o, 2, 2, 3.5, 90, EntityType.MANNEQUIN));
|
||||
@@ -955,10 +1166,10 @@ public class PPTest extends JavaPlugin {
|
||||
}
|
||||
|
||||
private void misc(Location o) {
|
||||
w.dropItem(o.clone().add(0, 0.2, 0), it(Material.DIAMOND_SWORD), i -> i.setVelocity(new Vector()));
|
||||
w.dropItem(o.clone().add(1, 0.2, 0), it(Material.OAK_LOG), i -> i.setVelocity(new Vector()));
|
||||
w.dropItem(o.clone().add(2, 0.2, 0), new ItemStack(Material.COBBLESTONE, 32), i -> i.setVelocity(new Vector()));
|
||||
w.dropItem(o.clone().add(3, 0.2, 0), it(Material.APPLE), i -> i.setVelocity(new Vector()));
|
||||
drop(o, 0, 0, 0, it(Material.DIAMOND_SWORD), 30);
|
||||
drop(o, 1, 0, 0, it(Material.OAK_LOG), 60);
|
||||
drop(o, 2, 0, 0, new ItemStack(Material.COBBLESTONE, 32), 0);
|
||||
drop(o, 3, 0, 0, it(Material.APPLE), 90);
|
||||
FallingBlock fb = w.spawn(o.clone().add(4.5, 0.5, 0.5), FallingBlock.class, f -> f.setGravity(false));
|
||||
fb.setBlockData(Bukkit.createBlockData("minecraft:sand"));
|
||||
TNTPrimed tnt = w.spawn(o.clone().add(6, 0, 0.5), TNTPrimed.class);
|
||||
|
||||
Reference in new issue
Block a user