Jump to content

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


Recommended Posts

Posted

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?

Posted

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.

Posted

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.

Posted

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

Posted

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.

Posted

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.

Posted

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.

Posted (edited)

 

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
Posted (edited)

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
Posted (edited)

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

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.

Posted

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!

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

    • So me and a couple of friends are playing with a shitpost mod pack and one of the mods in the pack is corail tombstone and for some reason there is a problem with it, where on death to fire the player will get kicked out of the server and the tombstone will not spawn basically deleting an entire inventory, it doesn't matter what type of fire it is, whether it's from vanilla fire/lava, or from modded fire like ice&fire/lycanites and it's common enough to where everyone on the server has experienced at least once or twice and it doesn't give any crash log. a solution to this would be much appreciated thank you!
    • It is 1.12.2 - I have no idea if there is a 1.12 pack
    • Okay, but does the modpack works with 1.12 or just with 1.12.2, because I need the Forge client specifically for Minecraft 1.12, not 1.12.2
    • Version 1.19 - Forge 41.0.63 I want to create a wolf entity that I can ride, so far it seems to be working, but the problem is that when I get on the wolf, I can’t control it. I then discovered that the issue is that the server doesn’t detect that I’m riding the wolf, so I’m struggling with synchronization. However, it seems to not be working properly. As I understand it, the server receives the packet but doesn’t register it correctly. I’m a bit new to Java, and I’ll try to provide all the relevant code and prints *The comments and prints are translated by chatgpt since they were originally in Spanish* Thank you very much in advance No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. MountableWolfEntity package com.vals.valscraft.entity; import com.vals.valscraft.network.MountSyncPacket; import com.vals.valscraft.network.NetworkHandler; import net.minecraft.client.Minecraft; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.animal.Wolf; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.Entity; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.network.PacketDistributor; public class MountableWolfEntity extends Wolf { private boolean hasSaddle; private static final EntityDataAccessor<Byte> DATA_ID_FLAGS = SynchedEntityData.defineId(MountableWolfEntity.class, EntityDataSerializers.BYTE); public MountableWolfEntity(EntityType<? extends Wolf> type, Level level) { super(type, level); this.hasSaddle = false; } @Override protected void defineSynchedData() { super.defineSynchedData(); this.entityData.define(DATA_ID_FLAGS, (byte)0); } public static AttributeSupplier.Builder createAttributes() { return Wolf.createAttributes() .add(Attributes.MAX_HEALTH, 20.0) .add(Attributes.MOVEMENT_SPEED, 0.3); } @Override public InteractionResult mobInteract(Player player, InteractionHand hand) { ItemStack itemstack = player.getItemInHand(hand); if (itemstack.getItem() == Items.SADDLE && !this.hasSaddle()) { if (!player.isCreative()) { itemstack.shrink(1); } this.setSaddle(true); return InteractionResult.SUCCESS; } else if (!level.isClientSide && this.hasSaddle()) { player.startRiding(this); MountSyncPacket packet = new MountSyncPacket(true); // 'true' means the player is mounted NetworkHandler.CHANNEL.sendToServer(packet); // Ensure the server handles the packet return InteractionResult.SUCCESS; } return InteractionResult.PASS; } @Override public void travel(Vec3 travelVector) { if (this.isVehicle() && this.getControllingPassenger() instanceof Player) { System.out.println("The wolf has a passenger."); System.out.println("The passenger is a player."); Player player = (Player) this.getControllingPassenger(); // Ensure the player is the controller this.setYRot(player.getYRot()); this.yRotO = this.getYRot(); this.setXRot(player.getXRot() * 0.5F); this.setRot(this.getYRot(), this.getXRot()); this.yBodyRot = this.getYRot(); this.yHeadRot = this.yBodyRot; float forward = player.zza; float strafe = player.xxa; if (forward <= 0.0F) { forward *= 0.25F; } this.flyingSpeed = this.getSpeed() * 0.1F; this.setSpeed((float) this.getAttributeValue(Attributes.MOVEMENT_SPEED) * 1.5F); this.setDeltaMovement(new Vec3(strafe, travelVector.y, forward).scale(this.getSpeed())); this.calculateEntityAnimation(this, false); } else { // The wolf does not have a passenger or the passenger is not a player System.out.println("No player is mounted, or the passenger is not a player."); super.travel(travelVector); } } public boolean hasSaddle() { return this.hasSaddle; } public void setSaddle(boolean hasSaddle) { this.hasSaddle = hasSaddle; } @Override protected void dropEquipment() { super.dropEquipment(); if (this.hasSaddle()) { this.spawnAtLocation(Items.SADDLE); this.setSaddle(false); } } @SubscribeEvent public static void onServerTick(TickEvent.ServerTickEvent event) { if (event.phase == TickEvent.Phase.START) { MinecraftServer server = net.minecraftforge.server.ServerLifecycleHooks.getCurrentServer(); if (server != null) { for (ServerPlayer player : server.getPlayerList().getPlayers()) { if (player.isPassenger() && player.getVehicle() instanceof MountableWolfEntity) { MountableWolfEntity wolf = (MountableWolfEntity) player.getVehicle(); System.out.println("Tick: " + player.getName().getString() + " is correctly mounted on " + wolf); } } } } } private boolean lastMountedState = false; @Override public void tick() { super.tick(); if (!this.level.isClientSide) { // Only on the server boolean isMounted = this.isVehicle() && this.getControllingPassenger() instanceof Player; // Only print if the state changed if (isMounted != lastMountedState) { if (isMounted) { Player player = (Player) this.getControllingPassenger(); // Verify the passenger is a player System.out.println("Server: Player " + player.getName().getString() + " is now mounted."); } else { System.out.println("Server: The wolf no longer has a passenger."); } lastMountedState = isMounted; } } } @Override public void addPassenger(Entity passenger) { super.addPassenger(passenger); if (passenger instanceof Player) { Player player = (Player) passenger; if (!this.level.isClientSide && player instanceof ServerPlayer) { // Send the packet to the server to indicate the player is mounted NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), new MountSyncPacket(true)); } } } @Override public void removePassenger(Entity passenger) { super.removePassenger(passenger); if (passenger instanceof Player) { Player player = (Player) passenger; if (!this.level.isClientSide && player instanceof ServerPlayer) { // Send the packet to the server to indicate the player is no longer mounted NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), new MountSyncPacket(false)); } } } @Override public boolean isControlledByLocalInstance() { Entity entity = this.getControllingPassenger(); return entity instanceof Player; } @Override public void positionRider(Entity passenger) { if (this.hasPassenger(passenger)) { double xOffset = Math.cos(Math.toRadians(this.getYRot() + 90)) * 0.4; double zOffset = Math.sin(Math.toRadians(this.getYRot() + 90)) * 0.4; passenger.setPos(this.getX() + xOffset, this.getY() + this.getPassengersRidingOffset() + passenger.getMyRidingOffset(), this.getZ() + zOffset); } } } MountSyncPacket package com.vals.valscraft.network; import com.vals.valscraft.entity.MountableWolfEntity; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; import net.minecraftforge.network.NetworkEvent; import java.util.function.Supplier; public class MountSyncPacket { private final boolean isMounted; public MountSyncPacket(boolean isMounted) { this.isMounted = isMounted; } public void encode(FriendlyByteBuf buffer) { buffer.writeBoolean(isMounted); } public static MountSyncPacket decode(FriendlyByteBuf buffer) { return new MountSyncPacket(buffer.readBoolean()); } public void handle(NetworkEvent.Context context) { context.enqueueWork(() -> { ServerPlayer player = context.getSender(); // Get the player from the context if (player != null) { // Verifies if the player has dismounted if (!isMounted) { Entity vehicle = player.getVehicle(); if (vehicle instanceof MountableWolfEntity wolf) { // Logic to remove the player as a passenger wolf.removePassenger(player); System.out.println("Server: Player " + player.getName().getString() + " is no longer mounted."); } } } }); context.setPacketHandled(true); // Marks the packet as handled } } networkHandler package com.vals.valscraft.network; import com.vals.valscraft.valscraft; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.network.NetworkRegistry; import net.minecraftforge.network.simple.SimpleChannel; import net.minecraftforge.network.NetworkEvent; import java.util.function.Supplier; public class NetworkHandler { private static final String PROTOCOL_VERSION = "1"; public static final SimpleChannel CHANNEL = NetworkRegistry.newSimpleChannel( new ResourceLocation(valscraft.MODID, "main"), () -> PROTOCOL_VERSION, PROTOCOL_VERSION::equals, PROTOCOL_VERSION::equals ); public static void init() { int packetId = 0; // Register the mount synchronization packet CHANNEL.registerMessage( packetId++, MountSyncPacket.class, MountSyncPacket::encode, MountSyncPacket::decode, (msg, context) -> msg.handle(context.get()) // Get the context with context.get() ); } }  
  • Topics

×
×
  • Create New...

Important Information

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