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.



  • Recently Browsing

    • No registered users viewing this page.
  • Posts

    • When trying to load Craft to Exile 2, game crashes and this error message pops up:   https://api.mclo.gs/1/raw/B2oYte0
    • FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':processResources'. > Could not copy file 'C:\Users\jedil\Downloads\forge-1.20-46.0.14-mdk\src\main\resources\META-INF\mods.toml' to 'C:\Users\jedil\Downloads\forge-1.20-46.0.14-mdk\build\resources\main\META-INF\mods.toml'.    > Failed to parse template script (your template may contain an error or be trying to use expressions not currently supported): startup failed:      SimpleTemplateScript1.groovy: 1: Unexpected input: '(' @ line 1, column 10.         out.print("""# This is an example mods.toml file. It contains the data relating to the loading mods.                  ^   This is my mods.toml script: # This is an example mods.toml file. It contains the data relating to the loading mods. # There are several mandatory fields (#mandatory), and many more that are optional (#optional). # The overall format is standard TOML format, v0.5.0. # Note that there are a couple of TOML lists in this file. # Find more information on toml format here: https://github.com/toml-lang/toml # The name of the mod loader type to load - for regular FML @Mod mods it should be javafml modLoader="javafml" #mandatory # A version range to match for said mod loader - for regular FML @Mod it will be the forge version loaderVersion="${46.0.14}" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions. # The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties. # Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here. license="${All Rights Reserved}" # A URL to refer people to when problems occur with this mod #issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional # A list of mods - how many allowed here is determined by the individual mod loader [[mods]] #mandatory # The modid of the mod modId="${MCRefined}" #mandatory # The version number of the mod version="${1.0.0}" #mandatory # A display name for the mod displayName="${Minecraft Refined}" #mandatory # A URL to query for updates for this mod. See the JSON update specification https://docs.minecraftforge.net/en/latest/misc/updatechecker/ #updateJSONURL="https://change.me.example.invalid/updates.json" #optional # A URL for the "homepage" for this mod, displayed in the mod UI #displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional # A file name (in the root of the mod JAR) containing a logo for display #logoFile="examplemod.png" #optional # A text field displayed in the mod UI #credits="" #optional # A text field displayed in the mod UI authors="${me}" #optional # Display Test controls the display for your mod in the server connection screen # MATCH_VERSION means that your mod will cause a red X if the versions on client and server differ. This is the default behaviour and should be what you choose if you have server and client elements to your mod. # IGNORE_SERVER_VERSION means that your mod will not cause a red X if it's present on the server but not on the client. This is what you should use if you're a server only mod. # IGNORE_ALL_VERSION means that your mod will not cause a red X if it's present on the client or the server. This is a special case and should only be used if your mod has no server component. # NONE means that no display test is set on your mod. You need to do this yourself, see IExtensionPoint.DisplayTest for more information. You can define any scheme you wish with this value. # IMPORTANT NOTE: this is NOT an instruction as to which environments (CLIENT or DEDICATED SERVER) your mod loads on. Your mod should load (and maybe do nothing!) whereever it finds itself. #displayTest="MATCH_VERSION" # MATCH_VERSION is the default if nothing is specified (#optional) # The description text for the mod (multi line!) (#mandatory) description='''${Minecraft, but it's, like, better.}''' # A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. I tried using --scan or --stacktrace, those were no help. I also tried Ctrl+Alt+S, the template I used did not appear. HELP
    • They were intended to be used on tutorial posts so that people could easily find tutorials based on their skill level, but instead the tags were abused for unrelated things that made the original intent useless... for example, people often posted crash reports with the "beginner" tag, so instead of finding tutorials for beginners, you got crash reports showing up in searches.
    • The crash says: Exception caught when registering wandering trader java.lang.NullPointerException: Cannot invoke "net.minecraft.world.entity.Entity.m_9236_()" because "entity" is null at com.telepathicgrunt.repurposedstructures.misc.maptrades.StructureSpecificMaps$TreasureMapForEmeralds.m_213663_(StructureSpecificMaps.java:53) ~[repurposed_structures-7.1.15+1.20.1-forge.jar%23708!/:?] at jeresources.collection.TradeList.addMerchantRecipe(TradeList.java:58) ~[JustEnoughResources-1.20.1-1.4.0.247.jar%23630!/:1.4.0.247] JustEnoughResources is mentioned, too Does it work without one of these mods?
    • I have been trying to place a jigsaw structure for about a week now and cant get it to work. I have the template pool etc set up and working it does randomly generate now I just want to place it in the world using a coded trigger. I cant seem to find any useful information on the internet and am completely stuck I think I need to use : JigsawPlacement.generateJigsaw() But I cant get the Holder<StructureTemplatePool>
  • Topics

×
×
  • Create New...

Important Information

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