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.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.core.Direction;
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 final Object2ObjectLinkedOpenHashMap<Pair<BlockPos, ServerboundPlayerActionPacket.Action>, PosAndRot> unAckedActions;
    private int carriedIndex;
    
    public MultiPlayerGameMode(final Minecraft dbn, final ClientPacketListener dnp) {
        this.destroyBlockPos = new BlockPos(-1, -1, -1);
        this.destroyingItem = ItemStack.EMPTY;
        this.localPlayerMode = GameType.SURVIVAL;
        this.unAckedActions = (Object2ObjectLinkedOpenHashMap<Pair<BlockPos, ServerboundPlayerActionPacket.Action>, PosAndRot>)new Object2ObjectLinkedOpenHashMap();
        this.minecraft = dbn;
        this.connection = dnp;
    }
    
    public static void creativeDestroyBlock(final Minecraft dbn, final MultiPlayerGameMode dnr, final BlockPos fk, final Direction fp) {
        if (!dbn.level.extinguishFire(dbn.player, fk, fp)) {
            dnr.destroyBlock(fk);
        }
    }
    
    public void adjustPlayer(final Player ayj) {
        this.localPlayerMode.updatePlayerAbilities(ayj.abilities);
    }
    
    public void setLocalMode(final GameType bjt) {
        (this.localPlayerMode = bjt).updatePlayerAbilities(this.minecraft.player.abilities);
    }
    
    public boolean canHurtPlayer() {
        return this.localPlayerMode.isSurvival();
    }
    
    public boolean destroyBlock(final BlockPos fk) {
        if (this.minecraft.player.blockActionRestricted(this.minecraft.level, fk, this.localPlayerMode)) {
            return false;
        }
        final Level bjw3 = this.minecraft.level;
        final BlockState byj4 = bjw3.getBlockState(fk);
        if (!this.minecraft.player.getMainHandItem().getItem().canAttackBlock(byj4, bjw3, fk, this.minecraft.player)) {
            return false;
        }
        final Block bph5 = byj4.getBlock();
        if ((bph5 instanceof CommandBlock || bph5 instanceof StructureBlock || bph5 instanceof JigsawBlock) && !this.minecraft.player.canUseGameMasterBlocks()) {
            return false;
        }
        if (byj4.isAir()) {
            return false;
        }
        bph5.playerWillDestroy(bjw3, fk, byj4, this.minecraft.player);
        final FluidState coj6 = bjw3.getFluidState(fk);
        final boolean boolean7 = bjw3.setBlock(fk, coj6.createLegacyBlock(), 11);
        if (boolean7) {
            bph5.destroy(bjw3, fk, byj4);
        }
        return boolean7;
    }
    
    public boolean startDestroyBlock(final BlockPos fk, final Direction fp) {
        if (this.minecraft.player.blockActionRestricted(this.minecraft.level, fk, this.localPlayerMode)) {
            return false;
        }
        if (!this.minecraft.level.getWorldBorder().isWithinBounds(fk)) {
            return false;
        }
        if (this.localPlayerMode.isCreative()) {
            final BlockState byj4 = this.minecraft.level.getBlockState(fk);
            this.minecraft.getTutorial().onDestroyBlock(this.minecraft.level, fk, byj4, 1.0f);
            this.sendBlockAction(ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK, fk, fp);
            creativeDestroyBlock(this.minecraft, this, fk, fp);
            this.destroyDelay = 5;
        }
        else if (!this.isDestroying || !this.sameDestroyTarget(fk)) {
            if (this.isDestroying) {
                this.sendBlockAction(ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK, this.destroyBlockPos, fp);
            }
            final BlockState byj4 = this.minecraft.level.getBlockState(fk);
            this.minecraft.getTutorial().onDestroyBlock(this.minecraft.level, fk, byj4, 0.0f);
            this.sendBlockAction(ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK, fk, fp);
            final boolean boolean5 = !byj4.isAir();
            if (boolean5 && this.destroyProgress == 0.0f) {
                byj4.attack(this.minecraft.level, fk, this.minecraft.player);
            }
            if (boolean5 && byj4.getDestroyProgress(this.minecraft.player, this.minecraft.player.level, fk) >= 1.0f) {
                this.destroyBlock(fk);
            }
            else {
                this.isDestroying = true;
                this.destroyBlockPos = fk;
                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 byj2 = this.minecraft.level.getBlockState(this.destroyBlockPos);
            this.minecraft.getTutorial().onDestroyBlock(this.minecraft.level, this.destroyBlockPos, byj2, -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 fk, final Direction fp) {
        this.ensureHasSentCarriedItem();
        if (this.destroyDelay > 0) {
            --this.destroyDelay;
            return true;
        }
        if (this.localPlayerMode.isCreative() && this.minecraft.level.getWorldBorder().isWithinBounds(fk)) {
            this.destroyDelay = 5;
            final BlockState byj4 = this.minecraft.level.getBlockState(fk);
            this.minecraft.getTutorial().onDestroyBlock(this.minecraft.level, fk, byj4, 1.0f);
            this.sendBlockAction(ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK, fk, fp);
            creativeDestroyBlock(this.minecraft, this, fk, fp);
            return true;
        }
        if (!this.sameDestroyTarget(fk)) {
            return this.startDestroyBlock(fk, fp);
        }
        final BlockState byj4 = this.minecraft.level.getBlockState(fk);
        if (byj4.isAir()) {
            return this.isDestroying = false;
        }
        this.destroyProgress += byj4.getDestroyProgress(this.minecraft.player, this.minecraft.player.level, fk);
        if (this.destroyTicks % 4.0f == 0.0f) {
            final SoundType bum5 = byj4.getSoundType();
            this.minecraft.getSoundManager().play(new SimpleSoundInstance(bum5.getHitSound(), SoundSource.BLOCKS, (bum5.getVolume() + 1.0f) / 8.0f, bum5.getPitch() * 0.5f, fk));
        }
        ++this.destroyTicks;
        this.minecraft.getTutorial().onDestroyBlock(this.minecraft.level, fk, byj4, Mth.clamp(this.destroyProgress, 0.0f, 1.0f));
        if (this.destroyProgress >= 1.0f) {
            this.isDestroying = false;
            this.sendBlockAction(ServerboundPlayerActionPacket.Action.STOP_DESTROY_BLOCK, fk, fp);
            this.destroyBlock(fk);
            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 fk) {
        final ItemStack ben3 = this.minecraft.player.getMainHandItem();
        boolean boolean4 = this.destroyingItem.isEmpty() && ben3.isEmpty();
        if (!this.destroyingItem.isEmpty() && !ben3.isEmpty()) {
            boolean4 = (ben3.getItem() == this.destroyingItem.getItem() && ItemStack.tagMatches(ben3, this.destroyingItem) && (ben3.isDamageableItem() || ben3.getDamageValue() == this.destroyingItem.getDamageValue()));
        }
        return fk.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 dqb, final ClientLevel dno, final InteractionHand ajk, final BlockHitResult cvg) {
        this.ensureHasSentCarriedItem();
        final BlockPos fk6 = cvg.getBlockPos();
        if (!this.minecraft.level.getWorldBorder().isWithinBounds(fk6)) {
            return InteractionResult.FAIL;
        }
        final ItemStack ben7 = dqb.getItemInHand(ajk);
        if (this.localPlayerMode == GameType.SPECTATOR) {
            this.connection.send(new ServerboundUseItemOnPacket(ajk, cvg));
            return InteractionResult.SUCCESS;
        }
        final boolean boolean8 = !dqb.getMainHandItem().isEmpty() || !dqb.getOffhandItem().isEmpty();
        final boolean boolean9 = dqb.isSecondaryUseActive() && boolean8;
        if (!boolean9) {
            final InteractionResult ajl10 = dno.getBlockState(fk6).use(dno, dqb, ajk, cvg);
            if (ajl10.consumesAction()) {
                this.connection.send(new ServerboundUseItemOnPacket(ajk, cvg));
                return ajl10;
            }
        }
        this.connection.send(new ServerboundUseItemOnPacket(ajk, cvg));
        if (ben7.isEmpty() || dqb.getCooldowns().isOnCooldown(ben7.getItem())) {
            return InteractionResult.PASS;
        }
        final UseOnContext bfz11 = new UseOnContext(dqb, ajk, cvg);
        InteractionResult ajl10;
        if (this.localPlayerMode.isCreative()) {
            final int integer12 = ben7.getCount();
            ajl10 = ben7.useOn(bfz11);
            ben7.setCount(integer12);
        }
        else {
            ajl10 = ben7.useOn(bfz11);
        }
        return ajl10;
    }
    
    public InteractionResult useItem(final Player ayj, final Level bjw, final InteractionHand ajk) {
        if (this.localPlayerMode == GameType.SPECTATOR) {
            return InteractionResult.PASS;
        }
        this.ensureHasSentCarriedItem();
        this.connection.send(new ServerboundUseItemPacket(ajk));
        final ItemStack ben5 = ayj.getItemInHand(ajk);
        if (ayj.getCooldowns().isOnCooldown(ben5.getItem())) {
            return InteractionResult.PASS;
        }
        final int integer6 = ben5.getCount();
        final InteractionResultHolder<ItemStack> ajm7 = ben5.use(bjw, ayj, ajk);
        final ItemStack ben6 = ajm7.getObject();
        if (ben6 != ben5 || ben6.getCount() != integer6) {
            ayj.setItemInHand(ajk, ben6);
        }
        return ajm7.getResult();
    }
    
    public LocalPlayer createPlayer(final ClientLevel dno, final StatsCounter aas, final ClientRecipeBook dbc) {
        return new LocalPlayer(this.minecraft, dno, this.connection, aas, dbc);
    }
    
    public void attack(final Player ayj, final Entity akq) {
        this.ensureHasSentCarriedItem();
        this.connection.send(new ServerboundInteractPacket(akq));
        if (this.localPlayerMode != GameType.SPECTATOR) {
            ayj.attack(akq);
            ayj.resetAttackStrengthTicker();
        }
    }
    
    public InteractionResult interact(final Player ayj, final Entity akq, final InteractionHand ajk) {
        this.ensureHasSentCarriedItem();
        this.connection.send(new ServerboundInteractPacket(akq, ajk));
        if (this.localPlayerMode == GameType.SPECTATOR) {
            return InteractionResult.PASS;
        }
        return ayj.interactOn(akq, ajk);
    }
    
    public InteractionResult interactAt(final Player ayj, final Entity akq, final EntityHitResult cvh, final InteractionHand ajk) {
        this.ensureHasSentCarriedItem();
        final Vec3 cvl6 = cvh.getLocation().subtract(akq.getX(), akq.getY(), akq.getZ());
        this.connection.send(new ServerboundInteractPacket(akq, ajk, cvl6));
        if (this.localPlayerMode == GameType.SPECTATOR) {
            return InteractionResult.PASS;
        }
        return akq.interactAt(ayj, cvl6, ajk);
    }
    
    public ItemStack handleInventoryMouseClick(final int integer1, final int integer2, final int integer3, final ClickType bav, final Player ayj) {
        final short short7 = ayj.containerMenu.backup(ayj.inventory);
        final ItemStack ben8 = ayj.containerMenu.clicked(integer2, integer3, bav, ayj);
        this.connection.send(new ServerboundContainerClickPacket(integer1, integer2, integer3, bav, ben8, short7));
        return ben8;
    }
    
    public void handlePlaceRecipe(final int integer, final Recipe<?> bgw, final boolean boolean3) {
        this.connection.send(new ServerboundPlaceRecipePacket(integer, bgw, boolean3));
    }
    
    public void handleInventoryButtonClick(final int integer1, final int integer2) {
        this.connection.send(new ServerboundContainerButtonClickPacket(integer1, integer2));
    }
    
    public void handleCreativeModeItemAdd(final ItemStack ben, final int integer) {
        if (this.localPlayerMode.isCreative()) {
            this.connection.send(new ServerboundSetCreativeModeSlotPacket(integer, ben));
        }
    }
    
    public void handleCreativeModeItemDrop(final ItemStack ben) {
        if (this.localPlayerMode.isCreative() && !ben.isEmpty()) {
            this.connection.send(new ServerboundSetCreativeModeSlotPacket(-1, ben));
        }
    }
    
    public void releaseUsingItem(final Player ayj) {
        this.ensureHasSentCarriedItem();
        this.connection.send(new ServerboundPlayerActionPacket(ServerboundPlayerActionPacket.Action.RELEASE_USE_ITEM, BlockPos.ZERO, Direction.DOWN));
        ayj.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 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 fk, final Direction fp) {
        final LocalPlayer dqb5 = this.minecraft.player;
        this.unAckedActions.put(Pair.of((Object)fk, (Object)a), new PosAndRot(dqb5.position(), dqb5.xRot, dqb5.yRot));
        this.connection.send(new ServerboundPlayerActionPacket(a, fk, fp));
    }
    
    public void handleBlockBreakAck(final ClientLevel dno, final BlockPos fk, final BlockState byj, final ServerboundPlayerActionPacket.Action a, final boolean boolean5) {
        final PosAndRot cvj7 = (PosAndRot)this.unAckedActions.remove(Pair.of((Object)fk, (Object)a));
        if (cvj7 == null || !boolean5 || (a != ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK && dno.getBlockState(fk) != byj)) {
            dno.setKnownState(fk, byj);
            if (cvj7 != null) {
                final Vec3 cvl8 = cvj7.pos();
                this.minecraft.player.absMoveTo(cvl8.x, cvl8.y, cvl8.z, cvj7.yRot(), cvj7.xRot());
            }
        }
        while (this.unAckedActions.size() >= 50) {
            final Pair<BlockPos, ServerboundPlayerActionPacket.Action> pair8 = (Pair<BlockPos, ServerboundPlayerActionPacket.Action>)this.unAckedActions.firstKey();
            this.unAckedActions.removeFirst();
            MultiPlayerGameMode.LOGGER.error("Too many unacked block actions, dropping " + pair8);
        }
    }
    
    static {
        LOGGER = LogManager.getLogger();
    }
}
