Recipe based question

First of all, to the folks who worked on the recipe system, kudos. Much more broadthinking than bukkit, with the ability to create different types of recipes, not just crafting ones… SOOO easy to add simple recipes to furnaces to act as reclaimers like melting railways into iron, etc…

SpongeVanilla 1.12-beta-300 ; Sp API 7

That said, are the recipe systems that take itemstacks as definitions supposed to ignore things like display name and lore data?

Example: If I want to introduce a non-mod vanilla based new block to peoples inventories that is a block of granite called “Copper Ore” and has some lore data on it to distinguish it from a regular granite block, and add that as a recipe to smelt down into some cocoabeans called “Copper nuggets” … that works, and allows me to smelt that granite block to get the new items.

But it also allows smelting regular granite blocks into copper nuggets.

Is there a way to produce an item stack (does it require data, and will the recipe distinguish data-holding items from non for the recipe?) such that the recipe won’t work with the lowest-common denominator item?

I’m trying to think of a way even to limit this via inventory-clicking events, etc - but i cant say 'the cooking slot must contain x-tag/x-data/x-lore info" in general, since that kills all the normal smelting operations

Just make your own SmeltingRecipe implementation. Then you have carte blanche.

Fast reply, thanks. Unfortunately “just” is a very subjective word without knowing what the system should be like in the end.

Would you have an example of how to go about doing that to see how it would be done? I think that would also be then expandable to the other recipes systems once I have something in mind to adapt and play with…

1 Like

Untested, but looks sane:

Excellent - looks reasonable code , understandable there, extrapolatable to other things…

That creates a distinct class for one recipe…
How would the recipe be registered?

Would this approach be limited by requiring precoding a class for every special (lore /name or data etc) recipe, because part of my plan was to have external configuration allow for my plugin to read in the representations of the itemstacks in pairs, allowing me to dynamically add another special recipe … a class with code to define the ingredients and validity checks feels like it could be generalized to take inputs and definie instances of that class based on the defining parameters…

So that would be doable depending how this system registers the recipe - does it require a special class for each, or would it require this special class, making an instance object of it, and registering that object - in which case, generlizing the class to use parameterized itemstacks should work?

I’ll try this code out out in a few minutes, not knowing how to register but banging some stones together that should throw sparks…

Im doing

        CopperRecipe sr = new CopperRecipe();
        System.out.println("registering smelt recipe");
        Sponge.getRegistry().getSmeltingRecipeRegistry().register(sr);

as the registration attempt, but getting the following error (I know you back-of-enveloped the sample code, untested, so an error maybe isn’t unsurprising but is it obvious what it may be?

    [22:15:28 ERROR] [Sponge]: Could not pass GamePostInitializationEvent$Impl to Plugin{id=boo_sftest_help, name=help test plugin code, version=0,
ecipetest.jar}
java.lang.IllegalStateException: Could not get the result for the exemplary ingredient.
        at net.minecraft.item.crafting.FurnaceRecipes.lambda$register$0(SourceFile:650) ~[akn.class:?]
        at java.util.Optional.orElseThrow(Unknown Source) ~[?:1.8.0_111]
        at net.minecraft.item.crafting.FurnaceRecipes.register(SourceFile:650) ~[akn.class:?]
        at net.minecraft.item.crafting.FurnaceRecipes.register(SourceFile:555) ~[akn.class:?]
        at com.prennet.boomod.sftest.sftest.onStart(sftest.java:48) ~[sftest.class:?]
        at org.spongepowered.common.event.listener.GamePostInitializationEventListener_sftest_onStart11.handle(Unknown Source) ~[?:?]
        at org.spongepowered.common.event.RegisteredListener.handle(RegisteredListener.java:95) ~[RegisteredListener.class:1.12-7.0.0-BETA-300]
        at org.spongepowered.common.event.SpongeEventManager.post(SpongeEventManager.java:349) [SpongeEventManager.class:1.12-7.0.0-BETA-300]
        at org.spongepowered.common.event.SpongeEventManager.post(SpongeEventManager.java:366) [SpongeEventManager.class:1.12-7.0.0-BETA-300]
        at org.spongepowered.common.event.SpongeEventManager.post(SpongeEventManager.java:370) [SpongeEventManager.class:1.12-7.0.0-BETA-300]
        at org.spongepowered.common.SpongeImpl.postState(SpongeImpl.java:205) [SpongeImpl.class:1.12-7.0.0-BETA-300]
        at org.spongepowered.server.SpongeVanilla.initialize(SpongeVanilla.java:154) [SpongeVanilla.class:1.12-7.0.0-BETA-300]
        at net.minecraft.server.dedicated.DedicatedServer.handler$onServerInitialize$zog000(SourceFile:1237) [nx.class:?]
        at net.minecraft.server.dedicated.DedicatedServer.func_71197_b(SourceFile:188) [nx.class:?]
        at net.minecraft.server.MinecraftServer.run(SourceFile:434) [MinecraftServer.class:?]
        at java.lang.Thread.run(Unknown Source) [?:1.8.0_111]

Oh, my b - I was calling equals() on an Optional rather than calling get() first. Updated the gist.

Meanwhile, it’s not like there’s any magic hackery going on here. It needs an instance of SmeltingRecipe; you give it an instance of SmeltingRecipe. How you obtain that instance is up to you - there’s no reason why you would need a discrete class for every recipe.

1 Like

Excellent confirmation about just an instance, I think it would be definitely doable to generalize an approach if I can get this sample working.

I was trying to find somewhere dealing with optionals that made no sense, didn’t see that part you fixed somehow. But even so, that doesn’t change the result I get now. With the replacement code, I still get the same error. Even deleted the old jar first to be 100.00% sure of no carry-over before recompiling it.

Line 48 from the plugin is the register command line…

I’m executing the registration in the GamePostInitializationEvent – this worked fine for registering the test recipes earlier, so I left it there…

Is the error due to registering at a wrong time - using this approach, is it too late or too early to access the things that this approach goes through? Or does this look more like a core sponge bug?

Everything looks clear to me in that code…

===========
EDIT: AH HAH - some debug output tracking…
I broke down the if-conditions as they went along…

  @Override
    public boolean isValid(ItemStackSnapshot ingredient) {
        if (!ingredient.getType().equals(ItemTypes.STONE)) {
            System.out.println("Heynah stoney");
            return false;
        }
        if (!ingredient.get(Keys.STONE_TYPE).get().equals(StoneTypes.GRANITE)) {
            System.out.println("Heynnaay granite");
            return false;
        }
        Optional<Text> name = ingredient.get(Keys.DISPLAY_NAME);
        System.out.println("Heynaah, display name?");
        if (!name.isPresent()) {
            System.out.println("Hoonah - name not present");
            return false;
        }
        if (!name.get().equals(ingredientName)) {
            System.out.println("hooney - not equal name  ");
            System.out.println(name.get().toString());
            System.out.println(ingredientName.toString());
            return false;
        }
        return true;

And this is the output on startup:

[02:17:03 INFO]: Using default channel type
[02:17:03 INFO] [STDOUT]: registering smelt recipe
[02:17:03 INFO] [STDOUT]: Heynaah, display name?
[02:17:03 INFO] [STDOUT]: hooney - not equal name
[02:17:03 INFO] [STDOUT]: Text{children=[Text{format=TextFormat{color=white, style=TextStyle{}}, Copper Ore}], }
[02:17:03 INFO] [STDOUT]: Text{format=TextFormat{color=white, style=TextStyle{}}, Copper Ore}
[02:17:03 ERROR] [Sponge]: Could not pass GamePostInitializationEvent$Impl to Plugin{id=boo_sftest_help, name=help
ecipetest.jar}
java.lang.IllegalStateException: Could not get the result for the exemplary ingredient.
        at net.minecraft.item.crafting.FurnaceRecipes.lambda$register$0(SourceFile:650) ~[akn.class:?]
        at java.util.Optional.orElseThrow(Unknown Source) ~[?:1.8.0_111]
        at net.minecraft.item.crafting.FurnaceRecipes.register(SourceFile:650) ~[akn.class:?]

The TEXT objects do not match for the recipe (given that we are pumping itself onto a system that compares to itself, thats odd) and the string-output version of it shows a different heirarchy?

@ryantheleach I know you’re watching this thread, I can feel you over my shoulder, as this all comes around to our discussions earlier this year :smiley: Perhaps now that we can see comparing the texts doesn’t work, someone else can see that either 1) its a sponge bug in how something is being rearranged or 2) we’re getting the wrong text thing and need to get it from a lower or higher node , or 3) things just dont line up here, but nothing is wrong with sponge, and this just needs a workaround to go…

So, text reasoning, anyone?

Text{children=[Text{format=TextFormat{color=white, style=TextStyle{}}, Copper Ore}], }
Text{format=TextFormat{color=white, style=TextStyle{}}, Copper Ore}

Easy solution would be not to compare texts, and instead attach data.

Also I had a half drafted reply here from earlier, then you freaking call me out! :slight_smile: My housemates can attest to my amusement as they heard it across the house.

I’m also weary of just saying implement the interface, as it might just be an interface for exposing minecraft internals as opposed to something they expect you to implement? My knowledge of Java visibility for inheritance is a little off though.

I suspect it should work though, as long as the DataAPI is ready early enough, which is what @Faithcaio 's theory was of why it might not work.

Agreed on the attached data part being a ‘better’ solution, but being able to define recipes at the same time I’m constructing these special things from config systems prevents having to code a lot of event systems elsewhere.
Though even with data, it looks like this process would be doable for comparing a data element within the recipe maybe.

My concern here on this debug output is that a text object is assigned to a thing in one place, and then when pulled off, automagically has been morphed into something else - like sticking a dollar in your pocket, then later pulling out 4 quarters instead – its the ‘same thing’, but different now: Is this a bug in sponge in that the getter is returning a text of this construction (It looks like an array of texts) after being set? Or is this the true and proper expectation from the get that is coded to sponge, with a reason, and in this case pushing and pulling is expected at the dev level to modify it this way? Is it a unique broken thing that manefests in this spot only, or is it a globally behaving property that results in this re-structuring of the text popped on and off the item? A unique broken thing of unexpected surprise contrary to what the system is supposed to give means filing a bug report, a global expectation means knowing that pulling the text out means getting the text with a gold star on it, and peeling off the gold star to see what it is.

Text.of() is a helper method in order to construct the underlying representation. I’m not terribly surprised that the output differs once processed or serialized to NBT. It looks like it’s being wrapped in another Text somewhere.

What happens if you set the name data with the text that’s returned?
e.g. println(item.offer(Key.name, item.get(Key.name)).get(Key.name))

If you can create a small recreatable test plugin, that all it does is serialize the text to the item and back for the name, and prove it’s not .equal, PR it to the SpongeCommon dev test suite, and maybe someone can take a look.

Followup on method - it doesn’t work.

I worked-around for now by using the getChildren().get(0) for text compare, and that allowed it to load up and register just fine. I have my test plugin set my in-hand inventory item to the ‘copper ore’ via copy/paste of that itemstack builder when the player logs in.

The furnace smelts the copper ore to nuggets…

But it also still smelts regular granite to nuggets, and with the debugging comments in place still , it spams while it ticks the smelting:

[08:22:57 INFO] [STDOUT]: Heynaah, display name?
[08:22:57 INFO] [STDOUT]: Hoonah - name not present
[08:22:57 INFO] [STDOUT]: Heynaah, display name?
[08:22:57 INFO] [STDOUT]: Hoonah - name not present
[08:22:57 INFO] [STDOUT]: Heynaah, display name?
[08:22:57 INFO] [STDOUT]: Hoonah - name not present
[08:22:57 INFO] [STDOUT]: Heynaah, display name?
over and over

This shows that the isValid test is returning false for the granite only, yet the system keeps ticking away cooking and testing it despite this…

I think I know what you’re trying to get at, but that format seems to be fail on a few levels. I can try something else that might be what you mean. Show result of get display name from stack, offer displayname back, show result display name, offer that back again… or do you mean to serialize it with stuff like configurate things and data-holder outputs?

    ItemStack ingredient = ItemStack.builder()
            .itemType(ItemTypes.STONE)
            .keyValue(Keys.STONE_TYPE, StoneTypes.GRANITE)
            .keyValue(Keys.DISPLAY_NAME, ingredientName)
            .build();        
    System.out.println("Getting from build0: " + ingredient.get(Keys.DISPLAY_NAME).get().toString());
    System.out.println(ingredient.offer(Keys.DISPLAY_NAME, ingredient.get(Keys.DISPLAY_NAME).get()));
    System.out.println("Getting from offer1: " + ingredient.get(Keys.DISPLAY_NAME).get().toString());
    System.out.println(ingredient.offer(Keys.DISPLAY_NAME, ingredient.get(Keys.DISPLAY_NAME).get()));
    System.out.println("Getting from offer2: " + ingredient.get(Keys.DISPLAY_NAME).get().toString());
    System.out.println(ingredient.offer(Keys.DISPLAY_NAME, ingredient.get(Keys.DISPLAY_NAME).get()));
    System.out.println(ingredient.get(Keys.DISPLAY_NAME));

Its not burying the text as a child inception-style. For the purposes of this test, getting the ingredientname back from the stack after setting it on the stack would seem the solution for this class. Initialize with the desired text object, assign to stack, then reset it to the get displayname value…

[08:48:33 INFO] [STDOUT]: Getting from build0: Text{children=[Text{format=TextFormat{color=white, style=TextStyle{}}, Copper Ore}], }
[08:48:33 INFO] [STDOUT]: DataTransactionResult{resultType=SUCCESS, rejectedData=[], replacedData=[ImmutableSpongeValue{key=Key{Value:Value<Text>, Query: DisplayName}, default
Value=Text{}, actualValue=Text{children=[Text{format=TextFormat{color=white, style=TextStyle{}}, Copper Ore}], }}], successfulData=[ImmutableSpongeValue{key=Key{Value:Value<Text>, Query: DisplayName}, defaultValue=Text{}, actualValue=Text{children=[Text{format=TextFormat{color=white, style=TextStyle{}}, Copper Ore}], }}]}

[08:48:33 INFO] [STDOUT]: Getting from offer1: Text{children=[Text{format=TextFormat{color=white, style=TextStyle{}}, Copper Ore}], }
[08:48:33 INFO] [STDOUT]: DataTransactionResult{resultType=SUCCESS, rejectedData=[], replacedData=[ImmutableSpongeValue{key=Key{Value:Value<Text>, Query: DisplayName}, default
Value=Text{}, actualValue=Text{children=[Text{format=TextFormat{color=white, style=TextStyle{}}, Copper Ore}], }}], successfulData=[ImmutableSpongeValue{key=Key{Value:Value<Text>, Query: DisplayName}, defaultValue=Text{}, actualValue=Text{children=[Text{format=TextFormat{color=white, style=TextStyle{}}, Copper Ore}], }}]}

[08:48:33 INFO] [STDOUT]: Getting from offer2: Text{children=[Text{format=TextFormat{color=white, style=TextStyle{}}, Copper Ore}], }
[08:48:33 INFO] [STDOUT]: DataTransactionResult{resultType=SUCCESS, rejectedData=[], replacedData=[ImmutableSpongeValue{key=Key{Value:Value<Text>, Query: DisplayName}, default
Value=Text{}, actualValue=Text{children=[Text{format=TextFormat{color=white, style=TextStyle{}}, Copper Ore}], }}], successfulData=[ImmutableSpongeValue{key=Key{Value:Value<Text>, Query: DisplayName}, defaultValue=Text{}, actualValue=Text{children=[Text{format=TextFormat{color=white, style=TextStyle{}}, Copper Ore}], }}]}
[08:48:33 INFO] [STDOUT]: Optional[Text{children=[Text{format=TextFormat{color=white, style=TextStyle{}}, Copper Ore}], }]

Still, none of this helps with the sad fact that the system seems to be ignoring the isValid false return value :frowning:

That’s true in the majority of cases, but the recipe interfaces’ Javadocs explicitly say this is allowed.

1 Like

Thanks for checking.

More spamming, sorry…

When isValid is true, its good. When isValid is false, it still is good. Except when isValid is false under different circumstances… yeah, you read that right.

I’ve been playing with this more further, and have managed to reset the text for comparison after applying to stack…

But I think there is something much deeper at work for the non-specificity:

If I use the copper ore item in the smelter, the log spams that it is a valid match (debug added at the return true point) and works.

If I use granite, the log spams that the name is not equal, returning false, and yet still continues to cook the granite into copper nuggets.

If I use andesite, the log spams that it is not granite (as it is a stone with wrong type) and does NOT cook it at all.

    @Override
    public boolean isValid(ItemStackSnapshot ingredient) {

        if (!ingredient.getType().equals(ItemTypes.STONE)) {
            System.out.println("Invalid smleting, not stone");
            return false;
        }
        if (!ingredient.get(Keys.STONE_TYPE).get().equals(StoneTypes.GRANITE)) {
            System.out.println("Invalid smelting, not granite");
            return false;
        }
        Optional<Text> name = ingredient.get(Keys.DISPLAY_NAME);
        if (!name.isPresent()) {
            System.out.println("Invalid smelting, noname");
            return false;
        }
        if (!name.get().equals(ingredientName)) {
            System.out.println("Invalid smelting, name mismatch"); // Gets here on regular granite item over and over
            return false;
        }
        System.out.println("Valid smelting"); // Gets here when it should (over and over)
        return true;
    }

How can a function return false and fail at one point, yet return false and not fail at another?

Its like there is a different result that is occuring despite this logic check…

There HAS to be some other “I dont care what this boolean is, we push the result anyways!” mechanism in place since two systems are returning false at the exact same checkpoint even, based on further tweaking

getExemplaryIngredient()
An exemplary ItemStackSnapshot, which will always make isValid(ItemStackSnapshot) return true.

Is this maybe meant to indicate that regardless of what our override of the isValid function sets it, the system performs its own check against the exemplary ingredient and says “true or false, dont care, its gunna be true”? Who

Is it possible that the exemplary ingredient snapshot is just the item type and flavor, and not the rest, and shortcuts the result?

My further playing included commenting out the stone and granite checks, starting with just the optional text name line… Copper ore gives a valid smelting output and smelts; normal granite gives “Invalid smelting, noname” and yet it still smelts; andesite gives “Invalid smelting, noname” and does not smelt.

Can someone chase the exemplaryIngredient chain and see if there is a forced return result bypass in the implimentation??

===

UPDATE: It looks like the isValid test is only meaningful for testing the recipe has a valid ingredient when registered??
I modified the isValid method to be wrapped in a check boolean - when the recipe initializes it goes through all the IFs, and when a valid match is found, it then sets a boolean so that every other time, it just shortcut returns FALSE regardless , no testing.

Full class code:

@Faithcaio is the recipe person, if I interpret PRs correctly… Are things here behaving as intended? Are we overlooking something?

That, uh, goes against the entire point of implementing the recipe yourself. That definitely needs to be fixed.

Did not read thorugh all of this but it sounds like a bug.
Did you try just return false; Will it still smelt the exampleIngredient?
If yes its probably the vanilla check going through ignoring the custom result.
Would you mind posting an issue for it. (SpongeCommon)