Jump to content

[1.12.2] What's the best way to make a crafting recipe that just changes NBT?


Recommended Posts

Posted

What I'm trying to do is have a recipe with an item and materials based on the main item's NBT, that results in the original item with additional/changed NBT.

 

For example, if Weapon(NO NBT) is in a recipe with MaterialOne, I want to add element tag Fire resulting in Weapon(Fire), or if Weapon(Fire) is in a recipe with MaterialTwo I want to remove the tag Fire resulting in Weapon(NO NBT).

Or, if Weapon(Fire, Level1) is in a recipe with MaterialThree, I want to increase its Level tag so it becomes Weapon(Fire, Level2), etc.

 

I've only figured out a way to do this WITHOUT NBT, but unfortunately that involves creating a new item and recipe for every possible combination of element and levels, which results in around 2000 model/recipe json files to be generated for each weapon, which is obviously a bad idea.

 

What's the best way to actually DO this?

 

This is all I've got so far, but I'm not sure if it's even right, much less where to go from here.

Spoiler

public class CraftingUpgrades
{
	@SubscribeEvent
	public void onCraft(ItemCraftedEvent event)
	{
		if (event.crafting != null)
		{
			ItemStack stack = event.crafting;
			if (stack.getItem() instanceof Weapon || stack.getItem() instanceof Shield)
			{
				ItemStack material = getUpgradeMaterial(event.craftMatrix);
				if (material != null)
				{
					// TODO Increase level based on material
				}
				else
				{
					material = getInfusionMaterial(event.craftMatrix);
					if (material != null)
					{
						// TODO Set infusion based on material
					}
				}
			}
		}
	}

	private ItemStack getUpgradeMaterial(IInventory matrix)
	{
		ItemStack stack;
		for (int i = 0; i < matrix.getSizeInventory(); i++)
		{
			if (matrix.getStackInSlot(i) != null)
			{
				stack = matrix.getStackInSlot(i);
				if (stack.getItem() instanceof UpgradeMaterial)
				{
					return stack;
				}
			}
		}
		return null;
	}

	private ItemStack getInfusionMaterial(IInventory matrix)
	{
		ItemStack stack;
		for (int i = 0; i < matrix.getSizeInventory(); i++)
		{
			if (matrix.getStackInSlot(i) != null)
			{
				stack = matrix.getStackInSlot(i);
				if (stack.getItem() instanceof InfusionGem)
				{
					return stack;
				}
			}
		}
		return null;
	}
}

 

 

Posted
2 minutes ago, SaltStation said:

You could add additional parameters to the constructor of your sword class.

 

 

And that would help me how?

Like I already said, I do not want to have thousands of instances of every variation of an item.

Posted

No you only have one item but depending on the parameters it can have different damage/effects/whateveryouwant

Sword(String name, int material, String infusion)

in your ModItem class:

 

FIRESWORD_LEVEL_TWO = new Sword(firesword_level_two,2, fire)

 

Posted
1 minute ago, SaltStation said:

No you only have one item but depending on the parameters it can have different damage/effects/whateveryouwant

Sword(String name, int material, String infusion)

in your ModItem class:

 

FIRESWORD_LEVEL_TWO = new Sword(firesword_level_two,2, fire)

 

That's exactly my point....

 

Your way would require:

 

sword = new Sword(name, 0, none)

sword1 = new Sword(name, 1, none)

sword2 = new Sword(name, 2, none)

sword3 = new Sword(name, 3, none)

sword4 = new Sword(name, 4, none)

firesword = new Sword(name, 0, fire)

firesword1 = new Sword(name, 1, fire)

firesword2 = new Sword(name, 2, fire)

firesword3 = new Sword(name, 3, fire)

firesword4 = new Sword(name, 4, fire)

 

...and so on, for 10 levels each with 8 different elements for each level on over 200 weapons I'm adding.

That's 16,000 new items, which would then need about 100 recipes each to allow changing them to and from other variants.

 

I want to create ONE of each weapon, and just add the level and element using NBT to save literally thousands of file creations and dozens of megabytes in total file size...

Posted
10 minutes ago, V0idWa1k3r said:

Loops exist.

But in your usecase sure, NBT may be preferrable.

You can make a custom IRecipe implementation that deals with the ingredients passed and determines the results.

Loops are exactly how I already did it, resulting in thousands of items that I wanted to avoid, therefore my wanting to use NBT instead.

But I forgot IRecipe was a thing.

 

I assume I could just use something similar to how RecipeFireworks does it?

And the proper way to register it is this something like this?

@SubscribeEvent
public static void registerRecipes(Register<IRecipe> event)
{
	event.getRegistry().register(new RecipeWeapon());
}

 

Posted

The proper way is to make a _factories.json file where you would register a factory for your custom IRecipe implementation and register it though json. It may still be just one file, just registered via a json and not via a registry event.

The reason for that is once you are migrating to 1.13 and up all recipes must have a json file defining them(so they can be disabled by a datapack).

Posted (edited)
34 minutes ago, V0idWa1k3r said:

The proper way is to make a _factories.json file where you would register a factory for your custom IRecipe implementation and register it though json. It may still be just one file, just registered via a json and not via a registry event.

The reason for that is once you are migrating to 1.13 and up all recipes must have a json file defining them(so they can be disabled by a datapack).

So... something like this?

{
    "recipes": {
        "weaponvariant": "avrye.soulsstuff.util.RecipeWeapon.Factory"
    }
}

 

How am I supposed to return an IRecipe in parse()? I can't find any implementations of IRecipeFactory in the code.

All examples I can find use either outdated code or helper classes that just make things way more complicated than what I need...

package avrye.soulsstuff.util;

public class RecipeWeapon extends Impl<IRecipe> implements IRecipe
{
	// ...

	public static class Factory implements IRecipeFactory
	{
		@Override
		public IRecipe parse(JsonContext context, JsonObject json)
		{
			//TODO ?
			return null;
		}
	}
}

 

Edited by SapphireSky
Posted

If you are just using it to register your ONE particular recipe that handles all kind of cases then just return a new instance of your IRecipe implementation since you don't need to parse anything

Posted
19 minutes ago, V0idWa1k3r said:

If you are just using it to register your ONE particular recipe that handles all kind of cases then just return a new instance of your IRecipe implementation since you don't need to parse anything

That's easier than I expected then.

 

But now how exactly do I tell the IRecipe which items to be removed during crafting?

Looking at RecipeFireworks, matches() just checks to see if the needed items are available and creates the result item.

And getRemainingItems() seems relevant but doesn't check for any of the needed items, so I'm confused how that's done.

Posted
1 hour ago, SapphireSky said:

But now how exactly do I tell the IRecipe which items to be removed during crafting?

 

 

1 hour ago, SapphireSky said:

getRemainingItems()

returns a list of itemstacks that are "left" after the craft is done.

Posted
19 hours ago, V0idWa1k3r said:

 

returns a list of itemstacks that are "left" after the craft is done.

I know but how do I tell it which items are actually used up during crafting?

I can't find anything that actually determines which items should be removed and removes them.

Posted

I repeat myself yet again

20 hours ago, V0idWa1k3r said:
21 hours ago, SapphireSky said:

getRemainingItems()

returns a list of itemstacks that are "left" after the craft is done.

 

If you have a recipe of AB = C and you want A to be consumed then the list would return just B. Assuming A's max stack is 1.

It is a NonNullList though, so all empty slots are ItemStack.EMPTY.

 

1 hour ago, SapphireSky said:

I can't find anything that actually determines which items should be removed and removes them.

By default all items are consumed if their container item is empty, otherwise their container item is returned.

Posted
4 minutes ago, V0idWa1k3r said:

I repeat myself yet again

 

If you have a recipe of AB = C and you want A to be consumed then the list would return just B. Assuming A's max stack is 1.

It is a NonNullList though, so all empty slots are ItemStack.EMPTY.

 

By default all items are consumed if their container item is empty, otherwise their container item is returned.

 

That still doesn't answer my question...

HOW does the list know what to return if you're not telling it what to return?

 

matches() is basically just roughly this:

public boolean matches(InventoryCrafting inv, World world)
{
    if(inv contains materialitem && inv contains mainitem)
    {
        ItemStack result = new ItemStack(mainitem);
        result.setNBTBasedOn(materialitem);
        return true;
    }
    return false;
}

 

while getRemainingItems() contains:

public NonNullList<ItemStack> getRemainingItems(InventoryCrafting inv)
{
	NonNullList<ItemStack> nonnulllist = NonNullList.<ItemStack>withSize(inv.getSizeInventory(), ItemStack.EMPTY);
	for (int i = 0; i < nonnulllist.size(); ++i)
	{
		ItemStack itemstack = inv.getStackInSlot(i);
		nonnulllist.set(i, ForgeHooks.getContainerItem(itemstack));
	}
	return nonnulllist;
}

 

I'm still failing to see how getRemainingItems() either gets the ingredients or consumes them.

Unless I'm supposed to add to and manually pick out the items from in getRemainingItems() ?

In which case, that's not exactly obvious since that method for Fireworks or Dyed Armour doesn't do that either.

Posted
34 minutes ago, SapphireSky said:

I'm still failing to see how getRemainingItems() either gets the ingredients or consumes them.

 

I am sorry, I do not know how to be more obvious.

getRemainingItems is the list of INGREDIENTS that REMAIN in the slots AFTER the crafting is done.

It doesn't consume anything. It is what's LEFT in the slots.

The ingredients currently in the crafting table are passed to you in the parameter of the method.

The default implementation goes through all ingredients

37 minutes ago, SapphireSky said:

ItemStack itemstack = inv.getStackInSlot(i);

and does the following:

If the item has a container item(like a bucket of milk has an empty bucket as it's container item) then the container item is added, otherwise an ampty stack is.

 

You can see how the vanilla's crafting system handles it in SlotCrafting#onTake.

It iterates through all items in the crafting matrix, decreasing their stack size by 1.

Then if the stack in the slot is empty but the getRemainingItems has a non-empty stack at that position then that stack in the list is inserted into the slot.

If the stack in the slot isn't empty but is identical(NBT + meta) to the one returned by the getRemainingItems then the stack in the slot's count is incremented by the size of the stack in the list.

Otherwise the item in the list is either added to the player's inventory or dropped.

Posted
48 minutes ago, V0idWa1k3r said:

You can see how the vanilla's crafting system handles it in SlotCrafting#onTake.

It iterates through all items in the crafting matrix, decreasing their stack size by 1.

.....

 

THIS is what I was asking.....

I did not know the creation and use of the recipe were handled separately.

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
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
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.

Announcements



×
×
  • Create New...

Important Information

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