From cfc97c5703b78b4f5b3deda0a9b74b86d83967e8 Mon Sep 17 00:00:00 2001 From: gnembon Date: Tue, 21 Jul 2020 05:03:19 -0300 Subject: [PATCH] block breaking / active block entity API --- docs/scarpet/Full.md | 53 +++++++++++++++++++ docs/scarpet/api/Entities.md | 53 +++++++++++++++++++ ...rverPlayerInteractionManagerInterface.java | 12 +++++ ...InteractionManager_scarpetEventsMixin.java | 34 +++++++++++- .../java/carpet/script/value/EntityValue.java | 35 +++++++++++- 5 files changed, 185 insertions(+), 2 deletions(-) create mode 100644 src/main/java/carpet/fakes/ServerPlayerInteractionManagerInterface.java diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index 50792541..2013a398 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -2781,6 +2781,53 @@ If `slot` is not specified, it defaults to the main hand. Number indicating the selected slot of entity's inventory. Currently only applicable to players. +### `query(e, 'active_block')` + +Returns currently mined block by the player, as registered by the game server. + +### `query(e, 'breaking_progress')` + +Returns current breaking progress of a current player mining block, or `null`, if no block is mined. +Breaking progress, it not null, is any number 0 or above, while 10 means that the block should already be +broken by the client. This value may tick above 10, if the client / connection is lagging + +Example: + +The following program provides custom breaking times, including nice block breaking animations, including instamine, for +blocks that otherwise would take longer to mine. + +[Video demo](https://youtu.be/zvEEuGxgCio) +```py +global_blocks = { + 'oak_planks' -> 0, + 'obsidian' -> 1, + 'end_portal_frame' -> 5, + 'bedrock' -> 10 +}; + +__on_player_clicks_block(player, block, face) -> +( + step = global_blocks:str(block); + if (step == 0, + destroy(block, -1); // instamine + , step != null, + schedule(0, '_break', player, pos(block), str(block), step, 0); + ) +); + +_break(player, pos, name, step, lvl) -> +( + current = player~'active_block'; + if (current != name || pos(current) != pos, + modify(player, 'breaking_progress', null); + , + modify(player, 'breaking_progress', lvl); + if (lvl >= 10, destroy(pos, -1)); + schedule(step, '_break', player, pos, name, step, lvl+1) + ); +) +``` + ### `query(e, 'facing', order?)` Returns where the entity is facing. optional order (number from 0 to 5, and negative), indicating primary directions @@ -3013,6 +3060,12 @@ Modifies directly player raw hunger components. Has no effect on non-players adds exhaustion value to the current player exhaustion level - that's the method you probably want to use to manipulate how much 'food' some action cost. +### `modify(e, 'breaking_progress', value)` + +Modifies a breaking progress of a player currently mined block. Value of `null`, `-1` make it reset. +Values `0` to `10` will show respective animation of a breaking block. Check `query(e, 'breaking_progress')` for +examples. + ### `modify(e, 'nbt_merge', partial_tag)` Merges a partial tag into the entity data and reloads the entity from its updated tag. Cannot be applied to players diff --git a/docs/scarpet/api/Entities.md b/docs/scarpet/api/Entities.md index 2cdd80dc..8e80f1c2 100644 --- a/docs/scarpet/api/Entities.md +++ b/docs/scarpet/api/Entities.md @@ -347,6 +347,53 @@ If `slot` is not specified, it defaults to the main hand. Number indicating the selected slot of entity's inventory. Currently only applicable to players. +### `query(e, 'active_block')` + +Returns currently mined block by the player, as registered by the game server. + +### `query(e, 'breaking_progress')` + +Returns current breaking progress of a current player mining block, or `null`, if no block is mined. +Breaking progress, it not null, is any number 0 or above, while 10 means that the block should already be +broken by the client. This value may tick above 10, if the client / connection is lagging + +Example: + +The following program provides custom breaking times, including nice block breaking animations, including instamine, for +blocks that otherwise would take longer to mine. + +[Video demo](https://youtu.be/zvEEuGxgCio) +```py +global_blocks = { + 'oak_planks' -> 0, + 'obsidian' -> 1, + 'end_portal_frame' -> 5, + 'bedrock' -> 10 +}; + +__on_player_clicks_block(player, block, face) -> +( + step = global_blocks:str(block); + if (step == 0, + destroy(block, -1); // instamine + , step != null, + schedule(0, '_break', player, pos(block), str(block), step, 0); + ) +); + +_break(player, pos, name, step, lvl) -> +( + current = player~'active_block'; + if (current != name || pos(current) != pos, + modify(player, 'breaking_progress', null); + , + modify(player, 'breaking_progress', lvl); + if (lvl >= 10, destroy(pos, -1)); + schedule(step, '_break', player, pos, name, step, lvl+1) + ); +) +``` + ### `query(e, 'facing', order?)` Returns where the entity is facing. optional order (number from 0 to 5, and negative), indicating primary directions @@ -579,6 +626,12 @@ Modifies directly player raw hunger components. Has no effect on non-players adds exhaustion value to the current player exhaustion level - that's the method you probably want to use to manipulate how much 'food' some action cost. +### `modify(e, 'breaking_progress', value)` + +Modifies a breaking progress of a player currently mined block. Value of `null`, `-1` make it reset. +Values `0` to `10` will show respective animation of a breaking block. Check `query(e, 'breaking_progress')` for +examples. + ### `modify(e, 'nbt_merge', partial_tag)` Merges a partial tag into the entity data and reloads the entity from its updated tag. Cannot be applied to players diff --git a/src/main/java/carpet/fakes/ServerPlayerInteractionManagerInterface.java b/src/main/java/carpet/fakes/ServerPlayerInteractionManagerInterface.java new file mode 100644 index 00000000..ed43336a --- /dev/null +++ b/src/main/java/carpet/fakes/ServerPlayerInteractionManagerInterface.java @@ -0,0 +1,12 @@ +package carpet.fakes; + +import net.minecraft.util.math.BlockPos; + +public interface ServerPlayerInteractionManagerInterface +{ + BlockPos getCurrentBreakingBlock(); + + int getCurrentBlockBreakingProgress(); + + void setBlockBreakingProgress(int progress); +} diff --git a/src/main/java/carpet/mixins/ServerPlayerInteractionManager_scarpetEventsMixin.java b/src/main/java/carpet/mixins/ServerPlayerInteractionManager_scarpetEventsMixin.java index d8de5ba6..d01c5820 100644 --- a/src/main/java/carpet/mixins/ServerPlayerInteractionManager_scarpetEventsMixin.java +++ b/src/main/java/carpet/mixins/ServerPlayerInteractionManager_scarpetEventsMixin.java @@ -1,5 +1,6 @@ package carpet.mixins; +import carpet.fakes.ServerPlayerInteractionManagerInterface; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.block.entity.BlockEntity; @@ -7,10 +8,12 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerInteractionManager; +import net.minecraft.server.world.ServerWorld; import net.minecraft.util.ActionResult; import net.minecraft.util.Hand; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -24,10 +27,18 @@ import static carpet.script.CarpetEventServer.Event.PLAYER_INTERACTS_WITH_BLOCK; @Mixin(ServerPlayerInteractionManager.class) -public class ServerPlayerInteractionManager_scarpetEventsMixin +public class ServerPlayerInteractionManager_scarpetEventsMixin implements ServerPlayerInteractionManagerInterface { @Shadow public ServerPlayerEntity player; + @Shadow private boolean mining; + + @Shadow private BlockPos miningPos; + + @Shadow private int blockBreakingProgress; + + @Shadow public ServerWorld world; + @Inject(method = "tryBreakBlock", locals = LocalCapture.CAPTURE_FAILHARD, at = @At( value = "INVOKE", target = "Lnet/minecraft/block/Block;onBreak(Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/entity/player/PlayerEntity;)V", @@ -46,4 +57,25 @@ public class ServerPlayerInteractionManager_scarpetEventsMixin { PLAYER_INTERACTS_WITH_BLOCK.onBlockHit(player, hand, blockHitResult); } + + @Override + public BlockPos getCurrentBreakingBlock() + { + if (!mining) return null; + return miningPos; + } + + @Override + public int getCurrentBlockBreakingProgress() + { + if (!mining) return -1; + return blockBreakingProgress; + } + + @Override + public void setBlockBreakingProgress(int progress) + { + blockBreakingProgress = MathHelper.clamp(progress, -1, 10); + world.setBlockBreakingInfo(-1*this.player.getEntityId(), miningPos, blockBreakingProgress); + } } diff --git a/src/main/java/carpet/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java index 682c4f76..5c87cfb1 100644 --- a/src/main/java/carpet/script/value/EntityValue.java +++ b/src/main/java/carpet/script/value/EntityValue.java @@ -5,6 +5,7 @@ import carpet.fakes.ItemEntityInterface; import carpet.fakes.LivingEntityInterface; import carpet.fakes.MobEntityInterface; import carpet.fakes.HungerManagerInterface; +import carpet.fakes.ServerPlayerInteractionManagerInterface; import carpet.helpers.Tracer; import carpet.patches.EntityPlayerMPFake; import carpet.script.CarpetContext; @@ -464,9 +465,32 @@ public class EntityValue extends Value put("selected_slot", (e, a) -> { if (e instanceof PlayerEntity) return new NumericValue(((PlayerEntity) e).inventory.selectedSlot); - return null; + return Value.NULL; }); + put("active_block", (e, a) -> { + if (e instanceof ServerPlayerEntity) + { + ServerPlayerInteractionManagerInterface manager = (ServerPlayerInteractionManagerInterface) (((ServerPlayerEntity) e).interactionManager); + BlockPos pos = manager.getCurrentBreakingBlock(); + if (pos == null) return Value.NULL; + return new BlockValue(null, ((ServerPlayerEntity) e).getServerWorld(), pos); + } + return Value.NULL; + }); + + put("breaking_progress", (e, a) -> { + if (e instanceof ServerPlayerEntity) + { + ServerPlayerInteractionManagerInterface manager = (ServerPlayerInteractionManagerInterface) (((ServerPlayerEntity) e).interactionManager); + int progress = manager.getCurrentBlockBreakingProgress(); + if (progress < 0) return Value.NULL; + return new NumericValue(progress); + } + return Value.NULL; + }); + + put("facing", (e, a) -> { int index = 0; if (a != null) @@ -1033,6 +1057,15 @@ public class EntityValue extends Value if(e instanceof PlayerEntity) ((PlayerEntity) e).getHungerManager().setSaturationLevelClient((float)NumericValue.asNumber(v).getLong()); }); + put("breaking_progress", (e, a) -> { + if (e instanceof ServerPlayerEntity) + { + int progress = (a == null || a == Value.NULL)?-1:NumericValue.asNumber(a).getInt(); + ServerPlayerInteractionManagerInterface manager = (ServerPlayerInteractionManagerInterface) (((ServerPlayerEntity) e).interactionManager); + manager.setBlockBreakingProgress(progress); + } + }); + put("nbt", (e, v) -> { if (!(e instanceof PlayerEntity)) {