2025-11-24 22:52:51 +03:00

525 lines
25 KiB
Java

/*
* 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<ServerGamePacketListener> 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<Slot> 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<HashedStack>)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));
}
}