item guard: photos cannot be cloned, locked or scaled in the cartography table, the anvil only renames camera, film and photo, the crafter does not craft with them (it handed out the static load/copy placeholders and ate the ingredients), camera and film cannot be placed as heads (the broken skull lost the markers) or put into mount saddle slots by horse inventory or dispenser

This commit is contained in:
MineTec committed 2026-11-05 10:44:35 +01:00
1 parent 4c52bc2dca
commit 5d581dc482
2 files changed
+127 -2

No files matched your search

@@ -19,6 +19,7 @@ import eu.mhsl.minecraft.pixelpics.render.render.DefaultScreenRenderer;
import eu.mhsl.minecraft.pixelpics.render.tint.BiomeTintProvider;
import eu.mhsl.minecraft.pixelpics.survival.CameraListener;
import eu.mhsl.minecraft.pixelpics.survival.CraftingListener;
import eu.mhsl.minecraft.pixelpics.survival.ItemGuardListener;
import eu.mhsl.minecraft.pixelpics.survival.JoinListener;
import eu.mhsl.minecraft.pixelpics.survival.PendingPhotos;
import eu.mhsl.minecraft.pixelpics.survival.SurvivalRecipes;
@@ -62,8 +63,12 @@ public final class Main extends JavaPlugin {
initRenderManager();
Objects.requireNonNull(getCommand("pixelPic")).setExecutor(new PixelPicsCommand());
for (Listener listener :
List.of(new OnMapInitialize(), new CameraListener(), new CraftingListener(), new JoinListener())) {
for (Listener listener : List.of(
new OnMapInitialize(),
new CameraListener(),
new CraftingListener(),
new ItemGuardListener(),
new JoinListener())) {
Bukkit.getPluginManager().registerEvents(listener, this);
}
SurvivalRecipes.register();
@@ -0,0 +1,120 @@
package eu.mhsl.minecraft.pixelpics.survival;
import com.destroystokyo.paper.event.inventory.PrepareResultEvent;
import io.papermc.paper.event.player.CartographyItemEvent;
import org.bukkit.block.Crafter;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockDispenseArmorEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.block.CrafterCraftEvent;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.AnvilInventory;
import org.bukkit.inventory.CartographyInventory;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
/**
* Keeps camera, film and photo out of the vanilla workstations that would copy or rewrite them for free. A photo may
* only be copied by the copy recipe (with film): the cartography table would clone it, and locking or scaling moves it
* to a fresh map id without the picture, so a photo in the table gets no result. The anvil only renames them (no
* books or other items combined in). The crafter cannot compute the load/copy results (it would hand out the static
* placeholders) and would feed our items into vanilla recipes such as map cloning, so it does not craft with them;
* the ingredients stay in the crafter. Camera and film cannot be placed as head blocks either: breaking the placed skull
* drops a plain head without our markers (from the main hand a camera takes a photo instead, but an off-hand camera
* or film would be placed). Their unwearable equippable component uses the saddle slot, which every saddleable mount
* accepts, so they are kept out of mount equipment slots (horse inventory, dispenser onto a pig or horse).
*/
public class ItemGuardListener implements Listener {
@EventHandler
public void onPrepareResult(PrepareResultEvent event) {
Inventory inv = event.getInventory();
if (inv instanceof CartographyInventory) {
if (CameraItems.isPhoto(inv.getItem(0))) event.setResult(null);
} else if (inv instanceof AnvilInventory) {
ItemStack first = inv.getItem(0), second = inv.getItem(1);
boolean combining = second != null && !second.isEmpty();
if (combining && (isOurs(first) || isOurs(second))) event.setResult(null);
}
}
@EventHandler
public void onCartographyTake(CartographyItemEvent event) {
if (event.getSlotType() == InventoryType.SlotType.RESULT
&& CameraItems.isPhoto(event.getInventory().getItem(0))) {
event.setCancelled(true);
}
}
@EventHandler
public void onCrafterCraft(CrafterCraftEvent event) {
if (!(event.getBlock().getState(false) instanceof Crafter crafter)) return;
for (ItemStack it : crafter.getInventory().getContents()) {
if (isOurs(it)) {
// An empty result makes the crafter fail like a missing recipe: nothing is consumed.
event.setResult(ItemStack.empty());
return;
}
}
}
@EventHandler
public void onPlace(BlockPlaceEvent event) {
ItemStack item = event.getItemInHand();
if (isWearable(item)) event.setCancelled(true);
}
@EventHandler
public void onDispenseEquip(BlockDispenseArmorEvent event) {
if (isWearable(event.getItem())) event.setCancelled(true);
}
@EventHandler
public void onMountClick(InventoryClickEvent event) {
if (!isMountInventory(event.getView().getTopInventory())) return;
boolean intoEquipment = event.getRawSlot() < MOUNT_EQUIPMENT_SLOTS;
ItemStack moved;
if (event.isShiftClick()) {
moved = event.getRawSlot() >= event.getView().getTopInventory().getSize() ? event.getCurrentItem() : null;
intoEquipment = true;
} else if (event.getClick() == ClickType.NUMBER_KEY) {
moved = event.getView().getBottomInventory().getItem(event.getHotbarButton());
} else if (event.getClick() == ClickType.SWAP_OFFHAND) {
moved = event.getWhoClicked().getInventory().getItemInOffHand();
} else {
moved = event.getCursor();
}
if (intoEquipment && isWearable(moved)) event.setCancelled(true);
}
@EventHandler
public void onMountDrag(InventoryDragEvent event) {
if (!isMountInventory(event.getView().getTopInventory()) || !isWearable(event.getOldCursor())) return;
for (int raw : event.getRawSlots()) {
if (raw < MOUNT_EQUIPMENT_SLOTS) {
event.setCancelled(true);
return;
}
}
}
/** Saddle and body armour are the first two slots of a mount's inventory menu, chest slots follow. */
private static final int MOUNT_EQUIPMENT_SLOTS = 2;
private static boolean isMountInventory(Inventory inv) {
return inv.getHolder(false) instanceof LivingEntity;
}
private static boolean isWearable(ItemStack item) {
return CameraItems.isCamera(item) || CameraItems.isFilm(item);
}
private static boolean isOurs(ItemStack item) {
return isWearable(item) || CameraItems.isPhoto(item);
}
}