Cancelling Event Throws Weird Error

Hey there!

I’ve created a custom DataManipulator to offer to ItemStacks. I’ve generated a test Inventory with 1 ItemStack of Stone. I’m listening to a ClickInventoryEvent. Once I check if the ItemSnapshot

event.getCursorTransaction()

contains my custom Data, I then proceed to call an external method to see if the event should be cancelled. When I try to cancel the event, this is thrown. I’ve tried to read through this error report but I’m not too sure what I’m looking for. I can see that it mentions an ItemStack of AIR (which would be the result of the click after I click on the Stone in the Slot (simply put there for testing purposes)), so that makes me further think that it has something to do with the cancelling of the event. However I also see it mentioning the “.offer()” method and DataManipulator, however even when commenting out any code having to do with that within this external method, this error is still thrown. Any help would be appreciated!

Could you show us the code of the event? As well as the data manipulator code? (So the key registration, manipulator builder, etc)

I’ll do my best to make this understandable (it’s a lot of custom code).

So first, the event listener:

@Listener
public void onClick(ClickInventoryEvent event, @First Player player){

    ItemStackSnapshot itemStack = event.getCursorTransaction().getFinal();

    if(itemStack.get(AtlasKeys.CLICK_ACTION).isPresent()){
        if(itemStack.get(AtlasKeys.CLICK_ACTION).get().perform(event, AtlasInventory.ClickType.getClickTypeFor(event), player,
                event.getTransactions().get(0), itemStack, null)){
            event.setCancelled(true);
        }
    }
}

As far as I can tell, everything here, including the custom Data, works well except when trying to cancel the event. If I comment that out, the error doesn’t get thrown.

Here’s the external method which I call in the above code:

@Override
        public boolean perform(ClickInventoryEvent event, AtlasInventory.ClickType clickType, Player player,
                               SlotTransaction slotTransaction, ItemStackSnapshot item, AtlasInventory atlasInventory) {
            /**
             * Return TRUE if event should be cancelled.
             */

            Formatter.debug("1");
            Slot slot = slotTransaction.getSlot();
            int index = slot.getInventoryProperty(SlotIndex.class).get().getValue();

            if(clickType == AtlasInventory.ClickType.PRIMARY){
                Formatter.debug("2");

                if(!item.get(AtlasKeys.CURRENT_PRICE).isPresent()){
                    Formatter.debug("3");
                    return true;
                }

                double price = item.get(AtlasKeys.CURRENT_PRICE).get();

                TransactionService transactionService = AtlasCore.INSTANCE.getEconomyService().getTransactionService();
                Optional<Account> account = transactionService.findAccount(player.getUniqueId());
                if(!account.isPresent()){
                    Formatter.debug("4");
                    return true;
                }

                EconomyEvent.PlayerBuyItem.Pre e = new EconomyEvent.PlayerBuyItem.Pre(account.get(), event.getCursorTransaction().getFinal());
                Sponge.getEventManager().post(e);

                if(!e.isCancelled()){
                    if(!transactionService.withdraw(player.getUniqueId(), price)){
                        Formatter.debug("5");
                        return true;
                    }

                    //We've now withdrawn the money needed for this item.
                    if(item.get(AtlasKeys.SUPPLY).get() > 1){
                        vItem.decreaseSupply(1);
                        Formatter.debug("6");
                        return true;
                    } else {
                        vItem.setSupply(0);
                        Formatter.debug("7");
                        return false;
                    }
                }

            }

            return true;
        }

From what I can tell with my debug messages, it gets through all this code fine. I’m wanting this to get to debug number 6 and it does that (all the correct messages leading up to that point are printed in console). It just has something to do with canceling this event and I cannot figure it out.

I’ll provide my DataManipulator code below, although no errors are being thrown when offering or getting my custom data from the ItemStack, so I am to assume that’s working fine. But nonetheless, I’ll provide it. Thanks for the help!

My mutable AbstractData:

public class VendorShopItemData extends AbstractData<VendorShopItemData, ImmutableVendorShopItemData> {

private AtlasInventory.ClickAction itemClickAction;
private double currentPrice;
private double vendorPrice;
private int supply;

public VendorShopItemData(){
    this(0, 0, 0, null);
}

public VendorShopItemData(double currentPrices, double vendorPrice, int supply, AtlasInventory.ClickAction clickAction) {
    this.itemClickAction = clickAction;
    this.currentPrice = currentPrices;
    this.vendorPrice = vendorPrice;
    this.supply = supply;
    registerGettersAndSetters();
}

public AtlasInventory.ClickAction getItemClickAction() {
    return itemClickAction;
}

public double getCurrentPrice() {
    return currentPrice;
}

public double getVendorPrice() {
    return vendorPrice;
}

public int getSupply() {
    return supply;
}

public void setItemClickAction(AtlasInventory.ClickAction itemClickAction) {
    this.itemClickAction = itemClickAction;
}

public void setCurrentPrice(double currentPrice) {
    this.currentPrice = currentPrice;
}

public void setVendorPrice(double vendorPrice) {
    this.vendorPrice = vendorPrice;
}

public void setSupply(int supply) {
    this.supply = supply;
}

protected Value<AtlasInventory.ClickAction> clickAction(){
    return this.itemClickAction == null ? null : Sponge.getRegistry().getValueFactory().createValue(AtlasKeys.CLICK_ACTION, this.itemClickAction);
}

protected Value<Double> currentPrice() {
    return Sponge.getRegistry().getValueFactory().createValue(AtlasKeys.CURRENT_PRICE, this.currentPrice);
}

protected Value<Double> vendorPrice() {
    return Sponge.getRegistry().getValueFactory().createValue(AtlasKeys.VENDOR_PRICE, this.vendorPrice);
}

protected Value<Integer> supply(){
    return Sponge.getRegistry().getValueFactory().createValue(AtlasKeys.SUPPLY, this.supply);
}

@Override
protected void registerGettersAndSetters() {
    registerFieldGetter(AtlasKeys.CLICK_ACTION, this::getItemClickAction);
    registerFieldSetter(AtlasKeys.CLICK_ACTION, this::setItemClickAction);
    registerKeyValue(AtlasKeys.CLICK_ACTION, this::clickAction);

    registerFieldGetter(AtlasKeys.CURRENT_PRICE, this::getCurrentPrice);
    registerFieldSetter(AtlasKeys.CURRENT_PRICE, this::setCurrentPrice);
    registerKeyValue(AtlasKeys.CURRENT_PRICE, this::currentPrice);

    registerFieldGetter(AtlasKeys.VENDOR_PRICE, this::getVendorPrice);
    registerFieldSetter(AtlasKeys.VENDOR_PRICE, this::setVendorPrice);
    registerKeyValue(AtlasKeys.VENDOR_PRICE, this::vendorPrice);

    registerFieldGetter(AtlasKeys.SUPPLY, this::getSupply);
    registerFieldSetter(AtlasKeys.SUPPLY, this::setSupply);
    registerKeyValue(AtlasKeys.SUPPLY, this::supply);
}

@Override
public Optional<VendorShopItemData> fill(DataHolder dataHolder) {
    return Optional.ofNullable(dataHolder.get(VendorShopItemData.class).orElse(null));
}

@Override
public Optional<VendorShopItemData> fill(DataHolder dataHolder, MergeFunction overlap) {
    VendorShopItemData vendorShopItemData = overlap.merge(this, dataHolder.get(VendorShopItemData.class).orElse(null));

    setItemClickAction(vendorShopItemData.getItemClickAction());
    setCurrentPrice(vendorShopItemData.getCurrentPrice());
    setVendorPrice(vendorShopItemData.getVendorPrice());
    setSupply(vendorShopItemData.getSupply());

    return Optional.of(this);
}

@Override
public Optional<VendorShopItemData> from(DataContainer container) {

    if(container.contains(AtlasKeys.CLICK_ACTION, AtlasKeys.INITIAL_PRICE, AtlasKeys.CURRENT_PRICE,
            AtlasKeys.VENDOR_PRICE, AtlasKeys.SUPPLY)){

        setItemClickAction((AtlasInventory.ClickAction) container.get(AtlasKeys.CLICK_ACTION.getQuery()).get());
        setCurrentPrice(container.getDouble(AtlasKeys.CURRENT_PRICE.getQuery()).get());
        setVendorPrice(container.getDouble(AtlasKeys.VENDOR_PRICE.getQuery()).get());
        setSupply(container.getInt(AtlasKeys.SUPPLY.getQuery()).get());

        return Optional.of(this);
    }
    return Optional.empty();
}

@Override
public VendorShopItemData copy() {
    return new VendorShopItemData(getCurrentPrice(), getVendorPrice(), getSupply(), getItemClickAction());
}

@Override
public ImmutableVendorShopItemData asImmutable() {
    return new ImmutableVendorShopItemData();
}

@Override
public int getContentVersion() {
    return 1;
}

}

My Immutable AbstractData:

public class ImmutableVendorShopItemData extends AbstractImmutableData<ImmutableVendorShopItemData, VendorShopItemData> {

private final AtlasInventory.ClickAction itemClickAction;
private final double currentPrice;
private final double vendorPrice;
private final int supply;

public ImmutableVendorShopItemData(){
    this(null, 0, 0, 0);
}

public ImmutableVendorShopItemData(AtlasInventory.ClickAction clickAction, double currentPrice, double vendorPrice, int supply) {
    this.itemClickAction = clickAction;
    this.currentPrice = currentPrice;
    this.vendorPrice = vendorPrice;
    this.supply = supply;
    registerGetters();
}

public AtlasInventory.ClickAction getItemClickAction() {
    return itemClickAction;
}

public double getCurrentPrice() {
    return currentPrice;
}

public double getVendorPrice() {
    return vendorPrice;
}

public int getSupply() {
    return supply;
}

protected ImmutableValue<AtlasInventory.ClickAction> clickAction(){
    return Sponge.getRegistry().getValueFactory()
            .createValue(AtlasKeys.CLICK_ACTION, this.itemClickAction)
            .asImmutable();
}

protected ImmutableValue<Double> currentPrices() {
    return Sponge.getRegistry().getValueFactory().createValue(AtlasKeys.CURRENT_PRICE, this.currentPrice).asImmutable();
}

protected ImmutableValue<Double> vendorPrices() {
    return Sponge.getRegistry().getValueFactory().createValue(AtlasKeys.VENDOR_PRICE, this.vendorPrice).asImmutable();
}

protected ImmutableValue<Integer> supply(){
    return Sponge.getRegistry().getValueFactory().createValue(AtlasKeys.SUPPLY, this.supply).asImmutable();
}

@Override
protected void registerGetters() {
    registerFieldGetter(AtlasKeys.CLICK_ACTION, this::getItemClickAction);
    registerKeyValue(AtlasKeys.CLICK_ACTION, this::clickAction);

    registerFieldGetter(AtlasKeys.CURRENT_PRICE, this::getCurrentPrice);
    registerKeyValue(AtlasKeys.CURRENT_PRICE, this::currentPrices);

    registerFieldGetter(AtlasKeys.VENDOR_PRICE, this::getVendorPrice);
    registerKeyValue(AtlasKeys.VENDOR_PRICE, this::vendorPrices);

    registerFieldGetter(AtlasKeys.SUPPLY, this::getSupply);
    registerKeyValue(AtlasKeys.SUPPLY, this::supply);
}

@Override
public VendorShopItemData asMutable() {
    return new VendorShopItemData(currentPrice, vendorPrice, supply, null);
}

@Override
public int getContentVersion() {
    return 1;
}

@Override
public DataContainer toContainer(){
    return super.toContainer().set(AtlasKeys.CLICK_ACTION.getQuery(), this.itemClickAction)
            .set(AtlasKeys.CURRENT_PRICE.getQuery(), this.currentPrice)
            .set(AtlasKeys.VENDOR_PRICE.getQuery(), this.vendorPrice)
            .set(AtlasKeys.SUPPLY.getQuery(), this.supply);
}

}

My Builder for this data:

public class VendorShopItemDataBuilder extends AbstractDataBuilder<VendorShopItemData> implements DataManipulatorBuilder<VendorShopItemData, ImmutableVendorShopItemData> {

public VendorShopItemDataBuilder() {
    super(VendorShopItemData.class, 1);
}

@Override
public VendorShopItemData create() {
    return new VendorShopItemData(0, 0, 0, null);
}

@Override
public Optional<VendorShopItemData> createFrom(DataHolder dataHolder) {
    return create().fill(dataHolder);
}

@Override
protected Optional<VendorShopItemData> buildContent(DataView container) throws InvalidDataException {
    return create().from(container.getContainer());
}

}

And now my Keys:

public class AtlasKeys {

public static final Key<Value<AtlasInventory.ClickAction>> CLICK_ACTION = Key.builder()
        .type(new TypeToken<Value<AtlasInventory.ClickAction>>(){})
        .id("click_action")
        .name("Click Action")
        .query(DataQuery.of(".", "click.action"))
        .build();

/**
 * This is the initial price an item started selling at when it first entered a {@link com.atlas.atlascore.economy.ai.Market}.
 */
public static final Key<Value<Double>> INITIAL_PRICE = Key.builder()
        .type(new TypeToken<Value<Double>>(){})
        .id("initial_price")
        .name("Initial Price")
        .query(DataQuery.of(".", "initial.price"))
        .build();

/**
 * This is the price a {@link com.atlas.atlascore.economy.ai.vendor.Vendor} is selling a particular item.
 */
public static final Key<Value<Double>> VENDOR_PRICE = Key.builder()
        .type(new TypeToken<Value<Double>>(){})
        .id("vendor_price")
        .name("Vendor Price")
        .query(DataQuery.of(".", "vendor.price"))
        .build();

/**
 * This is the current average price of an item among all Vendors within a given {@link com.atlas.atlascore.economy.ai.Market}.
 */
public static final Key<Value<Double>> CURRENT_PRICE = Key.builder()
        .type(new TypeToken<Value<Double>>(){})
        .id("current_price")
        .name("Current Price")
        .query(DataQuery.of(".", "current.price"))
        .build();

/**
 * This is the supply of a {@link com.atlas.atlascore.economy.ai.vendor.VendorItem}
 * that a {@link com.atlas.atlascore.economy.ai.vendor.Vendor} has.
 */
public static final Key<Value<Integer>> SUPPLY = Key.builder()
        .type(TypeTokens.INTEGER_VALUE_TOKEN)
        .id("supply")
        .name("Supply")
        .query(DataQuery.of("", "supply"))
        .build();

}

Finally, the registration in GamePreInitializationEvent:

private void registerData(){
    DataRegistration.builder()
            .dataName("Vendor Shop Item Data")
            .manipulatorId("vendor_shop_item_data")
            .dataClass(VendorShopItemData.class)
            .immutableClass(ImmutableVendorShopItemData.class)
            .builder(new VendorShopItemDataBuilder())
            .buildAndRegister(pluginContainer);
}

I don’t think clickAction() should returns null. Try using OptionalValue instead of Value for this situation.

That’s what I was going to do originally. But 2 things. First, I couldn’t find a way to make an immutable Optional value. And second, I know by the way I’ll be using this Data that if it is present on an ItemStack, it means it’s an ItemStack from a Vendor’s menu, meaning that it will always have a ClickAction tied to it to perform some sort of action. I do agree the Optional route would be better practice, but I couldn’t figure out how to make an immutable Optional value.

To create an immutable value, create a value and then call toImmutable on it.