Jump to content
Search In
  • More options...
Find results that contain...
Find results in...

Player-based Crafting Recipes


Recommended Posts

Introduction

 

You may have seen Tutorials about this before, and they most likely used the ItemCraftedEvent from FML. That works (kinda) but has the disadvantage that the actual recipe-result is only computed once the player actually takes out the Item, so the preview does not work. Not nice.

I'm going to show you a different method today.

 

For our player-based crafting recipe we cannot use the normal crafting mechanic, but instead we need to implement the IRecipe interface directly.

The IRecipe interface comes with several methods:

  • boolean matches(InventoryCrafting, World)
    - Pretty obvious, checks if your recipe matches the current contents of the crafting grid
 
  • ItemStack getCraftingResult(InventoryCrafting)

    - Should also be obvious, creates the result based on the contents of the crafting grid

 
  • boolean canFit(int, int)

    - Whether the recipe can fit into a grid of the given size

 
  • ItemStack getRecipeOutput()

    - A generic output of your recipe, this one is never given to the player though

 

  • NonNullList<ItemStack> getRemainingItems(InventoryCrafting)

- The items that remain in the crafting grid after the recipe has been applied, you almost never need to override it.

 

  • NonNullList<Ingredient> getIngredients()

- A list of ingredients for this recipe, most recipe classes don't actually implement this.

 

  • boolean isHidden()

- Related to whether this recipe is unlocked by default.

 

  • String getGroup()

- The group of this recipe.

 

In this tutorial I will not go into how to implement any complex matching algorithms, instead we will be making the classic "Dirt to diamond" recipe.

 

Getting the player

Now, to the actual topic of this tutorial, getting the player currently crafting. As you probably have noticed, all we get from vanilla Minecraft to determine the output of our recipe is the World and the crafting grid (InventoryCrafting). The 2nd one is the key, because it holds a reference to the player, via some tricks.

If you look at the InventoryCrafting class you can see that it has a reference to the Container containing the crafting grid (field eventHandler). The field is private, so we use Java Reflection to access it.

There are 2 possibilities for this container that we care about:

  • ContainerPlayer, used when the player is just viewing the normal inventory and using the 2x2 crafting grid
  • ContainerWorkbench, used when the player is using a workbench to craft

 

There are more possibilities for this, it could for example be a Container by a mod with a custom crafting table or a ContainerSheep (yes, that exists). In case of another mod's workbench you'll have to find other ways to find the player, if you want those to be supported. We don't care about ContainerSheep.

 

Getting the player from ContainerPlayer

 

Once you've determined the type of container to be a ContainerPlayer (use an instanceof check for this), getting the player is easy: ContainerPlayer has a field player, which holds the player. That field is again private, but as before you can use Reflection to access it.

 

Getting the player from ContainerWorkbench

 

If the container is a ContainerWorkbench, the player instance is a bit harder to obtain. If you look at the class, there is no obvious reference to any player to be found. The first slot being added however is a SlotCrafting, which holds a reference to the player again. To get that Slot, use the getSlot method from the Container class to get the first Slot (index 0). Then you can get the field player from the SlotCrafting, again via reflection.

 

Wrapping up

 

Almost done.

To register your recipe you will need a few json files (check out the CraftingSystemTest class in the forge test mods), which I will not go into here.

 

Example

 

Yep, lot's of reflection tricky going on here, so let me provide you with an example (please don't just copy and paste it):

 

class DirtToDiamond implements IRecipe {

  @Override
  public boolean matches(InventoryCrafting inv, World world) {
    EntityPlayer player = findPlayer(inv);
    return player != null // player can be null if we can't determine it
      && player.getCommandSenderName().startsWith("d") // do whatever checks you need to about the player
      && hasDirt(inv); // the actual recipe check
  }

  @Override
  public ItemStack getCraftingResult(InventoryCrafting inv) {
    // only called if matches() returns true, so no need to check again
    return new ItemStack(Items.diamond);
  }

  @Override
  public boolean canFit(int width, int height) {
    return true;
  }

  @Override
   public ItemStack getRecipeOutput() {
    return new ItemStack(Items.diamond);
  }

  private static boolean hasDirt(InventoryCrafting inv) {
    Item dirt = Item.getItemFromBlock(Blocks.dirt);
    for (int i = 0; i < inv.getSizeInventory(); i++) {
      ItemStack stack = inv.getStackInSlot(i);
      if (stack != null && stack.getItem() == dirt) {
        return true;
      }
    }
    return false;
  }

  // TODO: SRG names for non-dev environment
  private static final Field eventHandlerField = ReflectionHelper.findField(InventoryCrafting.class, "eventHandler");
  private static final Field containerPlayerPlayerField = ReflectionHelper.findField(ContainerPlayer.class, "player");
  private static final Field slotCraftingPlayerField = ReflectionHelper.findField(SlotCrafting.class, "player");

  private static EntityPlayer findPlayer(InventoryCrafting inv) {
    try {
      Container container = (Container) eventHandlerField.get(inv);
      if (container instanceof ContainerPlayer) {
        return (EntityPlayer) containerPlayerPlayerField.get(container);
      } else if (container instanceof ContainerWorkbench) {
        return (EntityPlayer) slotCraftingPlayerField.get(container.getSlot(0));
      } else {
        // don't know the player
        return null;
      }
    } catch (Exception e) {
      throw Throwables.propagate(e);
    }
  }
}

 

Edited by diesieben07
Update for new forum formatting and newer Minecraft versions
  • Like 3
Link to post
Share on other sites
  • 2 weeks later...

Hum I wouldn't suggest throwing an exception just for a failed player detection in a recipe. Especially since you can still rely on ItemCraftedEvent to get you the final data with far less risk.

 

I am also unsure how that tutorial relates to any "preview" issue as stated in the introduction ?

 

Anyways, it kinda is nice to see a bit of reflection explained to the masses :)

Link to post
Share on other sites

Hum I wouldn't suggest throwing an exception just for a failed player detection in a recipe.

The code only throws an exception when the fields cannot be accessed via Reflection, which is a programmer problem and should never happen. If the exception is thrown it's a Bug and it's ok to crash.
Especially since you can still rely on ItemCraftedEvent to get you the final data with far less risk.
You cannot. ItemCraftedEvent is not designed to modify the results of the crafting.

I am also unsure how that tutorial relates to any "preview" issue as stated in the introduction ?

Assuming you cancel the crafting via ItemCraftedEvent for a certain Player X (ignoring the fact that that is not what the Event is made for). If that player now tries to craft, it first looks like it is possible (the result shows up) and only when they try to take out the result it will magically vanish. With the method explained here the result would not even show up in the first place if Player X tries to craft.

Anyways, it kinda is nice to see a bit of reflection explained to the masses :)

Thanks.
Link to post
Share on other sites

The code only throws an exception when the fields cannot be accessed via Reflection, which is a programmer problem and should never happen. If the exception is thrown it's a Bug and it's ok to crash.

There is always the SecurityManager issue when dealing with Reflection. This matter is a bit more subtle than the usual bugs one can encounter in type-safety situations. Just wanted to point that out, since this could also count as an introductory tutorial on reflection.

 

You cannot. ItemCraftedEvent is not designed to modify the results of the crafting.

Except you can. ItemStack is mutable. AFAIK, this is the only point of the event, to add/change data to the craft. Or make statistics (meh. boring.)

 

Assuming you cancel the crafting via ItemCraftedEvent for a certain Player X (ignoring the fact that that is not what the Event is made for). If that player now tries to craft, it first looks like it is possible (the result shows up) and only when they try to take out the result it will magically vanish. With the method explained here the result would not even show up in the first place if Player X tries to craft.

Well you can't cancel that event so that solves it. Didn't think of such case though. Sounds kinda unfair to not give a specific player his item if he wants to spend the ressources for it...but still a valid point.

Link to post
Share on other sites

There is always the SecurityManager issue when dealing with Reflection. This matter is a bit more subtle than the usual bugs one can encounter in type-safety situations. Just wanted to point that out, since this could also count as an introductory tutorial on reflection.

This is by no means a tutorial on reflection. And in the Minecraft environment a Security Manager is not an issue (as of right now).

 

Except you can. ItemStack is mutable. AFAIK, this is the only point of the event, to add/change data to the craft. Or make statistics (meh. boring.)

Yes, ItemStack is mutable. Yes, you could change the result of the crafting. But that is the entire point of this tutorial: It would have the "preview" issue I described. If you e.g. change the resulting Item it will look weird when being taken out of the crafting grid, because it suddenly changes. Which it does not with this method.

 

Well you can't cancel that event so that solves it. Didn't think of such case though. Sounds kinda unfair to not give a specific player his item if he wants to spend the ressources for it...but still a valid point.

Well, people still use it for that. And yeah, see above.
Link to post
Share on other sites
  • 11 months later...

One more question!:)

 

So in my class that extends ShapedOreRecipes, I have a member variable, named requiredLevel, that is assigned a value through a constructor.  How would I later obtain this value?  Better yet, how would I obtain this value through the event, "ItemCraftedEvent"?

 

Thanks again for all the help

Link to post
Share on other sites

I am not quite sure I understand your question. Why would you use the ItemCraftedEvent? You need to override the matches() method and in there check if the player is allowed to craft this recipe.

 

Upon crafting the item, the player gets crafting xp which depends on the level that is required to craft it.  That level is stored as a member variable inside my customShapedRecipe class.

 

I use ItemCraftedEvent to give the player xp when he crafts it.

Link to post
Share on other sites

You store the required level somewhere, e.g. in a Map or whatever, it depends on the algorithm that determines this level.

Then in the recipe matches() method you check if that level is satisfied by the player.

Then in the ItemCraftedEvent you get it again (not from the recipe!) and then give that XP to the player.

Link to post
Share on other sites

You store the required level somewhere, e.g. in a Map or whatever, it depends on the algorithm that determines this level.

Then in the recipe matches() method you check if that level is satisfied by the player. (DONE)

Then in the ItemCraftedEvent you get it again (not from the recipe!) and then give that XP to the player.

 

So in the map, the key would be the crafting recipe I'm guessing.

 

Why shouldn't I store the value in my customShapedRecipe?

 

Here's the class, just in case you were wondering what exactly it is I'm doing.  (It mostly copies ShapedOreRecipe)

http://pastebin.com/NqyNydHM

 

How I register the crafting recipe

        GameRegistry.addRecipe(new customShapedRecipe(new ItemStack(Blocks.stone, 1), new Object[] {"CCC","CCC","CCC", 'C', Blocks.cobblestone, Ints.asList(5, 1), Arrays.asList("WoodCutting", "Mining"), Arrays.asList("IronMan", "Daniel wasn't there")}));

Link to post
Share on other sites

Ohgodwhy.

 

I said to extend ShapedOreRecipe so you don't need to do all that copy-pasta bullshit.

Only override matches. Call super and check an additional condition (enough XP). That's all.

 

You can't store it in the recipe, because in ItemCraftedEvent you don't have access to the recipe.

And no, the key in the Map would not be the recipe. It would be whatever you need to determine the XP required (Item? I don't know! It's your mod).

Link to post
Share on other sites

Oh man I feel dumb!  Haha and btw, I really enjoy how you respond.  With such horror:p. They're memorable.

 

As for overriding absolutely everything, I gave that a thought, but then life happened..  Thanks for answering my questions.  They solved my problem, as well as some of my ideas.

Link to post
Share on other sites
  • 3 weeks later...

In this way one cannot get player from crafting done by inventory.

I think there was forge hook on getting player..

I. Stellarium for Minecraft: Configurable Universe for Minecraft! (WIP)

II. Stellar Sky, Better Star Rendering&Sky Utility mod, had separated from Stellarium.

Link to post
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.

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.



×
×
  • Create New...

Important Information

By using this site, you agree to our Privacy Policy.