#1431: Add API for InventoryView derivatives

By: Miles Holder <mwholder2005@gmail.com>
This commit is contained in:
CraftBukkit/Spigot 2024-07-27 10:01:29 +10:00
parent 7fae51f18d
commit bfad1aa117
23 changed files with 537 additions and 72 deletions

View file

@ -260,6 +260,7 @@ import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.InventoryView;
import org.bukkit.inventory.Recipe;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.inventory.view.AnvilView;
import org.bukkit.potion.PotionEffect;
import org.bukkit.util.Vector;
@ -1541,7 +1542,7 @@ public class CraftEventFactory {
return event;
}
public static PrepareAnvilEvent callPrepareAnvilEvent(InventoryView view, ItemStack item) {
public static PrepareAnvilEvent callPrepareAnvilEvent(AnvilView view, ItemStack item) {
PrepareAnvilEvent event = new PrepareAnvilEvent(view, CraftItemStack.asCraftMirror(item).clone());
event.getView().getPlayer().getServer().getPluginManager().callEvent(event);
event.getInventory().setItem(2, event.getResult());

View file

@ -1,20 +1,32 @@
package org.bukkit.craftbukkit.inventory;
import com.google.common.base.Preconditions;
import java.util.function.Consumer;
import net.minecraft.world.IInventory;
import net.minecraft.world.inventory.ContainerAnvil;
import org.bukkit.Location;
import org.bukkit.craftbukkit.inventory.view.CraftAnvilView;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.AnvilInventory;
public class CraftInventoryAnvil extends CraftResultInventory implements AnvilInventory {
private final Location location;
private final ContainerAnvil container;
private static final int DEFAULT_REPAIR_COST = 0;
private static final int DEFAULT_REPAIR_COST_AMOUNT = 0;
private static final int DEFAULT_MAXIMUM_REPAIR_COST = 40;
public CraftInventoryAnvil(Location location, IInventory inventory, IInventory resultInventory, ContainerAnvil container) {
private final Location location;
private String renameText;
private int costAmount;
private int repairAmount;
private int maximumRepairCost;
public CraftInventoryAnvil(Location location, IInventory inventory, IInventory resultInventory) {
super(inventory, resultInventory);
this.location = location;
this.container = container;
this.renameText = null;
this.costAmount = DEFAULT_REPAIR_COST_AMOUNT;
this.repairAmount = DEFAULT_REPAIR_COST;
this.maximumRepairCost = DEFAULT_MAXIMUM_REPAIR_COST;
}
@Override
@ -24,37 +36,81 @@ public class CraftInventoryAnvil extends CraftResultInventory implements AnvilIn
@Override
public String getRenameText() {
return container.itemName;
syncWithArbitraryViewValue((cav) -> this.renameText = cav.getRenameText());
return this.renameText;
}
@Override
public int getRepairCostAmount() {
return container.repairItemCountCost;
syncWithArbitraryViewValue((cav) -> this.costAmount = cav.getRepairItemCountCost());
return this.repairAmount;
}
@Override
public void setRepairCostAmount(int amount) {
container.repairItemCountCost = amount;
this.repairAmount = amount;
syncViews((cav) -> cav.setRepairItemCountCost(amount));
}
@Override
public int getRepairCost() {
return container.cost.get();
syncWithArbitraryViewValue((cav) -> this.repairAmount = cav.getRepairCost());
return this.costAmount;
}
@Override
public void setRepairCost(int i) {
container.cost.set(i);
this.costAmount = i;
syncViews((cav) -> cav.setRepairCost(i));
}
@Override
public int getMaximumRepairCost() {
return container.maximumRepairCost;
syncWithArbitraryViewValue((cav) -> this.maximumRepairCost = cav.getMaximumRepairCost());
return this.maximumRepairCost;
}
@Override
public void setMaximumRepairCost(int levels) {
Preconditions.checkArgument(levels >= 0, "Maximum repair cost must be positive (or 0)");
container.maximumRepairCost = levels;
this.maximumRepairCost = levels;
syncViews((cav) -> cav.setMaximumRepairCost(levels));
}
public boolean isRepairCostSet() {
return this.costAmount != DEFAULT_REPAIR_COST;
}
public boolean isRepairCostAmountSet() {
return this.repairAmount != DEFAULT_REPAIR_COST_AMOUNT;
}
public boolean isMaximumRepairCostSet() {
return this.maximumRepairCost != DEFAULT_MAXIMUM_REPAIR_COST;
}
// used to lazily update and apply values from the view to the inventory
private void syncViews(Consumer<CraftAnvilView> consumer) {
for (HumanEntity viewer : getViewers()) {
if (viewer.getOpenInventory() instanceof CraftAnvilView cav) {
consumer.accept(cav);
}
}
}
/*
* This method provides the best effort guess on whatever the value could be
* It is possible these values are wrong given there are more than 1 views of this inventory,
* however it is a limitation seeing as these anvil values are supposed to be in the Container
* not the inventory.
*/
private void syncWithArbitraryViewValue(Consumer<CraftAnvilView> consumer) {
if (getViewers().isEmpty()) {
return;
}
final HumanEntity entity = getViewers().get(0);
if (entity != null && entity.getOpenInventory() instanceof CraftAnvilView cav) {
consumer.accept(cav);
}
}
}

View file

@ -15,14 +15,14 @@ import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;
import org.bukkit.inventory.ItemStack;
public class CraftInventoryView extends CraftAbstractInventoryView {
private final Container container;
public class CraftInventoryView<T extends Container> extends CraftAbstractInventoryView {
protected final T container;
private final CraftHumanEntity player;
private final CraftInventory viewing;
private final String originalTitle;
private String title;
public CraftInventoryView(HumanEntity player, Inventory viewing, Container container) {
public CraftInventoryView(HumanEntity player, Inventory viewing, T container) {
// TODO: Should we make sure it really IS a CraftHumanEntity first? And a CraftInventory?
this.player = (CraftHumanEntity) player;
this.viewing = (CraftInventory) viewing;

View file

@ -0,0 +1,66 @@
package org.bukkit.craftbukkit.inventory.view;
import net.minecraft.world.inventory.ContainerAnvil;
import org.bukkit.craftbukkit.inventory.CraftInventoryAnvil;
import org.bukkit.craftbukkit.inventory.CraftInventoryView;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.view.AnvilView;
import org.jetbrains.annotations.Nullable;
public class CraftAnvilView extends CraftInventoryView<ContainerAnvil> implements AnvilView {
public CraftAnvilView(final HumanEntity player, final Inventory viewing, final ContainerAnvil container) {
super(player, viewing, container);
}
@Nullable
@Override
public String getRenameText() {
return container.itemName;
}
@Override
public int getRepairItemCountCost() {
return container.repairItemCountCost;
}
@Override
public int getRepairCost() {
return container.getCost();
}
@Override
public int getMaximumRepairCost() {
return container.maximumRepairCost;
}
@Override
public void setRepairItemCountCost(final int cost) {
container.repairItemCountCost = cost;
}
@Override
public void setRepairCost(final int cost) {
container.cost.set(cost);
}
@Override
public void setMaximumRepairCost(final int cost) {
container.maximumRepairCost = cost;
}
public void updateFromLegacy(CraftInventoryAnvil legacy) {
if (legacy.isRepairCostSet()) {
setRepairCost(legacy.getRepairCost());
}
if (legacy.isRepairCostAmountSet()) {
setRepairItemCountCost(legacy.getRepairCostAmount());
}
if (legacy.isMaximumRepairCostSet()) {
setMaximumRepairCost(legacy.getMaximumRepairCost());
}
}
}

View file

@ -0,0 +1,45 @@
package org.bukkit.craftbukkit.inventory.view;
import net.minecraft.world.inventory.ContainerBeacon;
import net.minecraft.world.level.block.entity.TileEntityBeacon;
import org.bukkit.craftbukkit.inventory.CraftInventoryView;
import org.bukkit.craftbukkit.potion.CraftPotionEffectType;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.view.BeaconView;
import org.bukkit.potion.PotionEffectType;
import org.jetbrains.annotations.Nullable;
public class CraftBeaconView extends CraftInventoryView<ContainerBeacon> implements BeaconView {
public CraftBeaconView(final HumanEntity player, final Inventory viewing, final ContainerBeacon container) {
super(player, viewing, container);
}
@Override
public int getTier() {
return container.getLevels();
}
@Nullable
@Override
public PotionEffectType getPrimaryEffect() {
return container.getPrimaryEffect() != null ? CraftPotionEffectType.minecraftHolderToBukkit(container.getPrimaryEffect()) : null;
}
@Nullable
@Override
public PotionEffectType getSecondaryEffect() {
return container.getSecondaryEffect() != null ? CraftPotionEffectType.minecraftHolderToBukkit(container.getSecondaryEffect()) : null;
}
@Override
public void setPrimaryEffect(@Nullable final PotionEffectType effectType) {
container.setData(TileEntityBeacon.DATA_PRIMARY, ContainerBeacon.encodeEffect(CraftPotionEffectType.bukkitToMinecraftHolder(effectType)));
}
@Override
public void setSecondaryEffect(@Nullable final PotionEffectType effectType) {
container.setData(TileEntityBeacon.DATA_SECONDARY, ContainerBeacon.encodeEffect(CraftPotionEffectType.bukkitToMinecraftHolder(effectType)));
}
}

View file

@ -0,0 +1,38 @@
package org.bukkit.craftbukkit.inventory.view;
import com.google.common.base.Preconditions;
import net.minecraft.world.inventory.ContainerBrewingStand;
import net.minecraft.world.level.block.entity.TileEntityBrewingStand;
import org.bukkit.craftbukkit.inventory.CraftInventoryView;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.view.BrewingStandView;
public class CraftBrewingStandView extends CraftInventoryView<ContainerBrewingStand> implements BrewingStandView {
public CraftBrewingStandView(final HumanEntity player, final Inventory viewing, final ContainerBrewingStand container) {
super(player, viewing, container);
}
@Override
public int getFuelLevel() {
return container.getFuel();
}
@Override
public int getBrewingTicks() {
return container.getBrewingTicks();
}
@Override
public void setFuelLevel(final int fuelLevel) {
Preconditions.checkArgument(fuelLevel > 0, "The given fuel level must be greater than 0");
container.setData(TileEntityBrewingStand.DATA_FUEL_USES, fuelLevel);
}
@Override
public void setBrewingTicks(final int brewingTicks) {
Preconditions.checkArgument(brewingTicks > 0, "The given brewing ticks must be greater than 0");
container.setData(TileEntityBrewingStand.DATA_BREW_TIME, brewingTicks);
}
}

View file

@ -0,0 +1,32 @@
package org.bukkit.craftbukkit.inventory.view;
import com.google.common.base.Preconditions;
import net.minecraft.world.inventory.CrafterMenu;
import org.bukkit.craftbukkit.inventory.CraftInventoryView;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.view.CrafterView;
public class CraftCrafterView extends CraftInventoryView<CrafterMenu> implements CrafterView {
public CraftCrafterView(final HumanEntity player, final Inventory viewing, final CrafterMenu container) {
super(player, viewing, container);
}
@Override
public boolean isSlotDisabled(final int slot) {
return container.isSlotDisabled(slot);
}
@Override
public boolean isPowered() {
return container.isPowered();
}
@Override
public void setSlotDisabled(final int slot, final boolean disabled) {
Preconditions.checkArgument(slot >= 0 && slot < 9, "Invalid slot index %s for Crafter", slot);
container.setSlotState(slot, disabled);
}
}

View file

@ -0,0 +1,59 @@
package org.bukkit.craftbukkit.inventory.view;
import com.google.common.base.Preconditions;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.inventory.ContainerEnchantTable;
import net.minecraft.world.item.enchantment.Enchantment;
import org.bukkit.craftbukkit.CraftRegistry;
import org.bukkit.craftbukkit.enchantments.CraftEnchantment;
import org.bukkit.craftbukkit.inventory.CraftInventoryView;
import org.bukkit.enchantments.EnchantmentOffer;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.view.EnchantmentView;
import org.jetbrains.annotations.NotNull;
public class CraftEnchantmentView extends CraftInventoryView<ContainerEnchantTable> implements EnchantmentView {
public CraftEnchantmentView(final HumanEntity player, final Inventory viewing, final ContainerEnchantTable container) {
super(player, viewing, container);
}
@Override
public int getEnchantmentSeed() {
return container.getEnchantmentSeed();
}
@NotNull
@Override
public EnchantmentOffer[] getOffers() {
Registry<Holder<Enchantment>> registry = CraftRegistry.getMinecraftRegistry().registryOrThrow(Registries.ENCHANTMENT).asHolderIdMap();
EnchantmentOffer[] offers = new EnchantmentOffer[3];
for (int i = 0; i < 3; i++) {
org.bukkit.enchantments.Enchantment enchantment = (container.enchantClue[i] >= 0) ? CraftEnchantment.minecraftHolderToBukkit(registry.byId(container.enchantClue[i])) : null;
offers[i] = (enchantment != null) ? new EnchantmentOffer(enchantment, container.levelClue[i], container.costs[i]) : null;
}
return offers;
}
@Override
public void setOffers(@NotNull final EnchantmentOffer[] offers) {
Preconditions.checkArgument(offers.length != 3, "There must be 3 offers given");
Registry<Holder<Enchantment>> registry = CraftRegistry.getMinecraftRegistry().registryOrThrow(Registries.ENCHANTMENT).asHolderIdMap();
for (int i = 0; i < offers.length; i++) {
final EnchantmentOffer offer = offers[i];
if (offer == null) {
container.enchantClue[i] = -1;
container.levelClue[i] = -1;
container.costs[i] = 0;
continue;
}
container.enchantClue[i] = registry.getIdOrThrow(CraftEnchantment.bukkitToMinecraftHolder(offer.getEnchantment()));
container.levelClue[i] = offer.getEnchantmentLevel();
container.costs[i] = offer.getCost();
}
}
}

View file

@ -0,0 +1,42 @@
package org.bukkit.craftbukkit.inventory.view;
import net.minecraft.world.inventory.ContainerFurnace;
import net.minecraft.world.level.block.entity.TileEntityFurnace;
import org.bukkit.craftbukkit.inventory.CraftInventoryView;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.view.FurnaceView;
public class CraftFurnaceView extends CraftInventoryView<ContainerFurnace> implements FurnaceView {
public CraftFurnaceView(final HumanEntity player, final Inventory viewing, final ContainerFurnace container) {
super(player, viewing, container);
}
@Override
public float getCookTime() {
return container.getBurnProgress();
}
@Override
public float getBurnTime() {
return container.getLitProgress();
}
@Override
public boolean isBurning() {
return container.isLit();
}
@Override
public void setCookTime(final int cookProgress, final int cookDuration) {
container.setData(TileEntityFurnace.DATA_COOKING_PROGRESS, cookProgress);
container.setData(TileEntityFurnace.DATA_COOKING_TOTAL_TIME, cookDuration);
}
@Override
public void setBurnTime(final int burnProgress, final int burnDuration) {
container.setData(TileEntityFurnace.DATA_LIT_TIME, burnProgress);
container.setData(TileEntityFurnace.DATA_LIT_DURATION, burnDuration);
}
}

View file

@ -0,0 +1,27 @@
package org.bukkit.craftbukkit.inventory.view;
import com.google.common.base.Preconditions;
import net.minecraft.world.inventory.ContainerLectern;
import net.minecraft.world.level.block.entity.TileEntityLectern;
import org.bukkit.craftbukkit.inventory.CraftInventoryView;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.view.LecternView;
public class CraftLecternView extends CraftInventoryView<ContainerLectern> implements LecternView {
public CraftLecternView(final HumanEntity player, final Inventory viewing, final ContainerLectern container) {
super(player, viewing, container);
}
@Override
public int getPage() {
return container.getPage();
}
@Override
public void setPage(final int page) {
Preconditions.checkArgument(page >= 0, "The minimum page is 0");
container.setData(TileEntityLectern.DATA_PAGE, page);
}
}

View file

@ -0,0 +1,35 @@
package org.bukkit.craftbukkit.inventory.view;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.core.Holder;
import net.minecraft.world.inventory.ContainerLoom;
import net.minecraft.world.level.block.entity.EnumBannerPatternType;
import org.bukkit.block.banner.PatternType;
import org.bukkit.craftbukkit.block.banner.CraftPatternType;
import org.bukkit.craftbukkit.inventory.CraftInventoryView;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.view.LoomView;
public class CraftLoomView extends CraftInventoryView<ContainerLoom> implements LoomView {
public CraftLoomView(final HumanEntity player, final Inventory viewing, final ContainerLoom container) {
super(player, viewing, container);
}
@Override
public List<PatternType> getSelectablePatterns() {
final List<Holder<EnumBannerPatternType>> selectablePatterns = container.getSelectablePatterns();
final List<PatternType> patternTypes = new ArrayList<>(selectablePatterns.size());
for (final Holder<EnumBannerPatternType> selectablePattern : selectablePatterns) {
patternTypes.add(CraftPatternType.minecraftHolderToBukkit(selectablePattern));
}
return patternTypes;
}
@Override
public int getSelectedPatternIndex() {
return container.getSelectedBannerPatternIndex();
}
}

View file

@ -0,0 +1,26 @@
package org.bukkit.craftbukkit.inventory.view;
import net.minecraft.world.inventory.ContainerMerchant;
import net.minecraft.world.item.trading.IMerchant;
import org.bukkit.craftbukkit.inventory.CraftInventoryView;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.Merchant;
import org.bukkit.inventory.view.MerchantView;
import org.jetbrains.annotations.NotNull;
public class CraftMerchantView extends CraftInventoryView<ContainerMerchant> implements MerchantView {
private final IMerchant trader;
public CraftMerchantView(final HumanEntity player, final Inventory viewing, final ContainerMerchant container, final IMerchant trader) {
super(player, viewing, container);
this.trader = trader;
}
@NotNull
@Override
public Merchant getMerchant() {
return this.trader.getCraftMerchant();
}
}

View file

@ -0,0 +1,40 @@
package org.bukkit.craftbukkit.inventory.view;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.world.inventory.ContainerStonecutter;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.RecipeStonecutting;
import org.bukkit.craftbukkit.inventory.CraftInventoryView;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.StonecuttingRecipe;
import org.bukkit.inventory.view.StonecutterView;
import org.jetbrains.annotations.NotNull;
public class CraftStonecutterView extends CraftInventoryView<ContainerStonecutter> implements StonecutterView {
public CraftStonecutterView(final HumanEntity player, final Inventory viewing, final ContainerStonecutter container) {
super(player, viewing, container);
}
@Override
public int getSelectedRecipeIndex() {
return container.getSelectedRecipeIndex();
}
@NotNull
@Override
public List<StonecuttingRecipe> getRecipes() {
final List<StonecuttingRecipe> recipes = new ArrayList<>();
for (final RecipeHolder<RecipeStonecutting> recipe : container.getRecipes()) {
recipes.add((StonecuttingRecipe) recipe.toBukkitRecipe());
}
return recipes;
}
@Override
public int getRecipeAmount() {
return container.getNumRecipes();
}
}