9

I have been experiencing an odd bug in my Spigot/Bukkit plugin lately which totally does not make any sense. Please note that this question may be long due to the fact that the project I am working on is fairly big and since the source code (provided below) contains classes that will not be included here I will try my best to describe my problem.

In Minecraft, using Bukkit API you can create graphical menus (GUI's) in which you can place items in certain slots and when clicked, you can adjust your functionality.

In my case, I have created the Menu foundation class which contains basic methods for the creation of such a menu. Then, the PurchaseMenu which extends the Menu class is used to trigger functionality in specific locations to simulate a transaction for product that can be clicked from the menu.

In depth, the menu I will include contains "Kit" displays (like game classes) that when one Left-Click's the display the player can buy it, or if it's already bought, the player will just select that kit for use. When one Right-Clicks's the display, the player can level up the kit up to 3.

In both cases a transaction menu must be popped up in order to buy or level up the clicked kit. My design is really straight forward, by passing the clicked kit from the constructor on each PurchaseMenu class.

The problem on all the above is that the Kit is passed correctly from the constructor, but the kit that is actually being bought or level up is a random one (obviously not, but haven't found any evidence yet) and that usually happens after some tries.

My Design:

  • Each Menu class contains two methods. InventoryClick is called when a menu display is clicked (interacted). InventoryConstruct is called when a menu is being created (opened).
  • The Purchase Menu class contains two methods as well. PurchaseProduct is called when a display is purchased successfully. PrePaymentChecks is necessary checks that need to be ran before the purchase.

My questions:

  • How do I patch that problem, and save the correct Kit in a private field of the class?
  • How can I improve my design (approach) in certain projects to avoid such problems?

Any form of help is appreciated, I hope I described my problem in detail, if you require any further source code, please post a comment below.

Update

Because of the fact that the question will be more than 30k characters, I uploaded the whole project into a Git repository.

https://github.com/parat26/core

Menu.java: https://github.com/parat26/core/blob/master/src/src/ares/core/menu/Menu.java

PurchaseMenu.java:https://github.com/parat26/core/blob/master/src/src/ares/core/menu/PurchaseMenu.java

BattleOptionsMenu.java: https://github.com/parat26/core/blob/master/src/src/ares/core/menu/type/MenuBattleOptions.java

PurchaseMenuKit.java: https://github.com/parat26/core/blob/master/src/src/ares/core/menu/type/PurchaseMenuKit.java

Thanos Paravantis
  • 7,393
  • 3
  • 17
  • 24
  • I have not read the details but could it be a threading issue? Are you supposed to use a specific thread to push events to your UI and maybe you have used another thread, creating a synchronization issue where some variables are not updated in the UI thread as they should? – assylias Mar 30 '15 at 11:42
  • Thanks for your comment, I am not a professional, however as far as I know, I do not involve different threads to that structure. Only Spigot's by default. – Thanos Paravantis Mar 30 '15 at 11:47
  • 1
    "The problem on all the above is that the Kit is passed correctly from the constructor, but the kit that is actually being bought or level up is a random one (obviously not, but haven't found any evidence yet) and that usually happens after some tries." I can't reproduce the problem by writing test code to plug into your classes. I believe the problem lies somewhere else. For instance, `kit.getPersonalDisplay(client)` is calling each of `manager.getKitBag()` from `BattleManager.getInstance()`, which its source isn't attached. Consider linking all relevant methods used by the code. – Unihedron Mar 30 '15 at 12:51
  • 1
    You should follow the [Java naming conventions](http://www.oracle.com/technetwork/java/codeconventions-135099.html). – spongebob Mar 30 '15 at 13:22
  • 1
    Putting a breakpoint on field "set" and figuring out what's going on might be a good idea –  Mar 30 '15 at 19:12
  • Could you include the source for `src.ares.core.battle.kit.Kit`, as well as `BattleManager.getKitManager()`, which is called in `BattleOptionsMenu.SelectOrBuyKit()`? The problem may be there. – Jojodmo Mar 30 '15 at 19:24
  • Added repository: https://github.com/parat26/core Menus: https://github.com/parat26/core/tree/master/src/src/ares/core/menu – Thanos Paravantis Mar 30 '15 at 19:42
  • 2
    Try adding debugging in every method, printing the most important variables and information to the console, along with what method is being logged in... It'll take a while, but you can find out what changes, and where it changes that way. – Jojodmo Apr 02 '15 at 20:42
  • I have updated the repository and the question's source code with the update ones including debug messages. http://prntscr.com/6ps3is – Thanos Paravantis Apr 05 '15 at 09:25
  • 1
    If you haven't been able to solve it yet, your best bet may be to just completely recode this part of your project. Sure, it'll be annoying, but it'll be better than just waiting to find the answer, make sure you save your old code, just incase. If you do this, you may also be able to find the problem with the original code. – Jojodmo Apr 09 '15 at 02:03

1 Answers1

2

Try changing PurchaseMenu.object to final and see what the output is. Also, System.out.println() is your friend.

I do not suggest you use Object for your kit as it may be changing the object when you set object to sellableKit. Make an interface called Sellable<T> so that you can enforce the type (called Strong Typing) with:

private final ItemStack item;
protected final Sellable<? super Kit> kit;
private final GoldCurrency currency;

public PurchaseMenu(String menuName, ItemStack sellableItem, Sellable<? super Kit> sellableKit, GoldCurrency currencyType)
{
    super(Material.STONE, menuName, 54);

    item = sellableItem;
    kit = sellableKit;
    currency = currencyType;

    DependOnEvents(true);
}

Making those fields final also is good practice since it doesn't appear that you are setting those fields after object creation and lets anyone know immediately those objects will never change. It should also enlighten the situation since a final object cannot be changed.

You should remove the private Kit kit; field from BuyKitOwnershipMenu since your just making a copy of the object. Your PurchaseProduct() method can be changed to

@Override
    public void PurchaseProduct(GoldCurrency currency, Client client)
    {
        client.getManager().setKitOwned(kit, true);
    }

Since kit is now protected and in the super class.

AMDG
  • 1,118
  • 1
  • 13
  • 29
  • With the updated code, here's the result in my debug messages. http://prntscr.com/6ps3is The problem seems to be lying somewhere on the pre-checks. – Thanos Paravantis Apr 05 '15 at 09:24
  • What class(es) and method(s) perform pre-checks on the `Sellable super Kit>`? – AMDG Apr 05 '15 at 17:09