package net.minecraft.client.multiplayer;

import org.apache.logging.log4j.LogManager;
import net.minecraft.network.protocol.game.ServerboundPickItemPacket;
import net.minecraft.world.entity.animal.horse.AbstractHorse;
import net.minecraft.network.protocol.game.ServerboundSetCreativeModeSlotPacket;
import net.minecraft.network.protocol.game.ServerboundContainerButtonClickPacket;
import net.minecraft.network.protocol.game.ServerboundPlaceRecipePacket;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.network.protocol.game.ServerboundContainerClickPacket;
import net.minecraft.world.inventory.ClickType;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.network.protocol.game.ServerboundInteractPacket;
import net.minecraft.world.entity.Entity;
import net.minecraft.client.ClientRecipeBook;
import net.minecraft.stats.StatsCounter;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.network.protocol.game.ServerboundUseItemPacket;
import net.minecraft.world.item.UseOnContext;
import net.minecraft.network.protocol.game.ServerboundUseItemOnPacket;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.InteractionHand;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ServerboundSetCarriedItemPacket;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.util.Mth;
import net.minecraft.client.resources.sounds.SoundInstance;
import net.minecraft.client.resources.sounds.SimpleSoundInstance;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.core.Direction;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.JigsawBlock;
import net.minecraft.world.level.block.StructureBlock;
import net.minecraft.world.level.block.CommandBlock;
import net.minecraft.world.level.Level;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.PosAndRot;
import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
import com.mojang.datafixers.util.Pair;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import net.minecraft.world.level.GameType;
import net.minecraft.world.item.ItemStack;
import net.minecraft.core.BlockPos;
import net.minecraft.client.Minecraft;
import org.apache.logging.log4j.Logger;

public class MultiPlayerGameMode {
    private static final Logger LOGGER;
    private final Minecraft minecraft;
    private final ClientPacketListener connection;
    private BlockPos destroyBlockPos;
    private ItemStack destroyingItem;
    private float destroyProgress;
    private float destroyTicks;
    private int destroyDelay;
    private boolean isDestroying;
    private GameType localPlayerMode;
    private GameType previousLocalPlayerMode;
    private final Object2ObjectLinkedOpenHashMap<Pair<BlockPos, ServerboundPlayerActionPacket.Action>, PosAndRot> unAckedActions;
    private int carriedIndex;
    
    public MultiPlayerGameMode(final Minecraft dlx, final ClientPacketListener dyn) {
        this.destroyBlockPos = new BlockPos(-1, -1, -1);
        this.destroyingItem = ItemStack.EMPTY;
        this.localPlayerMode = GameType.SURVIVAL;
        this.previousLocalPlayerMode = GameType.NOT_SET;
        this.unAckedActions = (Object2ObjectLinkedOpenHashMap<Pair<BlockPos, ServerboundPlayerActionPacket.Action>, PosAndRot>)new Object2ObjectLinkedOpenHashMap();
        this.minecraft = dlx;
        this.connection = dyn;
    }
    
    public void adjustPlayer(final Player bec) {
        this.localPlayerMode.updatePlayerAbilities(bec.abilities);
    }
    
    public void setPreviousLocalMode(final GameType bpy) {
        this.previousLocalPlayerMode = bpy;
    }
    
    public void setLocalMode(final GameType bpy) {
        if (bpy != this.localPlayerMode) {
            this.previousLocalPlayerMode = this.localPlayerMode;
        }
        (this.localPlayerMode = bpy).updatePlayerAbilities(this.minecraft.player.abilities);
    }
    
    public boolean canHurtPlayer() {
        return this.localPlayerMode.isSurvival();
    }
    
    public boolean destroyBlock(final BlockPos fu) {
        if (this.minecraft.player.blockActionRestricted(this.minecraft.level, fu, this.localPlayerMode)) {
            return false;
        }
        final Level bqb3 = this.minecraft.level;
        final BlockState cfj4 = bqb3.getBlockState(fu);
        if (!this.minecraft.player.getMainHandItem().getItem().canAttackBlock(cfj4, bqb3, fu, this.minecraft.player)) {
            return false;
        }
        final Block bvr5 = cfj4.getBlock();
        if ((bvr5 instanceof CommandBlock || bvr5 instanceof StructureBlock || bvr5 instanceof JigsawBlock) && !this.minecraft.player.canUseGameMasterBlocks()) {
            return false;
        }
        if (cfj4.isAir()) {
            return false;
        }
        bvr5.playerWillDestroy(bqb3, fu, cfj4, this.minecraft.player);
        final FluidState cxa6 = bqb3.getFluidState(fu);
        final boolean boolean7 = bqb3.setBlock(fu, cxa6.createLegacyBlock(), 11);
        if (boolean7) {
            bvr5.destroy(bqb3, fu, cfj4);
        }
        return boolean7;
    }
    
    public boolean startDestroyBlock(final BlockPos fu, final Direction fz) {
        if (this.minecraft.player.blockActionRestricted(this.minecraft.level, fu, this.localPlayerMode)) {
            return false;
        }
        if (!this.minecraft.level.getWorldBorder().isWithinBounds(fu)) {
            return false;
        }
        if (this.localPlayerMode.isCreative()) {
            final BlockState cfj4 = this.minecraft.level.getBlockState(fu);
            this.minecraft.getTutorial().onDestroyBlock(this.minecraft.level, fu, cfj4, 1.0f);
            this.sendBlockAction(ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK, fu, fz);
            this.destroyBlock(fu);
            this.destroyDelay = 5;
        }
        else if (!this.isDestroying || !this.sameDestroyTarget(fu)) {
            if (this.isDestroying) {
                this.sendBlockAction(ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK, this.destroyBlockPos, fz);
            }
            final BlockState cfj4 = this.minecraft.level.getBlockState(fu);
            this.minecraft.getTutorial().onDestroyBlock(this.minecraft.level, fu, cfj4, 0.0f);
            this.sendBlockAction(ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK, fu, fz);
            final boolean boolean5 = !cfj4.isAir();
            if (boolean5 && this.destroyProgress == 0.0f) {
                cfj4.attack(this.minecraft.level, fu, this.minecraft.player);
            }
            if (boolean5 && cfj4.getDestroyProgress(this.minecraft.player, this.minecraft.player.level, fu) >= 1.0f) {
                this.destroyBlock(fu);
            }
            else {
                this.isDestroying = true;
                this.destroyBlockPos = fu;
                this.destroyingItem = this.minecraft.player.getMainHandItem();
                this.destroyProgress = 0.0f;
                this.destroyTicks = 0.0f;
                this.minecraft.level.destroyBlockProgress(this.minecraft.player.getId(), this.destroyBlockPos, (int)(this.destroyProgress * 10.0f) - 1);
            }
        }
        return true;
    }
    
    public void stopDestroyBlock() {
        if (this.isDestroying) {
            final BlockState cfj2 = this.minecraft.level.getBlockState(this.destroyBlockPos);
            this.minecraft.getTutorial().onDestroyBlock(this.minecraft.level, this.destroyBlockPos, cfj2, -1.0f);
            this.sendBlockAction(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(final BlockPos fu, final Direction fz) {
        this.ensureHasSentCarriedItem();
        if (this.destroyDelay > 0) {
            --this.destroyDelay;
            return true;
        }
        if (this.localPlayerMode.isCreative() && this.minecraft.level.getWorldBorder().isWithinBounds(fu)) {
            this.destroyDelay = 5;
            final BlockState cfj4 = this.minecraft.level.getBlockState(fu);
            this.minecraft.getTutorial().onDestroyBlock(this.minecraft.level, fu, cfj4, 1.0f);
            this.sendBlockAction(ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK, fu, fz);
            this.destroyBlock(fu);
            return true;
        }
        if (!this.sameDestroyTarget(fu)) {
            return this.startDestroyBlock(fu, fz);
        }
        final BlockState cfj4 = this.minecraft.level.getBlockState(fu);
        if (cfj4.isAir()) {
            return this.isDestroying = false;
        }
        this.destroyProgress += cfj4.getDestroyProgress(this.minecraft.player, this.minecraft.player.level, fu);
        if (this.destroyTicks % 4.0f == 0.0f) {
            final SoundType cbh5 = cfj4.getSoundType();
            this.minecraft.getSoundManager().play(new SimpleSoundInstance(cbh5.getHitSound(), SoundSource.BLOCKS, (cbh5.getVolume() + 1.0f) / 8.0f, cbh5.getPitch() * 0.5f, fu));
        }
        ++this.destroyTicks;
        this.minecraft.getTutorial().onDestroyBlock(this.minecraft.level, fu, cfj4, Mth.clamp(this.destroyProgress, 0.0f, 1.0f));
        if (this.destroyProgress >= 1.0f) {
            this.isDestroying = false;
            this.sendBlockAction(ServerboundPlayerActionPacket.Action.STOP_DESTROY_BLOCK, fu, fz);
            this.destroyBlock(fu);
            this.destroyProgress = 0.0f;
            this.destroyTicks = 0.0f;
            this.destroyDelay = 5;
        }
        this.minecraft.level.destroyBlockProgress(this.minecraft.player.getId(), this.destroyBlockPos, (int)(this.destroyProgress * 10.0f) - 1);
        return true;
    }
    
    public float getPickRange() {
        if (this.localPlayerMode.isCreative()) {
            return 5.0f;
        }
        return 4.5f;
    }
    
    public void tick() {
        this.ensureHasSentCarriedItem();
        if (this.connection.getConnection().isConnected()) {
            this.connection.getConnection().tick();
        }
        else {
            this.connection.getConnection().handleDisconnection();
        }
    }
    
    private boolean sameDestroyTarget(final BlockPos fu) {
        final ItemStack bki3 = this.minecraft.player.getMainHandItem();
        boolean boolean4 = this.destroyingItem.isEmpty() && bki3.isEmpty();
        if (!this.destroyingItem.isEmpty() && !bki3.isEmpty()) {
            boolean4 = (bki3.getItem() == this.destroyingItem.getItem() && ItemStack.tagMatches(bki3, this.destroyingItem) && (bki3.isDamageableItem() || bki3.getDamageValue() == this.destroyingItem.getDamageValue()));
        }
        return fu.equals(this.destroyBlockPos) && boolean4;
    }
    
    private void ensureHasSentCarriedItem() {
        final int integer2 = this.minecraft.player.inventory.selected;
        if (integer2 != this.carriedIndex) {
            this.carriedIndex = integer2;
            this.connection.send(new ServerboundSetCarriedItemPacket(this.carriedIndex));
        }
    }
    
    public InteractionResult useItemOn(final LocalPlayer ebf, final ClientLevel dym, final InteractionHand anf, final BlockHitResult deh) {
        this.ensureHasSentCarriedItem();
        final BlockPos fu6 = deh.getBlockPos();
        if (!this.minecraft.level.getWorldBorder().isWithinBounds(fu6)) {
            return InteractionResult.FAIL;
        }
        final ItemStack bki7 = ebf.getItemInHand(anf);
        if (this.localPlayerMode == GameType.SPECTATOR) {
            this.connection.send(new ServerboundUseItemOnPacket(anf, deh));
            return InteractionResult.SUCCESS;
        }
        final boolean boolean8 = !ebf.getMainHandItem().isEmpty() || !ebf.getOffhandItem().isEmpty();
        final boolean boolean9 = ebf.isSecondaryUseActive() && boolean8;
        if (!boolean9) {
            final InteractionResult ang10 = dym.getBlockState(fu6).use(dym, ebf, anf, deh);
            if (ang10.consumesAction()) {
                this.connection.send(new ServerboundUseItemOnPacket(anf, deh));
                return ang10;
            }
        }
        this.connection.send(new ServerboundUseItemOnPacket(anf, deh));
        if (bki7.isEmpty() || ebf.getCooldowns().isOnCooldown(bki7.getItem())) {
            return InteractionResult.PASS;
        }
        final UseOnContext blv11 = new UseOnContext(ebf, anf, deh);
        InteractionResult ang10;
        if (this.localPlayerMode.isCreative()) {
            final int integer12 = bki7.getCount();
            ang10 = bki7.useOn(blv11);
            bki7.setCount(integer12);
        }
        else {
            ang10 = bki7.useOn(blv11);
        }
        return ang10;
    }
    
    public InteractionResult useItem(final Player bec, final Level bqb, final InteractionHand anf) {
        if (this.localPlayerMode == GameType.SPECTATOR) {
            return InteractionResult.PASS;
        }
        this.ensureHasSentCarriedItem();
        this.connection.send(new ServerboundUseItemPacket(anf));
        final ItemStack bki5 = bec.getItemInHand(anf);
        if (bec.getCooldowns().isOnCooldown(bki5.getItem())) {
            return InteractionResult.PASS;
        }
        final int integer6 = bki5.getCount();
        final InteractionResultHolder<ItemStack> anh7 = bki5.use(bqb, bec, anf);
        final ItemStack bki6 = anh7.getObject();
        if (bki6 != bki5) {
            bec.setItemInHand(anf, bki6);
        }
        return anh7.getResult();
    }
    
    public LocalPlayer createPlayer(final ClientLevel dym, final StatsCounter acv, final ClientRecipeBook dlk) {
        return this.createPlayer(dym, acv, dlk, false, false);
    }
    
    public LocalPlayer createPlayer(final ClientLevel dym, final StatsCounter acv, final ClientRecipeBook dlk, final boolean boolean4, final boolean boolean5) {
        return new LocalPlayer(this.minecraft, dym, this.connection, acv, dlk, boolean4, boolean5);
    }
    
    public void attack(final Player bec, final Entity aom) {
        this.ensureHasSentCarriedItem();
        this.connection.send(new ServerboundInteractPacket(aom, bec.isShiftKeyDown()));
        if (this.localPlayerMode != GameType.SPECTATOR) {
            bec.attack(aom);
            bec.resetAttackStrengthTicker();
        }
    }
    
    public InteractionResult interact(final Player bec, final Entity aom, final InteractionHand anf) {
        this.ensureHasSentCarriedItem();
        this.connection.send(new ServerboundInteractPacket(aom, anf, bec.isShiftKeyDown()));
        if (this.localPlayerMode == GameType.SPECTATOR) {
            return InteractionResult.PASS;
        }
        return bec.interactOn(aom, anf);
    }
    
    public InteractionResult interactAt(final Player bec, final Entity aom, final EntityHitResult dei, final InteractionHand anf) {
        this.ensureHasSentCarriedItem();
        final Vec3 dem6 = dei.getLocation().subtract(aom.getX(), aom.getY(), aom.getZ());
        this.connection.send(new ServerboundInteractPacket(aom, anf, dem6, bec.isShiftKeyDown()));
        if (this.localPlayerMode == GameType.SPECTATOR) {
            return InteractionResult.PASS;
        }
        return aom.interactAt(bec, dem6, anf);
    }
    
    public ItemStack handleInventoryMouseClick(final int integer1, final int integer2, final int integer3, final ClickType bgq, final Player bec) {
        final short short7 = bec.containerMenu.backup(bec.inventory);
        final ItemStack bki8 = bec.containerMenu.clicked(integer2, integer3, bgq, bec);
        this.connection.send(new ServerboundContainerClickPacket(integer1, integer2, integer3, bgq, bki8, short7));
        return bki8;
    }
    
    public void handlePlaceRecipe(final int integer, final Recipe<?> bmu, final boolean boolean3) {
        this.connection.send(new ServerboundPlaceRecipePacket(integer, bmu, boolean3));
    }
    
    public void handleInventoryButtonClick(final int integer1, final int integer2) {
        this.connection.send(new ServerboundContainerButtonClickPacket(integer1, integer2));
    }
    
    public void handleCreativeModeItemAdd(final ItemStack bki, final int integer) {
        if (this.localPlayerMode.isCreative()) {
            this.connection.send(new ServerboundSetCreativeModeSlotPacket(integer, bki));
        }
    }
    
    public void handleCreativeModeItemDrop(final ItemStack bki) {
        if (this.localPlayerMode.isCreative() && !bki.isEmpty()) {
            this.connection.send(new ServerboundSetCreativeModeSlotPacket(-1, bki));
        }
    }
    
    public void releaseUsingItem(final Player bec) {
        this.ensureHasSentCarriedItem();
        this.connection.send(new ServerboundPlayerActionPacket(ServerboundPlayerActionPacket.Action.RELEASE_USE_ITEM, BlockPos.ZERO, Direction.DOWN));
        bec.releaseUsingItem();
    }
    
    public boolean hasExperience() {
        return this.localPlayerMode.isSurvival();
    }
    
    public boolean hasMissTime() {
        return !this.localPlayerMode.isCreative();
    }
    
    public boolean hasInfiniteItems() {
        return this.localPlayerMode.isCreative();
    }
    
    public boolean hasFarPickRange() {
        return this.localPlayerMode.isCreative();
    }
    
    public boolean isServerControlledInventory() {
        return this.minecraft.player.isPassenger() && this.minecraft.player.getVehicle() instanceof AbstractHorse;
    }
    
    public boolean isAlwaysFlying() {
        return this.localPlayerMode == GameType.SPECTATOR;
    }
    
    public GameType getPreviousPlayerMode() {
        return this.previousLocalPlayerMode;
    }
    
    public GameType getPlayerMode() {
        return this.localPlayerMode;
    }
    
    public boolean isDestroying() {
        return this.isDestroying;
    }
    
    public void handlePickItem(final int integer) {
        this.connection.send(new ServerboundPickItemPacket(integer));
    }
    
    private void sendBlockAction(final ServerboundPlayerActionPacket.Action a, final BlockPos fu, final Direction fz) {
        final LocalPlayer ebf5 = this.minecraft.player;
        this.unAckedActions.put(Pair.of((Object)fu, (Object)a), new PosAndRot(ebf5.position(), ebf5.xRot, ebf5.yRot));
        this.connection.send(new ServerboundPlayerActionPacket(a, fu, fz));
    }
    
    public void handleBlockBreakAck(final ClientLevel dym, final BlockPos fu, final BlockState cfj, final ServerboundPlayerActionPacket.Action a, final boolean boolean5) {
        final PosAndRot dek7 = (PosAndRot)this.unAckedActions.remove(Pair.of((Object)fu, (Object)a));
        final BlockState cfj2 = dym.getBlockState(fu);
        if ((dek7 == null || !boolean5 || (a != ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK && cfj2 != cfj)) && cfj2 != cfj) {
            dym.setKnownState(fu, cfj);
            if (dek7 != null) {
                final Vec3 dem9 = dek7.pos();
                this.minecraft.player.absMoveTo(dem9.x, dem9.y, dem9.z, dek7.yRot(), dek7.xRot());
            }
        }
        while (this.unAckedActions.size() >= 50) {
            final Pair<BlockPos, ServerboundPlayerActionPacket.Action> pair9 = (Pair<BlockPos, ServerboundPlayerActionPacket.Action>)this.unAckedActions.firstKey();
            this.unAckedActions.removeFirst();
            MultiPlayerGameMode.LOGGER.error("Too many unacked block actions, dropping " + pair9);
        }
    }
    
    static {
        LOGGER = LogManager.getLogger();
    }
}
