Jump to content

Recommended Posts

Posted (edited)

Hi Forge community,

 

I'm quite new to forge mod development and still searching for the single point of truth tutorial / documentation for Forge 1.12. ;)

 

I would like to implement a mod with various client-only commands (i.e. /realtime - displays the current time in players chat).

Searching for "command" in the Docs section of this site unfortunaly does not display any useful resources for me.

After using Google, I found various sites using a "ClientCommandHandler".

But I can't see if this class is supported in 1.12 and if so, how to use it to register and execute client-only commands.

 

Could anyone post a tutorial or some hints how I could start?

And a last question: does an online java doc existst for the forge classes where I can browse around to explore all possibilities that I have with forge?

 

Hope to be welcomed by forge community ;)

Proxxo

Edited by Proxxo
Posted
17 minutes ago, Proxxo said:

After using Google, I found various sites using a "ClientCommandHandler".

But I can't see if this class is supported in 1.12 and if so, how to use it to register and execute client-only commands.

So, my question here is, "Why wouldn't it be supported in 1.12?"

Anyway, take a search on these forums and see what you can dig up. It's come up in the past.

18 minutes ago, Proxxo said:

And a last question: does an online java doc existst for the forge classes where I can browse around to explore all possibilities that I have with forge?

No, because it would be out of date in less than a week. All the javadoc you need is in the code itself. And if there ISN'T javadoc on something that means there's NO javadoc at all for it. All the javadoc is handled via MCPBot, the same way human readable names for methods and field names are handled.

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Posted

Hi Draco,

thanks for your quick post.

 

4 hours ago, Draco18s said:

So, my question here is, "Why wouldn't it be supported in 1.12?"

Because of changes in the api. I do not assume that all classes (i.e. ClientCommandHandler) in version 1.7.x are still existing in 1.12. Or they may got renamed. Or deprecated.

 

4 hours ago, Draco18s said:

No, because it would be out of date in less than a week.

This sounds crazy to me o.O. Is every build of forge a release version that people use in production? Coming from c# world, I am familiar with apis that update their documentation on every major/minor release. Intermediate work or bugfixes are handled in separate releases / hotfixes etc. Is this not the case with forge? When I look at the github pages, it seems that this may have changed, or am I wrong? https://github.com/MinecraftForge/MinecraftForge/releases

 

4 hours ago, Draco18s said:

Anyway, take a search on these forums and see what you can dig up.

Thanks for this tip. If there is no documentation about this, it seems that I have no other choise. :( 

Posted
17 minutes ago, Proxxo said:

This sounds crazy to me o.O. Is every build of forge a release version that people use in production? Coming from c# world, I am familiar with apis that update their documentation on every major/minor release.

Forge doesn't even maintain the Javadoc, that's all on the MCPBot side.

17 minutes ago, Proxxo said:

Intermediate work or bugfixes are handled in separate releases / hotfixes etc. Is this not the case with forge? When I look at the github pages, it seems that this may have changed, or am I wrong? https://github.com/MinecraftForge/MinecraftForge/releases

This would be more accurate:

http://files.minecraftforge.net/

The last build was 05:37 AM this morning (with a new recommended build that has no changes, 10 minutes later), and the previous build was on the 9th.  MCP exports? Those happen daily.

  • Like 1

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Posted

Thanks for your clarifications. I guess I first need to get more comfortable with these fast evolving Java ecosystems. :D

I will post the answer to my own question after I found all code puzzle pieces to get client-only commands up and ready.

Posted

Implementing client-only commands works like this:

 

1. Implement ICommand

I created an example command that will show the current time to a user like this:

public class GetTimeCommand implements ICommand {

	@Override
	public int compareTo(ICommand arg0) {
		return 0;
	}

	@Override
	public String getName() {
		return "realtime";
	}

	@Override
	public String getUsage(ICommandSender sender) {
		return "/realtime";
	}

	@Override
	public List<String> getAliases() {
		List<String> aliases = Lists.<String>newArrayList();
		aliases.add("/realtime");
		return aliases;
	}

	@Override
	public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException {
		DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
		String time = dateFormat.format(new Date(System.currentTimeMillis()));
		sender.sendMessage(format(net.minecraft.util.text.TextFormatting.DARK_GREEN, time));
	}

	@Override
	public boolean checkPermission(MinecraftServer server, ICommandSender sender) {
		return true;
	}

	@Override
	public List<String> getTabCompletions(MinecraftServer server, ICommandSender sender, String[] args,
			BlockPos targetPos) {
		return null;
	}

	@Override
	public boolean isUsernameIndex(String[] args, int index) {
		return false;
	}
	
	private TextComponentTranslation format(TextFormatting color, String str, Object... args)
    {
        TextComponentTranslation ret = new TextComponentTranslation(str, args);
        ret.getStyle().setColor(color);
        return ret;
    }
}

 

2. Register your command

Find a good place to register your command(s) at mod start up. I decided to first implement proxies for server and client like described in the Docs: https://mcforge.readthedocs.io/en/latest/concepts/sides/.

Then, in my ClientProxy class, I put command registrations into the preInit event handler:

public class ClientProxy extends CommonProxy {

	@Override
	public void preInit(FMLPreInitializationEvent e) {
		ClientCommandHandler.instance.registerCommand(new GetTimeCommand());
    }

	...
}

 

Thats it. In game, I can now input "/realtime" in chat and get the current time displayed.

  • Like 1
Posted
6 hours ago, Proxxo said:

This sounds crazy to me o.O. Is every build of forge a release version that people use in production? Coming from c# world, I am familiar with apis that update their documentation on every major/minor release. Intermediate work or bugfixes are handled in separate releases / hotfixes etc. Is this not the case with forge?

 

If you're coming from a professional coding environment, yes both Minecraft code itself and Forge's API are pretty "crazy". This is because Minecraft wasn't originally developed by professional programmers and furthermore was developed incrementally so there is a lot of inconsistency and a lot of stuff that would be frowned upon by software or engineering organizations. For example, the scope of fields and methods is really inconsistent with no proper encapsulation --  so you can reach into the code and change key values without any validation or synchronization. There are also often two methods which might do the same things. There is also abandoned code, code which does the same thing in multiple places (especially since without encapsulation the data validation is sprinkled throughout the code) and such.

 

Forge is done by good quality programmers, but it has been an incremental effort with lots of different contributors. It is also limited in that it tries to minimize the patching of vanilla Minecraft. Because it is incremental, every now and then the API gets entirely overhauled (for example the move to JSON format to control a number of features, the new registry event system, and so forth). Because there are lots of different contributors there are inconsistencies in style, for example the use of functional programming is creeping into the code but isn't consistently used throughout. And because they're trying to minimize the patching it is really constraining -- for example I'm trying to submit my first contribution related to custom fluids acting more like water but because Material.WATER is hard-coded throughout the vanilla code it is somewhat dubious to try to take this on as it creates a lot of patches.

 

I do find it wearing that the APIs change constantly -- keeping old mods up to date is a full time job and the modding community is getting fractured (you'll see on Minecraft Forum that majority of modders have stayed with 1.7.10 or 1.8.9). However, it is also the interesting part of the modding hobby and frankly has made me a much better coder by trying to keep up as it teaches a lot about the benefits of different approaches.

 

Sorry for the long essay, but I understand the surprise you might feel when you hear an API is constantly changing...

  • Like 1

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Posted

Hi jabelar,

 

thank you very much for this insights into the development process of Forge and Minecraft. Constantly working on my mod to keep it up to date with the latest Forge version was something that came into my mind when I read @Draco18s post. I will do my best. :D

 

However, Forge is a great project and it's exciting to learn some fundamental different concepts compared to what I used in the past. :)

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

    • 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() ); } }  
    • Do you use features of inventory profiles next (ipnext) or is there a change without it?
    • Remove rubidium - you are already using embeddium, which is a fork of rubidium
  • Topics

  • Who's Online (See full list)

×
×
  • Create New...

Important Information

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