Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for anvil recipes #732

Merged
merged 12 commits into from
Feb 15, 2017
Merged

Add support for anvil recipes #732

merged 12 commits into from
Feb 15, 2017

Conversation

gigaherz
Copy link
Contributor

[11:52] (gigaherz): morning ppl
[11:52] (gigaherz): does JEI come with a template for anvil recipes?
[11:53] (@mezz): nope, it should though. you can PR if you are feeling motivated

So here it is.

I decided not to attempt generating recipe wrapper instances for the vanilla book enchanting and repair, due to how those are hardcoded:

  • For books, I'd need to test each possible item against each possible enchantment, and see if they are allowed. This is somewhat doable, but it seems ugly.
  • For repair, Minecraft calls Item#getIsRepairable(ItemStack,ItemStack), with the contents of the anvil slots, and returns true if the material matches the tool. There is no way to figure out the right combinations besides a a nested loop and matching all items against all items.

I'm open to suggestions.

@CLAassistant
Copy link

CLAassistant commented Feb 12, 2017

CLA assistant check
All committers have signed the CLA.


public class AnvilRecipeCategory extends BlankRecipeCategory<AnvilRecipeWrapper>
{
@Nonnull
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just use @MethodsNonnullByDefault and @ParametersAreNonnullByDefault in package info file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I copied from the wrong example. ;P

import javax.annotation.Nonnull;

public class AnvilRecipeCategory extends BlankRecipeCategory<AnvilRecipeWrapper>
{
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code style...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I noticed that JEI uses the wrong brace location after I clicked to create the PR.

@mezz
Copy link
Owner

mezz commented Feb 13, 2017

Looks good!
This will need a few things to move on to the next step:

  • We need a simple API to add recipes, similar to mezz.jei.api.IModRegistry#addDescription()
  • Manually add vanilla repair recipes using the API, an itemstack and its repair materials manually. Dynamic generation is too slow here unfortunately. Vanilla only has like 5 item classes that override getIsRepairable so it shouldn't be too bad.
  • See how JER adds enchantment recipes, maybe they found a solution? If it's very inefficient, we'll have to think more about it or just leave enchantments out for now.
  • Review from @HenryLoenwind since EnderIO has anvil recipes implemented already.

@HenryLoenwind
Copy link
Contributor

Looks basically the same as our anvil recipes. I think I could adapt them to use this instead in about 5 minutes. 👍

@way2muchnoise
Copy link
Contributor

way2muchnoise commented Feb 13, 2017

JER just tests if an enchantment can be applied to an ItemStack (see entry point), I don't know if it is the most efficient way tho. (actual checking). I guess I could look into it again and see if I can find some thing.

@HenryLoenwind
Copy link
Contributor

"codacy"? Ok, I'll remember that as "junk to avoid"...

@gigaherz
Copy link
Contributor Author

I'm thinking of adding a 5th parameter to the anvil recipes, so that the recipe can show like, "required: 2+", for cases where the levels cost is variable. Opinions?

@liach
Copy link

liach commented Feb 13, 2017

@gigaherz I think that is not defined in a recipe; it is determined by inputs and are calculatable.

@HenryLoenwind
Copy link
Contributor

Yes, I think subclassing is the better way here. Maybe split drawInfo(), e.g. in line 40, to make subclassing easier.

@gigaherz
Copy link
Contributor Author

gigaherz commented Feb 13, 2017

Does this look right for the repair recipes?

Example Recipes

The damage numbers are semi-hardcoded (25%, 50%, 75% of max) right now. I don't know if there's a way to show different damage levels and their respective output dynamically.

…aked the formatting settings so that my code is even closer to the existing.
@gigaherz
Copy link
Contributor Author

The repair recipes are now implemented. So far as I can tell, I added all the repairable items and their materials, but if there's something I missed, I'd like to know. ;P

As for enchanting, iterating through all items and testing each one against all enchants seems a bit overkill. It's not hard to implement, but I imagine it would take a rather long time on a big modpack? Are there any numbers from JER regarding that, @way2muchnoise?

@gigaherz
Copy link
Contributor Author

gigaherz commented Feb 13, 2017

I did a quick test, for vanilla recipes (786 ingredients, 30 enchantments with varying number of levels):

[21:08:42] [Client thread/INFO]: [STDOUT]: Registering repair recipes took 6581 microseconds
[21:08:42] [Client thread/INFO]: [STDOUT]: Registering repair recipes took 24656 microseconds

So it's not too bad. Event with 10x the items, and 2x the enchantments, it would still be less than half a second.

@gigaherz
Copy link
Contributor Author

Right now, the enchantment recipes show one by one, including separate entries for each book level. Is there some way to show all book levels as different (rotating) alternatives of the same recipe?

@mezz
Copy link
Owner

mezz commented Feb 13, 2017

@HenryLoenwind I tested out codacy to see if it would be usable for style-checking Forge, but I haven't had much time to configure it properly so it's really strict. It may still be useful.

@gigaherz looks good, that doesn't take as long as I thought.
For rotating recipes, just make an input list of (level 1, 2, 3) and output of applied (level 1, 2, 3) and they will rotate together. It's good enough for this purpose.
For the repair recipes, I think you should only show the recipe for (max damaged item -> somewhat repaired item). That way it's easy to see how much is repaired without having recipe spam.

…s rotation. Make repair show an almost-broken item on the left, so that the output represents the repair amount.
@HenryLoenwind
Copy link
Contributor

Be careful with the enchantments, Ender IO has one that can be applied to any item (soulbound).

(semi OT) @mezz, I wasn't commenting on it's strictness, but on its dumbness. Rules that check the signature of a @Overrideing method do not make any sense in the first place. I take having such rules as a sign for "we don't care but want the biggest number of rules and findings to impress those clueless company buyers and execs so they buy our software which is not even half as good as the free/open source alternatives". I get to work with that kind of !%&$ software at work much too often.

@gigaherz
Copy link
Contributor Author

gigaherz commented Feb 14, 2017

@HenryLoenwind This has changed in 1.11, it seems. ALL means "all of the above" now, instead of literally "all the items". There doesn't seem to be any actual way to have enchantments that can be applied to all items.
EDIT: I was wrong, there's still EnumHelper.addEnchantmentType which can help. XD

@HenryLoenwind
Copy link
Contributor

HenryLoenwind commented Feb 14, 2017

So canApplyAtEnchantingTable() was moved from the enchantment to the item so we cannot override it anymore? What's the point in that other than annoying modders?

Edit: I looked at the call stack, it is still routed through the enchantment. So still full control for the enchantment by returning true from canApply().

Thx, for the heads-up. Expect people to ASM their hooks in...

@gigaherz
Copy link
Contributor Author

I added a custom enchantment, set to apply to all items.

[20:54:34] [Client thread/INFO] [jei]: Registered vanilla repair recipes in 1 ms
[20:54:34] [Client thread/INFO] [jei]: Registered enchantment recipes in 33 ms

in contrast, without that one enchant, I get

[20:57:16] [Client thread/INFO] [jei]: Registered vanilla repair recipes in 1 ms
[20:57:16] [Client thread/INFO] [jei]: Registered enchantment recipes in 17 ms

So it it takes about as long to register one "all items" enchantment, as it takes to register all the other ones. On the upside, it's in the milliseconds, so even with 10x the items, and 2x the enchants, that's still within a second. :)

@HenryLoenwind
Copy link
Contributor

Try one that returns true from canApply() instead.

@gigaherz
Copy link
Contributor Author

gigaherz commented Feb 14, 2017

@HenryLoenwind Oh I was talking about EnumEnchantmentType.ALL and how it doesn't mean "ALL" anymore.
I mean, yes, if you override canApply, it works. ;P

@HenryLoenwind
Copy link
Contributor

Yeah, I know. I skipped a step and looked up what to do to make an enchantment that is applicable to all items again. There are 2 ways: (a) canApply(ItemStack stack) { return true; } and (b)

EnumHelper.addEnchantmentType("REALLY_ALL", new Predicate<Item>() {
    @Override
    public boolean apply(@Nullable Item input) {
      return true;
    }```

Copy link
Owner

@mezz mezz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. There are a few things I want changed before I test and pull it, but it's pretty much ready.

void addAnvilRecipe(ItemStack leftInput, ItemStack rightInput, ItemStack output, int levelsCost);
void addAnvilRecipe(ItemStack leftInput, ItemStack rightInput, ItemStack output);
void addAnvilRecipe(ItemStack leftInput, List<ItemStack> rightInput, List<ItemStack> output, int levelsCost);
void addAnvilRecipe(ItemStack leftInput, List<ItemStack> rightInput, List<ItemStack> output);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be one method only, not four. Just keep

void addAnvilRecipe(ItemStack leftInput, List<ItemStack> rightInput, List<ItemStack> output, int levelsCost);

I don't think hiding the level cost is valid, if there is no cost they can put 0.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that makes the use of the API unnecessarily ugly, but your choice.
However, for context, the option to not have a cost shown wasn't about the recipe not having a cost, it was about the recipe's cost not being known beforehand, such as for enchanted item repair, where the cost depends on how many times the item has been repaired, and how many enchants it has.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to automatically determine the cost, maybe using a fake ContainerRepair?

Pair.of(new ItemStack(Items.WOODEN_SWORD), repairWood),
Pair.of(new ItemStack(Items.WOODEN_PICKAXE), repairWood),
Pair.of(new ItemStack(Items.WOODEN_AXE), repairWood),
Pair.of(new ItemStack(Items.WOODEN_SHOVEL), repairWood),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be cleaner as a Map:

Map<ItemStack, List<ItemStack>> items = new HashMap<>();
items.put(repairWood, Arrays.asList(
    new ItemStack(Items.WOODEN_SWORD),
    new ItemStack(Items.WOODEN_PICKAXE),
    ...
));

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree, but it's your code so I'll fix it to your preference.

@@ -133,17 +141,21 @@ public void register(IModRegistry registry) {
recipeTransferRegistry.addRecipeTransferHandler(ContainerFurnace.class, VanillaRecipeCategoryUid.SMELTING, 0, 1, 3, 36);
recipeTransferRegistry.addRecipeTransferHandler(ContainerFurnace.class, VanillaRecipeCategoryUid.FUEL, 1, 1, 3, 36);
recipeTransferRegistry.addRecipeTransferHandler(ContainerBrewingStand.class, VanillaRecipeCategoryUid.BREWING, 0, 4, 5, 36);
recipeTransferRegistry.addRecipeTransferHandler(ContainerRepair.class, VanillaRecipeCategoryUid.ANVIL, 0, 2, 3, 4 * 9);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 * 9 should be 36 like the other lines above it

this(leftInput, Collections.singletonList(rightInput), Collections.singletonList(output), levelsCost);
}

public AnvilRecipeWrapper(ItemStack leftInput, List<ItemStack> rightInputs, List<ItemStack> outputs) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the API, this class should only need one constructor.

this(leftInput, rightInputs, outputs, -1);
}

@SuppressWarnings("unchecked")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SuppressWarnings("unchecked") should not be needed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it isn't, I don't know how. I tried both Lists.newArrayList and Arrays.asList. So far as I can tell, Java is stupid and doesn't know how to handle generics in arrays, so it panics when it sees the varargs parameter type as List<ItemStack>.

Copy link
Owner

@mezz mezz Feb 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's possible to use Arrays.asList with genetics this way, you're right.
Try creating a new arraylist and then adding the inputs to it after creation.

* Reduced number of overloads
* Changed the list of pairs to a map of lists
* Computed the result of a multiplication.
* Unsilenced an unchecked operation.
@gigaherz
Copy link
Contributor Author

I think this makes all the comments addressed.

@mezz
Copy link
Owner

mezz commented Feb 15, 2017

Is it possible to automatically determine the level cost, maybe using a fake ContainerRepair?

@HenryLoenwind
Copy link
Contributor

HenryLoenwind commented Feb 15, 2017 via email

@mezz
Copy link
Owner

mezz commented Feb 15, 2017

It's better than no information, and means that nobody has to specify the level with the API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants