Jump to content

[1.7.2] KeyHandler to KeyEvents, how to replace keyUP() method?


Recommended Posts

Posted

Hello Forge users,

I'm updating my mod from 1.6.4 to 1.7.2.

My main problem right now is the new way for handling key events.

I mean, they are working fine, but I don't know how to do stuff only when the key is up (after it has been pressed).

So the problem is that I don't know how to update the old KeyHandler method called keyUp().

 

I have found only this topic: http://www.minecraftforge.net/forum/index.php?topic=18138.0

but the solution suggested is really terrible (I don't want to check for keys in a onUpdate() function..)  :-\

 

Thanks for the help in advance,

Axel

Posted

There's not much that can be done that I know of; for my own mod, I took the following approach:

 

1. Rethink my implementations to do the work when the key is pressed instead of released

 

2. For those that absolutely must use keyUp type functionality, attempt to encase it in a custom object that can be updated each tick when necessary, but also removed when no longer needed so as not to waste time every tick. Example: a skill that activates when key is released -> create a skill class, instantiate the object during key input event for that key and, now that the object exists, it updates each tick checking if the key is still pressed; as soon as it isn't, the skill does its thing and removes itself as needed.

 

3. Failing all the above, use a solution such as that in the topic you mentioned.

 

Sorry that may not be very helpful, but those are the only things I was able to come up with to attempt to replace the sadly extinct keyUp method. I hope we see it make a come-back some day.

Posted

While it is good practice to avoid any extra processing every tick (due to the processing burden), the reality is that the code is already doing tons of stuff every tick and a simple event handler that is updating some boolean flag isn't going to break a modern computer.  I agree it would be better to have an built-in (i.e. performance optimized) keyUp type event but I wouldn't be so loathe to implement it yourself.  A pain yes but hardly a performance killer.

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

Posted

Thanks guys for your answers, I will probably follow coolAlias steps. It's just that I seriously don't like when the code is messy and having all the key events around the code is surely a terrible behaviour for any good coder :)

 

Hope Forge guys will re-add this useful feature anytime soon,

Axel

Posted

EDIT: I still don't get how can I detect the key released in a onUpdate function. I tried this, but it will override also all MC keybinds:

 

@SubscribeEvent
public void tickClient(ClientTickEvent e)
{
	System.out.println("TESTING");
	while (Keyboard.next())
	{
		System.out.println("TESTING STUFF0");
		if (Keyboard.getEventKeyState())
		{
			System.out.println("TESTING STUFF1a");
			FMLCommonHandler.instance().fireKeyInput(); //KeyInputEvent (on key press)
		} else {
			System.out.println("TESTING STUFF1b");
			SWKeyHandler.keyUp(Keyboard.getEventKey()); //My KeyOutputEvent (on key release)
		}
	}
}

 

Do you have any other solution? :)

Thanks again,

Axel

Posted

I store when my custom keys are pressed, and loop through that each tick to see if they are unpressed, rather than all the keys.

 

The best way I've found is still using the custom objects, so when I press my 'special attack' key, it activates an instance of special attack or whatever, and that object is then updated each tick (charging up or whatever) and checks as it updates if the custom key is still pressed or not; this way, I'm only checking keys that I know were pressed.

 

You could do the same with a dynamic list of currently pressed keys, or if you made an array for your keybindings it would probably be more economic to simply run through it each tick instead of adding and removing entries to a list, depending on how many keys you have.

Posted

Thanks coolAlias for your useful answer,

on the keyDown event I'll add that key to a list which is looped every tick. For each entry of that list I will check if it is still pressed. If it's has just been released, it will apply the correct code and remove that entry from the list. Seems really fair to me, but dirty :P

 

Thanks again!

Posted

The problem here (imho) is where FML placed the KeyInputEvent hook.

It fires from within the Keyboard.next() loop of Minecraft, but only if getEventKeyState is true, meaning it only fires for key-down events. The hook should be moved down one line, and it could be used properly.

 

Yeah, I was looking at this too last night.  Furthermore the FML KeyInputEvent event parameter passed could have more useful fields.  It seems that polling Keyboard directly and maybe firing custom events (or just processing at the time) is more generally useful, although ideally you'd want to implement the key binding as well to allow user mapping.

 

One question about the Keyboard.next() is: does invoking this method clear the list of key events from Keyboard?  I mean if you call next() do you then have to fully process it or will it still work as expected in addition to anything you might do?

 

Another question: Regarding the "key up" detection, doesn't the getEventKeyState() method do this?  From the comment description for the method, it seems that if Keyboard has a key event where getEventKeyState() returns false that means that the key was released.

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

Posted

KeyEvent fires for every key event that happens. It is up to you to retrieve the information from the Keyboard class....Indeed it does. If getEventKeyState is false, that means that the current event is a "release-key" event. The problem is: due to the placement of the FML hook, FML's event does not fire in this case, which was my point.

My point about polling and creating own events was about augmenting/replacing the FML hook event.  In other words, by accessing Keyboard class KeyEvent directly you can detect both key down (including repeat it seems) and key up.  So can't you poll and then fire your own events for KeyUp?

 

A question about polling performance.  On the one hand doing something every tick seems like a performance burden, but of course the built-in code is already doing a lot of such polling.  Is the built-in code performance optimized in some way?  It just seems to me that polling, while a bit heavy, is something that computer should still be able to handle fairly well -- just poll KeyBoard.next() and fire off a KeyUp custom event as needed.

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

Posted

Oh. Yeah I misunderstood you. The problem is that the only place to reliably detect key inputs is in a .hasNext() .next() loop. Which MC already has, so you can't really just put in your own.

 

Okay, so I just tried the following:

	@SubscribeEvent(priority=EventPriority.NORMAL, receiveCanceled=true)
public void onEvent(ClientTickEvent event)
{
	while (Keyboard.next())
	{
		System.out.println("Keyboard event detected");
		FMLCommonHandler.instance().fireKeyInput();
	}
}

 

And it "nicely" detects both key down and key up events.  However, it seems to entirely override any other keyboard processing by Minecraft (even ESC no longer works!).  Basically, (like I suspected when asking my first question) that iterating through the Keyboard KeyEvents removes them from the queue and so they no longer get processed by rest of Minecraft. 

 

So the problem with creating your own key up hook is figuring out how to do it without blocking vanilla keyboard processing.  One way might be to put the stuff that is read from the Keyboard.readBuffer back.  There is a readNext() method that is private that is called by the public next() method, and readNext() just reads out the values from the buffer.  You could probably use reflection (right) to access readBuffer and put back what was just read out.  I suspect that would allow vanilla keyboard processing to continue.

 

The other method would be to recreate all of the Minecraft class' keyboard processing but I suspect that would be difficult as it would likely run into issues with private fields/method calls and also potentially would change the sequence of processing in a way that might muck up actual game. 

 

I think I'll play around with using reflection to restore the readBuffer as it seems like a fairly possible way to make the KeyInputEvent hook extend to include key up.

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

Posted

Okay, I'm stuck on the reflection already -- I'm not sure what the instance of the Keyboard  class really is.  It seems that there is no constructor or any call that instantiates it.  Instead it creates its own "implementation" instance of InputImplementation but that is also a private static field within the class.

 

Diesieben07, you're good at reflection -- is there any way to use reflection on this readBuffer field in Keyboard class?  Without an instance to reference I'm not sure how to use reflection...

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

Posted

Oh hell please don't hack into the internals of LWJGL :P You'll most likely cause all sorts of problems, because most of LWJGL is natives.

The right solution is to make a PR to FML to fix the placement of the hook :P

 

yeah, I could tell I was getting into dangerous territory.  I think I'll try polling the keybindings instead and see if I can fire a useful key up event from there.

 

But out of interests' sake: is there a way to do reflection on fields in a non-instantiated class?

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

Posted

As diesieben07 said, the only way is moving the Forge line in the Minecraft run() method. It's an easy fix, but someone must do it (I'm not good at github at all unluckily).

Jabelar, I tried the same stuff yesterday and I already said your thoughts in my previous posts, like:

 

EDIT: I still don't get how can I detect the key released in a onUpdate function. I tried this, but it will override also all MC keybinds:

 

Obviously, you mustn't use reflections for native code and using the workaroud diesieben07 suggested is good enough, just remember to remove the keybind from the list when it has been released.

Anyway we are all waiting for this little feature and I hope to see it in the next Forge versions :)

 

Have a good day guys!

Axel

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

    • 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() ); } }  
    • Do you use features of inventory profiles next (ipnext) or is there a change without it?
  • Topics

×
×
  • Create New...

Important Information

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