365 lines
17 KiB
Java
365 lines
17 KiB
Java
/*
|
|
* Decompiled with CFR 0.152.
|
|
*
|
|
* Could not load the following classes:
|
|
* com.mojang.logging.LogUtils
|
|
* org.jspecify.annotations.Nullable
|
|
* org.slf4j.Logger
|
|
*/
|
|
package net.minecraft.server.level;
|
|
|
|
import com.mojang.logging.LogUtils;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
import net.minecraft.SharedConstants;
|
|
import net.minecraft.advancements.CriteriaTriggers;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Direction;
|
|
import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;
|
|
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
|
|
import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.server.level.ServerPlayer;
|
|
import net.minecraft.world.InteractionHand;
|
|
import net.minecraft.world.InteractionResult;
|
|
import net.minecraft.world.MenuProvider;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.entity.EquipmentSlot;
|
|
import net.minecraft.world.entity.player.Abilities;
|
|
import net.minecraft.world.item.Item;
|
|
import net.minecraft.world.item.ItemStack;
|
|
import net.minecraft.world.item.context.UseOnContext;
|
|
import net.minecraft.world.item.enchantment.EnchantmentHelper;
|
|
import net.minecraft.world.level.GameType;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.block.Block;
|
|
import net.minecraft.world.level.block.GameMasterBlock;
|
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.phys.BlockHitResult;
|
|
import net.minecraft.world.phys.Vec3;
|
|
import net.minecraft.world.phys.shapes.VoxelShape;
|
|
import org.jspecify.annotations.Nullable;
|
|
import org.slf4j.Logger;
|
|
|
|
public class ServerPlayerGameMode {
|
|
private static final double FLIGHT_DISABLE_RANGE = 1.0;
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
protected ServerLevel level;
|
|
protected final ServerPlayer player;
|
|
private GameType gameModeForPlayer = GameType.DEFAULT_MODE;
|
|
private @Nullable GameType previousGameModeForPlayer;
|
|
private boolean isDestroyingBlock;
|
|
private int destroyProgressStart;
|
|
private BlockPos destroyPos = BlockPos.ZERO;
|
|
private int gameTicks;
|
|
private boolean hasDelayedDestroy;
|
|
private BlockPos delayedDestroyPos = BlockPos.ZERO;
|
|
private int delayedTickStart;
|
|
private int lastSentState = -1;
|
|
|
|
public ServerPlayerGameMode(ServerPlayer player) {
|
|
this.player = player;
|
|
this.level = player.level();
|
|
}
|
|
|
|
public boolean changeGameModeForPlayer(GameType gameModeForPlayer) {
|
|
if (gameModeForPlayer == this.gameModeForPlayer) {
|
|
return false;
|
|
}
|
|
Abilities abilities = this.player.getAbilities();
|
|
this.setGameModeForPlayer(gameModeForPlayer, this.gameModeForPlayer);
|
|
if (abilities.flying && gameModeForPlayer != GameType.SPECTATOR && this.isInRangeOfGround()) {
|
|
abilities.flying = false;
|
|
}
|
|
this.player.onUpdateAbilities();
|
|
this.level.getServer().getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, this.player));
|
|
this.level.updateSleepingPlayerList();
|
|
if (gameModeForPlayer == GameType.CREATIVE) {
|
|
this.player.resetCurrentImpulseContext();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
protected void setGameModeForPlayer(GameType gameModeForPlayer, @Nullable GameType previousGameModeForPlayer) {
|
|
this.previousGameModeForPlayer = previousGameModeForPlayer;
|
|
this.gameModeForPlayer = gameModeForPlayer;
|
|
Abilities abilities = this.player.getAbilities();
|
|
gameModeForPlayer.updatePlayerAbilities(abilities);
|
|
}
|
|
|
|
private boolean isInRangeOfGround() {
|
|
List<VoxelShape> clipping = Entity.collectAllColliders(this.player, this.level, this.player.getBoundingBox());
|
|
return clipping.isEmpty() && this.player.getAvailableSpaceBelow(1.0) < 1.0;
|
|
}
|
|
|
|
public GameType getGameModeForPlayer() {
|
|
return this.gameModeForPlayer;
|
|
}
|
|
|
|
public @Nullable GameType getPreviousGameModeForPlayer() {
|
|
return this.previousGameModeForPlayer;
|
|
}
|
|
|
|
public boolean isSurvival() {
|
|
return this.gameModeForPlayer.isSurvival();
|
|
}
|
|
|
|
public boolean isCreative() {
|
|
return this.gameModeForPlayer.isCreative();
|
|
}
|
|
|
|
public void tick() {
|
|
++this.gameTicks;
|
|
if (this.hasDelayedDestroy) {
|
|
BlockState blockState = this.level.getBlockState(this.delayedDestroyPos);
|
|
if (blockState.isAir()) {
|
|
this.hasDelayedDestroy = false;
|
|
} else {
|
|
float destroyProgress = this.incrementDestroyProgress(blockState, this.delayedDestroyPos, this.delayedTickStart);
|
|
if (destroyProgress >= 1.0f) {
|
|
this.hasDelayedDestroy = false;
|
|
this.destroyBlock(this.delayedDestroyPos);
|
|
}
|
|
}
|
|
} else if (this.isDestroyingBlock) {
|
|
BlockState blockState = this.level.getBlockState(this.destroyPos);
|
|
if (blockState.isAir()) {
|
|
this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1);
|
|
this.lastSentState = -1;
|
|
this.isDestroyingBlock = false;
|
|
} else {
|
|
this.incrementDestroyProgress(blockState, this.destroyPos, this.destroyProgressStart);
|
|
}
|
|
}
|
|
}
|
|
|
|
private float incrementDestroyProgress(BlockState blockState, BlockPos delayedDestroyPos, int destroyStartTick) {
|
|
int ticksSpentDestroying = this.gameTicks - destroyStartTick;
|
|
float destroyProgress = blockState.getDestroyProgress(this.player, this.player.level(), delayedDestroyPos) * (float)(ticksSpentDestroying + 1);
|
|
int state = (int)(destroyProgress * 10.0f);
|
|
if (state != this.lastSentState) {
|
|
this.level.destroyBlockProgress(this.player.getId(), delayedDestroyPos, state);
|
|
this.lastSentState = state;
|
|
}
|
|
return destroyProgress;
|
|
}
|
|
|
|
private void debugLogging(BlockPos pos, boolean allGood, int sequence, String message) {
|
|
if (SharedConstants.DEBUG_BLOCK_BREAK) {
|
|
LOGGER.debug("Server ACK {} {} {} {}", new Object[]{sequence, pos, allGood, message});
|
|
}
|
|
}
|
|
|
|
public void handleBlockBreakAction(BlockPos pos, ServerboundPlayerActionPacket.Action action, Direction direction, int maxY, int sequence) {
|
|
if (!this.player.canInteractWithBlock(pos, 1.0)) {
|
|
this.debugLogging(pos, false, sequence, "too far");
|
|
return;
|
|
}
|
|
if (pos.getY() > maxY) {
|
|
this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos)));
|
|
this.debugLogging(pos, false, sequence, "too high");
|
|
return;
|
|
}
|
|
if (action == ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK) {
|
|
if (!this.level.mayInteract(this.player, pos)) {
|
|
this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos)));
|
|
this.debugLogging(pos, false, sequence, "may not interact");
|
|
return;
|
|
}
|
|
if (this.player.getAbilities().instabuild) {
|
|
this.destroyAndAck(pos, sequence, "creative destroy");
|
|
return;
|
|
}
|
|
if (this.player.blockActionRestricted(this.level, pos, this.gameModeForPlayer)) {
|
|
this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos)));
|
|
this.debugLogging(pos, false, sequence, "block action restricted");
|
|
return;
|
|
}
|
|
this.destroyProgressStart = this.gameTicks;
|
|
float progress = 1.0f;
|
|
BlockState blockState = this.level.getBlockState(pos);
|
|
if (!blockState.isAir()) {
|
|
EnchantmentHelper.onHitBlock(this.level, this.player.getMainHandItem(), this.player, this.player, EquipmentSlot.MAINHAND, Vec3.atCenterOf(pos), blockState, item -> this.player.onEquippedItemBroken((Item)item, EquipmentSlot.MAINHAND));
|
|
blockState.attack(this.level, pos, this.player);
|
|
progress = blockState.getDestroyProgress(this.player, this.player.level(), pos);
|
|
}
|
|
if (!blockState.isAir() && progress >= 1.0f) {
|
|
this.destroyAndAck(pos, sequence, "insta mine");
|
|
} else {
|
|
if (this.isDestroyingBlock) {
|
|
this.player.connection.send(new ClientboundBlockUpdatePacket(this.destroyPos, this.level.getBlockState(this.destroyPos)));
|
|
this.debugLogging(pos, false, sequence, "abort destroying since another started (client insta mine, server disagreed)");
|
|
}
|
|
this.isDestroyingBlock = true;
|
|
this.destroyPos = pos.immutable();
|
|
int state = (int)(progress * 10.0f);
|
|
this.level.destroyBlockProgress(this.player.getId(), pos, state);
|
|
this.debugLogging(pos, true, sequence, "actual start of destroying");
|
|
this.lastSentState = state;
|
|
}
|
|
} else if (action == ServerboundPlayerActionPacket.Action.STOP_DESTROY_BLOCK) {
|
|
if (pos.equals(this.destroyPos)) {
|
|
int ticksSpentDestroying = this.gameTicks - this.destroyProgressStart;
|
|
BlockState state = this.level.getBlockState(pos);
|
|
if (!state.isAir()) {
|
|
float destroyProgress = state.getDestroyProgress(this.player, this.player.level(), pos) * (float)(ticksSpentDestroying + 1);
|
|
if (destroyProgress >= 0.7f) {
|
|
this.isDestroyingBlock = false;
|
|
this.level.destroyBlockProgress(this.player.getId(), pos, -1);
|
|
this.destroyAndAck(pos, sequence, "destroyed");
|
|
return;
|
|
}
|
|
if (!this.hasDelayedDestroy) {
|
|
this.isDestroyingBlock = false;
|
|
this.hasDelayedDestroy = true;
|
|
this.delayedDestroyPos = pos;
|
|
this.delayedTickStart = this.destroyProgressStart;
|
|
}
|
|
}
|
|
}
|
|
this.debugLogging(pos, true, sequence, "stopped destroying");
|
|
} else if (action == ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK) {
|
|
this.isDestroyingBlock = false;
|
|
if (!Objects.equals(this.destroyPos, pos)) {
|
|
LOGGER.warn("Mismatch in destroy block pos: {} {}", (Object)this.destroyPos, (Object)pos);
|
|
this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1);
|
|
this.debugLogging(pos, true, sequence, "aborted mismatched destroying");
|
|
}
|
|
this.level.destroyBlockProgress(this.player.getId(), pos, -1);
|
|
this.debugLogging(pos, true, sequence, "aborted destroying");
|
|
}
|
|
}
|
|
|
|
public void destroyAndAck(BlockPos pos, int sequence, String exitId) {
|
|
if (this.destroyBlock(pos)) {
|
|
this.debugLogging(pos, true, sequence, exitId);
|
|
} else {
|
|
this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos)));
|
|
this.debugLogging(pos, false, sequence, exitId);
|
|
}
|
|
}
|
|
|
|
public boolean destroyBlock(BlockPos pos) {
|
|
BlockState state = this.level.getBlockState(pos);
|
|
if (!this.player.getMainHandItem().canDestroyBlock(state, this.level, pos, this.player)) {
|
|
return false;
|
|
}
|
|
BlockEntity blockEntity = this.level.getBlockEntity(pos);
|
|
Block block = state.getBlock();
|
|
if (block instanceof GameMasterBlock && !this.player.canUseGameMasterBlocks()) {
|
|
this.level.sendBlockUpdated(pos, state, state, 3);
|
|
return false;
|
|
}
|
|
if (this.player.blockActionRestricted(this.level, pos, this.gameModeForPlayer)) {
|
|
return false;
|
|
}
|
|
BlockState adjustedState = block.playerWillDestroy(this.level, pos, state, this.player);
|
|
boolean changed = this.level.removeBlock(pos, false);
|
|
if (SharedConstants.DEBUG_BLOCK_BREAK) {
|
|
LOGGER.info("server broke {} {} -> {}", new Object[]{pos, adjustedState, this.level.getBlockState(pos)});
|
|
}
|
|
if (changed) {
|
|
block.destroy(this.level, pos, adjustedState);
|
|
}
|
|
if (this.player.preventsBlockDrops()) {
|
|
return true;
|
|
}
|
|
ItemStack itemStack = this.player.getMainHandItem();
|
|
ItemStack destroyedWith = itemStack.copy();
|
|
boolean canDestroy = this.player.hasCorrectToolForDrops(adjustedState);
|
|
itemStack.mineBlock(this.level, adjustedState, pos, this.player);
|
|
if (changed && canDestroy) {
|
|
block.playerDestroy(this.level, this.player, pos, adjustedState, blockEntity, destroyedWith);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public InteractionResult useItem(ServerPlayer player, Level level, ItemStack itemStack, InteractionHand hand) {
|
|
ItemStack resultStack;
|
|
if (this.gameModeForPlayer == GameType.SPECTATOR) {
|
|
return InteractionResult.PASS;
|
|
}
|
|
if (player.getCooldowns().isOnCooldown(itemStack)) {
|
|
return InteractionResult.PASS;
|
|
}
|
|
int oldCount = itemStack.getCount();
|
|
int oldDamage = itemStack.getDamageValue();
|
|
InteractionResult result = itemStack.use(level, player, hand);
|
|
if (result instanceof InteractionResult.Success) {
|
|
InteractionResult.Success success = (InteractionResult.Success)result;
|
|
resultStack = Objects.requireNonNullElse(success.heldItemTransformedTo(), player.getItemInHand(hand));
|
|
} else {
|
|
resultStack = player.getItemInHand(hand);
|
|
}
|
|
if (resultStack == itemStack && resultStack.getCount() == oldCount && resultStack.getUseDuration(player) <= 0 && resultStack.getDamageValue() == oldDamage) {
|
|
return result;
|
|
}
|
|
if (result instanceof InteractionResult.Fail && resultStack.getUseDuration(player) > 0 && !player.isUsingItem()) {
|
|
return result;
|
|
}
|
|
if (itemStack != resultStack) {
|
|
player.setItemInHand(hand, resultStack);
|
|
}
|
|
if (resultStack.isEmpty()) {
|
|
player.setItemInHand(hand, ItemStack.EMPTY);
|
|
}
|
|
if (!player.isUsingItem()) {
|
|
player.inventoryMenu.sendAllDataToRemote();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public InteractionResult useItemOn(ServerPlayer player, Level level, ItemStack itemStack, InteractionHand hand, BlockHitResult hitResult) {
|
|
InteractionResult success;
|
|
BlockPos pos = hitResult.getBlockPos();
|
|
BlockState state = level.getBlockState(pos);
|
|
if (!state.getBlock().isEnabled(level.enabledFeatures())) {
|
|
return InteractionResult.FAIL;
|
|
}
|
|
if (this.gameModeForPlayer == GameType.SPECTATOR) {
|
|
MenuProvider menuProvider = state.getMenuProvider(level, pos);
|
|
if (menuProvider != null) {
|
|
player.openMenu(menuProvider);
|
|
return InteractionResult.CONSUME;
|
|
}
|
|
return InteractionResult.PASS;
|
|
}
|
|
boolean haveSomethingInOurHands = !player.getMainHandItem().isEmpty() || !player.getOffhandItem().isEmpty();
|
|
boolean suppressUsingBlock = player.isSecondaryUseActive() && haveSomethingInOurHands;
|
|
ItemStack usedItemStack = itemStack.copy();
|
|
if (!suppressUsingBlock) {
|
|
InteractionResult use;
|
|
InteractionResult itemUse = state.useItemOn(player.getItemInHand(hand), level, player, hand, hitResult);
|
|
if (itemUse.consumesAction()) {
|
|
CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger(player, pos, usedItemStack);
|
|
return itemUse;
|
|
}
|
|
if (itemUse instanceof InteractionResult.TryEmptyHandInteraction && hand == InteractionHand.MAIN_HAND && (use = state.useWithoutItem(level, player, hitResult)).consumesAction()) {
|
|
CriteriaTriggers.DEFAULT_BLOCK_USE.trigger(player, pos);
|
|
return use;
|
|
}
|
|
}
|
|
if (itemStack.isEmpty() || player.getCooldowns().isOnCooldown(itemStack)) {
|
|
return InteractionResult.PASS;
|
|
}
|
|
UseOnContext context = new UseOnContext(player, hand, hitResult);
|
|
if (player.hasInfiniteMaterials()) {
|
|
int count = itemStack.getCount();
|
|
success = itemStack.useOn(context);
|
|
itemStack.setCount(count);
|
|
} else {
|
|
success = itemStack.useOn(context);
|
|
}
|
|
if (success.consumesAction()) {
|
|
CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger(player, pos, usedItemStack);
|
|
}
|
|
return success;
|
|
}
|
|
|
|
public void setLevel(ServerLevel newLevel) {
|
|
this.level = newLevel;
|
|
}
|
|
}
|
|
|