Jump to content

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


SapphireSky

Recommended Posts

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;
	}
}

 

 

Link to comment
Share on other sites

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)

 

Link to comment
Share on other sites

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

Link to comment
Share on other sites

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());
}

 

Link to comment
Share on other sites

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

Link to comment
Share on other sites

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
Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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.

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



  • Recently Browsing

    • No registered users viewing this page.
  • Posts

    • i tried downloading the drivers but it says no AMD graphics hardware has been detected    
    • Update your AMD/ATI drivers - get the drivers from their website - do not update via system  
    • As the title says i keep on crashing on forge 1.20.1 even without any mods downloaded, i have the latest drivers (nvidia) and vanilla minecraft works perfectly fine for me logs: https://pastebin.com/5UR01yG9
    • Hello everyone, I'm making this post to seek help for my modded block, It's a special block called FrozenBlock supposed to take the place of an old block, then after a set amount of ticks, it's supposed to revert its Block State, Entity, data... to the old block like this :  The problem I have is that the system breaks when handling multi blocks (I tried some fix but none of them worked) :  The bug I have identified is that the function "setOldBlockFields" in the item's "setFrozenBlock" function gets called once for the 1st block of multiblock getting frozen (as it should), but gets called a second time BEFORE creating the first FrozenBlock with the data of the 1st block, hence giving the same data to the two FrozenBlock :   Old Block Fields set BlockState : Block{minecraft:black_bed}[facing=east,occupied=false,part=head] BlockEntity : net.minecraft.world.level.block.entity.BedBlockEntity@73681674 BlockEntityData : id:"minecraft:bed",x:3,y:-60,z:-6} Old Block Fields set BlockState : Block{minecraft:black_bed}[facing=east,occupied=false,part=foot] BlockEntity : net.minecraft.world.level.block.entity.BedBlockEntity@6d1aa3da BlockEntityData : {id:"minecraft:bed",x:2,y:-60,z:-6} Frozen Block Entity set BlockState : Block{minecraft:black_bed}[facing=east,occupied=false,part=foot] BlockPos{x=3, y=-60, z=-6} BlockEntity : net.minecraft.world.level.block.entity.BedBlockEntity@6d1aa3da BlockEntityData : {id:"minecraft:bed",x:2,y:-60,z:-6} Frozen Block Entity set BlockState : Block{minecraft:black_bed}[facing=east,occupied=false,part=foot] BlockPos{x=2, y=-60, z=-6} BlockEntity : net.minecraft.world.level.block.entity.BedBlockEntity@6d1aa3da BlockEntityData : {id:"minecraft:bed",x:2,y:-60,z:-6} here is the code inside my custom "freeze" item :    @Override     public @NotNull InteractionResult useOn(@NotNull UseOnContext pContext) {         if (!pContext.getLevel().isClientSide() && pContext.getHand() == InteractionHand.MAIN_HAND) {             BlockPos blockPos = pContext.getClickedPos();             BlockPos secondBlockPos = getMultiblockPos(blockPos, pContext.getLevel().getBlockState(blockPos));             if (secondBlockPos != null) {                 createFrozenBlock(pContext, secondBlockPos);             }             createFrozenBlock(pContext, blockPos);             return InteractionResult.SUCCESS;         }         return super.useOn(pContext);     }     public static void createFrozenBlock(UseOnContext pContext, BlockPos blockPos) {         BlockState oldState = pContext.getLevel().getBlockState(blockPos);         BlockEntity oldBlockEntity = oldState.hasBlockEntity() ? pContext.getLevel().getBlockEntity(blockPos) : null;         CompoundTag oldBlockEntityData = oldState.hasBlockEntity() ? oldBlockEntity.serializeNBT() : null;         if (oldBlockEntity != null) {             pContext.getLevel().removeBlockEntity(blockPos);         }         BlockState FrozenBlock = setFrozenBlock(oldState, oldBlockEntity, oldBlockEntityData);         pContext.getLevel().setBlockAndUpdate(blockPos, FrozenBlock);     }     public static BlockState setFrozenBlock(BlockState blockState, @Nullable BlockEntity blockEntity, @Nullable CompoundTag blockEntityData) {         BlockState FrozenBlock = BlockRegister.FROZEN_BLOCK.get().defaultBlockState();         ((FrozenBlock) FrozenBlock.getBlock()).setOldBlockFields(blockState, blockEntity, blockEntityData);         return FrozenBlock;     }  
    • It is an issue with quark - update it to this build: https://www.curseforge.com/minecraft/mc-mods/quark/files/3642325
  • Topics

×
×
  • Create New...

Important Information

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