9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-26 02:19:23 +00:00

重构挖掘

This commit is contained in:
XiaoMoMi
2025-04-15 04:13:35 +08:00
parent 2694c521a0
commit f6e29c5fb0
15 changed files with 350 additions and 161 deletions

View File

@@ -148,6 +148,12 @@ block:
# - Use `client-bound-item-data` to safely sync custom block data to clients.
# Documentation: https://mo-mi.gitbook.io/xiaomomi-plugins/craftengine/plugin-wiki/craftengine/add-new-contents/items/item-data/client-bound-item-data
simplify-adventure-break-check: false
# Whether plugin should predict the next block to break
# This can help improve mining experience to some extent
predict-breaking:
enable: true
interval: 5
extended-interaction-range: 0.5
furniture:
# Automatically remove outdated furniture entities when a chunk is loaded.

View File

@@ -661,7 +661,7 @@ templates#settings#blocks:
- "default:sound/stone"
- "default:pickaxe_power/level_{break_power}"
overrides:
hardness: 4.5
hardness: 3.0
resistance: 3.0
push-reaction: NORMAL
is-redstone-conductor: true
@@ -677,7 +677,7 @@ templates#settings#blocks:
- "default:sound/deepslate"
- "default:pickaxe_power/level_{break_power}"
overrides:
hardness: 6.0
hardness: 4.5
resistance: 3.0
push-reaction: NORMAL
is-redstone-conductor: true

View File

@@ -17,6 +17,7 @@ import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.context.ContextHolder;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.WorldEvents;
import net.momirealms.sparrow.nbt.CompoundTag;
import org.bukkit.Location;
import org.bukkit.Material;
@@ -183,7 +184,7 @@ public final class CraftEngineBlocks {
world.playBlockSound(vec3d, state.sounds().breakSound());
}
if (sendParticles) {
FastNMS.INSTANCE.method$Level$levelEvent(world.serverWorld(), 2001, LocationUtils.toBlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), state.customBlockState().registryId());
FastNMS.INSTANCE.method$Level$levelEvent(world.serverWorld(), WorldEvents.BLOCK_BREAK_EFFECT, LocationUtils.toBlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), state.customBlockState().registryId());
}
block.setType(Material.AIR, applyPhysics);
return true;

View File

@@ -7,15 +7,14 @@ import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.*;
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
import net.momirealms.craftengine.core.block.BlockSettings;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemKeys;
import net.momirealms.craftengine.core.loot.LootTable;
import net.momirealms.craftengine.core.loot.parameter.LootParameters;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.util.context.ContextHolder;
import net.momirealms.craftengine.core.world.BlockPos;
@@ -38,7 +37,6 @@ import org.bukkit.event.world.GenericGameEvent;
import org.bukkit.inventory.ItemStack;
import java.util.List;
import java.util.Optional;
public class BlockEventListener implements Listener {
private final BukkitCraftEngine plugin;
@@ -95,7 +93,7 @@ public class BlockEventListener implements Listener {
}
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) // I forget why it's LOW before
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onPlayerBreak(BlockBreakEvent event) {
org.bukkit.block.Block block = event.getBlock();
Object blockState = BlockStateUtils.blockDataToBlockState(block.getBlockData());
@@ -106,9 +104,8 @@ public class BlockEventListener implements Listener {
if (!state.isEmpty()) {
Location location = block.getLocation();
BukkitServerPlayer serverPlayer = this.plugin.adapt(player);
// double check to prevent dupe
// if simply adventure check, player would be survival mode for the moment
if (serverPlayer.isAdventureMode() && !serverPlayer.canBreak(LocationUtils.toBlockPos(location))) {
// double check adventure mode to prevent dupe
if (!FastNMS.INSTANCE.mayBuild(serverPlayer.serverPlayer()) && !serverPlayer.canBreak(LocationUtils.toBlockPos(location))) {
return;
}
@@ -133,15 +130,19 @@ public class BlockEventListener implements Listener {
// play sound
Vec3d vec3d = new Vec3d(location.getBlockX() + 0.5, location.getBlockY() + 0.5, location.getBlockZ() + 0.5);
world.playBlockSound(vec3d, state.sounds().breakSound());
if (player.getGameMode() == GameMode.CREATIVE) {
if (player.getGameMode() == GameMode.CREATIVE || !customBreakEvent.dropItems()) {
return;
}
Item<ItemStack> itemInHand = serverPlayer.getItemInHand(InteractionHand.MAIN_HAND);
Key itemId = Optional.ofNullable(itemInHand).map(Item::id).orElse(ItemKeys.AIR);
// do not drop if it's not the correct tool
if (!state.settings().isCorrectTool(itemId) || !customBreakEvent.dropItems()) {
return;
BlockSettings settings = state.settings();
if (settings.requireCorrectTool()) {
if (itemInHand == null) return;
if (!settings.isCorrectTool(itemInHand.id()) &&
(!settings.respectToolComponent() || !FastNMS.INSTANCE.method$ItemStack$isCorrectToolForDrops(itemInHand.getLiteralObject(), state.customBlockState().handle()))) {
return;
}
}
// drop items
ContextHolder.Builder builder = ContextHolder.builder();

View File

@@ -133,6 +133,7 @@ public class BukkitCustomBlock extends CustomBlock {
if (settings.burnable()) {
Reflections.method$FireBlock$setFlammable.invoke(Reflections.instance$Blocks$FIRE, mcBlock, settings.burnChance(), settings.fireSpreadChance());
}
Reflections.field$BlockStateBase$requiresCorrectToolForDrops.set(mcBlockState, settings.requireCorrectTool());
}
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to init block settings", e);

View File

@@ -19,6 +19,7 @@ import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.util.context.ContextHolder;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldEvents;
import net.momirealms.craftengine.shared.block.BlockBehavior;
import org.bukkit.Bukkit;
import org.bukkit.Material;
@@ -76,7 +77,7 @@ public class BushBlockBehavior extends BukkitBlockBehavior {
world.dropItemNaturally(vec3d, item);
}
world.playBlockSound(vec3d, previousState.sounds().breakSound());
FastNMS.INSTANCE.method$Level$levelEvent(level, 2001, blockPos, stateId);
FastNMS.INSTANCE.method$Level$levelEvent(level, WorldEvents.BLOCK_BREAK_EFFECT, blockPos, stateId);
}
return Reflections.method$Block$defaultBlockState.invoke(Reflections.instance$Blocks$AIR);
}

View File

@@ -21,6 +21,7 @@ import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.util.context.ContextHolder;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldEvents;
import net.momirealms.craftengine.shared.block.BlockBehavior;
import java.util.List;
@@ -69,7 +70,7 @@ public class SugarCaneBlockBehavior extends BushBlockBehavior {
world.dropItemNaturally(vec3d, item);
}
world.playBlockSound(vec3d, currentState.sounds().breakSound());
FastNMS.INSTANCE.method$Level$levelEvent(level, 2001, blockPos, stateId);
FastNMS.INSTANCE.method$Level$levelEvent(level, WorldEvents.BLOCK_BREAK_EFFECT, blockPos, stateId);
}
}
}

View File

@@ -28,6 +28,7 @@ import net.momirealms.craftengine.core.plugin.network.NetWorkUser;
import net.momirealms.craftengine.core.plugin.network.NetworkManager;
import net.momirealms.craftengine.core.util.*;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.WorldEvents;
import net.momirealms.craftengine.core.world.chunk.Palette;
import net.momirealms.craftengine.core.world.chunk.PalettedContainer;
import net.momirealms.craftengine.core.world.chunk.packet.MCSection;
@@ -221,7 +222,7 @@ public class PacketConsumers {
try {
FriendlyByteBuf buf = event.getBuffer();
int eventId = buf.readInt();
if (eventId != 2001) return;
if (eventId != WorldEvents.BLOCK_BREAK_EFFECT) return;
BlockPos blockPos = buf.readBlockPos(buf);
int state = buf.readInt();
boolean global = buf.readBoolean();
@@ -1118,17 +1119,19 @@ public class PacketConsumers {
if (Config.enableSoundSystem()) {
Object blockOwner = Reflections.field$StateHolder$owner.get(blockState);
if (BukkitBlockManager.instance().isBlockSoundRemoved(blockOwner)) {
player.startMiningBlock(world, pos, blockState, false, null);
player.startMiningBlock(pos, blockState, null);
return;
}
}
if (player.isMiningBlock() || player.shouldSyncAttribute()) {
if (player.isMiningBlock()) {
player.stopMiningBlock();
} else {
player.setClientSideCanBreakBlock(true);
}
return;
}
if (player.isAdventureMode()) {
if (Config.simplyAdventureCheck()) {
if (Config.simplifyAdventureCheck()) {
ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId);
if (!player.canBreak(pos, state.vanillaBlockState().handle())) {
player.preventMiningBlock();
@@ -1141,7 +1144,7 @@ public class PacketConsumers {
}
}
}
player.startMiningBlock(world, pos, blockState, true, BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId));
player.startMiningBlock(pos, blockState, BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId));
} else if (action == Reflections.instance$ServerboundPlayerActionPacket$Action$ABORT_DESTROY_BLOCK) {
if (player.isMiningBlock()) {
player.abortMiningBlock();

View File

@@ -9,20 +9,21 @@ import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.util.*;
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
import net.momirealms.craftengine.core.block.BlockSettings;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.PackedBlockState;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemKeys;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.network.ConnectionState;
import net.momirealms.craftengine.core.util.Direction;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.*;
import net.momirealms.craftengine.core.world.WorldEvents;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.inventory.EquipmentSlot;
@@ -62,6 +63,7 @@ public class BukkitServerPlayer extends Player {
private boolean swingHandAck;
private float miningProgress;
private int lastSuccessfulBreak;
private int resentSoundTick;
private int resentSwingTick;
@@ -73,6 +75,8 @@ public class BukkitServerPlayer extends Player {
private final Map<Integer, List<Integer>> furnitureView = new ConcurrentHashMap<>();
private final Map<Integer, Object> entityTypeView = new ConcurrentHashMap<>();
private boolean clientSideCanBreak = true;
public BukkitServerPlayer(BukkitCraftEngine plugin, Channel channel) {
this.channel = channel;
this.plugin = plugin;
@@ -108,8 +112,8 @@ public class BukkitServerPlayer extends Player {
@Override
public boolean shouldSyncAttribute() {
long current = System.currentTimeMillis();
if (current - this.lastAttributeSyncTime > 10000) {
long current = gameTicks();
if (current - this.lastAttributeSyncTime > 100) {
this.lastAttributeSyncTime = current;
return true;
}
@@ -209,13 +213,7 @@ public class BukkitServerPlayer extends Player {
@Override
public int gameTicks() {
try {
Object serverPlayer = serverPlayer();
Object gameMode = Reflections.field$ServerPlayer$gameMode.get(serverPlayer);
return (int) Reflections.field$ServerPlayerGameMode$gameTicks.get(gameMode);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to get current tick", e);
}
return FastNMS.INSTANCE.field$MinecraftServer$currentTick();
}
@Override
@@ -266,6 +264,11 @@ public class BukkitServerPlayer extends Player {
this.plugin.networkManager().sendPacket(this, packet, immediately);
}
@Override
public void sendPackets(List<Object> packet, boolean immediately) {
this.plugin.networkManager().sendPackets(this, packet, immediately);
}
@Override
public void receivePacket(Object packet) {
this.plugin.networkManager().receivePacket(this, packet);
@@ -319,45 +322,75 @@ public class BukkitServerPlayer extends Player {
if (this.isDestroyingBlock) {
this.tickBlockDestroy();
}
// if it's not destroying custom blocks, we do predict
if (Config.predictBreaking()) {
if (!this.isDestroyingCustomBlock && (gameTicks() + entityID()) % Config.predictBreakingInterval() == 0) {
this.predictNextBlockToMine();
}
}
}
@Override
public float getDestroyProgress(Object blockState, BlockPos pos) {
try {
Object serverPlayer = serverPlayer();
Object blockPos = LocationUtils.toBlockPos(pos.x(), pos.y(), pos.z());
return (float) Reflections.method$BlockStateBase$getDestroyProgress.invoke(blockState, serverPlayer, Reflections.method$Entity$level.invoke(serverPlayer), blockPos);
} catch (ReflectiveOperationException e) {
this.plugin.logger().warn("Failed to get destroy progress for player " + platformPlayer().getName());
return 0f;
return FastNMS.INSTANCE.method$BlockStateBase$getDestroyProgress(blockState, serverPlayer(), FastNMS.INSTANCE.field$CraftWorld$ServerLevel(platformPlayer().getWorld()), LocationUtils.toBlockPos(pos));
}
private void predictNextBlockToMine() {
double range = getInteractionRange() + Config.extendedInteractionRange();
RayTraceResult result = platformPlayer().rayTraceBlocks(range, FluidCollisionMode.NEVER);
if (result == null) {
if (!this.clientSideCanBreak) {
setClientSideCanBreakBlock(true);
}
return;
}
Block hitBlock = result.getHitBlock();
if (hitBlock == null) {
if (!this.clientSideCanBreak) {
setClientSideCanBreakBlock(true);
}
return;
}
int stateId = BlockStateUtils.blockDataToId(hitBlock.getBlockData());
if (BlockStateUtils.isVanillaBlock(stateId)) {
if (!this.clientSideCanBreak) {
setClientSideCanBreakBlock(true);
}
return;
}
if (this.clientSideCanBreak) {
setClientSideCanBreakBlock(false);
}
}
public void startMiningBlock(org.bukkit.World world, BlockPos pos, Object state, boolean custom, @Nullable ImmutableBlockState immutableBlockState) {
public void startMiningBlock(BlockPos pos, Object state, @Nullable ImmutableBlockState immutableBlockState) {
// instant break
boolean custom = immutableBlockState != null;
if (custom && getDestroyProgress(state, pos) >= 1f) {
assert immutableBlockState != null;
// not an instant break on client side
PackedBlockState vanillaBlockState = immutableBlockState.vanillaBlockState();
// if it's not an instant break on client side, we should resend level event
if (vanillaBlockState != null && getDestroyProgress(vanillaBlockState.handle(), pos) < 1f) {
try {
Object levelEventPacket = Reflections.constructor$ClientboundLevelEventPacket.newInstance(2001, LocationUtils.toBlockPos(pos), BlockStateUtils.blockStateToId(this.destroyedState), false);
sendPacket(levelEventPacket, false);
} catch (ReflectiveOperationException e) {
this.plugin.logger().warn("Failed to send level event packet", e);
}
Object levelEventPacket = FastNMS.INSTANCE.constructor$ClientboundLevelEventPacket(
WorldEvents.BLOCK_BREAK_EFFECT, LocationUtils.toBlockPos(pos), BlockStateUtils.blockStateToId(this.destroyedState), false);
sendPacket(levelEventPacket, false);
}
//ParticleUtils.addBlockBreakParticles(world, LocationUtils.toBlockPos(pos), state);
return;
}
setCanBreakBlock(!custom);
// if it's a custom one, we prevent it, otherwise we allow it
setClientSideCanBreakBlock(!custom);
// set some base info
setDestroyPos(pos);
setDestroyedState(state);
setIsDestroyingBlock(true, custom);
}
private void setCanBreakBlock(boolean canBreak) {
@Override
public void setClientSideCanBreakBlock(boolean canBreak) {
try {
if (this.clientSideCanBreak == canBreak && !shouldSyncAttribute()) {
return;
}
this.clientSideCanBreak = canBreak;
if (canBreak) {
if (VersionHelper.isVersionNewerThan1_20_5()) {
Object serverPlayer = serverPlayer();
@@ -379,8 +412,7 @@ public class BukkitServerPlayer extends Player {
} else {
Object fatiguePacket = MobEffectUtils.createPacket(Reflections.instance$MobEffecr$mining_fatigue, entityID(), (byte) 9, -1, false, false, false);
Object hastePacket = MobEffectUtils.createPacket(Reflections.instance$MobEffecr$haste, entityID(), (byte) 0, -1, false, false, false);
sendPacket(fatiguePacket, true);
sendPacket(hastePacket, true);
sendPackets(List.of(fatiguePacket, hastePacket), true);
}
}
} catch (ReflectiveOperationException e) {
@@ -390,17 +422,26 @@ public class BukkitServerPlayer extends Player {
@Override
public void stopMiningBlock() {
setCanBreakBlock(true);
setClientSideCanBreakBlock(true);
setIsDestroyingBlock(false, false);
}
@Override
public void preventMiningBlock() {
setCanBreakBlock(false);
setClientSideCanBreakBlock(false);
setIsDestroyingBlock(false, false);
abortMiningBlock();
}
@Override
public void abortMiningBlock() {
this.swingHandAck = false;
this.miningProgress = 0;
if (this.destroyPos != null) {
this.broadcastDestroyProgress(platformPlayer(), this.destroyPos, LocationUtils.toBlockPos(this.destroyPos), -1);
}
}
private void resetEffect(Object mobEffect) throws ReflectiveOperationException {
Object effectInstance = Reflections.method$ServerPlayer$getEffect.invoke(serverPlayer(), mobEffect);
Object packet;
@@ -412,24 +453,13 @@ public class BukkitServerPlayer extends Player {
sendPacket(packet, true);
}
@Override
public void abortMiningBlock() {
abortDestroyProgress();
}
private void tickBlockDestroy() {
// prevent server from taking over breaking blocks
if (this.isDestroyingCustomBlock) {
try {
Object serverPlayer = serverPlayer();
Object gameMode = Reflections.field$ServerPlayer$gameMode.get(serverPlayer);
Reflections.field$ServerPlayerGameMode$isDestroyingBlock.set(gameMode, false);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
// if player swings hand is this tick
if (!this.swingHandAck) return;
this.swingHandAck = false;
int currentTick = gameTicks();
// optimize break speed, otherwise it would be too fast
if (currentTick - this.lastSuccessfulBreak <= 5) return;
try {
org.bukkit.entity.Player player = platformPlayer();
double range = getInteractionRange();
@@ -444,8 +474,8 @@ public class BukkitServerPlayer extends Player {
}
Object blockPos = LocationUtils.toBlockPos(hitPos);
Object serverPlayer = serverPlayer();
Object gameMode = Reflections.field$ServerPlayer$gameMode.get(serverPlayer);
int currentTick = (int) Reflections.field$ServerPlayerGameMode$gameTicks.get(gameMode);
// send hit sound if the sound is removed
if (currentTick - this.lastHitBlockTime > 3) {
Object blockOwner = Reflections.field$StateHolder$owner.get(this.destroyedState);
Object soundType = Reflections.field$BlockBehaviour$soundType.get(blockOwner);
@@ -457,9 +487,14 @@ public class BukkitServerPlayer extends Player {
// accumulate progress (custom blocks only)
if (this.isDestroyingCustomBlock) {
// prevent server from taking over breaking custom blocks
Object gameMode = FastNMS.INSTANCE.field$ServerPlayer$gameMode(serverPlayer);
Reflections.field$ServerPlayerGameMode$isDestroyingBlock.set(gameMode, false);
// check item in hand
Item<ItemStack> item = this.getItemInHand(InteractionHand.MAIN_HAND);
if (item != null) {
Material itemMaterial = item.getItem().getType();
// creative mode + invalid item in hand
if (canInstabuild() && (itemMaterial == Material.DEBUG_STICK
|| itemMaterial == Material.TRIDENT
|| (VersionHelper.isVersionNewerThan1_20_5() && itemMaterial == MaterialUtils.MACE)
@@ -468,37 +503,65 @@ public class BukkitServerPlayer extends Player {
}
}
float progressToAdd = (float) Reflections.method$BlockStateBase$getDestroyProgress.invoke(this.destroyedState, serverPlayer, Reflections.method$Entity$level.invoke(serverPlayer), blockPos);
float progressToAdd = getDestroyProgress(this.destroyedState, hitPos);
int id = BlockStateUtils.blockStateToId(this.destroyedState);
ImmutableBlockState customState = BukkitBlockManager.instance().getImmutableBlockState(id);
// double check custom block
if (customState != null && !customState.isEmpty()) {
if (!customState.settings().isCorrectTool(item == null ? ItemKeys.AIR : item.id())) {
progressToAdd *= customState.settings().incorrectToolSpeed();
BlockSettings blockSettings = customState.settings();
if (blockSettings.requireCorrectTool()) {
if (item != null) {
// it's correct on plugin side
if (blockSettings.isCorrectTool(item.id())) {
// but not on serverside
if (!FastNMS.INSTANCE.method$ItemStack$isCorrectToolForDrops(item.getLiteralObject(), this.destroyedState)) {
// we fix the speed
progressToAdd = progressToAdd * (10f / 3f);
}
} else {
// not a correct tool on plugin side and not a correct tool on serverside
if (!blockSettings.respectToolComponent() || !FastNMS.INSTANCE.method$ItemStack$isCorrectToolForDrops(item.getLiteralObject(), this.destroyedState)) {
progressToAdd = progressToAdd * (10f / 3f) * blockSettings.incorrectToolSpeed();
}
}
} else {
// item is null, but it requires correct tool, then we reset the speed
progressToAdd = progressToAdd * (10f / 3f) * blockSettings.incorrectToolSpeed();
}
}
// accumulate progress
this.miningProgress = progressToAdd + miningProgress;
int packetStage = (int) (this.miningProgress * 10.0F);
if (packetStage != this.lastSentState) {
this.lastSentState = packetStage;
// broadcast changes
broadcastDestroyProgress(player, hitPos, blockPos, packetStage);
}
// can break now
if (this.miningProgress >= 1f) {
if (isAdventureMode() && Config.simplyAdventureCheck()) {
// for simplified adventure break, switch mayBuild temporarily
if (isAdventureMode() && Config.simplifyAdventureCheck()) {
// check the appearance state
if (canBreak(hitPos, customState.vanillaBlockState().handle())) {
player.setGameMode(GameMode.SURVIVAL);
// Error might occur so we use try here
try {
FastNMS.INSTANCE.setMayBuild(serverPlayer, true);
Reflections.method$ServerPlayerGameMode$destroyBlock.invoke(gameMode, blockPos);
} finally {
player.setGameMode(GameMode.ADVENTURE);
FastNMS.INSTANCE.setMayBuild(serverPlayer, false);
}
}
} else {
// normal break check
Reflections.method$ServerPlayerGameMode$destroyBlock.invoke(gameMode, blockPos);
}
Object levelEventPacket = Reflections.constructor$ClientboundLevelEventPacket.newInstance(2001, blockPos, id, false);
sendPacket(levelEventPacket, false);
this.stopMiningBlock();
// send break particle + (removed sounds)
sendPacket(FastNMS.INSTANCE.constructor$ClientboundLevelEventPacket(WorldEvents.BLOCK_BREAK_EFFECT, blockPos, id, false), false);
this.lastSuccessfulBreak = currentTick;
this.destroyPos = null;
this.setIsDestroyingBlock(false, false);
}
}
}
@@ -507,8 +570,8 @@ public class BukkitServerPlayer extends Player {
}
}
private void broadcastDestroyProgress(org.bukkit.entity.Player player, BlockPos hitPos, Object blockPos, int stage) throws ReflectiveOperationException {
Object packet = Reflections.constructor$ClientboundBlockDestructionPacket.newInstance(Integer.MAX_VALUE - entityID(), blockPos, stage);
private void broadcastDestroyProgress(org.bukkit.entity.Player player, BlockPos hitPos, Object blockPos, int stage) {
Object packet = FastNMS.INSTANCE.constructor$ClientboundBlockDestructionPacket(Integer.MAX_VALUE - entityID(), blockPos, stage);
for (org.bukkit.entity.Player other : player.getWorld().getPlayers()) {
Location otherLocation = other.getLocation();
double d0 = (double) hitPos.x() - otherLocation.getX();
@@ -522,52 +585,22 @@ public class BukkitServerPlayer extends Player {
@Override
public double getInteractionRange() {
try {
if (VersionHelper.isVersionNewerThan1_20_5()) {
Object attributeInstance = Reflections.method$ServerPlayer$getAttribute.invoke(serverPlayer(), Reflections.instance$Holder$Attribute$block_interaction_range);
if (attributeInstance == null) return 4.5d;
return (double) Reflections.method$AttributeInstance$getValue.invoke(attributeInstance);
} else {
return 4.5d;
}
} catch (ReflectiveOperationException e) {
plugin.logger().warn("Failed to get interaction range for player " + platformPlayer().getName(), e);
return 4.5d;
}
return FastNMS.INSTANCE.getInteractionRange(serverPlayer());
}
public void setIsDestroyingBlock(boolean value, boolean custom) {
if (value) {
this.isDestroyingBlock = true;
this.isDestroyingCustomBlock = custom;
this.swingHandAck = true;
this.miningProgress = 0;
} else {
this.isDestroyingBlock = false;
this.swingHandAck = false;
if (this.destroyPos != null) {
try {
this.broadcastDestroyProgress(platformPlayer(), this.destroyPos, LocationUtils.toBlockPos(this.destroyPos), -1);
} catch (ReflectiveOperationException e) {
plugin.logger().warn("Failed to set isDestroyingCustomBlock", e);
}
}
this.destroyPos = null;
this.miningProgress = 0;
this.destroyedState = null;
this.isDestroyingCustomBlock = false;
}
}
@Override
public void abortDestroyProgress() {
this.swingHandAck = false;
public void setIsDestroyingBlock(boolean is, boolean custom) {
this.miningProgress = 0;
if (this.destroyPos == null) return;
try {
this.broadcastDestroyProgress(platformPlayer(), this.destroyPos, LocationUtils.toBlockPos(this.destroyPos), -1);
} catch (ReflectiveOperationException e) {
plugin.logger().warn("Failed to abort destroyProgress", e);
this.isDestroyingBlock = is;
this.isDestroyingCustomBlock = custom && is;
if (is) {
this.swingHandAck = true;
} else {
this.swingHandAck = false;
this.destroyedState = null;
if (this.destroyPos != null) {
this.broadcastDestroyProgress(platformPlayer(), this.destroyPos, LocationUtils.toBlockPos(this.destroyPos), -1);
this.destroyPos = null;
}
}
}

View File

@@ -2418,6 +2418,12 @@ public class Reflections {
)
);
public static final Field field$BlockStateBase$requiresCorrectToolForDrops = requireNonNull(
ReflectionUtils.getDeclaredField(
clazz$BlockStateBase, boolean.class, 5
)
);
public static final Field field$BlockStateBase$canOcclude = requireNonNull(
ReflectionUtils.getDeclaredField(
clazz$BlockStateBase, boolean.class, 6
@@ -2925,11 +2931,11 @@ public class Reflections {
)
);
public static final Field field$ServerPlayer$gameMode = requireNonNull(
ReflectionUtils.getDeclaredField(
clazz$ServerPlayer, clazz$ServerPlayerGameMode, 0
)
);
// public static final Field field$ServerPlayer$gameMode = requireNonNull(
// ReflectionUtils.getDeclaredField(
// clazz$ServerPlayer, clazz$ServerPlayerGameMode, 0
// )
// );
public static final Field field$ServerPlayerGameMode$destroyProgressStart = requireNonNull(
ReflectionUtils.getDeclaredField(
@@ -2937,11 +2943,11 @@ public class Reflections {
)
);
public static final Field field$ServerPlayerGameMode$gameTicks = requireNonNull(
ReflectionUtils.getDeclaredField(
clazz$ServerPlayerGameMode, int.class, 1
)
);
// public static final Field field$ServerPlayerGameMode$gameTicks = requireNonNull(
// ReflectionUtils.getDeclaredField(
// clazz$ServerPlayerGameMode, int.class, 1
// )
// );
public static final Field field$ServerPlayerGameMode$delayedTickStart = requireNonNull(
ReflectionUtils.getDeclaredField(
@@ -3009,11 +3015,11 @@ public class Reflections {
)
);
public static final Method method$BlockStateBase$getDestroyProgress = requireNonNull(
ReflectionUtils.getDeclaredMethod(
clazz$BlockStateBase, float.class, clazz$Player, clazz$BlockGetter, clazz$BlockPos
)
);
// public static final Method method$BlockStateBase$getDestroyProgress = requireNonNull(
// ReflectionUtils.getDeclaredMethod(
// clazz$BlockStateBase, float.class, clazz$Player, clazz$BlockGetter, clazz$BlockPos
// )
// );
public static final Class<?> clazz$ClientboundBlockDestructionPacket = requireNonNull(
ReflectionUtils.getClazz(
@@ -3022,11 +3028,11 @@ public class Reflections {
)
);
public static final Constructor<?> constructor$ClientboundBlockDestructionPacket = requireNonNull(
ReflectionUtils.getConstructor(
clazz$ClientboundBlockDestructionPacket, int.class, clazz$BlockPos, int.class
)
);
// public static final Constructor<?> constructor$ClientboundBlockDestructionPacket = requireNonNull(
// ReflectionUtils.getConstructor(
// clazz$ClientboundBlockDestructionPacket, int.class, clazz$BlockPos, int.class
// )
// );
public static final Class<?> clazz$ServerboundSwingPacket = requireNonNull(
ReflectionUtils.getClazz(
@@ -4955,11 +4961,11 @@ public class Reflections {
)
);
public static final Constructor<?> constructor$ClientboundLevelEventPacket = requireNonNull(
ReflectionUtils.getConstructor(
clazz$ClientboundLevelEventPacket, int.class, clazz$BlockPos, int.class, boolean.class
)
);
// public static final Constructor<?> constructor$ClientboundLevelEventPacket = requireNonNull(
// ReflectionUtils.getConstructor(
// clazz$ClientboundLevelEventPacket, int.class, clazz$BlockPos, int.class, boolean.class
// )
// );
public static final Field field$ClientboundLevelEventPacket$eventId = requireNonNull(
ReflectionUtils.getDeclaredField(

View File

@@ -17,6 +17,8 @@ public class BlockSettings {
float resistance = 2f;
boolean canOcclude;
boolean fluidState;
boolean requireCorrectTools;
boolean respectToolComponent;
Tristate isRedstoneConductor = Tristate.UNDEFINED;
Tristate isSuffocating = Tristate.UNDEFINED;
Tristate isViewBlocking = Tristate.UNDEFINED;
@@ -74,6 +76,8 @@ public class BlockSettings {
newSettings.itemId = settings.itemId;
newSettings.tags = settings.tags;
newSettings.burnChance = settings.burnChance;
newSettings.requireCorrectTools = settings.requireCorrectTools;
newSettings.respectToolComponent = settings.respectToolComponent;
newSettings.fireSpreadChance = settings.fireSpreadChance;
newSettings.isRedstoneConductor = settings.isRedstoneConductor;
newSettings.isSuffocating = settings.isSuffocating;
@@ -130,6 +134,10 @@ public class BlockSettings {
return incorrectToolSpeed;
}
public boolean requireCorrectTool() {
return requireCorrectTools || !correctTools.isEmpty();
}
public String name() {
return name;
}
@@ -175,10 +183,13 @@ public class BlockSettings {
}
public boolean isCorrectTool(Key key) {
if (this.correctTools.isEmpty()) return true;
return this.correctTools.contains(key);
}
public boolean respectToolComponent() {
return respectToolComponent;
}
public BlockSettings correctTools(Set<Key> correctTools) {
this.correctTools = correctTools;
return this;
@@ -249,6 +260,16 @@ public class BlockSettings {
return this;
}
public BlockSettings requireCorrectTool(boolean requireCorrectTool) {
this.requireCorrectTools = requireCorrectTool;
return this;
}
public BlockSettings respectToolComponent(boolean respectToolComponent) {
this.respectToolComponent = respectToolComponent;
return this;
}
public BlockSettings incorrectToolSpeed(float incorrectToolSpeed) {
this.incorrectToolSpeed = incorrectToolSpeed;
return this;
@@ -392,6 +413,14 @@ public class BlockSettings {
List<String> tools = MiscUtils.getAsStringList(value);
return settings -> settings.correctTools(tools.stream().map(Key::of).collect(Collectors.toSet()));
}));
registerFactory("require-correct-tools", (value -> {
boolean booleanValue = (boolean) value;
return settings -> settings.requireCorrectTool(booleanValue);
}));
registerFactory("respect-tool-component", (value -> {
boolean booleanValue = (boolean) value;
return settings -> settings.respectToolComponent(booleanValue);
}));
registerFactory("incorrect-tool-dig-speed", (value -> {
float floatValue = MiscUtils.getAsFloat(value);
return settings -> settings.incorrectToolSpeed(floatValue);

View File

@@ -5,11 +5,11 @@ import net.momirealms.craftengine.core.entity.Entity;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.plugin.network.NetWorkUser;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.world.BlockHitResult;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.FluidCollisionRule;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public abstract class Player extends Entity implements NetWorkUser {
public abstract boolean isSecondaryUseActive();
@@ -23,8 +23,12 @@ public abstract class Player extends Entity implements NetWorkUser {
@Override
public abstract Object serverPlayer();
public abstract void sendPackets(List<Object> packet, boolean immediately);
public abstract float getDestroyProgress(Object blockState, BlockPos pos);
public abstract void setClientSideCanBreakBlock(boolean canBreak);
public abstract void stopMiningBlock();
public abstract void preventMiningBlock();
@@ -33,8 +37,6 @@ public abstract class Player extends Entity implements NetWorkUser {
public abstract double getInteractionRange();
public abstract void abortDestroyProgress();
public abstract void onSwingHand();
public abstract boolean isMiningBlock();

View File

@@ -111,7 +111,10 @@ public class Config {
protected boolean furniture$hide_base_entity;
protected boolean block$sound_system$enable;
protected boolean block$simply_adventure_break_check;
protected boolean block$simplify_adventure_break_check;
protected boolean block$predict_breaking;
protected int block$predict_breaking_interval;
protected double block$extended_interaction_range;
protected boolean recipe$enable;
protected boolean recipe$disable_vanilla_recipes$all;
@@ -282,7 +285,10 @@ public class Config {
// block
block$sound_system$enable = config.getBoolean("block.sound-system.enable", true);
block$simply_adventure_break_check = config.getBoolean("block.simplify-adventure-break-check", true);
block$simplify_adventure_break_check = config.getBoolean("block.simplify-adventure-break-check", false);
block$predict_breaking = config.getBoolean("block.predict-breaking.enable", true);
block$predict_breaking_interval = Math.max(config.getInt("block.predict-breaking.interval", 10), 1);
block$extended_interaction_range = Math.max(config.getDouble("block.predict-breaking.extended-interaction-range", 0.5), 0.0);
// recipe
recipe$enable = config.getBoolean("recipe.enable", true);
@@ -392,8 +398,8 @@ public class Config {
return instance.block$sound_system$enable;
}
public static boolean simplyAdventureCheck() {
return instance.block$simply_adventure_break_check;
public static boolean simplifyAdventureCheck() {
return instance.block$simplify_adventure_break_check;
}
public static boolean enableRecipeSystem() {
@@ -668,6 +674,18 @@ public class Config {
return instance.image$intercept_packets$set_score;
}
public static boolean predictBreaking() {
return instance.block$predict_breaking;
}
public static int predictBreakingInterval() {
return instance.block$predict_breaking_interval;
}
public static double extendedInteractionRange() {
return instance.block$extended_interaction_range;
}
public YamlDocument loadOrCreateYamlData(String fileName) {
File file = new File(this.plugin.dataFolderFile(), fileName);
if (!file.exists()) {

View File

@@ -0,0 +1,87 @@
package net.momirealms.craftengine.core.world;
public class WorldEvents {
public static final int DISPENSER_DISPENSES = 1000;
public static final int DISPENSER_FAILS_TO_DISPENSE = 1001;
public static final int DISPENSER_SHOOTS = 1002;
public static final int FIREWORK_SHOT = 1004;
public static final int FIRE_EXTINGUISHED = 1009;
public static final int PLAY_RECORD = 1010;
public static final int STOP_RECORD = 1011;
public static final int GHAST_WARNS = 1015;
public static final int GHAST_SHOOTS = 1016;
public static final int ENDER_DRAGON_SHOOTS = 1017;
public static final int BLAZE_SHOOTS = 1018;
public static final int ZOMBIE_ATTACKS_WOODEN_DOOR = 1019;
public static final int ZOMBIE_ATTACKS_IRON_DOOR = 1020;
public static final int ZOMBIE_BREAKS_WOODEN_DOOR = 1021;
public static final int WITHER_BREAKS_BLOCK = 1022;
public static final int WITHER_SPAWNED = 1023;
public static final int WITHER_SHOOTS = 1024;
public static final int BAT_TAKES_OFF = 1025;
public static final int ZOMBIE_INFECTS = 1026;
public static final int ZOMBIE_VILLAGER_CONVERTED = 1027;
public static final int ENDER_DRAGON_DIES = 1028;
public static final int ANVIL_DESTROYED = 1029;
public static final int ANVIL_USED = 1030;
public static final int ANVIL_LANDS = 1031;
public static final int PORTAL_TRAVEL = 1032;
public static final int CHORUS_FLOWER_GROWS = 1033;
public static final int CHORUS_FLOWER_DIES = 1034;
public static final int BREWING_STAND_BREWS = 1035;
public static final int END_PORTAL_CREATED = 1038;
public static final int PHANTOM_BITES = 1039;
public static final int ZOMBIE_CONVERTS_TO_DROWNED = 1040;
public static final int HUSK_CONVERTS_TO_ZOMBIE_BY_DROWNING = 1041;
public static final int GRINDSTONE_USED = 1042;
public static final int BOOK_PAGE_TURNED = 1043;
public static final int SMITHING_TABLE_USED = 1044;
public static final int POINTED_DRIPSTONE_LANDING = 1045;
public static final int LAVA_DRIPPING_ON_CAULDRON_FROM_DRIPSTONE = 1046;
public static final int WATER_DRIPPING_ON_CAULDRON_FROM_DRIPSTONE = 1047;
public static final int SKELETON_CONVERTS_TO_STRAY = 1048;
public static final int CRAFTER_SUCCESSFULLY_CRAFTS_ITEM = 1049;
public static final int CRAFTER_FAILS_TO_CRAFT_ITEM = 1050;
public static final int COMPOSTER_COMPOSTS = 1500;
public static final int LAVA_CONVERTS_BLOCK = 1501;
public static final int REDSTONE_TORCH_BURNS_OUT = 1502;
public static final int ENDER_EYE_PLACED_IN_END_PORTAL_FRAME = 1503;
public static final int FLUID_DRIPS_FROM_DRIPSTONE = 1504;
public static final int BONE_MEAL_PARTICLES_AND_SOUND = 1505;
public static final int DISPENSER_ACTIVATION_SMOKE = 2000;
public static final int BLOCK_BREAK_EFFECT = 2001;
public static final int SPLASH_POTION_EFFECT = 2002;
public static final int ENDER_EYE_ENTITY_BREAK_ANIMATION = 2003;
public static final int SPAWNER_SPAWNS_MOB = 2004;
public static final int DRAGON_BREATH = 2006;
public static final int INSTANT_SPLASH_POTION = 2007;
public static final int ENDER_DRAGON_DESTROYS_BLOCK = 2008;
public static final int WET_SPONGE_VAPORIZES = 2009;
public static final int CRAFTER_ACTIVATION_SMOKE = 2010;
public static final int BEE_FERTILIZES_PLANT = 2011;
public static final int TURTLE_EGG_PLACED = 2012;
public static final int SMASH_ATTACK = 2013;
public static final int END_GATEWAY_SPAWNS = 3000;
public static final int ENDER_DRAGON_RESURRECTED = 3001;
public static final int ELECTRIC_SPARK = 3002;
public static final int COPPER_APPLY_WAX = 3003;
public static final int COPPER_REMOVE_WAX = 3004;
public static final int COPPER_SCRAPE_OXIDATION = 3005;
public static final int SCULK_CHARGE = 3006;
public static final int SCULK_SHRIEKER_SHRIEK = 3007;
public static final int BLOCK_FINISHED_BRUSHING = 3008;
public static final int SNIFFER_EGG_CRACKS = 3009;
public static final int TRIAL_SPAWNER_SPAWNS_MOB_AT_SPAWNER = 3011;
public static final int TRIAL_SPAWNER_SPAWNS_MOB_AT_LOCATION = 3012;
public static final int TRIAL_SPAWNER_DETECTS_PLAYER = 3013;
public static final int TRIAL_SPAWNER_EJECTS_ITEM = 3014;
public static final int VAULT_ACTIVATES = 3015;
public static final int VAULT_DEACTIVATES = 3016;
public static final int VAULT_EJECTS_ITEM = 3017;
public static final int COBWEB_WEAVED = 3018;
public static final int OMINOUS_TRIAL_SPAWNER_DETECTS_PLAYER = 3019;
public static final int TRIAL_SPAWNER_TURNS_OMINOUS = 3020;
public static final int OMINOUS_ITEM_SPAWNER_SPAWNS_ITEM = 3021;
private WorldEvents() {}
}

View File

@@ -51,7 +51,7 @@ byte_buddy_version=1.17.5
ahocorasick_version=0.6.3
snake_yaml_version=2.4
anti_grief_version=0.13
nms_helper_version=0.58.1
nms_helper_version=0.58.7
# Ignite Dependencies
mixinextras_version=0.4.1
mixin_version=0.15.2+mixin.0.8.7