content: package com.alrex.parcool.common.action.impl;
  
  import com.alrex.parcool.api.SoundEvents;
  import com.alrex.parcool.client.animation.impl.CrawlAnimator;
  import com.alrex.parcool.client.animation.impl.SlidingAnimator;
  import com.alrex.parcool.client.input.KeyRecorder;
  import com.alrex.parcool.common.action.Action;
  import com.alrex.parcool.common.action.BehaviorEnforcer;
  import com.alrex.parcool.common.action.StaminaConsumeTiming;
  import com.alrex.parcool.common.capability.Animation;
  import com.alrex.parcool.common.capability.IStamina;
  import com.alrex.parcool.common.capability.Parkourability;
  import com.alrex.parcool.config.ParCoolConfig;
  import net.minecraft.block.BlockRenderType;
  import net.minecraft.block.BlockState;
  import net.minecraft.entity.ai.attributes.Attributes;
  import net.minecraft.entity.ai.attributes.ModifiableAttributeInstance;
  import net.minecraft.entity.player.PlayerEntity;
  import net.minecraft.particles.BlockParticleData;
  import net.minecraft.particles.ParticleTypes;
  import net.minecraft.util.math.BlockPos;
  import net.minecraft.util.math.vector.Vector3d;
  import net.minecraft.world.World;
  import net.minecraftforge.api.distmarker.Dist;
  import net.minecraftforge.api.distmarker.OnlyIn;
  
  import javax.annotation.Nullable;
  import java.nio.ByteBuffer;
  
  public class Slide extends Action {
  	private static final BehaviorEnforcer.ID ID_JUMP_CANCEL = BehaviorEnforcer.newID();
  	private Vector3d slidingVec = null;
  
  	@Override
  	public boolean canStart(PlayerEntity player, Parkourability parkourability, IStamina stamina, ByteBuffer startInfo) {
  		Vector3d lookingVec = player.getLookAngle().multiply(1, 0, 1).normalize();
  		startInfo.putDouble(lookingVec.x()).putDouble(lookingVec.z());
  		return (!stamina.isExhausted()
  				&& KeyRecorder.keyCrawlState.isPressed()
  				&& player.isOnGround()
  				&& !parkourability.get(Roll.class).isDoing()
  				&& !parkourability.get(Tap.class).isDoing()
  				&& parkourability.get(Crawl.class).isDoing()
  				&& !player.isInWaterOrBubble()
  				&& parkourability.get(FastRun.class).getDashTick(parkourability.getAdditionalProperties()) > 5
  		);
  	}
  
  	@Override
  	public boolean canContinue(PlayerEntity player, Parkourability parkourability, IStamina stamina) {
          int maxSlidingTick = Math.min(
                  parkourability.getActionInfo().getClientSetting().get(ParCoolConfig.Client.Integers.SlidingContinuableTick),
                  parkourability.getActionInfo().getServerLimitation().get(ParCoolConfig.Server.Integers.MaxSlidingContinuableTick)
          );
  		return getDoingTick() < maxSlidingTick
  				&& parkourability.get(Crawl.class).isDoing();
  	}
  
  	@Override
  	public void onStartInLocalClient(PlayerEntity player, Parkourability parkourability, IStamina stamina, ByteBuffer startData) {
  		slidingVec = new Vector3d(startData.getDouble(), 0, startData.getDouble());
  		if (ParCoolConfig.Client.Booleans.EnableActionSounds.get())
              player.playSound(SoundEvents.SLIDE.get(), 1f, 1f);
  		Animation animation = Animation.get(player);
  		if (animation != null) {
  			animation.setAnimator(new SlidingAnimator());
  		}
  		parkourability.getBehaviorEnforcer().addMarkerCancellingJump(ID_JUMP_CANCEL, this::isDoing);
  	}
  
  	@Override
  	public void onStartInOtherClient(PlayerEntity player, Parkourability parkourability, ByteBuffer startData) {
  		slidingVec = new Vector3d(startData.getDouble(), 0, startData.getDouble());
  		if (ParCoolConfig.Client.Booleans.EnableActionSounds.get())
  			player.playSound(SoundEvents.SLIDE.get(), 1f, 1f);
  		Animation animation = Animation.get(player);
  		if (animation != null) {
  			animation.setAnimator(new SlidingAnimator());
  		}
  	}
  
  	@Override
  	public void onWorkingTickInLocalClient(PlayerEntity player, Parkourability parkourability, IStamina stamina) {
  		if (slidingVec != null) {
  			ModifiableAttributeInstance attr = player.getAttribute(Attributes.MOVEMENT_SPEED);
  			double speedScale = 0.45;
  			if (attr != null) {
  				speedScale = attr.getValue() * 4.5;
  			}
  			Vector3d vec = slidingVec.scale(speedScale);
  			player.setDeltaMovement((player.isOnGround() ? vec : vec.scale(0.6)).add(0, player.getDeltaMovement().y(), 0));
  		}
  	}
  
  	@Override
  	public void onWorkingTickInClient(PlayerEntity player, Parkourability parkourability, IStamina stamina) {
  		spawnSlidingParticle(player);
  	}
  
  	@Override
  	public void onStopInLocalClient(PlayerEntity player) {
  		Animation animation = Animation.get(player);
  		if (animation != null && !animation.hasAnimator()) {
  			animation.setAnimator(new CrawlAnimator());
  		}
  	}
  
  	@Override
  	public void onStopInOtherClient(PlayerEntity player) {
  		Animation animation = Animation.get(player);
  		if (animation != null && !animation.hasAnimator()) {
  			animation.setAnimator(new CrawlAnimator());
  		}
  	}
  
  	@Nullable
  	public Vector3d getSlidingVector() {
  		return slidingVec;
  	}
  
  	@Override
  	public StaminaConsumeTiming getStaminaConsumeTiming() {
  		return StaminaConsumeTiming.None;
  	}
  
  	@OnlyIn(Dist.CLIENT)
  	private void spawnSlidingParticle(PlayerEntity player) {
  		if (!ParCoolConfig.Client.Booleans.EnableActionParticles.get()) return;
  		World level = player.level;
  		Vector3d pos = player.position();
  		BlockState feetBlock = player.level.getBlockState(player.blockPosition().below());
  		float width = player.getBbWidth();
  		Vector3d direction = getSlidingVector();
  		if (direction == null) return;
  
  		if (feetBlock.getRenderShape() != BlockRenderType.INVISIBLE) {
  			Vector3d particlePos = new Vector3d(
  					pos.x() + (player.getRandom().nextDouble() - 0.5D) * width,
  					pos.y() + 0.01D + 0.2 * player.getRandom().nextDouble(),
  					pos.z() + (player.getRandom().nextDouble() - 0.5D) * width
  			);
  			Vector3d particleSpeed = direction
  					.reverse()
  					.scale(2.5 + 5 * player.getRandom().nextDouble())
  					.add(0, 1.5, 0);
  			level.addParticle(
  					new BlockParticleData(ParticleTypes.BLOCK, feetBlock).setPos(new BlockPos(player.position().add(0, -0.5, 0))),
  					particlePos.x(),
  					particlePos.y(),
  					particlePos.z(),
  					particleSpeed.x(),
  					particleSpeed.y(),
  					particleSpeed.z()
  			);
  		}
  	}
  }
  