package pptest; import eu.mhsl.minecraft.pixelpics.Main; import eu.mhsl.minecraft.pixelpics.render.render.DefaultScreenRenderer; import eu.mhsl.minecraft.pixelpics.render.render.RenderJob; import eu.mhsl.minecraft.pixelpics.render.render.Resolution; import java.awt.image.BufferedImage; import java.io.File; import java.util.*; import java.util.function.Consumer; import javax.imageio.ImageIO; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.*; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.data.BlockData; import org.bukkit.block.data.type.Bed; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.*; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.util.Transformation; import org.bukkit.util.Vector; import org.joml.AxisAngle4f; import org.joml.Quaternionf; import org.joml.Vector3f; /** Test-only plugin: builds edge-case scenes and renders them through PixelPics into PNGs. */ public class PPTest extends JavaPlugin { record Scene(String name, Consumer build, double camX, double camY, double camZ, double lookX, double lookY, double lookZ) {} private final Map scenes = new LinkedHashMap<>(); private World w; @Override public void onEnable() { // Each scene sits on its own stone platform; the camera is placed relative to the platform origin. scenes.put("held", new Scene("held", this::held, 4.4, 2.6, -8.5, 4.4, 1.0, 1)); scenes.put("equip", new Scene("equip", this::equip, 4, 3, -8.5, 4, 1.0, 1.5)); scenes.put("riding", new Scene("riding", this::riding, 5, 3, -7.5, 5, 1.2, 1)); scenes.put("leash", new Scene("leash", this::leash, 5, 4, -9, 5, 2.5, 2)); scenes.put("displays", new Scene("displays", this::displays, 5, 3, -9, 5, 1.0, 1.5)); scenes.put("pixelblock", new Scene("pixelblock", this::pixelblock, 1.5, 1.9, -1.3, 0.5, 0.5, 0.5)); scenes.put("misc", new Scene("misc", this::misc, 5, 3, -7, 5, 0.8, 1.5)); scenes.put("dbg", new Scene("dbg", this::dbg, 1, 1.6, -3, 1, 0.8, 0.5)); scenes.put("shelf", new Scene("shelf", this::shelf, 3.5, 3.2, -2.5, 0.5, 1.5, 0.5)); scenes.put("shelftop", new Scene("shelftop", this::shelf, 0.52, 6, 0.5, 0.5, 0, 0.5)); scenes.put("rotated", new Scene("rotated", this::rotated, 3, 4.5, -4, 3, 0.5, 2)); scenes.put("rotated2", new Scene("rotated2", this::rotated, -0.5, 3.5, 9.5, -0.5, 0.5, 5)); scenes.put("redstone", new Scene("redstone", this::redstone, 3, 3, -3, 3, 0, 2)); scenes.put("piston", new Scene("piston", this::piston, 4.5, 3.2, -3.5, 5, 1, 1)); scenes.put("pistonback", new Scene("pistonback", this::piston, 5.5, 3.2, 5.5, 5, 1, 1)); scenes.put("pistondown", new Scene("pistondown", this::piston, -1.6, 2.6, -1.6, 0.5, 1.4, 0.5)); scenes.put("pistonup", new Scene("pistonup", this::piston, 0.4, 2.6, -1.6, 2.5, 1.4, 0.5)); scenes.put("vehicles", new Scene("vehicles", this::vehicles, 5, 3, -7, 5, 0.6, 1.5)); // Blockstate x rotations (+ x/y combined, uvlock): lying pillars, vertical observers/dispensers, top stairs. scenes.put("xrot", new Scene("xrot", this::xrot, 3.5, 5.5, -4.5, 6, 0.5, 3.5)); scenes.put("xrot2", new Scene("xrot2", this::xrot, 11, 5, 10.5, 6, 0.5, 3.5)); // Chests: same layout seen from all four sides (single/double per facing, trapped, ender, copper). scenes.put("chests_s", new Scene("chests_s", this::chests, 4.5, 5, 13, 4.5, 0, 3)); scenes.put("chests_n", new Scene("chests_n", this::chests, 4.5, 5, -7, 4.5, 0, 3)); 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)); // Conditional vanilla layers: collars (tamed only, dye-tinted), charged creeper / wither armor (additive // energy swirl), fox/dolphin mouth items. scenes.put("layers", new Scene("layers", this::layers, 4.8, 2.4, -5.5, 4.8, 0.6, 1.5)); scenes.put("layers_side", new Scene("layers_side", this::layers, 12.5, 2.2, 5.5, 5, 0.5, 2.5)); scenes.put("layers_close", new Scene("layers_close", this::layers, 5.5, 1.3, 0.3, 5.5, 0.4, 2.5)); scenes.put("layers_pets", new Scene("layers_pets", this::layers, 4.5, 1.6, -3.2, 4.5, 0.3, 0)); scenes.put("layers_dolphin", new Scene("layers_dolphin", this::layers, 10, 1.0, 1.0, 7.5, 0.4, 2.0)); scenes.put("layers_cats", new Scene("layers_cats", this::layers, 1.7, 1.1, -1.6, 1.7, 0.3, 0)); scenes.put("layers_cats_side", new Scene("layers_cats_side", this::layers, 1.7, 1.6, 1.4, 1.7, 0.2, 0)); scenes.put("layers_top", new Scene("layers_top", this::layers, 5.5, 6, 2.0, 5.5, 0, 2.5)); scenes.put("layers_wither", new Scene("layers_wither", this::withers, 3.5, 4, -9, 3.5, 2.2, 1)); scenes.put("layers_wither_close", new Scene("layers_wither_close", this::withers, 6, 3.4, -3.5, 6, 2.6, 1)); // Block entities (vanilla BlockEntityRenderer poses): signs, banners, heads in all 16 rotations + wall // mounts, and bells/pots/shulker boxes/lecterns/conduits, each layout seen from two sides. scenes.put("be_signs_s", new Scene("be_signs_s", this::beSigns, 7, 3, 8, 7, 0.5, 1)); scenes.put("be_signs_n", new Scene("be_signs_n", this::beSigns, 7, 1.3, -8, 7, 0.6, 1)); scenes.put("be_signs_close", new Scene("be_signs_close", this::beSigns, 3, 1.8, 4.2, 3, 0.8, 2)); scenes.put("be_signs_wall", new Scene("be_signs_wall", this::beSigns, 4.6, 2.2, 10.2, 3.5, 0.9, 7)); scenes.put("be_signs_wall_n", new Scene("be_signs_wall_n", this::beSigns, 1.4, 2.2, 3.8, 3.5, 0.9, 7)); scenes.put("be_signs_wall2", new Scene("be_signs_wall2", this::beSigns, 12.5, 2.4, 10.2, 10, 1.2, 7)); scenes.put("be_signs_hang", new Scene("be_signs_hang", this::beSigns, 4, 1.3, 0.2, 4, 1.4, -3)); scenes.put("be_banners_s", new Scene("be_banners_s", this::beBanners, 7, 3, 10, 7, 1, 1.5)); scenes.put("be_banners_n", new Scene("be_banners_n", this::beBanners, 7, 3, -6, 7, 1, 1.5)); scenes.put("be_banners_wall", new Scene("be_banners_wall", this::beBanners, 13, 3.5, 9, 16, 1.5, 5)); scenes.put("be_banners_wall_n", new Scene("be_banners_wall_n", this::beBanners, 13, 3.5, 1, 16, 1.5, 5)); scenes.put("be_heads_s", new Scene("be_heads_s", this::beHeads, 5, 5, 11, 5, 0, 3)); scenes.put("be_heads_n", new Scene("be_heads_n", this::beHeads, 5, 5, -6, 5, 0, 3)); scenes.put("be_heads_close", new Scene("be_heads_close", this::beHeads, 4, 2.2, 9.5, 4, 0.3, 5)); scenes.put("be_heads_wall_w", new Scene("be_heads_wall_w", this::beHeads, 7, 3, 2.5, 12, 1, 2.5)); scenes.put("be_heads_wall_e", new Scene("be_heads_wall_e", this::beHeads, 17, 3, 2.5, 12, 1, 2.5)); scenes.put("be_misc_s", new Scene("be_misc_s", this::beMisc, 6, 5, 11, 6, 0.5, 3)); scenes.put("be_misc_n", new Scene("be_misc_n", this::beMisc, 6, 5, -6, 6, 0.5, 3)); scenes.put("be_misc_close", new Scene("be_misc_close", this::beMisc, 3, 2.5, 3, 3, 0.5, 0)); scenes.put("be_misc_lectern", new Scene("be_misc_lectern", this::beMisc, 3, 2.2, 9.5, 3, 0.6, 6)); scenes.put("be_misc_shulker", new Scene("be_misc_shulker", this::beMisc, 11, 2.8, 9.5, 11, 0.3, 4)); scenes.put("be_items", new Scene("be_items", this::beItems, 4.5, 2.6, 11, 4.5, 0.8, 3)); scenes.put("be_items_back", new Scene("be_items_back", this::beItems, 4.5, 2.6, -5, 4.5, 0.8, 3)); scenes.put("proj_stuck_sw", new Scene("proj_stuck_sw", this::projStuck, -3, 6, 10, 8, 1.5, 2)); // Data-driven variants / pose flags / armor wearers (vanilla dump: asset_id, setupAnim flags, armor layers). scenes.put("rf_cats", new Scene("rf_cats", this::rfCats, 6.5, 2.6, -4.5, 6.5, 0.2, 1.2)); scenes.put("rf_wolves", new Scene("rf_wolves", this::rfWolves, 5.5, 3.2, -5.5, 5.5, 0.2, 1.8)); scenes.put("rf_farm", new Scene("rf_farm", this::rfFarm, 6, 3.4, -6, 6, 0.3, 1.8)); scenes.put("rf_stands", new Scene("rf_stands", this::rfStands, 5, 2.4, -4.5, 5, 0.9, 0)); scenes.put("rf_stands_back", new Scene("rf_stands_back", this::rfStands, 5, 2.4, 4.5, 5, 0.9, 0)); scenes.put("rf_chests", new Scene("rf_chests", this::rfChests, 7, 4, -7, 7, 0.8, 1)); scenes.put("rf_chests_side", new Scene("rf_chests_side", this::rfChests, 7, 2.5, 7, 7, 0.8, 1)); // Needs a datapack cat variant "pptest:ocelotcat" (asset_id entity/cat/ocelot) in the world's datapacks. scenes.put("rf_dpcat", new Scene("rf_dpcat", this::rfDatapackCat, 1.2, 1.4, -2.2, 1.2, 0.2, 0.5)); scenes.put("rf_armor", new Scene("rf_armor", this::rfArmor, 7, 3.2, -6.5, 7, 1.0, 1.5)); // Item frames (vanilla ItemFrameRenderer): north wall with rotations 0-7, block/flat/tool/block-entity // items, photo + synthetic + vanilla-data maps, invisible and glow frames; frames on all 6 block sides. scenes.put("frames", new Scene("frames", this::frames, 4.5, 2.8, -3.5, 4.5, 2.6, 4)); scenes.put("frames_close", new Scene("frames_close", this::frames, 2.6, 2.4, 0.8, 2.6, 2.3, 4)); scenes.put("frames_side", new Scene("frames_side", this::frames, -1.5, 3.2, -1.0, 4.5, 2.6, 3.6)); scenes.put("frames_dirs_ne", new Scene("frames_dirs_ne", this::framesDirs, 16, 7.5, -4, 8, 4, 3.5)); scenes.put("frames_dirs_sw", new Scene("frames_dirs_sw", this::framesDirs, 0, 1.2, 10, 8, 4, 3.5)); // Camera projection (pinhole, vertical FOV 70): frontal checkerboard wall / item-frame map (squares must be // square, same size everywhere), ceiling straight up, floor straight down at yaw 30, oblique wall with // stripes (edges must stay straight, the vertical stripe through the view centre vertical). *_sq = 384x384. scenes.put("cam_checker_sq", new Scene("cam_checker_sq", this::camChecker, 5.5, 5.5, 1, 5.5, 5.5, 6)); scenes.put("cam_checker", new Scene("cam_checker", this::camChecker, 5.5, 5.5, 1, 5.5, 5.5, 6)); scenes.put("cam_frame_sq", new Scene("cam_frame_sq", this::camFrame, 5.5, 5.5, 3, 5.5, 5.5, 6)); scenes.put("cam_up_sq", new Scene("cam_up_sq", this::camChecker, 5.5, 6, 0.5, 5.5, 11, 0.5)); scenes.put("cam_down_sq", new Scene("cam_down_sq", this::camChecker, 5.5, 4, 0.5, 5.4995, 0, 0.500866)); scenes.put("cam_oblique", new Scene("cam_oblique", this::camStripes, 2, 11, 1.5, 5.5, 5.5, 6)); // Performance (entity BVH): a 15x18 animal farm (270 mobs, randomizeData=false, fixed body yaw) and 300 // item displays from a seeded RNG -> deterministic, pixel-diffable; the render time is logged. scenes.put("perf_farm", new Scene("perf_farm", this::perfFarm, -6, 9, -13, 4, 0, 0)); scenes.put("perf_farm_close", new Scene("perf_farm_close", this::perfFarm, 0.5, 3, -11.5, 5, 0.5, -4)); scenes.put("perf_items", new Scene("perf_items", this::perfItems, 0.5, 5, -10, 5, 0, -2)); // Per-mob texture compositing (tinted-texture cache): villager type/profession/level, sheep colours, dyed // leather armor, wolf collars and patterned banners, all from a seeded RNG (deterministic). scenes.put("perf_village", new Scene("perf_village", this::perfVillage, -6, 9, -13, 4, 0, 0)); scenes.put("perf_village_close", new Scene("perf_village_close", this::perfVillage, 0.5, 3, -11.5, 5, 0.5, -4)); // Entity poses (vanilla setupAnim/renderer state from the dump): sitting/sleeping/crouching pets with mouth // items, cats lying, parrots, pandas; camel/sniffer/bat/dolphin pitch/fish in water/sleeping villager/ // mannequin poses/baby rider; armor stands with custom part poses. All rfSpawn (deterministic). scenes.put("pose_pets", new Scene("pose_pets", this::posePets, 4.3, 3.6, -6, 4.3, 0.2, 2)); scenes.put("pose_pets_side", new Scene("pose_pets_side", this::posePets, -3, 3, -3, 4, 0, 2)); scenes.put("pose_fox_close", new Scene("pose_fox_close", this::posePets, 2.2, 1.4, -2.2, 2.2, 0.2, 0)); scenes.put("pose_misc", new Scene("pose_misc", this::poseMisc, 6.5, 5, -10, 6.5, 0.5, 0)); scenes.put("pose_misc_side", new Scene("pose_misc_side", this::poseMisc, -4, 3, -7, 6, 0.5, -1)); scenes.put("pose_humans", new Scene("pose_humans", this::poseHumans, 6, 3.5, -7, 6, 0.6, 1)); scenes.put("pose_humans_side", new Scene("pose_humans_side", this::poseHumans, -3, 3, -4, 5, 0.5, 1)); scenes.put("pose_humans_close", new Scene("pose_humans_close", this::poseHumans, 4.8, 1.6, -5.2, 4.8, 0.5, -2.5)); scenes.put("pose_beds", new Scene("pose_beds", this::poseHumans, 1.3, 2.6, -3.2, 1.3, 0.4, 0.2)); scenes.put("pose_stands", new Scene("pose_stands", this::poseStands, 4, 2.4, -5, 4, 0.9, 0)); scenes.put("pose_stands_side", new Scene("pose_stands_side", this::poseStands, -3, 2.4, -3, 4, 0.9, 0)); // Entity texture / layer states (rfSpawn, deterministic): emissive eyes (spider, cave spider, enderman, // phantom, breeze, copper golem, warden), sheep wool colour / undercoat / baby wool / jeb_, angry wolves, // partial-alpha cutout textures (llama, tropical fish); small armor stands with armor, elytra at rest, // piglin / illager arm poses, skeleton holding a sword, invulnerable wither, tipped / potion arrows. scenes.put("rf_vlayers", new Scene("rf_vlayers", this::rfVisualLayers, 5.5, 3.4, -6, 5.5, 0.6, 2.5)); scenes.put("rf_vlayers_close", new Scene("rf_vlayers_close", this::rfVisualLayers, 3.2, 1.5, -2.4, 3.2, 0.5, 1)); scenes.put("rf_vstates", new Scene("rf_vstates", this::rfVisualStates, 5.5, 3.4, -6, 5.5, 0.9, 2.5)); scenes.put("rf_vstates_close", new Scene("rf_vstates_close", this::rfVisualStates, 3.2, 1.7, -2.6, 3.2, 0.9, 0.5)); } private void rfVisualLayers(Location o) { // Row 1: emissive eye layers. rfSpawn(o, 0, 0, 0, 180, Spider.class); rfSpawn(o, 1.6, 0, 0, 180, CaveSpider.class); rfSpawn(o, 3.2, 0, 0, 180, Enderman.class); rfSpawn(o, 4.8, 1.2, 0, 180, Phantom.class); rfSpawn(o, 6.4, 0, 0, 180, Breeze.class); rfSpawn(o, 8.0, 0, 0, 180, CopperGolem.class) .setWeatheringState(io.papermc.paper.world.WeatheringCopperState.OXIDIZED); rfSpawn(o, 10.2, 0, 0.5, 180, Warden.class); // Row 2: sheep (white, red, red sheared, baby red, jeb_, blue sheared), angry wolves (wild, tame). rfSpawn(o, 0, 0, 2.6, 180, Sheep.class); rfSpawn(o, 1.4, 0, 2.6, 180, Sheep.class).setColor(DyeColor.RED); Sheep sr = rfSpawn(o, 2.8, 0, 2.6, 180, Sheep.class); sr.setColor(DyeColor.RED); sr.setSheared(true); Sheep sb = rfSpawn(o, 4.2, 0, 2.6, 180, Sheep.class); sb.setColor(DyeColor.RED); sb.setBaby(); Sheep jeb = rfSpawn(o, 5.6, 0, 2.6, 180, Sheep.class); jeb.customName(Component.text("jeb_")); Sheep sbl = rfSpawn(o, 7.0, 0, 2.6, 180, Sheep.class); sbl.setColor(DyeColor.BLUE); sbl.setSheared(true); rfSpawn(o, 8.6, 0, 2.6, 180, Wolf.class).setAngry(true); Wolf wt = rfSpawn(o, 10.0, 0, 2.6, 180, Wolf.class); tame(wt, DyeColor.RED); wt.setAngry(true); // Row 3: partial-alpha cutout textures. rfSpawn(o, 0.5, 0, 5.2, 180, Llama.class).setColor(Llama.Color.GRAY); rfSpawn(o, 2.0, 0, 5.2, 180, Llama.class).setColor(Llama.Color.WHITE); TropicalFish tf = rfSpawn(o, 3.4, 0.3, 5.2, 180, TropicalFish.class); tf.setPattern(TropicalFish.Pattern.BRINELY); tf.setBodyColor(DyeColor.ORANGE); tf.setPatternColor(DyeColor.LIGHT_BLUE); for (Entity e : w.getNearbyEntities(o.clone().add(6, 1, 2), 9, 4, 6)) { if (e instanceof LivingEntity le) le.setBodyYaw(180); } } private void rfVisualStates(Location o) { // Row 1: small armor stands with armor / elytra, elytra at rest, piglin arm poses. ArmorStand small = rfSpawn(o, 0, 0, 0, 180, ArmorStand.class); small.setSmall(true); small.setArms(true); diamondArmor(small); ArmorStand smallE = rfSpawn(o, 1.2, 0, 0, 180, ArmorStand.class); smallE.setSmall(true); smallE.getEquipment().setChestplate(it(Material.ELYTRA)); rfSpawn(o, 2.4, 0, 0, 180, ArmorStand.class).getEquipment().setChestplate(it(Material.ELYTRA)); rfSpawn(o, 3.7, 0, 0, 180, Zombie.class).getEquipment().setChestplate(it(Material.ELYTRA)); Piglin p1 = rfSpawn(o, 5.0, 0, 0, 180, Piglin.class); p1.getEquipment().setItemInMainHand(it(Material.GOLDEN_SWORD)); Piglin p2 = rfSpawn(o, 6.3, 0, 0, 180, Piglin.class); p2.getEquipment().setItemInMainHand(it(Material.GOLDEN_SWORD)); p2.setAggressive(true); Piglin p3 = rfSpawn(o, 7.6, 0, 0, 180, Piglin.class); ItemStack cb = it(Material.CROSSBOW); org.bukkit.inventory.meta.CrossbowMeta cm = (org.bukkit.inventory.meta.CrossbowMeta) cb.getItemMeta(); cm.addChargedProjectile(it(Material.ARROW)); cb.setItemMeta(cm); p3.getEquipment().setItemInMainHand(cb); Piglin p4 = rfSpawn(o, 8.9, 0, 0, 180, Piglin.class); p4.getEquipment().setItemInOffHand(it(Material.GOLD_INGOT)); PiglinBrute pb = rfSpawn(o, 10.2, 0, 0, 180, PiglinBrute.class); pb.getEquipment().setItemInMainHand(it(Material.GOLDEN_AXE)); pb.setAggressive(true); for (Entity e : List.of(p1, p2, p3, p4, pb)) ((PiglinAbstract) e).setImmuneToZombification(true); // Row 2: illagers, skeleton with a sword, invulnerable wither. rfSpawn(o, 0, 0, 2.6, 180, Pillager.class); rfSpawn(o, 1.3, 0, 2.6, 180, Pillager.class).getEquipment().setItemInMainHand(it(Material.CROSSBOW)); rfSpawn(o, 2.6, 0, 2.6, 180, Vindicator.class).getEquipment().setItemInMainHand(it(Material.IRON_AXE)); Vindicator va = rfSpawn(o, 3.9, 0, 2.6, 180, Vindicator.class); va.getEquipment().setItemInMainHand(it(Material.IRON_AXE)); va.setAggressive(true); rfSpawn(o, 5.2, 0, 2.6, 180, Skeleton.class).getEquipment().setItemInMainHand(it(Material.IRON_SWORD)); rfSpawn(o, 8.5, 0, 3.2, 180, Wither.class).setInvulnerableTicks(100); // Row 3: hovering arrows: plain, tipped (custom colour without alpha bit), potion (opaque ARGB colour), spectral. projectile(o.clone().add(0.5, 1, 5.5), new Vector(1, 0, 0), 0.0001, false, Arrow.class); Arrow tipped = (Arrow) projectile(o.clone().add(0.5, 1.6, 5.5), new Vector(1, 0, 0), 0.0001, false, Arrow.class); tipped.setColor(Color.fromARGB(0x00FF2020)); Arrow potion = (Arrow) projectile(o.clone().add(0.5, 2.2, 5.5), new Vector(1, 0, 0), 0.0001, false, Arrow.class); potion.setBasePotionType(org.bukkit.potion.PotionType.POISON); projectile(o.clone().add(0.5, 2.8, 5.5), new Vector(1, 0, 0), 0.0001, false, SpectralArrow.class); for (Entity e : w.getNearbyEntities(o.clone().add(6, 1, 2), 9, 4, 6)) { if (e instanceof LivingEntity le && !(e instanceof ArmorStand)) le.setBodyYaw(180); } } private void perfFarm(Location o) { Random r = new Random(42); Class[] types = {Cow.class, Sheep.class, Chicken.class, Pig.class}; for (int i = 0; i < 15; i++) for (int j = 0; j < 18; j++) { @SuppressWarnings("unchecked") Class type = (Class) types[r.nextInt(types.length)]; LivingEntity e = rfSpawn(o, -5 + i * 1.2, 0, -10 + j * 1.2, 0, type); if (r.nextInt(5) == 0 && e instanceof Ageable a) a.setBaby(); e.setBodyYaw(r.nextInt(8) * 45); } } private void perfVillage(Location o) { Random r = new Random(11); List vTypes = registry(io.papermc.paper.registry.RegistryKey.VILLAGER_TYPE); List profs = registry(io.papermc.paper.registry.RegistryKey.VILLAGER_PROFESSION); DyeColor[] dyes = DyeColor.values(); for (int i = 0; i < 14; i++) for (int j = 0; j < 16; j++) { double x = -5 + i * 1.2, z = -10 + j * 1.2; DyeColor dye = dyes[r.nextInt(dyes.length)]; LivingEntity e; switch (r.nextInt(4)) { case 0 -> { Villager v = rfSpawn(o, x, 0, z, 0, Villager.class); v.setVillagerType(vTypes.get(r.nextInt(vTypes.size()))); if (r.nextInt(6) == 0) v.setBaby(); else { v.setProfession(profs.get(r.nextInt(profs.size()))); v.setVillagerLevel(1 + r.nextInt(5)); } e = v; } case 1 -> { Sheep sh = rfSpawn(o, x, 0, z, 0, Sheep.class); sh.setColor(dye); if (r.nextInt(6) == 0) sh.setBaby(); e = sh; } case 2 -> { Zombie zo = rfSpawn(o, x, 0, z, 0, Zombie.class); Material[] pieces = {Material.LEATHER_HELMET, Material.LEATHER_CHESTPLATE, Material.LEATHER_LEGGINGS, Material.LEATHER_BOOTS}; ItemStack[] armor = new ItemStack[4]; for (int k = 0; k < 4; k++) { armor[k] = it(pieces[k]); org.bukkit.inventory.meta.LeatherArmorMeta m = (org.bukkit.inventory.meta.LeatherArmorMeta) armor[k].getItemMeta(); m.setColor(dye.getColor()); armor[k].setItemMeta(m); } zo.getEquipment().setHelmet(armor[0]); zo.getEquipment().setChestplate(armor[1]); zo.getEquipment().setLeggings(armor[2]); zo.getEquipment().setBoots(armor[3]); e = zo; } default -> { Wolf wo = rfSpawn(o, x, 0, z, 0, Wolf.class); tame(wo, dye); if (r.nextInt(6) == 0) wo.setBaby(); e = wo; } } e.setBodyYaw(r.nextInt(8) * 45); } for (int b = 0; b < 10; b++) { int x = -4 + b * 2; put(o, x, 0, 10, dyes[r.nextInt(dyes.length)].name().toLowerCase() + "_banner[rotation=" + r.nextInt(16) + "]"); if (o.clone().add(x, 0, 10).getBlock().getState() instanceof org.bukkit.block.Banner bn) { bn.addPattern(new org.bukkit.block.banner.Pattern(dyes[r.nextInt(dyes.length)], org.bukkit.block.banner.PatternType.STRIPE_TOP)); bn.addPattern(new org.bukkit.block.banner.Pattern(dyes[r.nextInt(dyes.length)], org.bukkit.block.banner.PatternType.CREEPER)); bn.update(true, false); } } } // --- poses ------------------------------------------------------------------------------------ private void posePets(Location o) { // Row 1 (z=0): foxes — standing, sitting, sleeping, crouching, crouching+leaping, faceplanted. Fox f0 = rfSpawn(o, 0, 0, 0, 180, Fox.class); hands(f0, Material.SWEET_BERRIES, null); Fox f1 = rfSpawn(o, 1.2, 0, 0, 180, Fox.class); f1.setSitting(true); hands(f1, Material.SWEET_BERRIES, null); Fox f2 = rfSpawn(o, 2.4, 0, 0, 180, Fox.class); f2.setSleeping(true); hands(f2, Material.SWEET_BERRIES, null); Fox f3 = rfSpawn(o, 3.6, 0, 0, 180, Fox.class); f3.setCrouching(true); hands(f3, Material.FEATHER, null); Fox f4 = rfSpawn(o, 4.8, 0, 0, 180, Fox.class); f4.setCrouching(true); f4.setLeaping(true); Fox f5 = rfSpawn(o, 6.0, 0, 0, 180, Fox.class); f5.setFaceplanted(true); f5.setFoxType(Fox.Type.SNOW); Fox fb = rfSpawn(o, 7.2, 0, 0, 180, Fox.class); fb.setBaby(); fb.setSleeping(true); hands(fb, Material.SWEET_BERRIES, null); Fox fb2 = rfSpawn(o, 8.2, 0, 0, 180, Fox.class); fb2.setBaby(); fb2.setSitting(true); // Row 2 (z=1.8): cats & wolves — sitting, lying, lying + head up, baby sitting; wolf/pup sitting. Cat c0 = rfSpawn(o, 0, 0, 1.8, 180, Cat.class); c0.setSitting(true); Cat c1 = rfSpawn(o, 1.2, 0, 1.8, 180, Cat.class); c1.setLyingDown(true); c1.setCatType(Cat.Type.BLACK); Cat c2 = rfSpawn(o, 2.6, 0, 1.8, 180, Cat.class); c2.setLyingDown(true); c2.setHeadUp(true); c2.setCatType(Cat.Type.SIAMESE); Cat c3 = rfSpawn(o, 4.0, 0, 1.8, 180, Cat.class); c3.setBaby(); c3.setSitting(true); tame(c3, DyeColor.RED); Wolf w0 = rfSpawn(o, 5.2, 0, 1.8, 180, Wolf.class); w0.setSitting(true); Wolf w1 = rfSpawn(o, 6.6, 0, 1.8, 180, Wolf.class); tame(w1, DyeColor.BLUE); w1.setSitting(true); Wolf w2 = rfSpawn(o, 8.0, 0, 1.8, 180, Wolf.class); w2.setBaby(); w2.setSitting(true); // Row 3 (z=3.6): parrots (flying default, sitting, standing on the ground), pandas. Parrot p0 = rfSpawn(o, 0, 0, 3.6, 180, Parrot.class); p0.setVariant(Parrot.Variant.BLUE); Parrot p1 = rfSpawn(o, 0.8, 0, 3.6, 180, Parrot.class); p1.setSitting(true); p1.setVariant(Parrot.Variant.GREEN); Parrot p2 = rfSpawn(o, 1.6, 0, 3.6, 180, Parrot.class); p2.setVariant(Parrot.Variant.CYAN); if (p2 instanceof Mob m) { m.setAI(true); m.setAware(false); // no goals, but physics: lands -> on ground -> standing pose } Panda pa0 = rfSpawn(o, 3.2, 0, 3.6, 180, Panda.class); pa0.setSitting(true); Panda pa1 = rfSpawn(o, 5.2, 0, 3.6, 180, Panda.class); pa1.setOnBack(true); Panda pa2 = rfSpawn(o, 7.2, 0, 3.6, 180, Panda.class); pa2.setBaby(); pa2.setSitting(true); Panda pa3 = rfSpawn(o, 8.6, 0, 3.6, 180, Panda.class); pa3.setBaby(); pa3.setOnBack(true); faceNorth(o); } private void poseMisc(Location o) { Camel ca = rfSpawn(o, 0, 0, 0, 180, Camel.class); ca.setSitting(true); rfSpawn(o, 2.6, 0, 0, 180, Camel.class); Sniffer sn = rfSpawn(o, 5.4, 0, 0, 180, Sniffer.class); sn.setState(Sniffer.State.DIGGING); rfSpawn(o, 8.6, 0, 0, 180, Sniffer.class); // Bats: hanging under a block (resting) and flying. put(o, 11, 2, 0, "stone"); Bat b0 = rfSpawn(o, 11.5, 1.1, 0.5, 180, Bat.class); b0.setAwake(false); Bat b1 = rfSpawn(o, 12.8, 1.0, 0.5, 180, Bat.class); b1.setAwake(true); // Dolphins pitched down/up/level, a strider looking up, fish in water vs. flopping on land. Dolphin d0 = rfSpawn(o, 0, 0.6, -3.5, 180, Dolphin.class); d0.setRotation(180, 35); Dolphin d1 = rfSpawn(o, 2, 0.6, -3.5, 180, Dolphin.class); d1.setRotation(180, -35); hands(d1, Material.COD, null); Dolphin d2 = rfSpawn(o, 4, 0.6, -3.5, 180, Dolphin.class); hands(d2, Material.COD, null); Strider st = rfSpawn(o, 6, 0, -3.5, 180, Strider.class); st.setRotation(180, -30); for (int i = 0; i < 3; i++) put(o, 8 + i, 0, -4, "water"); rfSpawn(o, 8.5, 0.3, -3.5, 180, Cod.class); rfSpawn(o, 9.5, 0.3, -3.5, 180, Salmon.class); TropicalFish tf = rfSpawn(o, 10.5, 0.3, -3.5, 180, TropicalFish.class); tf.setPattern(TropicalFish.Pattern.BETTY); rfSpawn(o, 8.5, 0, -2, 180, Cod.class); rfSpawn(o, 9.5, 0, -2, 180, Salmon.class); TropicalFish tf2 = rfSpawn(o, 10.5, 0, -2, 180, TropicalFish.class); tf2.setPattern(TropicalFish.Pattern.BETTY); faceNorth(o); d0.setRotation(180, 35); d1.setRotation(180, -35); st.setRotation(180, -30); } private void poseHumans(Location o) { // Villager asleep in a bed (head at z=0 facing south -> lies along z), a baby villager in a bed facing east. put(o, 0, 0, 0, "red_bed[facing=south,part=head]"); put(o, 0, 0, -1, "red_bed[facing=south,part=foot]"); Villager v = rfSpawn(o, 0.5, 0, 1.5, 180, Villager.class); v.setProfession(Villager.Profession.LIBRARIAN); v.sleep(o.clone().add(0, 0, 0)); put(o, 2, 0, 0, "blue_bed[facing=east,part=head]"); put(o, 1, 0, 0, "blue_bed[facing=east,part=foot]"); Villager vb = rfSpawn(o, 2.5, 0, 1.5, 180, Villager.class); vb.setBaby(); vb.sleep(o.clone().add(2, 0, 0)); // Mannequins: standing, crouching with a sword, crawling, swimming in water, gliding with elytra, sleeping. Mannequin m0 = rfSpawn(o, 4, 0, 0, 180, Mannequin.class); hands(m0, Material.IRON_SWORD, null); Mannequin m1 = rfSpawn(o, 5.2, 0, 0, 180, Mannequin.class); m1.setPose(Pose.SNEAKING, true); hands(m1, Material.IRON_SWORD, null); Mannequin m2 = rfSpawn(o, 6.6, 0, 0, 180, Mannequin.class); m2.setPose(Pose.SWIMMING, true); put(o, 8, 0, 0, "water"); put(o, 8, 0, 1, "water"); Mannequin m3 = rfSpawn(o, 8.5, 0.1, 0.5, 180, Mannequin.class); m3.setPose(Pose.SWIMMING, true); Mannequin m4 = rfSpawn(o, 10, 1.0, 0, 180, Mannequin.class); m4.getEquipment().setChestplate(it(Material.ELYTRA)); m4.setPose(Pose.FALL_FLYING, true); Mannequin m5 = rfSpawn(o, 12, 0, 0, 180, Mannequin.class); m5.setPose(Pose.SLEEPING, true); // Baby zombie riding a chicken (jockey: baby model pivots in the passenger pose), crouching zombie. Chicken ch = rfSpawn(o, 4, 0, -2.5, 180, Chicken.class); Zombie bz = rfSpawn(o, 4, 0, -2.5, 180, Zombie.class); bz.setBaby(); ch.addPassenger(bz); Zombie zc = rfSpawn(o, 5.5, 0, -2.5, 180, Zombie.class); zc.setPose(Pose.SNEAKING, true); faceNorth(o); } private void poseStands(Location o) { // Custom armor-stand poses (head/body/arms/legs, radians), with arms, armor and held items. ArmorStand a = rfSpawn(o, 0, 0, 0, 180, ArmorStand.class); a.setArms(true); a.setHeadPose(new org.bukkit.util.EulerAngle(Math.toRadians(-20), Math.toRadians(30), 0)); a.setRightArmPose(new org.bukkit.util.EulerAngle(Math.toRadians(-90), 0, 0)); a.setLeftArmPose(new org.bukkit.util.EulerAngle(Math.toRadians(-30), 0, Math.toRadians(-40))); Ah(a); ArmorStand b = rfSpawn(o, 1.6, 0, 0, 180, ArmorStand.class); b.setArms(true); b.setBodyPose(new org.bukkit.util.EulerAngle(Math.toRadians(15), Math.toRadians(20), 0)); b.setRightLegPose(new org.bukkit.util.EulerAngle(Math.toRadians(-40), 0, 0)); b.setLeftLegPose(new org.bukkit.util.EulerAngle(Math.toRadians(30), 0, Math.toRadians(-10))); diamondArmor(b); hands(b, Material.IRON_SWORD, Material.SHIELD); ArmorStand c = rfSpawn(o, 3.2, 0, 0, 180, ArmorStand.class); c.setArms(true); c.setSmall(true); c.setRightArmPose(new org.bukkit.util.EulerAngle(Math.toRadians(-120), Math.toRadians(20), 0)); c.setHeadPose(new org.bukkit.util.EulerAngle(0, 0, Math.toRadians(25))); diamondArmor(c); hands(c, Material.TORCH, null); ArmorStand d = rfSpawn(o, 4.8, 0, 0, 180, ArmorStand.class); d.setHeadPose(new org.bukkit.util.EulerAngle(Math.toRadians(30), 0, 0)); d.getEquipment().setHelmet(it(Material.CARVED_PUMPKIN)); ArmorStand e = rfSpawn(o, 6.4, 0, 0, 180, ArmorStand.class); e.setArms(true); e.setInvisible(true); e.setRightArmPose(new org.bukkit.util.EulerAngle(Math.toRadians(-60), Math.toRadians(-30), 0)); e.setBodyPose(new org.bukkit.util.EulerAngle(0, Math.toRadians(30), 0)); diamondArmor(e); hands(e, Material.DIAMOND_SWORD, null); ArmorStand f = rfSpawn(o, 8, 0, 0, 180, ArmorStand.class); // default pose (must match rf_stands) f.setArms(true); } private static void Ah(ArmorStand a) { a.getEquipment().setHelmet(it(Material.GOLDEN_HELMET)); hands(a, Material.BOW, Material.APPLE); } private void perfItems(Location o) { Random r = new Random(7); Material[] mats = {Material.WHEAT, Material.CARROT, Material.EGG, Material.BONE, Material.ROTTEN_FLESH, Material.STRING, Material.GUNPOWDER, Material.IRON_INGOT, Material.COBBLESTONE, Material.OAK_LOG, Material.PUMPKIN}; for (int i = 0; i < 300; i++) { ItemDisplay d = spawn(o, -4 + r.nextDouble() * 16, 0.15 + r.nextDouble() * 0.2, -10 + r.nextDouble() * 16, r.nextInt(360), ItemDisplay.class); d.setItemStack(it(mats[r.nextInt(mats.length)])); d.setTransformation(new Transformation(new Vector3f(), new AxisAngle4f((float) (Math.PI / 2), 1, 0, 0), new Vector3f(0.4f), new AxisAngle4f())); } } @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { w = Bukkit.getWorlds().getFirst(); w.setTime(6000); w.setStorm(false); List todo = args.length == 0 || args[0].equals("all") ? new ArrayList<>(scenes.keySet()) : List.of(args); int i = 0; for (String n : todo) { Scene sc = scenes.get(n); if (sc == null) { getLogger().warning("unknown scene " + n); continue; } int idx = new ArrayList<>(scenes.keySet()).indexOf(n); Location origin = new Location(w, 5000 + idx * 64, 200, 5000); Bukkit.getScheduler().runTaskLater(this, () -> run(sc, origin), 1 + i * 60L); i++; } return true; } private void run(Scene sc, Location o) { try { prepareArea(o); sc.build.accept(o.clone()); } catch (Throwable t) { getLogger().severe("build " + sc.name + " failed: " + t); t.printStackTrace(); } // Let one tick pass (passengers/leashes settle), then render. 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) { DefaultScreenRenderer r = Main.getInstance().getScreenRenderer(); 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") ? new Resolution(960, 640) : new Resolution(480, 320), null); if (sc.name.equals("dbg")) { try { java.lang.reflect.Field f = DefaultScreenRenderer.class.getDeclaredField("objectBaker"); f.setAccessible(true); var ob = (eu.mhsl.minecraft.pixelpics.render.entity.ObjectBaker) f.get(r); for (var st : job.objects()) { double[] pos = st.toWorld().t; if (Math.abs(pos[0] - o.getX()) > 5 || Math.abs(pos[2] - o.getZ()) > 5) continue; try { var re = ob.bake(st); getLogger().info("dbg obj " + st.kind() + " item=" + st.item() + " ctx=" + st.context() + " -> " + (re == null ? "null" : re.cubes.size() + " cubes " + java.util.Arrays.toString(re.aabbMin))); } catch (Throwable t) { getLogger().warning("dbg bake failed: " + t); t.printStackTrace(); } } } catch (Throwable t) { 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, () -> { try { long t0 = System.nanoTime(); BufferedImage img = r.execute(job); getLogger().info(String.format("TIME %s %.1f ms", sc.name, (System.nanoTime() - t0) / 1e6)); File f = new File(getDataFolder(), sc.name + ".png"); f.getParentFile().mkdirs(); ImageIO.write(img, "png", f); getLogger().info("WROTE " + f); } catch (Throwable t) { getLogger().severe("render " + sc.name + " failed: " + t); t.printStackTrace(); } }); } /** Clears the area (entities + blocks) and lays a 14x14 stone floor at the origin's y-1. */ private void prepareArea(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(); 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); } // --- helpers --------------------------------------------------------------------------------- private T spawn(Location o, double x, double y, double z, float yaw, Class 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); 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); return e; } private static ItemStack it(Material m) { return new ItemStack(m); } private static void hands(Entity e, Material main, Material off) { if (!(e instanceof LivingEntity le) || le.getEquipment() == null) return; if (main != null) le.getEquipment().setItemInMainHand(it(main)); if (off != null) le.getEquipment().setItemInOffHand(it(off)); } // --- scenes ---------------------------------------------------------------------------------- private void held(Location o) { hands(spawn(o, 0, 0, 0, 180, Zombie.class), Material.IRON_SWORD, Material.SHIELD); hands(spawn(o, 1.6, 0, 0, 180, Skeleton.class), Material.BOW, null); hands(spawn(o, 3.2, 0, 0, 180, Piglin.class), Material.GOLDEN_SWORD, null); hands(spawn(o, 4.8, 0, 0, 180, EntityType.MANNEQUIN), Material.DIAMOND_PICKAXE, Material.TORCH); ArmorStand as = spawn(o, 6.4, 0, 0, 180, ArmorStand.class); as.setArms(true); hands(as, Material.TRIDENT, Material.GRASS_BLOCK); hands(spawn(o, 8.0, 0, 0, 180, Drowned.class), Material.TRIDENT, null); hands(spawn(o, 0.8, 0, 2, 180, Pillager.class), Material.CROSSBOW, null); hands(spawn(o, 2.4, 0, 2, 180, Vindicator.class), Material.IRON_AXE, null); hands(spawn(o, 4.0, 0, 2, 180, Fox.class), Material.SWEET_BERRIES, null); hands(spawn(o, 5.6, 0.5, 2, 180, Allay.class), Material.DIAMOND, null); hands(spawn(o, 7.2, 0, 2, 180, Witch.class), Material.POTION, null); hands(spawn(o, 8.8, 0, 2, 180, Villager.class), Material.EMERALD, null); } private void equip(Location o) { LivingEntity m = (LivingEntity) spawn(o, 0, 0, 0, 180, EntityType.MANNEQUIN); m.getEquipment().setChestplate(it(Material.ELYTRA)); m.getEquipment().setHelmet(it(Material.IRON_HELMET)); LivingEntity m2 = (LivingEntity) spawn(o, 1.6, 0, 0, 0, EntityType.MANNEQUIN); // back view: elytra m2.getEquipment().setChestplate(it(Material.ELYTRA)); ArmorStand pumpkin = spawn(o, 3.2, 0, 0, 180, ArmorStand.class); pumpkin.getEquipment().setHelmet(it(Material.CARVED_PUMPKIN)); pumpkin.getEquipment().setChestplate(it(Material.CHAINMAIL_CHESTPLATE)); ArmorStand head = spawn(o, 4.8, 0, 0, 180, ArmorStand.class); head.getEquipment().setHelmet(it(Material.CREEPER_HEAD)); head.setInvisible(true); Zombie z = spawn(o, 6.4, 0, 0, 180, Zombie.class); z.getEquipment().setHelmet(it(Material.GOLDEN_HELMET)); z.getEquipment().setChestplate(it(Material.COPPER_CHESTPLATE)); z.getEquipment().setLeggings(it(Material.NETHERITE_LEGGINGS)); z.getEquipment().setBoots(it(Material.LEATHER_BOOTS)); spawn(o, 8.0, 0, 0, 180, Snowman.class); Wolf wolf = spawn(o, 1, 0, 2.5, 180, Wolf.class); wolf.getEquipment().setItem(EquipmentSlot.BODY, it(Material.WOLF_ARMOR)); Horse h = spawn(o, 4, 0, 3, 90, Horse.class); h.getInventory().setSaddle(it(Material.SADDLE)); h.getInventory().setArmor(it(Material.GOLDEN_HORSE_ARMOR)); Zombie baby = spawn(o, 7.5, 0, 2.5, 180, Zombie.class); baby.setBaby(); baby.getEquipment().setHelmet(it(Material.DIAMOND_HELMET)); hands(baby, Material.STONE_SWORD, null); } private void riding(Location o) { Spider sp = spawn(o, 0, 0, 0, 180, Spider.class); sp.addPassenger(spawn(o, 0, 1, 0, 180, Skeleton.class)); Chicken ch = spawn(o, 2, 0, 0, 180, Chicken.class); Zombie bz = spawn(o, 2, 1, 0, 180, Zombie.class); bz.setBaby(); ch.addPassenger(bz); Horse h = spawn(o, 4.5, 0, 0.5, 180, Horse.class); h.getInventory().setSaddle(it(Material.SADDLE)); h.addPassenger(spawn(o, 4.5, 2, 0.5, 180, EntityType.MANNEQUIN)); Minecart mc = spawn(o, 7, 0, 0, 0, Minecart.class); 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)); 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)); c.addPassenger(spawn(o, 2, 2, 3.5, 90, Villager.class)); Strider st = spawn(o, 6, 0, 3.5, 180, Strider.class); st.setSaddle(true); st.addPassenger(spawn(o, 6, 2, 3.5, 180, Piglin.class)); Boat cb = (Boat) spawn(o, 9, 0, 3.5, 180, EntityType.BIRCH_CHEST_BOAT); cb.addPassenger(spawn(o, 9, 1, 3.5, 180, EntityType.MANNEQUIN)); } private void leash(Location o) { Block fence = o.clone().add(0, 0, 0).getBlock(); fence.setType(Material.OAK_FENCE); LeashHitch knot = w.spawn(fence.getLocation(), LeashHitch.class); Cow cow = spawn(o, 2.5, 0, 0.5, 180, Cow.class); cow.setLeashHolder(knot); LivingEntity holder = (LivingEntity) spawn(o, 5, 0, 0, 180, EntityType.MANNEQUIN); Wolf wolf = spawn(o, 7, 0, 0.5, 180, Wolf.class); wolf.setLeashHolder(holder); HappyGhast g = spawn(o, 4, 4, 5, 180, HappyGhast.class); g.getEquipment().setItem(EquipmentSlot.BODY, it(Material.BLUE_HARNESS)); Boat boat = (Boat) spawn(o, 4, 0, 4, 180, EntityType.OAK_BOAT); try { boat.getClass().getMethod("setLeashHolder", Entity.class).invoke(boat, g); } catch (Throwable t) { getLogger().warning("boat not leashable: " + t); } Llama l1 = spawn(o, 9, 0, 2, 180, Llama.class); Llama l2 = spawn(o, 10.5, 0, 3.5, 180, Llama.class); l2.setLeashHolder(l1); l1.getInventory().setDecor(it(Material.RED_CARPET)); l1.setCarryingChest(true); } private void displays(Location o) { // Text displays: default background, coloured + shadowed + billboard center, multi-line wrapped. TextDisplay t1 = spawn(o, 0.5, 1.5, 0, 0, TextDisplay.class); t1.text(Component.text("Default Text")); TextDisplay t2 = spawn(o, 2.5, 1.5, 0, 0, TextDisplay.class); t2.text(Component.text("Billboard CENTER", NamedTextColor.GOLD)); t2.setBillboard(Display.Billboard.CENTER); t2.setShadowed(true); t2.setBackgroundColor(Color.fromARGB(255, 30, 30, 90)); TextDisplay t3 = spawn(o, 5, 1.5, 0, 180, TextDisplay.class); t3.text(Component.text("Dies ist ein langer Text, der umgebrochen werden sollte weil er breit ist")); t3.setLineWidth(80); // Item displays. ItemDisplay i1 = spawn(o, 7, 1, 0, 0, ItemDisplay.class); i1.setItemStack(it(Material.DIAMOND_SWORD)); i1.setTransformation(new Transformation(new Vector3f(), new AxisAngle4f((float) Math.toRadians(45), 0, 0, 1), new Vector3f(1, 1, 1), new AxisAngle4f())); ItemDisplay i2 = spawn(o, 8.5, 0.5, 0, 30, ItemDisplay.class); i2.setItemStack(it(Material.CHEST)); ItemDisplay i3 = spawn(o, 0.5, 0.5, 2.5, 0, ItemDisplay.class); i3.setItemStack(it(Material.PLAYER_HEAD)); ItemDisplay i4 = spawn(o, 2, 0.5, 2.5, 0, ItemDisplay.class); i4.setItemStack(it(Material.SHIELD)); // Block displays: rotated stairs, scaled chest (block entity!), banner, bed, glass. BlockDisplay b1 = spawn(o, 3.5, 0, 2.5, 0, BlockDisplay.class); b1.setBlock(Bukkit.createBlockData("minecraft:oak_stairs[facing=east]")); b1.setTransformation(new Transformation(new Vector3f(), new Quaternionf().rotateY(0.6f), new Vector3f(0.8f), new Quaternionf())); BlockDisplay b2 = spawn(o, 5, 0, 2.5, 0, BlockDisplay.class); b2.setBlock(Bukkit.createBlockData("minecraft:chest[facing=north]")); BlockDisplay b3 = spawn(o, 6.5, 0, 2.5, 0, BlockDisplay.class); b3.setBlock(Bukkit.createBlockData("minecraft:red_banner")); BlockDisplay b4 = spawn(o, 8, 0, 2.5, 0, BlockDisplay.class); b4.setBlock(Bukkit.createBlockData("minecraft:light_blue_stained_glass")); BlockDisplay b5 = spawn(o, 9.5, 0, 2.5, 0, BlockDisplay.class); b5.setBlock(Bukkit.createBlockData("minecraft:oak_sign")); } /** Mimics PixelBlocks: 1/16-scaled block displays forming a mini build + the placeholder displays. */ private void pixelblock(Location o) { float s = 1 / 16f; String[] palette = {"minecraft:oak_planks", "minecraft:stone_bricks", "minecraft:glass", "minecraft:red_wool", "minecraft:chest[facing=north]", "minecraft:red_bed[part=head,facing=north]", "minecraft:oak_stairs[facing=south]", "minecraft:torch", "minecraft:grass_block"}; Random rnd = new Random(1); for (int x = 0; x < 16; x++) for (int z = 0; z < 16; z++) { int h = 1 + (int) (3 * Math.abs(Math.sin(x * 0.4) * Math.cos(z * 0.3))); for (int y = 0; y < h; y++) { String b = y == h - 1 ? palette[rnd.nextInt(palette.length)] : "minecraft:stone_bricks"; pixel(o, x, y, z, s, Bukkit.createBlockData(b)); } } // Placeholder look (small pixel blocks): end-crystal item cores + glass container, next to it. Location p = o.clone().add(-1.5, 0.5, 0.5); for (int i = 0; i <= 90; i += 90) { ItemDisplay core = w.spawn(p, ItemDisplay.class); core.setRotation(i, 0); core.setItemStack(it(Material.END_CRYSTAL)); Transformation t = core.getTransformation(); t.getScale().set(0.5); core.setTransformation(t); } ItemDisplay glass = w.spawn(p, ItemDisplay.class); glass.setItemStack(it(Material.WHITE_STAINED_GLASS)); Interaction hit = w.spawn(o.clone().add(0.5, 0, 0.5), Interaction.class); hit.setInteractionWidth(1); hit.setInteractionHeight(1); } private void pixel(Location o, int x, int y, int z, float s, BlockData data) { Location l = o.clone().add(x * s + s / 2, y * s + s / 2, z * s + s / 2); BlockDisplay d = w.spawn(l, BlockDisplay.class); d.setBlock(data); Transformation t = d.getTransformation(); t.getScale().set(s); t.getTranslation().set(-s / 2, -s / 2, -s / 2); d.setTransformation(t); } 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())); 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); tnt.setFuseTicks(10000); tnt.setGravity(false); w.spawn(o.clone().add(8, 0, 0.5), EnderCrystal.class); Sheep sh = spawn(o, 0.5, 0, 3, 180, Sheep.class); sh.setSheared(true); Sheep sh2 = spawn(o, 2.5, 0, 3, 180, Sheep.class); sh2.setColor(DyeColor.LIME); ArmorStand small = spawn(o, 4.5, 0, 3, 180, ArmorStand.class); small.setSmall(true); small.getEquipment().setHelmet(it(Material.LEATHER_HELMET)); spawn(o, 6, 0, 3, 180, EntityType.EXPERIENCE_ORB); Arrow a = w.spawn(o.clone().add(7, 0.5, 3), Arrow.class, ar -> ar.setGravity(false)); a.setVelocity(new Vector(1, -0.4, 0)); Trident tr = w.spawn(o.clone().add(9, 0.5, 3), Trident.class, ar -> ar.setGravity(false)); tr.setVelocity(new Vector(0, -1, 0.2)); spawn(o, 10, 0, 0, 180, TropicalFish.class); } private void dbg(Location o) { TextDisplay t1 = spawn(o, 0, 1.2, 0, 0, TextDisplay.class); t1.text(Component.text("Default Text")); ItemDisplay i4 = spawn(o, 2, 0.5, 0.5, 0, ItemDisplay.class); i4.setItemStack(it(Material.SHIELD)); getLogger().info("dbg text=" + t1.isValid() + " opacity=" + t1.getTextOpacity() + " shield=" + i4.getItemStack()); } /** World blocks: a log column with shelf mushrooms (26.x) on all four sides, both growth stages. */ private void shelf(Location o) { for (int y = 0; y < 3; y++) o.clone().add(0, y, 0).getBlock().setType(Material.OAK_LOG, false); o.clone().add(0, 0, -3).getBlock().setType(Material.DIAMOND_BLOCK, false); // north marker o.clone().add(3, 0, 0).getBlock().setType(Material.GOLD_BLOCK, false); // east marker String[] f = {"north", "south", "east", "west"}; int[][] off = {{0, -1}, {0, 1}, {1, 0}, {-1, 0}}; for (int i = 0; i < 4; i++) { for (int age = 0; age < 2; age++) { o.clone().add(off[i][0], 1 + age, off[i][1]).getBlock().setBlockData( Bukkit.createBlockData("minecraft:shelf_mushroom[age=" + age + ",facing=" + f[i] + "]"), false); } } } /** Blockstate y-rotations: ladders, stairs, wall torches, furnaces, logs on all sides of stone pillars. */ private void rotated(Location o) { String[] f = {"north", "south", "east", "west"}; int[][] off = {{0, -1}, {0, 1}, {1, 0}, {-1, 0}}; String[] kinds = {"ladder[facing=%s]", "wall_torch[facing=%s]", "oak_stairs[facing=%s]", "furnace[facing=%s]"}; for (int k = 0; k < kinds.length; k++) { Location c = o.clone().add(k * 3 - 1, 0, 2); c.getBlock().setType(Material.STONE, false); c.clone().add(0, 1, 0).getBlock().setType(Material.STONE, false); for (int i = 0; i < 4; i++) { c.clone().add(off[i][0], k == 2 || k == 3 ? 0 : 1, off[i][1]).getBlock().setBlockData( Bukkit.createBlockData("minecraft:" + String.format(kinds[k], f[i])), false); } } // Fence with a single connection to the east (stone post at +x), door facing east, chest-free check. o.clone().add(-3, 0, 5).getBlock().setBlockData(Bukkit.createBlockData("minecraft:oak_fence[east=true]"), false); o.clone().add(-2, 0, 5).getBlock().setType(Material.STONE, false); o.clone().add(1, 0, 5).getBlock().setBlockData( Bukkit.createBlockData("minecraft:oak_door[facing=east,half=lower,hinge=left,open=false]"), false); o.clone().add(1, 1, 5).getBlock().setBlockData( Bukkit.createBlockData("minecraft:oak_door[facing=east,half=upper,hinge=left,open=false]"), false); o.clone().add(9, 0, 2).getBlock().setBlockData(Bukkit.createBlockData("minecraft:oak_log[axis=x]"), false); o.clone().add(9, 0, 4).getBlock().setBlockData(Bukkit.createBlockData("minecraft:oak_log[axis=z]"), false); } /** * Row z=0: single chests facing S/W/N/E (x=0,3,6,9). Row z=3: double chests per facing — the LEFT half * at x, its RIGHT partner on facing.getClockWise(). Row z=6: trapped double (S), ender, copper, oxidized * copper double (N). */ private void chests(Location o) { String[] f = {"south", "west", "north", "east"}; int[][] cw = {{-1, 0}, {0, -1}, {1, 0}, {0, 1}}; java.util.function.BiConsumer put = (p, data) -> o.clone().add(p[0], p[1], p[2]).getBlock() .setBlockData(Bukkit.createBlockData("minecraft:" + data), false); for (int i = 0; i < 4; i++) { put.accept(new int[] {i * 3, 0, 0}, "chest[facing=" + f[i] + ",type=single]"); put.accept(new int[] {i * 3, 0, 3}, "chest[facing=" + f[i] + ",type=left]"); put.accept(new int[] {i * 3 + cw[i][0], 0, 3 + cw[i][1]}, "chest[facing=" + f[i] + ",type=right]"); } put.accept(new int[] {0, 0, 6}, "trapped_chest[facing=south,type=left]"); put.accept(new int[] {-1, 0, 6}, "trapped_chest[facing=south,type=right]"); put.accept(new int[] {3, 0, 6}, "ender_chest[facing=south]"); put.accept(new int[] {6, 0, 6}, "copper_chest[facing=east,type=single]"); put.accept(new int[] {9, 0, 6}, "oxidized_copper_chest[facing=north,type=left]"); put.accept(new int[] {10, 0, 6}, "oxidized_copper_chest[facing=north,type=right]"); } /** Redstone wire (dot, line, corner, climbing a wall, powered/unpowered) next to glass and panes. */ private void redstone(Location o) { java.util.function.BiConsumer put = (p, data) -> o.clone().add(p[0], p[1], p[2]).getBlock() .setBlockData(Bukkit.createBlockData("minecraft:" + data), false); put.accept(new int[] {0, 0, 0}, "redstone_wire[power=0]"); for (int x = 1; x <= 4; x++) put.accept(new int[] {x, 0, 0}, "redstone_wire[east=side,west=side,power=" + (x * 3) + "]"); put.accept(new int[] {5, 0, 0}, "redstone_wire[west=side,north=side,power=15]"); put.accept(new int[] {5, 0, -1}, "redstone_wire[south=side,north=up,power=15]"); put.accept(new int[] {5, 0, -2}, "stone"); put.accept(new int[] {5, 1, -2}, "redstone_wire[south=side,power=15]"); put.accept(new int[] {0, 0, 3}, "glass"); put.accept(new int[] {1, 0, 3}, "red_stained_glass"); put.accept(new int[] {2, 0, 3}, "glass_pane[east=true,west=false]"); put.accept(new int[] {3, 0, 3}, "blue_stained_glass_pane[east=true,west=true]"); put.accept(new int[] {4, 0, 3}, "blue_stained_glass_pane[west=true,north=true]"); } /** x/y blockstate rotations without uvlock: pistons in all six directions, observers, dispensers, logs. */ private void piston(Location o) { String[] f = {"down", "up", "north", "south", "east", "west"}; for (int i = 0; i < f.length; i++) { o.clone().add(i * 2, 1, 0).getBlock().setBlockData( Bukkit.createBlockData("minecraft:piston[facing=" + f[i] + ",extended=false]"), false); o.clone().add(i * 2, 1, 2).getBlock().setBlockData( Bukkit.createBlockData("minecraft:observer[facing=" + f[i] + "]"), false); o.clone().add(i * 2, 0, 2).getBlock().setBlockData( Bukkit.createBlockData("minecraft:dispenser[facing=" + f[i] + "]"), false); } o.clone().add(0, 0, -2).getBlock().setBlockData(Bukkit.createBlockData("minecraft:oak_log[axis=x]"), false); o.clone().add(2, 0, -2).getBlock().setBlockData(Bukkit.createBlockData("minecraft:oak_log[axis=z]"), false); o.clone().add(4, 0, -2).getBlock().setBlockData(Bukkit.createBlockData("minecraft:furnace[facing=north]"), false); } /** * Row z=0: oak log, basalt, quartz pillar, hay bale lying along x (x=90,y=90) and z (x=90). Row z=3: observer, * dispenser, dropper facing up and down. Row z=6: oak and stone-brick stairs half=top in all four facings * (x=180 + uvlock), oak buttons on the ceiling. A stone column at z=-2 marks north. */ private void xrot(Location o) { java.util.function.BiConsumer put = (p, data) -> o.clone().add(p[0], p[1], p[2]).getBlock() .setBlockData(Bukkit.createBlockData("minecraft:" + data), false); String[] pillars = {"oak_log", "basalt", "quartz_pillar", "hay_block"}; for (int i = 0; i < pillars.length; i++) { put.accept(new int[] {i * 3, 0, 0}, pillars[i] + "[axis=x]"); put.accept(new int[] {i * 3 + 1, 0, 0}, pillars[i] + "[axis=z]"); } String[] vertical = {"observer", "dispenser", "dropper"}; for (int i = 0; i < vertical.length; i++) { put.accept(new int[] {i * 3, 0, 3}, vertical[i] + "[facing=up]"); put.accept(new int[] {i * 3 + 1, 1, 3}, vertical[i] + "[facing=down]"); put.accept(new int[] {i * 3 + 1, 0, 3}, "glass"); } String[] f = {"north", "east", "south", "west"}; for (int i = 0; i < f.length; i++) { put.accept(new int[] {i * 2, 0, 6}, "oak_stairs[facing=" + f[i] + ",half=top]"); put.accept(new int[] {i * 2 + 1, 0, 6}, "stone_brick_stairs[facing=" + f[i] + ",half=top]"); } put.accept(new int[] {9, 1, 6}, "stone"); put.accept(new int[] {9, 0, 6}, "oak_button[face=ceiling,facing=north]"); put.accept(new int[] {10, 1, 6}, "stone"); put.accept(new int[] {10, 0, 6}, "oak_button[face=ceiling,facing=east]"); put.accept(new int[] {0, 0, -2}, "stone"); put.accept(new int[] {0, 1, -2}, "stone"); } private void vehicles(Location o) { spawn(o, 0, 0, 0, 0, EntityType.CHEST_MINECART); spawn(o, 1.8, 0, 0, 90, EntityType.FURNACE_MINECART); spawn(o, 3.6, 0, 0, 0, EntityType.HOPPER_MINECART); spawn(o, 5.4, 0, 0, 0, EntityType.TNT_MINECART); spawn(o, 7.2, 0, 0, 0, EntityType.SPAWNER_MINECART); spawn(o, 9, 0, 0, 0, EntityType.COMMAND_BLOCK_MINECART); spawn(o, 0.5, 0, 3, 30, EntityType.PALE_OAK_BOAT); spawn(o, 3, 0, 3, 60, EntityType.BAMBOO_CHEST_RAFT); spawn(o, 6, 0, 3, 90, EntityType.CHERRY_CHEST_BOAT); spawn(o, 9, 0, 3, 120, EntityType.MANGROVE_BOAT); } /** * Alpha blending: a stained-glass wall (inner faces culled) and stacked glass colours in front of a striped * wool wall, ice/tinted glass/slime/honey blocks, connected panes, glass under water, a slime and a sulfur * cube (translucent outer skins, one behind glass) and a text display with the default background. */ private void translucent(Location o) { java.util.function.BiConsumer put = (p, data) -> o.clone().add(p[0], p[1], p[2]).getBlock() .setBlockData(Bukkit.createBlockData("minecraft:" + data), false); for (int x = -1; x <= 10; x++) for (int y = 0; y <= 4; y++) put.accept(new int[] {x, y, 5}, (x + y) % 2 == 0 ? "white_wool" : "black_wool"); for (int x = 0; x <= 2; x++) for (int y = 0; y <= 1; y++) put.accept(new int[] {x, y, 1}, "red_stained_glass"); put.accept(new int[] {3, 0, 1}, "blue_stained_glass"); put.accept(new int[] {3, 0, 0}, "yellow_stained_glass"); put.accept(new int[] {4, 0, 1}, "ice"); put.accept(new int[] {4, 1, 1}, "tinted_glass"); put.accept(new int[] {5, 0, 1}, "slime_block"); put.accept(new int[] {6, 0, 1}, "honey_block"); put.accept(new int[] {7, 0, 1}, "lime_stained_glass_pane[east=true]"); put.accept(new int[] {8, 0, 1}, "lime_stained_glass_pane[west=true,east=true]"); put.accept(new int[] {9, 0, 1}, "lime_stained_glass_pane[west=true]"); for (int x = 0; x <= 2; x++) put.accept(new int[] {x, -1, -2}, "water"); put.accept(new int[] {1, -2, -2}, "magenta_stained_glass"); put.accept(new int[] {0, -2, -2}, "sand"); put.accept(new int[] {2, -2, -2}, "sand"); Slime slime = spawn(o, 1.5, 0, 3, 200, Slime.class); slime.setSize(2); Slime front = spawn(o, 7.5, 0, -1.5, 160, Slime.class); front.setSize(1); EntityType sulfur = Registry.ENTITY_TYPE.get(NamespacedKey.minecraft("sulfur_cube")); if (sulfur != null) spawn(o, 5.5, 0, -1.5, 200, sulfur); TextDisplay t = spawn(o, 4.5, 2.6, 0, 0, TextDisplay.class); t.text(Component.text("Default background")); t.setBillboard(Display.Billboard.CENTER); } // --- conditional layers ---------------------------------------------------------------------- private void layers(Location o) { tame(spawn(o, 0, 0, 0, 180, Cat.class), DyeColor.RED); tame(spawn(o, 1.2, 0, 0, 180, Cat.class), DyeColor.BLUE); spawn(o, 2.4, 0, 0, 180, Cat.class); // untamed: no collar Cat kitten = spawn(o, 3.4, 0, 0, 180, Cat.class); kitten.setBaby(); tame(kitten, DyeColor.YELLOW); tame(spawn(o, 5, 0, 0, 180, Wolf.class), DyeColor.RED); tame(spawn(o, 6.4, 0, 0, 180, Wolf.class), DyeColor.LIME); spawn(o, 7.8, 0, 0, 180, Wolf.class); // untamed: wild texture, no collar Wolf pup = spawn(o, 9.0, 0, 0, 180, Wolf.class); pup.setBaby(); tame(pup, DyeColor.MAGENTA); spawn(o, 0.5, 0, 2.5, 180, Creeper.class); spawn(o, 2.2, 0, 2.5, 180, Creeper.class).setPowered(true); hands(spawn(o, 4.2, 0, 2.5, 180, Fox.class), Material.EMERALD, null); Fox kit = spawn(o, 5.5, 0, 2.5, 180, Fox.class); kit.setBaby(); hands(kit, Material.SWEET_BERRIES, null); hands(spawn(o, 7.5, 0, 2.5, 180, Dolphin.class), Material.COD, null); // Spawned mobs get a random body yaw: face them all to the camera (north). for (Entity e : w.getNearbyEntities(o.clone().add(5, 0, 1), 7, 3, 4)) if (e instanceof LivingEntity le && !(e instanceof Player)) le.setBodyYaw(180); } private void tame(Tameable t, DyeColor collar) { t.setTamed(true); if (t instanceof Cat c) c.setCollarColor(collar); if (t instanceof Wolf wo) wo.setCollarColor(collar); } private void withers(Location o) { spawn(o, 1, 0, 1, 180, Wither.class); // full health: no armor Wither half = spawn(o, 6, 0, 1, 180, Wither.class); half.setHealth(half.getAttribute(org.bukkit.attribute.Attribute.MAX_HEALTH).getValue() / 2 - 1); for (Entity e : w.getNearbyEntities(o, 12, 8, 8)) if (e instanceof Wither wi) wi.setBodyYaw(180); } // --- data-driven variants / pose flags / armor wearers ------------------------------------------ private static List registry(io.papermc.paper.registry.RegistryKey key) { List out = new ArrayList<>(); io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(key).forEach(out::add); out.sort(Comparator.comparing(k -> k.getKey().toString())); return out; } /** Spawn without vanilla's random spawn data (baby chance, jockeys, gear, biome variants): reproducible. */ private T rfSpawn(Location o, double x, double y, double z, float yaw, Class type) { Location l = o.clone().add(x, y, z); l.setYaw(yaw); T e = w.spawn(l, type, false, en -> { if (en instanceof Mob m) m.setAI(false); en.setPersistent(false); }); if (e instanceof LivingEntity le) le.setRemoveWhenFarAway(false); return e; } private Entity rfSpawn(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, false); if (e instanceof Mob m) m.setAI(false); return e; } private void faceNorth(Location o) { for (Entity e : w.getNearbyEntities(o.clone().add(6, 0, 0), 14, 4, 12)) if (e instanceof LivingEntity le && !(e instanceof Player)) le.setBodyYaw(180); } private void rfCats(Location o) { List types = registry(io.papermc.paper.registry.RegistryKey.CAT_VARIANT); for (int i = 0; i < types.size(); i++) { rfSpawn(o, i * 1.2, 0, 0, 180, Cat.class).setCatType(types.get(i)); Cat kit = rfSpawn(o, i * 1.2, 0, 1.6, 180, Cat.class); kit.setCatType(types.get(i)); kit.setBaby(); if (i % 2 == 0) tame(kit, DyeColor.values()[i % 16]); } faceNorth(o); } private void rfDatapackCat(Location o) { Cat.Type custom = io.papermc.paper.registry.RegistryAccess.registryAccess() .getRegistry(io.papermc.paper.registry.RegistryKey.CAT_VARIANT) .get(new NamespacedKey("pptest", "ocelotcat")); if (custom == null) throw new IllegalStateException("datapack variant pptest:ocelotcat missing"); rfSpawn(o, 0, 0, 0, 180, Cat.class).setCatType(custom); Cat kit = rfSpawn(o, 1.2, 0, 0, 180, Cat.class); kit.setCatType(custom); kit.setBaby(); rfSpawn(o, 2.4, 0, 0, 180, Cat.class).setCatType(Cat.Type.TABBY); faceNorth(o); } private void rfWolves(Location o) { List vs = registry(io.papermc.paper.registry.RegistryKey.WOLF_VARIANT); for (int i = 0; i < vs.size(); i++) { rfSpawn(o, i * 1.3, 0, 0, 180, Wolf.class).setVariant(vs.get(i)); Wolf t = rfSpawn(o, i * 1.3, 0, 1.6, 180, Wolf.class); t.setVariant(vs.get(i)); tame(t, DyeColor.values()[i % 16]); Wolf pup = rfSpawn(o, i * 1.3, 0, 3.0, 180, Wolf.class); pup.setVariant(vs.get(i)); pup.setBaby(); if (i % 2 == 1) tame(pup, DyeColor.BLUE); } faceNorth(o); } private void rfFarm(Location o) { List cows = registry(io.papermc.paper.registry.RegistryKey.COW_VARIANT); List pigs = registry(io.papermc.paper.registry.RegistryKey.PIG_VARIANT); List chickens = registry(io.papermc.paper.registry.RegistryKey.CHICKEN_VARIANT); List frogs = registry(io.papermc.paper.registry.RegistryKey.FROG_VARIANT); List nautili = registry(io.papermc.paper.registry.RegistryKey.ZOMBIE_NAUTILUS_VARIANT); for (int i = 0; i < 3; i++) { double x = i * 4; setVar(rfSpawn(o, x, 0, 0, 180, Cow.class), cows.get(i)); Cow cb = rfSpawn(o, x + 1.6, 0, 0, 180, Cow.class); setVar(cb, cows.get(i)); cb.setBaby(); setVar(rfSpawn(o, x, 0, 1.8, 180, Pig.class), pigs.get(i)); Pig pb = rfSpawn(o, x + 1.4, 0, 1.8, 180, Pig.class); setVar(pb, pigs.get(i)); pb.setBaby(); setVar(rfSpawn(o, x + 2.6, 0, 1.8, 180, Chicken.class), chickens.get(i)); Chicken chb = rfSpawn(o, x + 3.3, 0, 1.8, 180, Chicken.class); setVar(chb, chickens.get(i)); chb.setBaby(); setVar(rfSpawn(o, x + 0.5, 0, 3.4, 180, Frog.class), frogs.get(i)); } for (int i = 0; i < nautili.size(); i++) setVar(rfSpawn(o, 2.5 + i * 3, 0, 4.8, 180, ZombieNautilus.class), nautili.get(i)); faceNorth(o); } /** setVariant via reflection (the variant setters' declaring interface differs between API and server). */ private static void setVar(Entity e, Object variant) { for (java.lang.reflect.Method m : e.getClass().getMethods()) if (m.getName().equals("setVariant") && m.getParameterCount() == 1 && m.getParameterTypes()[0].isInstance(variant)) { try { m.invoke(e, variant); return; } catch (ReflectiveOperationException ex) { throw new RuntimeException(ex); } } throw new IllegalStateException("no setVariant for " + variant); } private void rfStands(Location o) { rfSpawn(o, 0, 0, 0, 180, ArmorStand.class); rfSpawn(o, 1.5, 0, 0, 180, ArmorStand.class).setArms(true); rfSpawn(o, 3, 0, 0, 180, ArmorStand.class).setSmall(true); ArmorStand sa = rfSpawn(o, 4.2, 0, 0, 180, ArmorStand.class); sa.setSmall(true); sa.setArms(true); ArmorStand full = rfSpawn(o, 5.7, 0, 0, 180, ArmorStand.class); full.setArms(true); diamondArmor(full); hands(full, Material.IRON_SWORD, Material.SHIELD); ArmorStand sfull = rfSpawn(o, 7.2, 0, 0, 180, ArmorStand.class); sfull.setSmall(true); sfull.setArms(true); diamondArmor(sfull); hands(sfull, Material.TORCH, null); ArmorStand noArmsHeld = rfSpawn(o, 8.6, 0, 0, 180, ArmorStand.class); hands(noArmsHeld, Material.DIAMOND, null); ArmorStand inv = rfSpawn(o, 10, 0, 0, 180, ArmorStand.class); inv.setArms(true); inv.setInvisible(true); diamondArmor(inv); } private static void diamondArmor(LivingEntity e) { e.getEquipment().setHelmet(it(Material.DIAMOND_HELMET)); e.getEquipment().setChestplate(it(Material.DIAMOND_CHESTPLATE)); e.getEquipment().setLeggings(it(Material.DIAMOND_LEGGINGS)); e.getEquipment().setBoots(it(Material.DIAMOND_BOOTS)); } private void rfChests(Location o) { rfSpawn(o, 0, 0, 0, 180, Llama.class).setColor(Llama.Color.CREAMY); Llama lc = rfSpawn(o, 1.6, 0, 0, 180, Llama.class); lc.setColor(Llama.Color.BROWN); lc.setCarryingChest(true); Llama lcc = rfSpawn(o, 3.2, 0, 0, 180, Llama.class); lcc.setColor(Llama.Color.GRAY); lcc.setCarryingChest(true); lcc.getInventory().setDecor(it(Material.RED_CARPET)); TraderLlama tl = rfSpawn(o, 4.8, 0, 0, 180, TraderLlama.class); tl.setColor(Llama.Color.WHITE); tl.setCarryingChest(true); Llama bl = rfSpawn(o, 6.2, 0, 0, 180, Llama.class); bl.setColor(Llama.Color.CREAMY); bl.setBaby(); rfSpawn(o, 8, 0, 0, 180, Donkey.class); Donkey dc = rfSpawn(o, 9.6, 0, 0, 180, Donkey.class); dc.setCarryingChest(true); Donkey dcs = rfSpawn(o, 11.2, 0, 0, 180, Donkey.class); dcs.setCarryingChest(true); dcs.getInventory().setSaddle(it(Material.SADDLE)); Mule mc = rfSpawn(o, 12.8, 0, 0, 180, Mule.class); mc.setCarryingChest(true); Mule mcs = rfSpawn(o, 14.4, 0, 0, 180, Mule.class); mcs.setCarryingChest(true); mcs.getInventory().setSaddle(it(Material.SADDLE)); faceNorth(o); } private void rfArmor(Location o) { Class[] wearers = {Zombie.class, Husk.class, Drowned.class, ZombieVillager.class, PigZombie.class, Skeleton.class, Stray.class, WitherSkeleton.class, Bogged.class, Parched.class, Piglin.class, PiglinBrute.class}; for (int i = 0; i < wearers.length; i++) { @SuppressWarnings("unchecked") LivingEntity e = rfSpawn(o, i * 1.25, 0, 0, 180, (Class) wearers[i]); diamondArmor(e); } LivingEntity m = (LivingEntity) rfSpawn(o, 0, 0, 2.2, 180, EntityType.MANNEQUIN); diamondArmor(m); Zombie bz = rfSpawn(o, 1.3, 0, 2.2, 180, Zombie.class); bz.setBaby(); diamondArmor(bz); Piglin bp = rfSpawn(o, 2.5, 0, 2.2, 180, Piglin.class); bp.setBaby(); diamondArmor(bp); // Non-wearers given armor: vanilla draws nothing. Class[] others = {Villager.class, Pillager.class, Vindicator.class, Witch.class, Evoker.class, Creeper.class, Enderman.class, Cow.class, Fox.class}; for (int i = 0; i < others.length; i++) { @SuppressWarnings("unchecked") LivingEntity e = rfSpawn(o, 4 + i * 1.25, 0, 2.2, 180, (Class) others[i]); diamondArmor(e); } faceNorth(o); } // --- 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 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) (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) (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); } } } // --- block-entity scenes ------------------------------------------------------------------------ private void put(Location o, int x, int y, int z, String data) { o.clone().add(x, y, z).getBlock().setBlockData(Bukkit.createBlockData("minecraft:" + data), false); } private void signText(Location o, int x, int y, int z, String front, String back) { if (!(o.clone().add(x, y, z).getBlock().getState() instanceof org.bukkit.block.Sign sg)) return; sg.getSide(org.bukkit.block.sign.Side.FRONT).setLine(1, front); sg.getSide(org.bukkit.block.sign.Side.BACK).setLine(1, back); sg.getSide(org.bukkit.block.sign.Side.BACK).setColor(DyeColor.BLUE); sg.update(true, false); } private static final String[] DIRS = {"north", "east", "south", "west"}; private static final int[][] STEP = {{0, -1}, {1, 0}, {0, 1}, {-1, 0}}; /** A stone pillar with one wall-mounted block on each of its four sides ({@code %s} = facing). */ private void pillar(Location o, int x, int z, int y, String fmt) { put(o, x, y, z, "stone"); for (int i = 0; i < 4; i++) put(o, x + STEP[i][0], y, z + STEP[i][1], String.format(fmt, DIRS[i])); } /** Standing signs rot 0..15 (two rows), ceiling hanging signs rot 0..15, wall + wall-hanging signs. */ private void beSigns(Location o) { for (int r = 0; r < 16; r++) { int x = (r % 8) * 2, z = r < 8 ? 0 : 2; put(o, x, 0, z, "oak_sign[rotation=" + r + "]"); signText(o, x, 0, z, "F" + r, "B" + r); put(o, r, 2, -3, "stone"); put(o, r, 1, -3, "spruce_hanging_sign[rotation=" + r + ",attached=true]"); signText(o, r, 1, -3, "F" + r, "B" + r); } pillar(o, 3, 7, 0, "birch_wall_sign[facing=%s]"); pillar(o, 3, 7, 1, "cherry_wall_hanging_sign[facing=%s]"); for (int i = 0; i < 4; i++) { signText(o, 3 + STEP[i][0], 0, 7 + STEP[i][1], DIRS[i], "back"); signText(o, 3 + STEP[i][0], 1, 7 + STEP[i][1], DIRS[i], "back"); } pillar(o, 10, 7, 1, "dark_oak_wall_sign[facing=%s]"); for (int i = 0; i < 4; i++) signText(o, 10 + STEP[i][0], 1, 7 + STEP[i][1], DIRS[i].toUpperCase(), ""); } /** Standing banners rot 0..15 with patterns, wall banners on a pillar. */ private void beBanners(Location o) { DyeColor[] cols = DyeColor.values(); for (int r = 0; r < 16; r++) { int x = (r % 8) * 2, z = r < 8 ? 0 : 3; put(o, x, 0, z, cols[r].name().toLowerCase() + "_banner[rotation=" + r + "]"); if (o.clone().add(x, 0, z).getBlock().getState() instanceof org.bukkit.block.Banner b) { b.addPattern(new org.bukkit.block.banner.Pattern(DyeColor.BLACK, org.bukkit.block.banner.PatternType.STRIPE_TOP)); b.addPattern(new org.bukkit.block.banner.Pattern(DyeColor.WHITE, org.bukkit.block.banner.PatternType.CREEPER)); b.update(true, false); } } pillar(o, 16, 5, 2, "red_wall_banner[facing=%s]"); put(o, 16, 1, 5, "stone"); put(o, 16, 0, 5, "stone"); } /** Every head type in five rotations (0, 2, 4, 8, 12) per row, wall heads on pillars. */ private void beHeads(Location o) { String[] t = {"skeleton_skull", "wither_skeleton_skull", "zombie_head", "creeper_head", "player_head", "piglin_head", "dragon_head"}; int[] rots = {0, 2, 4, 8, 12}; for (int i = 0; i < t.length; i++) for (int j = 0; j < rots.length; j++) put(o, j * 2, 0, i, t[i] + "[rotation=" + rots[j] + "]"); String[] wall = {"skeleton_wall_skull", "dragon_wall_head", "piglin_wall_head", "player_wall_head"}; for (int i = 0; i < wall.length; i++) pillar(o, 12, i * 3 - 2, 1, wall[i] + "[facing=%s]"); for (int i = 0; i < wall.length; i++) put(o, 12, 0, i * 3 - 2, "stone"); } /** Bells (all attachments/facings), decorated pots with sherds, shulker boxes (6 facings), lecterns, conduit. */ private void beMisc(Location o) { for (int i = 0; i < 4; i++) { put(o, i * 2, 0, 0, "bell[attachment=floor,facing=" + DIRS[i] + "]"); put(o, i * 2, 2, 2, "stone"); put(o, i * 2, 1, 2, "bell[attachment=ceiling,facing=" + DIRS[i] + "]"); put(o, i * 2, 0, 4, "decorated_pot[facing=" + DIRS[i] + "]"); if (o.clone().add(i * 2, 0, 4).getBlock().getState() instanceof org.bukkit.block.DecoratedPot pot) { pot.setSherd(org.bukkit.block.DecoratedPot.Side.FRONT, Material.HEART_POTTERY_SHERD); pot.setSherd(org.bukkit.block.DecoratedPot.Side.LEFT, Material.SKULL_POTTERY_SHERD); pot.setSherd(org.bukkit.block.DecoratedPot.Side.RIGHT, Material.ANGLER_POTTERY_SHERD); pot.update(true, false); } put(o, i * 2, 0, 6, "lectern[facing=" + DIRS[i] + ",has_book=true]"); } put(o, 9, 1, 0, "stone"); put(o, 10, 1, 0, "bell[attachment=single_wall,facing=east]"); put(o, 12, 1, 0, "stone"); put(o, 11, 1, 0, "bell[attachment=double_wall,facing=east]"); String[] f6 = {"up", "down", "north", "south", "east", "west"}; String[] c6 = {"", "red_", "blue_", "lime_", "black_", "white_"}; for (int i = 0; i < 6; i++) put(o, 9 + (i % 3) * 2, 0, 3 + (i / 3) * 2, c6[i] + "shulker_box[facing=" + f6[i] + "]"); put(o, 9, 0, 7, "conduit"); put(o, 8, 0, 6, "lectern[facing=south,has_book=false]"); } /** Block-entity items: held by armor stands, worn on heads, as item displays and as block displays. */ private void beItems(Location o) { Material[] m = {Material.CHEST, Material.RED_BANNER, Material.SHULKER_BOX, Material.DECORATED_POT, Material.CONDUIT, Material.DRAGON_HEAD, Material.PIGLIN_HEAD, Material.CREEPER_HEAD, Material.PLAYER_HEAD}; for (int i = 0; i < m.length; i++) { ArmorStand as = spawn(o, i + 0.5, 0, 6.5, 0, ArmorStand.class); as.setArms(true); as.getEquipment().setItemInMainHand(it(m[i])); if (m[i].name().endsWith("_HEAD")) as.getEquipment().setHelmet(it(m[i])); ItemDisplay d = spawn(o, i + 0.5, 0.6, 3, 0, ItemDisplay.class); d.setItemStack(it(m[i])); d.setTransformation(new Transformation(new Vector3f(), new Quaternionf(), new Vector3f(0.6f), new Quaternionf())); } String[] blocks = {"chest[facing=south]", "red_banner[rotation=0]", "shulker_box[facing=up]", "decorated_pot[facing=south]", "skeleton_skull[rotation=8]", "lectern[facing=south,has_book=true]"}; for (int i = 0; i < blocks.length; i++) { BlockDisplay b = spawn(o, i * 1.5, 0, 0, 0, BlockDisplay.class); b.setBlock(Bukkit.createBlockData("minecraft:" + blocks[i])); } } // --- item frames ------------------------------------------------------------------------------- private void frames(Location o) { for (int x = -2; x <= 11; x++) for (int y = 0; y <= 5; y++) w.getBlockAt(o.getBlockX() + x, o.getBlockY() + y, o.getBlockZ() + 4).setType(Material.STONE, false); BlockFace f = BlockFace.NORTH; for (int r = 0; r < 8; r++) frame(o, r, 4, 3, f, false, it(Material.DIAMOND_SWORD), r, true); ItemStack banner = it(Material.RED_BANNER); org.bukkit.inventory.meta.BannerMeta bm = (org.bukkit.inventory.meta.BannerMeta) banner.getItemMeta(); bm.addPattern(new org.bukkit.block.banner.Pattern(DyeColor.YELLOW, org.bukkit.block.banner.PatternType.STRIPE_TOP)); banner.setItemMeta(bm); ItemStack[] row3 = {it(Material.STONE), it(Material.OAK_STAIRS), it(Material.GRASS_BLOCK), it(Material.APPLE), it(Material.TORCH), it(Material.CHEST), banner, eu.mhsl.minecraft.pixelpics.survival.CameraItems.createCamera(3), it(Material.CREEPER_HEAD), it(Material.SHIELD)}; for (int i = 0; i < row3.length; i++) frame(o, i - 1, 3, 3, f, false, row3[i], 0, true); ItemStack photo = photoMap(), synth = synthMap(), vmap = vanillaMap(); int[] photoRot = {0, 1, 2, 3, 5}; for (int i = 0; i < photoRot.length; i++) frame(o, i - 1, 2, 3, f, false, photo, photoRot[i], true); frame(o, 4, 2, 3, f, false, synth, 0, true); frame(o, 5, 2, 3, f, false, synth, 1, true); frame(o, 6, 2, 3, f, false, vmap, 0, true); frame(o, 7, 2, 3, f, false, it(Material.MAP), 0, true); frame(o, 8, 2, 3, f, false, it(Material.FILLED_MAP), 0, true); ItemStack[] inv = {it(Material.DIAMOND_SWORD), it(Material.STONE), synth, it(Material.CHEST)}; for (int i = 0; i < inv.length; i++) frame(o, i - 1, 1, 3, f, false, inv[i], i == 0 ? 1 : 0, false); ItemStack[] glow = {it(Material.APPLE), it(Material.STONE), synth, null}; for (int i = 0; i < glow.length; i++) frame(o, i + 3, 1, 3, f, true, glow[i], 0, true); frame(o, 7, 1, 3, f, false, null, 0, true); frame(o, 8, 1, 3, f, true, null, 0, true); } /** Three stone blocks with a frame on every side: block item, tool, synthetic map (all at rotation 1). */ private void framesDirs(Location o) { ItemStack[] items = {it(Material.OAK_STAIRS), it(Material.DIAMOND_SWORD), synthMap()}; BlockFace[] faces = {BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST, BlockFace.UP, BlockFace.DOWN}; for (int i = 0; i < items.length; i++) { int bx = 3 + i * 5, by = 4, bz = 3; w.getBlockAt(o.getBlockX() + bx, o.getBlockY() + by, o.getBlockZ() + bz).setType(Material.STONE, false); for (BlockFace face : faces) frame(o, bx + face.getModX(), by + face.getModY(), bz + face.getModZ(), face, i == 2, items[i], 1, true); } } /** Black/white concrete checkerboard: wall z=6 (x -6..17, y 0..11), ceiling y=11 and floor y=-1. */ private void camChecker(Location o) { for (int a = -6; a < 18; a++) for (int b = 0; b < 12; b++) { Material m = ((a + b) & 1) == 0 ? Material.WHITE_CONCRETE : Material.BLACK_CONCRETE; w.getBlockAt(o.getBlockX() + a, o.getBlockY() + b, o.getBlockZ() + 6).setType(m, false); } for (int a = -6; a < 18; a++) for (int c = -12; c < 6; c++) { Material m = ((a + c) & 1) == 0 ? Material.WHITE_CONCRETE : Material.BLACK_CONCRETE; w.getBlockAt(o.getBlockX() + a, o.getBlockY() + 11, o.getBlockZ() + c).setType(m, false); w.getBlockAt(o.getBlockX() + a, o.getBlockY() - 1, o.getBlockZ() + c).setType(m, false); } } /** Black wall z=6 with one item frame (synthetic map, 1x1 block) facing north at cell (5,5,5). */ private void camFrame(Location o) { for (int a = -6; a < 18; a++) for (int b = 0; b < 12; b++) w.getBlockAt(o.getBlockX() + a, o.getBlockY() + b, o.getBlockZ() + 6).setType(Material.BLACK_CONCRETE, false); frame(o, 5, 5, 5, BlockFace.NORTH, false, synthMap(), 0, true); } /** Black wall z=6 with a white vertical stripe (x=5) and a white horizontal stripe (y=5). */ private void camStripes(Location o) { for (int a = -6; a < 18; a++) for (int b = 0; b < 12; b++) w.getBlockAt(o.getBlockX() + a, o.getBlockY() + b, o.getBlockZ() + 6) .setType(a == 5 || b == 5 ? Material.WHITE_CONCRETE : Material.BLACK_CONCRETE, false); } private ItemFrame frame(Location o, int x, int y, int z, BlockFace face, boolean glow, ItemStack item, int rot, boolean visible) { Location l = o.clone().add(x + 0.5, y + 0.5, z + 0.5); Class c = glow ? GlowItemFrame.class : ItemFrame.class; ItemFrame fr = w.spawn(l, c, e -> { e.setFacingDirection(face, true); e.setPersistent(false); }); if (item != null) fr.setItem(item, false); fr.setRotation(Rotation.values()[rot]); fr.setVisible(visible); fr.setFixed(true); return fr; } private static ItemStack mapItem(org.bukkit.map.MapView view) { ItemStack s = it(Material.FILLED_MAP); org.bukkit.inventory.meta.MapMeta mm = (org.bukkit.inventory.meta.MapMeta) s.getItemMeta(); mm.setMapView(view); s.setItemMeta(mm); return s; } /** A real stored PixelPics photo (lowest managed map id on disk), or the synthetic map if none exists. */ private ItemStack photoMap() { File dir = new File(Main.getInstance().getDataFolder(), "images"); int best = Integer.MAX_VALUE; File[] files = dir.listFiles(); if (files != null) for (File f : files) { java.util.regex.Matcher m = java.util.regex.Pattern.compile("mapId_(\\d+)\\.mcmap").matcher(f.getName()); if (m.matches()) best = Math.min(best, Integer.parseInt(m.group(1))); } int id = best == Integer.MAX_VALUE ? -1 : best; @SuppressWarnings("deprecation") org.bukkit.map.MapView v = id < 0 ? null : Bukkit.getMap(id); if (v == null) return synthMap(); if (v.getRenderers().stream().noneMatch(r -> r instanceof eu.mhsl.minecraft.pixelpics.utils.ImageMapRenderer)) { byte[] idx = eu.mhsl.minecraft.pixelpics.utils.MapManager.readIndices(id); if (idx != null) eu.mhsl.minecraft.pixelpics.utils.MapManager.attachView(v, new eu.mhsl.minecraft.pixelpics.utils.ImageMapRenderer(idx)); } getLogger().info("frames: photo map id " + id); return mapItem(v); } private ItemStack synthMap; /** Asymmetric test picture as a PixelPics map: red top-left, blue top-right, green bottom-left, black "F". */ private ItemStack synthMap() { if (synthMap != null) return synthMap; BufferedImage img = new BufferedImage(128, 128, BufferedImage.TYPE_INT_RGB); java.awt.Graphics2D g = img.createGraphics(); g.setColor(java.awt.Color.WHITE); g.fillRect(0, 0, 128, 128); g.setColor(java.awt.Color.RED); g.fillRect(0, 0, 32, 32); g.setColor(java.awt.Color.BLUE); g.fillRect(96, 0, 32, 32); g.setColor(new java.awt.Color(0, 160, 0)); g.fillRect(0, 96, 32, 32); g.setColor(java.awt.Color.BLACK); g.fillRect(48, 24, 14, 80); g.fillRect(48, 24, 44, 14); g.fillRect(48, 56, 32, 12); g.dispose(); org.bukkit.map.MapView v = Bukkit.createMap(w); eu.mhsl.minecraft.pixelpics.utils.MapManager.attachView(v, new eu.mhsl.minecraft.pixelpics.utils.ImageMapRenderer(img)); return synthMap = mapItem(v); } /** A plain vanilla map whose server-side colours are written directly (diagonal colour bands + transparent corner). */ private ItemStack vanillaMap() { org.bukkit.map.MapView v = Bukkit.createMap(w); try { java.lang.reflect.Field wm = v.getClass().getDeclaredField("worldMap"); wm.setAccessible(true); Object data = wm.get(v); byte[] colors = (byte[]) data.getClass().getField("colors").get(data); byte[] palette = {(byte) (18 * 4 + 2), (byte) (7 * 4 + 2), (byte) (12 * 4 + 1), (byte) (29 * 4 + 2)}; for (int y = 0; y < 128; y++) for (int x = 0; x < 128; x++) colors[y * 128 + x] = x < 24 && y < 24 ? 0 : palette[((x + y) / 32) % palette.length]; } catch (ReflectiveOperationException e) { getLogger().warning("vanillaMap: " + e); } return mapItem(v); } }