Jump to content

Is there a way to edit a horse's inventory to allow my custom saddle to be placed in its inventory?


shrexish

Recommended Posts

This is my first ever mod. Im trying to do something fairly simple. I created a custom saddle for horses. The idea is that when you place the saddle in the horse's inventory, you can ride the horse with your friend. The saddle is called "Saddle For Two". I need to make the horse accept my saddle as normal saddle that would be able to be placed in its inventory. Later on I will begin to work on the functionality where two players can ride one horse. For now i just need to be able to place my custom saddle in the horse's inventory. Is it even possible to mess around with that?

Link to comment
Share on other sites

Hmm, I've never tried this, but it seems doable. I think you'd have to listen for the horse's container to open on the server with PlayerContainerEvent.Open and on the client with GuiOpenEvent, then replace the saddle slot with your custom slot that also accepts your new saddle.

I'm eager to learn and am prone to mistakes. Don't hesitate to tell me how I can improve.

Link to comment
Share on other sites

The game runs a server and a client, the server does most of the logical work while the client mainly handles input and rendering. You can read up on the server/client a bit here. The events I mentioned (PlayerContainerEvent.Open and GuiOpenEvent) are things you can subscribe to and run some code when the events occur. Read this to see how events work.

 

Both of those events offer a way of accessing an instance of HorseInventoryContainer, which is what controls what items are allowed into a horse's inventory. By changing the saddle slot in this container, you will be able to allow your saddle to be placed in it.

I'm eager to learn and am prone to mistakes. Don't hesitate to tell me how I can improve.

Link to comment
Share on other sites

Hey! After trying to understand what this PlayerContainerEvent.Open does,  I did this:
 

@SubscribeEvent
public void containerOpen(PlayerContainerEvent.Open event) {
	System.out.println("Opened...!");
}

To see if it would print on opening either my inventory or the horse's inventory. Sadly, nothing printed. Im very confused...

Link to comment
Share on other sites

Are you using @Mod.EventBusSubscriber? If so, your event methods need to be static.

Also, instead of printing directly to the console, it's generally better to print with a logger instance via LogManager.getLogger(). Using your IDE's debugger is also great for testing whether or not code is being run.

I'm eager to learn and am prone to mistakes. Don't hesitate to tell me how I can improve.

Link to comment
Share on other sites

Just using @SubscribeEvent isn't enough, you need to tell it to search the class or object for that annotation. There are multiple ways of doing this, but the simplest way is to annotate the class with @Mod.EventBusSubscriber and make all methods that use @SubscribeEvent static. You can alternatively call MinecraftForge.EVENT_BUS.register() and pass in the object or class you want it to scan.

I'm eager to learn and am prone to mistakes. Don't hesitate to tell me how I can improve.

Link to comment
Share on other sites

Ok:

 

package com.shrexish.horsemod;

import org.apache.logging.log4j.LogManager;

import net.minecraftforge.event.entity.player.PlayerContainerEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;

@Mod.EventBusSubscriber
public class EventHandler {
	@SubscribeEvent
	public static void containerOpen(PlayerContainerEvent.Open event) {
		LogManager.getLogger();
	}
}

This is my event handler. This is my main class:

package com.shrexish.horsemod;

import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;

@Mod(Reference.MOD_ID)
public class HorseMod {
	public HorseMod() {
		MinecraftForge.EVENT_BUS.register(this);
        FMLJavaModLoadingContext.get().getModEventBus().register(this);
        FMLJavaModLoadingContext.get().getModEventBus().addListener(this::onClientSetup);
	}
	
	private void onClientSetup(FMLClientSetupEvent event) {
        //PROXY.setupClient();
		MinecraftForge.EVENT_BUS.register(new EventHandler());
    }
}

This  the ModItems class:

package com.shrexish.horsemod.init;

import com.shrexish.horsemod.Reference;
import com.shrexish.horsemod.item.ItemSaddleForTwo;

import net.minecraft.item.Item;
import net.minecraft.item.ItemGroup;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.registries.ObjectHolder;

@ObjectHolder(Reference.MOD_ID)
@Mod.EventBusSubscriber(modid = Reference.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD)
public class ModItems
{
    public static final Item SADDLE_FOR_TWO = null;

    @SubscribeEvent
    @SuppressWarnings("unused")
    public static void register(final RegistryEvent.Register<Item> event)
    {
        event.getRegistry().register(new ItemSaddleForTwo(new Item.Properties().maxStackSize(1).group(ItemGroup.TRANSPORTATION)));
    }
}

This is the custom saddle class:

package com.shrexish.horsemod.item;

import com.shrexish.horsemod.Reference;

import net.minecraft.item.Item;
import net.minecraft.util.ResourceLocation;

public class ItemSaddleForTwo extends Item {
	public ItemSaddleForTwo(Properties properties) {
		super(properties);
		this.setRegistryName(new ResourceLocation(Reference.MOD_ID, "saddle_for_two"));
	}
}

This was practically everything.

Link to comment
Share on other sites

 

On 4/22/2020 at 3:26 PM, shrexish said:

[...]

 


package com.shrexish.horsemod;

import org.apache.logging.log4j.LogManager;

import net.minecraftforge.event.entity.player.PlayerContainerEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;

@Mod.EventBusSubscriber
public class EventHandler {
	@SubscribeEvent
	public static void containerOpen(PlayerContainerEvent.Open event) {
		LogManager.getLogger();
	}
}

[...]

You need to actually call the Logger#log function. (e.g. LogManager.getLogger().log(Level.INFO, "debug info");)

 

On 4/22/2020 at 3:26 PM, shrexish said:

[...]


package com.shrexish.horsemod;

import org.apache.logging.log4j.LogManager;

import net.minecraftforge.event.entity.player.PlayerContainerEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;

@Mod.EventBusSubscriber
public class EventHandler {
	@SubscribeEvent
	public static void containerOpen(PlayerContainerEvent.Open event) {
		LogManager.getLogger();
	}
}

This is my event handler. This is my main class:


package com.shrexish.horsemod;

import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;

@Mod(Reference.MOD_ID)
public class HorseMod {
	public HorseMod() {
		MinecraftForge.EVENT_BUS.register(this);
        FMLJavaModLoadingContext.get().getModEventBus().register(this);
        FMLJavaModLoadingContext.get().getModEventBus().addListener(this::onClientSetup);
	}
	
	private void onClientSetup(FMLClientSetupEvent event) {
        //PROXY.setupClient();
		MinecraftForge.EVENT_BUS.register(new EventHandler());
    }
}

[...]

Also you should not use both methods that @imacatlolol mentioned above but rather choose one.

 

 

If this works then you can access the container of the event (event.getContainer()) and can check if it's the HorseInventoryContainer (instanceof HorseInventoryContainer).

And after that point I have no idea how to continue. :D Maybe adding a new Slot at the position of the old one? (maybe not the best idea ?)

Edited by Boy132
Link to comment
Share on other sites

So looking into it, as I understand it, you'd need to do the following:

1) You would need to subscribe to the open container event, check if the container is an instance of HorseInventoryContainer and override the openContainer of the player (by casting to ServerPlayerEntity) with your own custom HorseInventoryContainer then add the listener.

1.5) Since the parameters horse and inventoryIn are not passed along with the event, and are private with no accessors, you'd probably have to use reflection to get those values and pass them to your new instance or make a constructor that does that for you. Look at ObfuscationReflectionHelper.

2) Subscribe to the GuiOpenEvent and wait for the ClientPlayNetHandler to open the HorseInventoryScreen(through call handleOpenHorseWindow) and in the event you'd want to set the gui to the new instance of the HorseInventoryScreen with your custom horsecontainer.

And that should do it. 

2.5) like 1.5 you'd need some parameters not passed by the event, so you'd need reflection again to get them from the old container.

Edited by ZDoctor
Link to comment
Share on other sites

And I went ahead and did it to prove I could.

 

First the custom HorseInventoryContainer:

public class TestHorseInventory extends HorseInventoryContainer {
    public TestHorseInventory(int id, PlayerInventory pInv, IInventory iinv, final AbstractHorseEntity horse) {
        super(id, pInv, iinv, horse);

        inventorySlots.set(0, new Slot(iinv, 0, 8, 18) {
            /**
             * Check if the stack is allowed to be placed in this slot, used for armor slots as well as furnace fuel.
             */
            public boolean isItemValid(ItemStack stack) {
                return (stack.getItem() == Items.SADDLE || stack.getItem() == Items.DIAMOND) && !this.getHasStack() && horse.canBeSaddled();
            }

            /**
             * Actualy only call when we want to render the white square effect over the slots. Return always True, except
             * for the armor slot of the Donkey/Mule (we can't interact with the Undead and Skeleton horses)
             */
            @OnlyIn(Dist.CLIENT)
            public boolean isEnabled() {
                return horse.canBeSaddled();
            }
        });
    }

}

 

The overriding the server container:

@Mod.EventBusSubscriber
public static class CommonEvents {    
	@SubscribeEvent
    public static void containerOpen(PlayerContainerEvent.Open event) {
        if (event.getContainer() instanceof HorseInventoryContainer) {
            LOGGER.info("Open Inventory");
            ServerPlayerEntity player = (ServerPlayerEntity) event.getPlayer();
            HorseInventoryContainer oldContainer = (HorseInventoryContainer) event.getContainer();
            AbstractHorseEntity horse = ObfuscationReflectionHelper.getPrivateValue(HorseInventoryContainer.class, oldContainer, "horse");
            IInventory horseInventory = ObfuscationReflectionHelper.getPrivateValue(HorseInventoryContainer.class, oldContainer, "horseInventory");

            if (player != null && horse != null && horseInventory != null) {
                player.openContainer = new TestHorseInventory(player.currentWindowId, player.inventory, horseInventory, horse);
                player.openContainer.addListener(player);
            }

        }
    }
}

 

Then the HorseInventoryScreen:

@Mod.EventBusSubscriber
public static class TestClientEvents {
    @SubscribeEvent
    public static void openGui(GuiOpenEvent event) {
        if (event.getGui() instanceof HorseInventoryScreen) {
            ClientPlayerEntity player = Minecraft.getInstance().player;
            if (player != null) {
                HorseInventoryScreen oldGui = (HorseInventoryScreen) event.getGui();
                HorseInventoryContainer oldContainer = oldGui.getContainer();
                AbstractHorseEntity horse = ObfuscationReflectionHelper.getPrivateValue(HorseInventoryContainer.class, oldContainer, "horse");
                IInventory horseInventory = ObfuscationReflectionHelper.getPrivateValue(HorseInventoryContainer.class, oldContainer, "horseInventory");
                if (horse != null && horseInventory != null) {
                    HorseInventoryContainer newContainer = new TestHorseInventory(oldContainer.windowId, player.inventory, horseInventory, horse);
                    player.openContainer = newContainer;
                    HorseInventoryScreen newGui = new HorseInventoryScreen(newContainer, player.inventory, horse);
                    event.setGui(newGui);
                }
            }

        }
    }
}

The finished product:

636052464_LiteralDiamondSaddle.thumb.png.73612f40779029b2b1441a3217f52e8c.png

Edited by ZDoctor
Spoiler broke code.
Link to comment
Share on other sites

Once I just do 

LogManager.getLogger().log(Level.INFO, "HorseInventory Opened!");

It prints that when I open my inventory when im on the horse. Off the horse and I open my inventory it doesnt print. So now i can know when the player opens the horse inventory. Now i just need to add my saddle to be able to be placed on horses. Hard part.

Link to comment
Share on other sites

Thanks so much! I can now use my custom saddle on my horse. Now I will research how I can add to player functionality on the saddle. I think this doesn't have to do with the saddle, but with the horse itself not accepting two players. I'm really learning a lot right now, thanks!

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.



×
×
  • Create New...

Important Information

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