Jump to content

[1.10.2]+ Getting Merchant Recipes/Shop Items


hugo_the_dwarf
 Share

Recommended Posts

Currently I'm trying to upgrade the existing Villager Trading for my mod, And I want to add some "basic" items that are always available (dirt, clay, etc. Changing based on villager profession) but then I also want the existing trades they have to be added into my GUI as well. This is so Villager types from other mods will still have their shopping lists (compatibility is key). So a farmer would always sell leads + what he/she would sell normally (wheat, potatos, etc)

However when I go to override the existing villager GUI
 

	@SubscribeEvent
	public void onGuiOpen(GuiOpenEvent e)
	{
		if (e.getGui() instanceof GuiMerchant)
		{
			GuiMerchant gui = (GuiMerchant) e.getGui();
			Minecraft.getMinecraft().displayGuiScreen(new GuiShop(gui.getMerchant(), Minecraft.getMinecraft().thePlayer));

			e.setCanceled(true);
		}
	}

 

The GuiMerchant "gui" doesn't have any recipes, and the merchantInventory also has null. Is this because it's all clientside? More or less all I need to know is what items that merchant is selling so I can create my own buttons to render those choices. I'll be creating my own packets to send buy/sell requests so I can use my new currency capability. And once I get this working I'll be adding a villager capability so I can attach more options. But right now I'm struggling just getting their current shop list

Link to comment
Share on other sites

8 minutes ago, hugo_the_dwarf said:

Minecraft.getMinecraft().displayGuiScreen(new GuiShop(gui.getMerchant(), Minecraft.getMinecraft().thePlayer));

From the docs of the GuiOpenEvent:

 

This event is called before any Gui will open.

If you want to override this Gui, simply set the gui variable to your own Gui.

There is a setGui method in the event.

 

This will also not work as you expect it to as you are only replacing the GUI, but the server still has the villager's original container open. You need to handle that too.

 

Apart from that - how do you know that merchantInventory is null? From what I can tell it should not be null and that is also what my debugger tells me.

In any case you can have anything you want as the merchant's inventory in your container. See how ContainerMerchant initializes the inventory. Nothing stops you from doing the same.

Link to comment
Share on other sites

Hmm, when I debug and view the loaded GuiMerchant object it's got nulls in all the places i'm interested in. Now this Event is registered in my ClientProxy (forgot the mention that) since it's only the client side I wish to alter. As for the Merchant Container being open I think that's ?fine? as it keeps the existing villager logic of "stare at customer while trading" I will probably setup something that when the GUI closes to fire off a packet to tell the villager everything is all good, not sure how I'd reference the container to kill it (other than the player physically moving away from the villager)

So looking at the container, and villager(merchant gui/container) I was hoping to just have the nifty Itemstacks of what they are selling and add them to my own list (minus Emeralds sells) because I also wanted to make it so you could "request" items for the villager to "get" and sell later (for mods that have something that is a bit harder/more rare to get) but I'd rather not break other mods villagers.

I attached a photo of my debug showing what the GuiMerchant contains for data, which is mostly null (except for the merchant and the player)

EventGui.png

Link to comment
Share on other sites

Well, the items in the inventory would be null at the time the GUI opens. Those are the items in the slots that the player physically puts in. By the time the GUI is open those are null as the player couldn't have placed anything in the container.

You can use MerchantInventory::getCurrentRecipe to get the currently selected recipe.

Link to comment
Share on other sites

Hmm.. I was hoping to cheap out and just use Custom Buttons for the "slots" so it was a more of click and request the amount deal rather than a container move items around deal. As I'd be able to create a "transaction" Buy 3 stacks of dirt (64 * 3 * 1 gold nugget = 21 gold ingots) but selling some stone gear (in perfect condition) would warrant 2 gold ingots. Making the total transaction just 19 ingots. the transaction list would be sent off tot he server, which would then do the inventory (player) manipulation and complete the sale.

But i'm not sure how'd I'd completely incorporate that using the containers (or at least not including the use of Slot)

Link to comment
Share on other sites

Actually if you want to do something like that a custom GUI should suffice. You can use your custom GUI to add buttons and handle recipes/transactions/etc. and just send it to the server which validates that the client is not trying to lie with the transaction/do the logic/etc. You can get all recipes at IMerchant::getRecipes, that returns you a MerchantRecipeList that is an implementation of ArrayList<MerchantRecipe> so you should be able to get all recipes this way. The MerchantRecipe contains MerchantRecipe::getItemToBuy(first requested item), MerchantRecipe::getSecondItemToBuy(second requested item) and getItemToSell(the result). All those are ItemStacks so you should not have issues with those. I do not know how you would implement your transaction logic, that is up to you. Just do not forget that all logic must be ran or at least checked on the server so a malicious client can't lie to the server and you should be fine.

Additionally there are other useful methods in MerchantRecipe that you may or may not need, like MerchantRecipe::isRecipeDisabled(checks if the player traded maximum amount of times for the recipe), MerchantRecipe::getToolUses(the amount of time the recipe was traded), MerchantRecipe::getMaxTradeUses(pretty self-descriptive :P) and others.

If you want the "press button -> send packet -> check trade -> spawn items & take currency" you should be able to bypass the container entirely as if it never existed.

Link to comment
Share on other sites

Yup that sounds pretty close to my initial plan. I'm just stuck on getting the existing recipes, was hoping that event would have had them populated (and if they are client-side I have no idea why it's NULL in my environment)
Was hoping I didn't have to create a bunch of back and forth communications packets and just reuse existing objects and only worry about the transaction packets

Currently I have this as my GUI for now, It needs alot of work, but I'm just trying to get the important guts working before I try to make it pretty and add the communication logic (which is not hard for me)

 

package htd.rot.client.gui;

import org.lwjgl.opengl.GL11;

import htd.rot.Rot;
import htd.rot.capability.playerextra.CapPlayerExtra;
import htd.rot.capability.playerextra.CapPlayerExtraData;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.entity.IMerchant;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.init.Items;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import net.minecraft.village.MerchantRecipe;

public class GuiShop extends GuiContainer
{
	private static final ResourceLocation texture = new ResourceLocation(Rot.MODID.toLowerCase(), "textures/gui/shop_gui.png");

	private EntityPlayer player;
	private IMerchant merchant;
	private CapPlayerExtraData capPlayerExtra;

	private int frameSize = 18;

	private int itemStartX = 5;
	private int shopItemStartY = 6;
	private int playerHotbarStarY = 141;

	private int buttonId = 0;

	public GuiShop(IMerchant merchant, EntityPlayer player)
	{
		super(new ContainerNull());

		this.player = player;
		this.merchant = merchant;
		this.xSize = 173;
		this.ySize = 166;
		capPlayerExtra = player.getCapability(CapPlayerExtra.CAPABILITY, null);
	}

	@Override
	public void initGui()
	{
		super.initGui();

		int currentItemId = 0;
		ItemStack[] items = new ItemStack[]
		{
			new ItemStack(Blocks.DIRT),
			new ItemStack(Items.APPLE)
		};

		for (int row = 0; row < 3; row++)
		{
			for (int column = 0; column < 9; column++)
			{
				if (currentItemId < items.length) buttonList.add(new GuiShopButton(buttonId++, itemStartX + (frameSize * column), shopItemStartY + (frameSize * row), "", items[currentItemId++], 0));
			}
		}

		//Attempt at getting the existing selling list
		if (merchant != null && player != null)
		{
			if (!merchant.getRecipes(player).isEmpty())// merchant.getRecipes is NULL, errors because I assumed it wouldn't be NULL
			{
				MerchantRecipe existingList;
				ItemStack soldItem;
				for (int i = 0; i < merchant.getRecipes(player).size(); i++)
				{
					existingList = merchant.getRecipes(player).get(i);
					soldItem = existingList.getItemToSell();
					if (!soldItem.getItem().equals(Items.EMERALD))
					{
						buttonList.add(new GuiShopButton(buttonId++, itemStartX + (frameSize * i), shopItemStartY, "", soldItem, 0));
					}
				}
			}
		}

	}

	@Override
	protected void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY)
	{
		GL11.glColor4f(1F, 1F, 1F, 1F);
		Minecraft.getMinecraft().renderEngine.bindTexture(texture);
		drawTexturedModalRect(this.guiLeft, this.guiTop, 0, 0, this.xSize, this.ySize);
	}

}

 

Link to comment
Share on other sites

The recipeList for the villager is indeed null on the client even if you intercept the interact event for the villager - the client simply recieves no data of the recipe list untill it's needed. When you open the Container of a villager MC sends a custom payload packet to the client. That packet contains the recipes for the villager. By the time the gui is opened on the client the packet is not yet recieved it seems. Waiting a bit does the trick. There is a big issue with using vanilla's packet though - it will only get processed if the GUI is an instance of GuiMerchant and the container IDs match. While there may be a dirty workaround for that(use a custom GUI that extends GuiMerchant and use the container of that GUI as your container...) I think that a cleaner solution is using custom packets. You do not need to create a "bunch" of them though - one should be enough. It would send the client the recipe list and be an indication for the client to open your GUI(the packet would then also need to carry some way of getting the villager on the client if you actually need that). Another somewhat helpful thing you have is that MerchantRecipeList already has the code in place for network writing/reading. It wants a PacketBuffer and not a ByteBuf though. You could find a workaround for that(like construct a PacketBuffer, pass it and reflectively get it's unerlying ByteBuf/get it via a method if possible). Or just use that/similar code adapted for ByteBuf, there is nothing special about that code that would be unique to PacketBuffer, it would be possible to replicate the functionality.

 

Link to comment
Share on other sites

  • 2 weeks later...

So my initial planned approach didn't work the way I had planned, which was:
Interrupt guiOpen of the vanilla GUI to open mine (works)
Take entityID from merchant to send to server so I can collect merchant items (Failed)

  • Apparently from the guiMerchant::getMerchant returns an IMerchant, which I cannot get an entity from because it's actually an instance of NPCMerchant which can't be cast to an entity at all making my plan a complete failure.

this was something I was hoping I could have figured out on my own but that's not working. Help is much appreciated on where to start.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share



×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.