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

Player-based Crafting Recipes


diesieben07
 Share

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 comment
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 comment
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 comment
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 comment
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 comment
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 comment
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 comment
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 comment
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 comment
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 comment
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 comment
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 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.

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



  • Recently Browsing

    No registered users viewing this page.

  • Posts

    • Thanks for the help everybody! I tried this and although the block did register, the fluid just appears as a flat plane you can only see if you break the block under it. I tried debugging it as much as I could but alas I couldn't solve it Here is the code for my "fixed" liquidblock as suggested by Luis_ST: package com.hotmail.majdroaydi.minitech.blocks; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.sounds.SoundEvent; import net.minecraft.tags.FluidTags; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.LiquidBlock; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.material.FlowingFluid; import net.minecraft.world.level.pathfinder.PathComputationType; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; import java.util.Optional; import java.util.function.Supplier; public class ForgeLiquidBlock extends LiquidBlock { public ForgeLiquidBlock(Supplier<? extends FlowingFluid> supplier, Properties properties) { super(supplier, properties); } @Override public VoxelShape getCollisionShape(BlockState p_54760_, BlockGetter p_54761_, BlockPos p_54762_, CollisionContext p_54763_) { return p_54763_.isAbove(STABLE_SHAPE, p_54762_, true) && p_54760_.getValue(LEVEL) == 0 && p_54763_.canStandOnFluid(p_54761_.getFluidState(p_54762_.above()), getFluid()) ? STABLE_SHAPE : Shapes.empty(); } @Override public boolean isPathfindable(BlockState p_54704_, BlockGetter p_54705_, BlockPos p_54706_, PathComputationType p_54707_) { return !getFluid().is(FluidTags.LAVA); } @Override public boolean skipRendering(BlockState p_54716_, BlockState p_54717_, Direction p_54718_) { return p_54717_.getFluidState().getType().isSame(getFluid()); } @Override public void onPlace(BlockState p_54754_, Level p_54755_, BlockPos p_54756_, BlockState p_54757_, boolean p_54758_) { if (this.shouldSpreadLiquid(p_54755_, p_54756_, p_54754_)) { p_54755_.getLiquidTicks().scheduleTick(p_54756_, p_54754_.getFluidState().getType(), getFluid().getTickDelay(p_54755_)); } } @Override public BlockState updateShape(BlockState p_54723_, Direction p_54724_, BlockState p_54725_, LevelAccessor p_54726_, BlockPos p_54727_, BlockPos p_54728_) { if (p_54723_.getFluidState().isSource() || p_54725_.getFluidState().isSource()) { p_54726_.getLiquidTicks().scheduleTick(p_54727_, p_54723_.getFluidState().getType(), getFluid().getTickDelay(p_54726_)); } //return super.updateShape(p_54723_, p_54724_, p_54725_, p_54726_, p_54727_, p_54728_); return p_54723_; // Calling super.updateShape will just call LiquidBlock's updateShape, not what we are looking for! Thankfully, Block.updateShape, simply enough, just returns the first parameter. } @Override public void neighborChanged(BlockState p_54709_, Level p_54710_, BlockPos p_54711_, Block p_54712_, BlockPos p_54713_, boolean p_54714_) { if (this.shouldSpreadLiquid(p_54710_, p_54711_, p_54709_)) { p_54710_.getLiquidTicks().scheduleTick(p_54711_, p_54709_.getFluidState().getType(), getFluid().getTickDelay(p_54710_)); } } private boolean shouldSpreadLiquid(Level p_54697_, BlockPos p_54698_, BlockState p_54699_) { if (getFluid().is(FluidTags.LAVA)) { boolean flag = p_54697_.getBlockState(p_54698_.below()).is(Blocks.SOUL_SOIL); for(Direction direction : POSSIBLE_FLOW_DIRECTIONS) { BlockPos blockpos = p_54698_.relative(direction.getOpposite()); if (p_54697_.getFluidState(blockpos).is(FluidTags.WATER)) { Block block = p_54697_.getFluidState(p_54698_).isSource() ? Blocks.OBSIDIAN : Blocks.COBBLESTONE; p_54697_.setBlockAndUpdate(p_54698_, net.minecraftforge.event.ForgeEventFactory.fireFluidPlaceBlockEvent(p_54697_, p_54698_, p_54698_, block.defaultBlockState())); this.fizz(p_54697_, p_54698_); return false; } if (flag && p_54697_.getBlockState(blockpos).is(Blocks.BLUE_ICE)) { p_54697_.setBlockAndUpdate(p_54698_, net.minecraftforge.event.ForgeEventFactory.fireFluidPlaceBlockEvent(p_54697_, p_54698_, p_54698_, Blocks.BASALT.defaultBlockState())); this.fizz(p_54697_, p_54698_); return false; } } } return true; } private void fizz(LevelAccessor p_54701_, BlockPos p_54702_) { p_54701_.levelEvent(1501, p_54702_, 0); } @Override public ItemStack pickupBlock(LevelAccessor p_153772_, BlockPos p_153773_, BlockState p_153774_) { if (p_153774_.getValue(LEVEL) == 0) { p_153772_.setBlock(p_153773_, Blocks.AIR.defaultBlockState(), 11); return new ItemStack(getFluid().getBucket()); } else { return ItemStack.EMPTY; } } @Override public Optional<SoundEvent> getPickupSound() { return getFluid().getPickupSound(); } } The code for my OilFluid can be found above.
    • Whoops! Sorry, my fault. But yes, a block entity (tile entity in 1.16-) *is* needed for storing data (i.e. furnace). But I think what OP is doing is making an item combiner, which doesn't need one
    • Looking to work with reliable Fabric & Forge developers to produce original mods for use on my YouTube channel! We'll be regularly working together and you'll be given a detailed brief for each commission that outlines exactly what I want. From there, you can give me a price that fits the scope of the project and we can get to work :thumbsup: Because these commissions are only for YouTube videos, you're essentially making vertical-slices / proof-of-concepts. They can have bugs, you can take shortcuts & we can use video editing to hide bugs & achieve some effects. With that in mind, turnaround speed depends on project complexity but for an average commission I'd expect delivery two days - five days Send me a email at either lmaololtbhhonest@gmail.com OR Business@tbhhonest.com if you're interested! preview of channel: https://imgur.com/a/bOVkiUo LINK:  https://www.youtube.com/channel/UCnwo3X3eCrUp7N1DZPc-MGQ  
    • If you want a tiny fraction of offset, use float.epsilon. 0.01 will actually be noticeable. It's about 1/6th of a texture pixel.
  • Topics

  • Who's Online (See full list)

×
×
  • Create New...

Important Information

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