Jump to content

[1.18] Extended villager behavior


Brokkoli3000

Recommended Posts

Hey guys,

I am trying to extend the vanilla villager behavior. Currently it shall be possible to talk to them via a simple GUI with choosable options like in other games. During these talks the villager shall follow the player with head and body exactly like during trading. I am still trying to figure out vast parts of the Minecraft code of the new version. I think the safest way to go would be to extend the villager CORE activity added in the Villager.registerBrainGoals method. There, an additional behavior could be added which mostly adopts the LookAndFollowTradingPlayerSink class.

But I can't find a way to add this behavior to the activity. I mainly see two possibilities: Intercepting every call to this method and replacing it by a copy including the additional behavior (but I can't find suitable Forge events). Or replacing the villager class by an own class extending Villager. Here I can't figure out how to do it properly in Minecraft 1.18. When I try to replace the entity type there is no error but also no villager shows up at all. But it doesn't seem to be just a rendering problem, because the constructor of my own villager class isn't even called.

Does anyone here have experience with villagers in the new version? Is my behavior approach generally good or would another variant be much easier and better? If the approach is good, how to get the additional behavior into the villager?

 

Thanks in advance! :)

Link to comment
Share on other sites

I did exactly this and added a Behavior there. Problem is, when the profession of the villager is reset, Villager.refreshBrain is called, which removes my behavior. And I can't find a way to listen for such a profession reset to add the behavior afterwards again.

So my updated approaches would be:

- Extend villager, but I couldn't find out how to replace all vanilla villagers with my custom class.

- Extend Brain and override the copyWithoutBehaviors method. Here the problem is that an extending class does not have access to the Brain.MemoryValue class. This could be hacked by using the access transformers maybe but it would be a dirty way...

Any more ideas?🤔

Link to comment
Share on other sites

19 hours ago, Brokkoli3000 said:

Extend villager, but I couldn't find out how to replace all vanilla villagers with my custom class.

"entity joins world" event - make your extended villager, copy data from original (if you care to), spawn your villager into the world instead of the original

19 hours ago, Brokkoli3000 said:

when the profession of the villager is reset, Villager.refreshBrain is called, which removes my behavior

ok, but refreshBrain is public - override it. either let the inherited code do its thing and re-add your stuff, or rewrite the method completely.

and it looks like you'll need ATs sooner or later - no worries, they are not a big problem.

  • Like 1
Link to comment
Share on other sites

1 hour ago, MFMods said:

"entity joins world" event - make your extended villager, copy data from original (if you care to), spawn your villager into the world instead of the original

Ah ok, this copying would be a possibility. I tried with overriding the EntityType for villager, but this led to the aforementioned problems. But than I'll try this other approach.

1 hour ago, MFMods said:

ok, but refreshBrain is public - override it. either let the inherited code do its thing and re-add your stuff, or rewrite the method completely.

Yeah exactly if I already extend the Villager class, then it shouldn't be a problem in the end.

1 hour ago, MFMods said:

and it looks like you'll need ATs sooner or later - no worries, they are not a big problem.

About the ATs: I tried them out and couldn't change visibility of fields. Probably because I used the deobfuscated names but read that it should be obfuscated names. But how do I get the original unreadable names if I already have the readable ones?

I actually don't really understand how Forge plugs different mods with different ATs together. Are the visibilities not available anymore in byte code, such that the ATs are only supposed to ensure that the mod code compiles?

And also: I updated to the newest version of Forge (39.0.8) and the additional jars (e.g. Forge events) are not shown in Eclipse anymore (probably they're gone?). Is this supposed to happen?

If this is already too off-topic, I can of course open a new thread.

Edited by Brokkoli3000
Link to comment
Share on other sites

4 hours ago, Brokkoli3000 said:

If this is already too off-topic, I can of course open a new thread.

you may want to break that up into a few threads, yes.

i'll handle the immediate practical problem.

4 hours ago, Brokkoli3000 said:

Probably because I used the deobfuscated names but read that it should be obfuscated names. But how do I get the original unreadable names if I already have the readable ones?

yes, you need the obfuscated name.
option 1) i think there is a discord bot that you just ask but i never used it. don't ask me.
option 2) minecraft developement plugin for idea ide - right click and say copy AT entry to clipboard.
option 3) find forge-VERSION_HERE-decomp.jar in .gradle directory inside your home folder. it contains game code with SRG names. compare the same class from that jar with the class with mapped names and copy the field name. for method names you need to specify proper signature (param types and return type).

in any case always put a mapped name one row above (comments start with #) so that you know what the row below is.

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.



  • Recently Browsing

    • No registered users viewing this page.
  • Posts

    • I made a block entity in forge 1.20.1, I want to prevent hopper from taking input slot item, i tried to override the extractItem method, it prevented hopper from taking input slot item, but the player also unable to take/change the item in input slot unless the slot is empty. public class FluidSeparatorBlockEntity extends BlockEntity implements MenuProvider { private static final int INPUT_SLOT = 0; private final CustomItemHandler itemHandler = new CustomItemHandler(3){ @Override protected void onContentsChanged(int slot) { setChanged(); } @Override public boolean isItemValid(int slot, @NotNull ItemStack stack) { return slot == INPUT_SLOT; } @Override public @NotNull ItemStack extractItem(int slot, int amount, boolean simulate) { if (slot == INPUT_SLOT) { return ItemStack.EMPTY; } return super.extractItem(slot, amount, simulate); } }; private LazyOptional<IItemHandler> lazyItemHandler = LazyOptional.empty(); protected final ContainerData data; private int progress = 0; private int maxProgress = 78; public FluidSeparatorBlockEntity(BlockPos pPos, BlockState pBlockState) { super(ModBlockEntities.FLUID_SEPARATOR_BE.get(), pPos, pBlockState); this.data = new ContainerData() { @Override public int get(int pIndex) { return switch (pIndex) { case 0 -> FluidSeparatorBlockEntity.this.progress; case 1, 2 -> FluidSeparatorBlockEntity.this.maxProgress; default -> 0; }; } @Override public void set(int pIndex, int pValue) { switch (pIndex) { case 0 -> FluidSeparatorBlockEntity.this.progress = pValue; case 1, 2 -> FluidSeparatorBlockEntity.this.maxProgress = pValue; } } @Override public int getCount() { return 3; } }; } @Override public @NotNull <T> LazyOptional<T> getCapability(@NotNull Capability<T> cap, @Nullable Direction side) { if(cap == ForgeCapabilities.ITEM_HANDLER) { return lazyItemHandler.cast(); } return super.getCapability(cap, side); } @Override public void onLoad() { super.onLoad(); lazyItemHandler = LazyOptional.of(() -> itemHandler); } @Override public void invalidateCaps() { super.invalidateCaps(); lazyItemHandler.invalidate(); } public void drops() { SimpleContainer inventory = new SimpleContainer(itemHandler.getSlots()); for(int i = 0; i < itemHandler.getSlots(); i++) { inventory.setItem(i, itemHandler.getStackInSlot(i)); } Containers.dropContents(this.level, this.worldPosition, inventory); } @Override public Component getDisplayName() { return Component.translatable("block.chemmaster.fluid_separator"); } @Nullable @Override public AbstractContainerMenu createMenu(int pContainerId, Inventory pPlayerInventory, Player pPlayer) { return new FluidSeparatorMenu(pContainerId, pPlayerInventory, this, this.data); } @Override protected void saveAdditional(CompoundTag pTag) { pTag.put("inventory", itemHandler.serializeNBT()); pTag.putInt("fluid_separator.progress", progress); super.saveAdditional(pTag); } @Override public void load(CompoundTag pTag) { super.load(pTag); itemHandler.deserializeNBT(pTag.getCompound("inventory")); progress = pTag.getInt("fluid_separator.progress"); } public void tick(Level pLevel, BlockPos pPos, BlockState pState) { ItemStack inputStack = this.itemHandler.getStackInSlot(INPUT_SLOT); if (inputStack.getCount() < 2) { resetProgress(); return; } if(hasRecipe()) { increaseCraftingProgress(); setChanged(pLevel, pPos, pState); if(hasProgressFinished()) { craftItem(); resetProgress(); } } else { resetProgress(); } } private void resetProgress() { progress = 0; } private void craftItem() { Optional<FluidSeparatingRecipe> recipe = getCurrentRecipe(); if (recipe.isPresent()) { List<ItemStack> results = recipe.get().getOutputs(); ItemStack inputStack = this.itemHandler.getStackInSlot(INPUT_SLOT); if (inputStack.getCount() < 2) { // If there are not enough items, do not proceed with crafting return; } // Extract the input item from the input slot this.itemHandler.internalExtractItem(INPUT_SLOT, 2, false); // Loop through each result item and find suitable output slots for (ItemStack result : results) { int outputSlot = findSuitableOutputSlot(result); if (outputSlot != -1) { this.itemHandler.setStackInSlot(outputSlot, new ItemStack(result.getItem(), this.itemHandler.getStackInSlot(outputSlot).getCount() + result.getCount())); } else { // Handle the case where no suitable output slot is found // This can be logging an error, throwing an exception, or any other handling logic System.err.println("No suitable output slot found for item: " + result); } } } } private int findSuitableOutputSlot(ItemStack result) { // Implement logic to find a suitable output slot for the given result // Return the slot index or -1 if no suitable slot is found for (int i = 0; i < this.itemHandler.getSlots(); i++) { // Ensure we do not place the output item in the input slot if (i == INPUT_SLOT) { continue; } ItemStack stackInSlot = this.itemHandler.getStackInSlot(i); if (stackInSlot.isEmpty() || (stackInSlot.getItem() == result.getItem() && stackInSlot.getCount() + result.getCount() <= stackInSlot.getMaxStackSize())) { return i; } } return -1; } private boolean hasRecipe() { Optional<FluidSeparatingRecipe> recipe = getCurrentRecipe(); if (recipe.isEmpty()) { return false; } List<ItemStack> results = recipe.get().getOutputs(); for (ItemStack result : results) { if (!canInsertAmountIntoOutputSlot(result) || !canInsertItemIntoOutputSlot(result.getItem())) { return false; } } return true; } private Optional<FluidSeparatingRecipe> getCurrentRecipe(){ SimpleContainer inventory = new SimpleContainer(this.itemHandler.getSlots()); for (int i = 0; i < itemHandler.getSlots(); i++) { inventory.setItem(i, this.itemHandler.getStackInSlot(i)); } return this.level.getRecipeManager().getRecipeFor(FluidSeparatingRecipe.Type.INSTANCE, inventory, level); } private boolean canInsertAmountIntoOutputSlot(ItemStack result) { for (int i = 1; i < this.itemHandler.getSlots(); i++) { ItemStack stackInSlot = this.itemHandler.getStackInSlot(i); if (stackInSlot.isEmpty() || (stackInSlot.getItem() == result.getItem() && stackInSlot.getCount() + result.getCount() <= stackInSlot.getMaxStackSize())) { return true; } } return false; } private boolean canInsertItemIntoOutputSlot(Item item) { for (int i = 1; i < this.itemHandler.getSlots(); i++) { ItemStack stackInSlot = this.itemHandler.getStackInSlot(i); if (stackInSlot.isEmpty() || stackInSlot.getItem() == item) { return true; } } return false; } private boolean hasProgressFinished() { return progress >= maxProgress; } private void increaseCraftingProgress() { progress++; } }  
    • No dice. Unfortunately this fix didn't work, thank you though.
    • Maybe you need a rayon mod. https://www.curseforge.com/minecraft/mc-mods/rayon
    • Not sure what's going on the logs are making even less sense than usual to me. Any help would be much appreciated.   https://paste.ee/p/KBHyP#s=0
  • Topics

×
×
  • Create New...

Important Information

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