/* * Decompiled with CFR 0.152. * * Could not load the following classes: * com.google.common.collect.Lists * com.google.common.primitives.Shorts * com.google.common.primitives.SignedBytes * com.mojang.logging.LogUtils * it.unimi.dsi.fastutil.ints.Int2ObjectMap * it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap * org.apache.commons.lang3.mutable.MutableObject * org.jspecify.annotations.Nullable * org.slf4j.Logger */ package net.minecraft.client.multiplayer; import com.google.common.collect.Lists; import com.google.common.primitives.Shorts; import com.google.common.primitives.SignedBytes; import com.mojang.logging.LogUtils; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import java.util.ArrayList; import java.util.Objects; import net.minecraft.SharedConstants; import net.minecraft.client.ClientRecipeBook; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.client.gui.screens.inventory.CreativeModeInventoryScreen; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.multiplayer.ClientPacketListener; import net.minecraft.client.multiplayer.prediction.BlockStatePredictionHandler; import net.minecraft.client.multiplayer.prediction.PredictiveAction; import net.minecraft.client.player.LocalPlayer; import net.minecraft.client.resources.sounds.SimpleSoundInstance; import net.minecraft.client.resources.sounds.SoundInstance; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.NonNullList; import net.minecraft.network.HashedStack; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ServerGamePacketListener; import net.minecraft.network.protocol.game.ServerboundContainerButtonClickPacket; import net.minecraft.network.protocol.game.ServerboundContainerClickPacket; import net.minecraft.network.protocol.game.ServerboundContainerSlotStateChangedPacket; import net.minecraft.network.protocol.game.ServerboundInteractPacket; import net.minecraft.network.protocol.game.ServerboundPickItemFromBlockPacket; import net.minecraft.network.protocol.game.ServerboundPickItemFromEntityPacket; import net.minecraft.network.protocol.game.ServerboundPlaceRecipePacket; import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket; import net.minecraft.network.protocol.game.ServerboundSetCarriedItemPacket; import net.minecraft.network.protocol.game.ServerboundSetCreativeModeSlotPacket; import net.minecraft.network.protocol.game.ServerboundUseItemOnPacket; import net.minecraft.network.protocol.game.ServerboundUseItemPacket; import net.minecraft.sounds.SoundSource; import net.minecraft.stats.StatsCounter; import net.minecraft.util.Mth; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.HasCustomInventoryScreen; import net.minecraft.world.entity.player.Input; import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.ClickType; import net.minecraft.world.inventory.Slot; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.component.PiercingWeapon; import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.item.crafting.display.RecipeDisplayId; import net.minecraft.world.level.GameType; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.GameMasterBlock; import net.minecraft.world.level.block.SoundType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.material.FluidState; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.EntityHitResult; import net.minecraft.world.phys.Vec3; import org.apache.commons.lang3.mutable.MutableObject; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; public class MultiPlayerGameMode { private static final Logger LOGGER = LogUtils.getLogger(); private final Minecraft minecraft; private final ClientPacketListener connection; private BlockPos destroyBlockPos = new BlockPos(-1, -1, -1); private ItemStack destroyingItem = ItemStack.EMPTY; private float destroyProgress; private float destroyTicks; private int destroyDelay; private boolean isDestroying; private GameType localPlayerMode = GameType.DEFAULT_MODE; private @Nullable GameType previousLocalPlayerMode; private int carriedIndex; public MultiPlayerGameMode(Minecraft minecraft, ClientPacketListener connection) { this.minecraft = minecraft; this.connection = connection; } public void adjustPlayer(Player player) { this.localPlayerMode.updatePlayerAbilities(player.getAbilities()); } public void setLocalMode(GameType mode, @Nullable GameType previousMode) { this.localPlayerMode = mode; this.previousLocalPlayerMode = previousMode; this.localPlayerMode.updatePlayerAbilities(this.minecraft.player.getAbilities()); } public void setLocalMode(GameType mode) { if (mode != this.localPlayerMode) { this.previousLocalPlayerMode = this.localPlayerMode; } this.localPlayerMode = mode; this.localPlayerMode.updatePlayerAbilities(this.minecraft.player.getAbilities()); } public boolean canHurtPlayer() { return this.localPlayerMode.isSurvival(); } public boolean destroyBlock(BlockPos pos) { if (this.minecraft.player.blockActionRestricted(this.minecraft.level, pos, this.localPlayerMode)) { return false; } ClientLevel level = this.minecraft.level; BlockState oldState = level.getBlockState(pos); if (!this.minecraft.player.getMainHandItem().canDestroyBlock(oldState, level, pos, this.minecraft.player)) { return false; } Block oldBlock = oldState.getBlock(); if (oldBlock instanceof GameMasterBlock && !this.minecraft.player.canUseGameMasterBlocks()) { return false; } if (oldState.isAir()) { return false; } oldBlock.playerWillDestroy(level, pos, oldState, this.minecraft.player); FluidState fluidState = level.getFluidState(pos); boolean changed = level.setBlock(pos, fluidState.createLegacyBlock(), 11); if (changed) { oldBlock.destroy(level, pos, oldState); } if (SharedConstants.DEBUG_BLOCK_BREAK) { LOGGER.error("client broke {} {} -> {}", new Object[]{pos, oldState, level.getBlockState(pos)}); } return changed; } public boolean startDestroyBlock(BlockPos pos, Direction direction) { if (this.minecraft.player.blockActionRestricted(this.minecraft.level, pos, this.localPlayerMode)) { return false; } if (!this.minecraft.level.getWorldBorder().isWithinBounds(pos)) { return false; } if (this.minecraft.player.getAbilities().instabuild) { BlockState state = this.minecraft.level.getBlockState(pos); this.minecraft.getTutorial().onDestroyBlock(this.minecraft.level, pos, state, 1.0f); if (SharedConstants.DEBUG_BLOCK_BREAK) { LOGGER.info("Creative start {} {}", (Object)pos, (Object)state); } this.startPrediction(this.minecraft.level, sequence -> { this.destroyBlock(pos); return new ServerboundPlayerActionPacket(ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK, pos, direction, sequence); }); this.destroyDelay = 5; } else if (!this.isDestroying || !this.sameDestroyTarget(pos)) { if (this.isDestroying) { if (SharedConstants.DEBUG_BLOCK_BREAK) { LOGGER.info("Abort old break {} {}", (Object)pos, (Object)this.minecraft.level.getBlockState(pos)); } this.connection.send(new ServerboundPlayerActionPacket(ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK, this.destroyBlockPos, direction)); } BlockState state = this.minecraft.level.getBlockState(pos); this.minecraft.getTutorial().onDestroyBlock(this.minecraft.level, pos, state, 0.0f); if (SharedConstants.DEBUG_BLOCK_BREAK) { LOGGER.info("Start break {} {}", (Object)pos, (Object)state); } this.startPrediction(this.minecraft.level, sequence -> { boolean notAir; boolean bl = notAir = !state.isAir(); if (notAir && this.destroyProgress == 0.0f) { state.attack(this.minecraft.level, pos, this.minecraft.player); } if (notAir && state.getDestroyProgress(this.minecraft.player, this.minecraft.player.level(), pos) >= 1.0f) { this.destroyBlock(pos); } else { this.isDestroying = true; this.destroyBlockPos = pos; this.destroyingItem = this.minecraft.player.getMainHandItem(); this.destroyProgress = 0.0f; this.destroyTicks = 0.0f; this.minecraft.level.destroyBlockProgress(this.minecraft.player.getId(), this.destroyBlockPos, this.getDestroyStage()); } return new ServerboundPlayerActionPacket(ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK, pos, direction, sequence); }); } return true; } public void stopDestroyBlock() { if (this.isDestroying) { BlockState state = this.minecraft.level.getBlockState(this.destroyBlockPos); this.minecraft.getTutorial().onDestroyBlock(this.minecraft.level, this.destroyBlockPos, state, -1.0f); if (SharedConstants.DEBUG_BLOCK_BREAK) { LOGGER.info("Stop dest {} {}", (Object)this.destroyBlockPos, (Object)state); } this.connection.send(new ServerboundPlayerActionPacket(ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK, this.destroyBlockPos, Direction.DOWN)); this.isDestroying = false; this.destroyProgress = 0.0f; this.minecraft.level.destroyBlockProgress(this.minecraft.player.getId(), this.destroyBlockPos, -1); this.minecraft.player.resetAttackStrengthTicker(); } } public boolean continueDestroyBlock(BlockPos pos, Direction direction) { this.ensureHasSentCarriedItem(); if (this.destroyDelay > 0) { --this.destroyDelay; return true; } if (this.minecraft.player.getAbilities().instabuild && this.minecraft.level.getWorldBorder().isWithinBounds(pos)) { this.destroyDelay = 5; BlockState state = this.minecraft.level.getBlockState(pos); this.minecraft.getTutorial().onDestroyBlock(this.minecraft.level, pos, state, 1.0f); if (SharedConstants.DEBUG_BLOCK_BREAK) { LOGGER.info("Creative cont {} {}", (Object)pos, (Object)state); } this.startPrediction(this.minecraft.level, sequence -> { this.destroyBlock(pos); return new ServerboundPlayerActionPacket(ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK, pos, direction, sequence); }); return true; } if (this.sameDestroyTarget(pos)) { BlockState state = this.minecraft.level.getBlockState(pos); if (state.isAir()) { this.isDestroying = false; return false; } this.destroyProgress += state.getDestroyProgress(this.minecraft.player, this.minecraft.player.level(), pos); if (this.destroyTicks % 4.0f == 0.0f) { SoundType soundType = state.getSoundType(); this.minecraft.getSoundManager().play(new SimpleSoundInstance(soundType.getHitSound(), SoundSource.BLOCKS, (soundType.getVolume() + 1.0f) / 8.0f, soundType.getPitch() * 0.5f, SoundInstance.createUnseededRandom(), pos)); } this.destroyTicks += 1.0f; this.minecraft.getTutorial().onDestroyBlock(this.minecraft.level, pos, state, Mth.clamp(this.destroyProgress, 0.0f, 1.0f)); if (this.destroyProgress >= 1.0f) { this.isDestroying = false; if (SharedConstants.DEBUG_BLOCK_BREAK) { LOGGER.info("Finished breaking {} {}", (Object)pos, (Object)state); } this.startPrediction(this.minecraft.level, sequence -> { this.destroyBlock(pos); return new ServerboundPlayerActionPacket(ServerboundPlayerActionPacket.Action.STOP_DESTROY_BLOCK, pos, direction, sequence); }); this.destroyProgress = 0.0f; this.destroyTicks = 0.0f; this.destroyDelay = 5; } } else { return this.startDestroyBlock(pos, direction); } this.minecraft.level.destroyBlockProgress(this.minecraft.player.getId(), this.destroyBlockPos, this.getDestroyStage()); return true; } private void startPrediction(ClientLevel level, PredictiveAction predictiveAction) { try (BlockStatePredictionHandler prediction = level.getBlockStatePredictionHandler().startPredicting();){ int sequence = prediction.currentSequence(); Packet packetConcludingPrediction = predictiveAction.predict(sequence); this.connection.send(packetConcludingPrediction); } } public void tick() { this.ensureHasSentCarriedItem(); if (this.connection.getConnection().isConnected()) { this.connection.getConnection().tick(); } else { this.connection.getConnection().handleDisconnection(); } } private boolean sameDestroyTarget(BlockPos pos) { ItemStack selected = this.minecraft.player.getMainHandItem(); return pos.equals(this.destroyBlockPos) && ItemStack.isSameItemSameComponents(selected, this.destroyingItem); } private void ensureHasSentCarriedItem() { int index = this.minecraft.player.getInventory().getSelectedSlot(); if (index != this.carriedIndex) { this.carriedIndex = index; this.connection.send(new ServerboundSetCarriedItemPacket(this.carriedIndex)); } } public InteractionResult useItemOn(LocalPlayer player, InteractionHand hand, BlockHitResult blockHit) { this.ensureHasSentCarriedItem(); if (!this.minecraft.level.getWorldBorder().isWithinBounds(blockHit.getBlockPos())) { return InteractionResult.FAIL; } MutableObject result = new MutableObject(); this.startPrediction(this.minecraft.level, sequence -> { result.setValue((Object)this.performUseItemOn(player, hand, blockHit)); return new ServerboundUseItemOnPacket(hand, blockHit, sequence); }); return (InteractionResult)result.get(); } private InteractionResult performUseItemOn(LocalPlayer player, InteractionHand hand, BlockHitResult blockHit) { InteractionResult success; boolean suppressUsingBlock; BlockPos pos = blockHit.getBlockPos(); ItemStack itemStack = player.getItemInHand(hand); if (this.localPlayerMode == GameType.SPECTATOR) { return InteractionResult.CONSUME; } boolean haveSomethingInOurHands = !player.getMainHandItem().isEmpty() || !player.getOffhandItem().isEmpty(); boolean bl = suppressUsingBlock = player.isSecondaryUseActive() && haveSomethingInOurHands; if (!suppressUsingBlock) { InteractionResult use; BlockState blockState = this.minecraft.level.getBlockState(pos); if (!this.connection.isFeatureEnabled(blockState.getBlock().requiredFeatures())) { return InteractionResult.FAIL; } InteractionResult itemUse = blockState.useItemOn(player.getItemInHand(hand), this.minecraft.level, player, hand, blockHit); if (itemUse.consumesAction()) { return itemUse; } if (itemUse instanceof InteractionResult.TryEmptyHandInteraction && hand == InteractionHand.MAIN_HAND && (use = blockState.useWithoutItem(this.minecraft.level, player, blockHit)).consumesAction()) { return use; } } if (itemStack.isEmpty() || player.getCooldowns().isOnCooldown(itemStack)) { return InteractionResult.PASS; } UseOnContext context = new UseOnContext(player, hand, blockHit); if (player.hasInfiniteMaterials()) { int count = itemStack.getCount(); success = itemStack.useOn(context); itemStack.setCount(count); } else { success = itemStack.useOn(context); } return success; } public InteractionResult useItem(Player player, InteractionHand hand) { if (this.localPlayerMode == GameType.SPECTATOR) { return InteractionResult.PASS; } this.ensureHasSentCarriedItem(); MutableObject interactionResult = new MutableObject(); this.startPrediction(this.minecraft.level, sequence -> { ItemStack result; ServerboundUseItemPacket packet = new ServerboundUseItemPacket(hand, sequence, player.getYRot(), player.getXRot()); ItemStack itemStack = player.getItemInHand(hand); if (player.getCooldowns().isOnCooldown(itemStack)) { interactionResult.setValue((Object)InteractionResult.PASS); return packet; } InteractionResult resultHolder = itemStack.use(this.minecraft.level, player, hand); if (resultHolder instanceof InteractionResult.Success) { InteractionResult.Success success = (InteractionResult.Success)resultHolder; result = Objects.requireNonNullElseGet(success.heldItemTransformedTo(), () -> player.getItemInHand(hand)); } else { result = player.getItemInHand(hand); } if (result != itemStack) { player.setItemInHand(hand, result); } interactionResult.setValue((Object)resultHolder); return packet; }); return (InteractionResult)interactionResult.get(); } public LocalPlayer createPlayer(ClientLevel level, StatsCounter stats, ClientRecipeBook recipeBook) { return this.createPlayer(level, stats, recipeBook, Input.EMPTY, false); } public LocalPlayer createPlayer(ClientLevel level, StatsCounter stats, ClientRecipeBook recipeBook, Input lastSentInput, boolean wasSprinting) { return new LocalPlayer(this.minecraft, level, this.connection, stats, recipeBook, lastSentInput, wasSprinting); } public void attack(Player player, Entity entity) { this.ensureHasSentCarriedItem(); this.connection.send(ServerboundInteractPacket.createAttackPacket(entity, player.isShiftKeyDown())); if (this.localPlayerMode != GameType.SPECTATOR) { player.attack(entity); player.resetAttackStrengthTicker(); } } public InteractionResult interact(Player player, Entity entity, InteractionHand hand) { this.ensureHasSentCarriedItem(); this.connection.send(ServerboundInteractPacket.createInteractionPacket(entity, player.isShiftKeyDown(), hand)); if (this.localPlayerMode == GameType.SPECTATOR) { return InteractionResult.PASS; } return player.interactOn(entity, hand); } public InteractionResult interactAt(Player player, Entity entity, EntityHitResult hitResult, InteractionHand hand) { this.ensureHasSentCarriedItem(); Vec3 location = hitResult.getLocation().subtract(entity.getX(), entity.getY(), entity.getZ()); this.connection.send(ServerboundInteractPacket.createInteractionPacket(entity, player.isShiftKeyDown(), hand, location)); if (this.localPlayerMode == GameType.SPECTATOR) { return InteractionResult.PASS; } return entity.interactAt(player, location, hand); } public void handleInventoryMouseClick(int containerId, int slotNum, int buttonNum, ClickType clickType, Player player) { AbstractContainerMenu containerMenu = player.containerMenu; if (containerId != containerMenu.containerId) { LOGGER.warn("Ignoring click in mismatching container. Click in {}, player has {}.", (Object)containerId, (Object)containerMenu.containerId); return; } NonNullList slots = containerMenu.slots; int slotCount = slots.size(); ArrayList itemsBeforeClick = Lists.newArrayListWithCapacity((int)slotCount); for (Slot slot : slots) { itemsBeforeClick.add(slot.getItem().copy()); } containerMenu.clicked(slotNum, buttonNum, clickType, player); Int2ObjectOpenHashMap changedSlots = new Int2ObjectOpenHashMap(); for (int i = 0; i < slotCount; ++i) { ItemStack after; ItemStack before = (ItemStack)itemsBeforeClick.get(i); if (ItemStack.matches(before, after = slots.get(i).getItem())) continue; changedSlots.put(i, (Object)HashedStack.create(after, this.connection.decoratedHashOpsGenenerator())); } HashedStack carriedItem = HashedStack.create(containerMenu.getCarried(), this.connection.decoratedHashOpsGenenerator()); this.connection.send(new ServerboundContainerClickPacket(containerId, containerMenu.getStateId(), Shorts.checkedCast((long)slotNum), SignedBytes.checkedCast((long)buttonNum), clickType, (Int2ObjectMap)changedSlots, carriedItem)); } public void handlePlaceRecipe(int containerId, RecipeDisplayId recipe, boolean useMaxItems) { this.connection.send(new ServerboundPlaceRecipePacket(containerId, recipe, useMaxItems)); } public void handleInventoryButtonClick(int containerId, int buttonId) { this.connection.send(new ServerboundContainerButtonClickPacket(containerId, buttonId)); } public void handleCreativeModeItemAdd(ItemStack clicked, int slot) { if (this.minecraft.player.hasInfiniteMaterials() && this.connection.isFeatureEnabled(clicked.getItem().requiredFeatures())) { this.connection.send(new ServerboundSetCreativeModeSlotPacket(slot, clicked)); } } public void handleCreativeModeItemDrop(ItemStack clicked) { boolean hasOtherInventoryOpen; boolean bl = hasOtherInventoryOpen = this.minecraft.screen instanceof AbstractContainerScreen && !(this.minecraft.screen instanceof CreativeModeInventoryScreen); if (this.minecraft.player.hasInfiniteMaterials() && !hasOtherInventoryOpen && !clicked.isEmpty() && this.connection.isFeatureEnabled(clicked.getItem().requiredFeatures())) { this.connection.send(new ServerboundSetCreativeModeSlotPacket(-1, clicked)); this.minecraft.player.getDropSpamThrottler().increment(); } } public void releaseUsingItem(Player player) { this.ensureHasSentCarriedItem(); this.connection.send(new ServerboundPlayerActionPacket(ServerboundPlayerActionPacket.Action.RELEASE_USE_ITEM, BlockPos.ZERO, Direction.DOWN)); player.releaseUsingItem(); } public void piercingAttack(PiercingWeapon weapon) { this.ensureHasSentCarriedItem(); this.connection.send(new ServerboundPlayerActionPacket(ServerboundPlayerActionPacket.Action.STAB, BlockPos.ZERO, Direction.DOWN)); this.minecraft.player.onAttack(); this.minecraft.player.lungeForwardMaybe(); weapon.makeSound(this.minecraft.player); } public boolean hasExperience() { return this.localPlayerMode.isSurvival(); } public boolean hasMissTime() { return !this.localPlayerMode.isCreative(); } public boolean isServerControlledInventory() { return this.minecraft.player.isPassenger() && this.minecraft.player.getVehicle() instanceof HasCustomInventoryScreen; } public boolean isSpectator() { return this.localPlayerMode == GameType.SPECTATOR; } public @Nullable GameType getPreviousPlayerMode() { return this.previousLocalPlayerMode; } public GameType getPlayerMode() { return this.localPlayerMode; } public boolean isDestroying() { return this.isDestroying; } public int getDestroyStage() { return this.destroyProgress > 0.0f ? (int)(this.destroyProgress * 10.0f) : -1; } public void handlePickItemFromBlock(BlockPos pos, boolean includeData) { this.connection.send(new ServerboundPickItemFromBlockPacket(pos, includeData)); } public void handlePickItemFromEntity(Entity entity, boolean includeData) { this.connection.send(new ServerboundPickItemFromEntityPacket(entity.getId(), includeData)); } public void handleSlotStateChanged(int slotId, int containerId, boolean newState) { this.connection.send(new ServerboundContainerSlotStateChangedPacket(slotId, containerId, newState)); } }