Jump to content

[1.19.2] Event that ticks during block mining by player


Adil Yilan

Recommended Posts

General idea is to create a block that inflicts damage to player when player is mining it.

If it takes 10 seconds to mine the block, player might receive 10 hit points of damage (5 hearts) until block is mined.

To implement this, I need some kind of event that "ticks" as long as player is breaking the block.

I have tried with PlayerInteractEvent.LeftClickBlock and BlockEvent.BreakEvent but these two only provide information when mining starts and ends, but not that it is still in progress.

Is there any way to do this with existing events?

Thanks!

Link to comment
Share on other sites

Maybe BlockEvent.BreakEvent it is fired when the player has mined the Block, but has not been removed from the World yet (after the Event).
If you create an EventHandler with EventPriority#LOWEST, you can make sure you are one of the last EventHandlers.
Then you can get the break speed of the given BlockState and you can calculate the start time.

Edited by Luis_ST
Link to comment
Share on other sites

@Luis_ST I am trying to have damage to be consistent during mining process.

If it takes 10 seconds to mine it, I would like to reduce health by 1 hit point for every second of mining.

So if player starts to mine, after 1 second it should already show that 1/2 of heart is lost.

If player decides to stop mining after 4 seconds, he should still end up with 2 hearts lost, even though block was not mined at the end.

I don't think that is possible with your solution or did I miss something? :)

Link to comment
Share on other sites

I digged a bit into the vanilla code and i found MultiPlayerGameMode#destroyTicks (requires an AT).
The only problem is this is client only, if you subscribe to ClientTickEvent in a client only EventHandler class you can access to the MultiPlayerGameMode instance via Minecraft#gameMode.
If you use destroyTicks % 20 == 0 you can call the code each second, inside there you send a custom Packet to the server which will damage the Player on server.

As I said before, this is unfortunately client only, which means it can easily be modified by other Mods.
For this reason it will be difficult to verify this on the server, in this case you might damage the Player even though it doesn't mine a Block.

Link to comment
Share on other sites

@Luis_ST I have found this in Minecraft protocol:

https://wiki.vg/Protocol#Player_Action

I presume this is how ticks that you have linked are sent to server, since server eventually has to know whether player has completed or canceled digging?

Do you think it would be a good idea to listen for this packet and react on it or it would be an overkill?

 

EDIT:

Nvm this, it still does not solve "ticking" idea of damage.

I will go with your proposal @Luis_ST as it seems to be only viable solution for now.

Thank you! :)

Edited by Adil Yilan
Link to comment
Share on other sites

That network packet is handled by ServerPlayerGameMode.handleBlockBreakAction()

You can see forge already has an event there PlayerInteractEvent.LeftClickBlock.

Your issue is that the event is not designed for your use case.

It is fired to determine what method is used to handle the packet or whether it should be handled at all.

 

You might be able to use it, but you would probably have to do

if (player instanceof ServerPlayer serverPlayer) {
    ServerPlayerGameMode gameMode = serverPlayer.gameMode
    // your logic
}

Then use an access transformer to get access to the block break progress fields in that class, e.g. "isDestroyingBlock", "destroyProgressStart", "gameTicks", etc.

Your problem will be that those fields have not been updated yet when the event is fired,

so you will have a kind of "off by one" issue. Like I said, the event is not really designed for this usecase.

 

Boilerplate:

If you don't post your logs/debug.log we can't help you. For curseforge you need to enable the forge debug.log in its minecraft settings. You should also post your crash report if you have one.

If there is no error in the log file and you don't have a crash report then post the launcher_log.txt from the minecraft folder. Again for curseforge this will be in your curseforge/minecraft/Install

Large files should be posted to a file sharing site like https://gist.github.com  You should also read the support forum sticky post.

Link to comment
Share on other sites

32 minutes ago, Adil Yilan said:

I presume this is how ticks that you have linked are sent to server, since server eventually has to know whether player has completed or canceled digging?

Do you think it would be a good idea to listen for this packet and react on it or it would be an overkill?

Show what you have tried.

Edited by Luis_ST
Link to comment
Share on other sites

@warjort Hmm, but I could use LivingTickEvent and access ServerPlayerGameMode from there?

I could check if isDestroyingBlock property is set to true, and then access block state based on destroyPos property to check if destroyed block is the one that I am trying to implement => damage player if it is.

Would this be a good approach?

 

 

Link to comment
Share on other sites

Alright @Luis_ST and @warjort, thanks to your great assistance and tips, I have managed to make my idea come alive. Thank you so much! :)

Here is the working code for future reference:

 

@EventBusSubscriber(modid = ExperimentalMod.MODID, bus = Bus.FORGE)
public final class PlayerTickEventHandler {

	@SubscribeEvent()
	public static void onPlayerTick(final PlayerTickEvent event) {
		
		// IF: Event was fired on client side.
		if(event.side == LogicalSide.CLIENT) {
			return;
		}
		
		// Get reference to player and cast it as ServerPlayer.
		ServerPlayer player = (ServerPlayer)event.player;
		
		// Get game mode for player.
		ServerPlayerGameMode gameMode = player.gameMode;
		
		// IF: Player is not destroying block.
		if(!gameMode.isDestroyingBlock) {
			return;
		}
	
		// IF: It is not end of the phase.
		if(event.phase != Phase.END) {
			return;
		}
		
		// Calculate ticks elapsed since breaking of block has started.
		int ticks = gameMode.gameTicks - gameMode.destroyProgressStart;
		
		// IF: It is not whole second.
		if(ticks % 20 != 0) {
			return;
		}
		
		// Get reference to level and cast it to ServerLevel.
		ServerLevel serverLevel = (ServerLevel)player.level;
		
		// Get block state that is being destroyed.
		BlockState blockState = serverLevel.getBlockState(gameMode.destroyPos);
		
		// IF: It is not runic wall.
		if(!blockState.is(HazardousBlocks.RUNIC_WALL.get())) {
			return;
		}
		
		// Hurt the player.
		player.hurt(DamageSource.MAGIC, 1);
	}
}

 

I tried to make it clean and commented as much as possible, but if you still see something that is off / might make issues, please let me know as I am still quite new into all of this. :)

Thanks once more! :)

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.