package com.minelittlepony.unicopia.ability; import java.util.*; import java.util.stream.Stream; import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.ability.data.Hit; import com.minelittlepony.unicopia.ability.data.Pos; import com.minelittlepony.unicopia.ability.data.tree.TreeType; import com.minelittlepony.unicopia.client.minelittlepony.MineLPDelegate; import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation; import com.minelittlepony.unicopia.entity.Living; import com.minelittlepony.unicopia.entity.damage.UDamageTypes; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.particle.ParticleUtils; import com.minelittlepony.unicopia.particle.UParticles; import com.minelittlepony.unicopia.server.world.BlockDestructionManager; import com.minelittlepony.unicopia.util.*; import net.minecraft.block.BeehiveBlock; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; import net.minecraft.block.entity.BeehiveBlockEntity; import net.minecraft.entity.ItemEntity; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.passive.BeeEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; import net.minecraft.predicate.entity.EntityPredicates; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.Identifier; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Box; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; import net.minecraft.world.WorldEvents; /** * Earth Pony kicking ability */ public class EarthPonyKickAbility implements Ability { @Override public int getWarmupTime(Pony player) { return 3; } @Override public int getCooldownTime(Pony player) { return 50; } @Override public boolean canUse(Race race) { return race.canUseEarth(); } @Override public Identifier getIcon(Pony player) { return getId().withPath(p -> "textures/gui/ability/" + p + "_" + (player.getObservedSpecies().isHuman() ? Race.EARTH : player.getObservedSpecies()).getId().getPath() + "_" + (getKickDirection(player) > 0 ? "forward" : "backward") + ".png"); } @Override public double getCostEstimate(Pony player) { return TraceHelper.findBlock(player.asEntity(), getKickDirection(player) * 6, 1) .filter(pos -> TreeType.at(pos, player.asWorld()) != TreeType.NONE) .isPresent() ? 3 : 1; } @Override public Optional prepareQuickAction(Pony player, ActivationType type) { return Optional.of(getDefaultKickLocation(player)); } @Override public boolean onQuickAction(Pony player, ActivationType type, Optional data) { if (type == ActivationType.TAP) { if (!player.isClient()) { data.ifPresent(kickLocation -> { Vec3d origin = player.getOriginVector(); World w = player.asWorld(); player.asEntity().addExhaustion(3); for (var e : VecHelper.findInRange(player.asEntity(), w, kickLocation.vec(), 2.5, EntityPredicates.EXCEPT_CREATIVE_OR_SPECTATOR)) { if (e instanceof LivingEntity entity) { float calculatedStrength = 0.5F * (1 + player.getLevel().getScaled(9)); entity.damage(player.damageOf(UDamageTypes.KICK, player), player.asWorld().random.nextBetween(2, 10) + calculatedStrength); entity.takeKnockback(calculatedStrength, origin.x - entity.getX(), origin.z - entity.getZ()); Living.updateVelocity(entity); player.subtractEnergyCost(3); player.setAnimation(Animation.KICK, Animation.Recipient.ANYONE); return; } } BlockPos pos = kickLocation.pos(); EarthPonyStompAbility.stompBlock(w, pos, 10 * (1 + player.getLevel().getScaled(5)) * w.getBlockState(pos).calcBlockBreakingDelta(player.asEntity(), w, pos)); player.setAnimation(Animation.KICK, Animation.Recipient.ANYONE); }); } return true; } if (type == ActivationType.DOUBLE_TAP && player.asEntity().isOnGround() && player.getMagicalReserves().getMana().get() > 40) { player.getPhysics().dashForward((float)player.asWorld().random.nextTriangular(3.5F, 0.3F)); player.subtractEnergyCost(4); player.asEntity().addExhaustion(5); return true; } return false; } @Nullable @Override public Optional prepare(Pony player) { return TraceHelper.findBlock(player.asEntity(), 6 * getKickDirection(player), 1) .filter(pos -> TreeType.at(pos, player.asWorld()) != TreeType.NONE) .map(Pos::new) .or(() -> Optional.of(getDefaultKickLocation(player))); } private int getKickDirection(Pony player) { return MineLPDelegate.getInstance().getPlayerPonyRace(player.asEntity()).isEquine() && player.asEntity().isInSneakingPose() ? -1 : 1; } private Pos getDefaultKickLocation(Pony player) { Vec3d kickVector = player.asEntity().getRotationVector().multiply(1, 0, 1); if (MineLPDelegate.getInstance().getPlayerPonyRace(player.asEntity()).isEquine()) { kickVector = kickVector.rotateY((float)Math.PI); } return new Pos(BlockPos.ofFloored(player.getOriginVector().add(kickVector))); } @Override public Hit.Serializer getSerializer() { return Pos.SERIALIZER; } @Override public boolean apply(Pony iplayer, Pos data) { BlockPos pos = data.pos(); TreeType treeType = TreeType.at(pos, iplayer.asWorld()); iplayer.setAnimation(Animation.KICK, Animation.Recipient.ANYONE); iplayer.subtractEnergyCost(treeType == TreeType.NONE ? 1 : 3); iplayer.asEntity().addExhaustion(3); return treeType.collectBlocks(iplayer.asWorld(), pos).filter(tree -> { ParticleUtils.spawnParticle(iplayer.asWorld(), UParticles.GROUND_POUND, data.vec(), Vec3d.ZERO); PlayerEntity player = iplayer.asEntity(); if (BlockDestructionManager.of(player.getWorld()).getBlockDestruction(pos) + 4 >= BlockDestructionManager.MAX_DAMAGE) { if (player.getWorld().random.nextInt(30) == 0) { tree.logs().forEach(player.getWorld(), (w, state, p) -> w.breakBlock(p, true)); tree.leaves().forEach(player.getWorld(), (w, state, p) -> { Block.dropStacks(w.getBlockState(p), w, p); w.setBlockState(p, Blocks.AIR.getDefaultState(), Block.NOTIFY_ALL); }); } iplayer.subtractEnergyCost(3); } else { tree.leaves().forEach(player.getWorld(), (w, state, p) -> { if (w.random.nextInt(30) == 0) { w.syncWorldEvent(WorldEvents.BLOCK_BROKEN, p, Block.getRawIdFromState(state)); } }); int cost = dropApples(player, pos); if (cost > 0) { iplayer.subtractEnergyCost(cost / 7F); } } return true; }).isPresent(); } @Override public void warmUp(Pony player, AbilitySlot slot) { player.getMagicalReserves().getExertion().addPercent(40); } @Override public void coolDown(Pony player, AbilitySlot slot) { player.asEntity().getHungerManager().addExhaustion(0.1F); } private int dropApples(PlayerEntity player, BlockPos pos) { TreeType treeType = TreeType.at(pos, player.getWorld()); return treeType.collectBlocks(player.getWorld(), pos).map(tree -> { tree.logs().forEach(player.getWorld(), (world, state, position) -> { affectBlockChange(player, position); }); int[] dropCount = {0}; tree.leaves().forEach(player.getWorld(), (world, state, position) -> { affectBlockChange(player, position); if (!buckBlock(treeType, state, world, position) .filter(i -> !i.isEmpty()) .map(stack -> createDrop(stack, position, world, dropCount)) .toList().isEmpty()) { world.syncWorldEvent(WorldEvents.BLOCK_BROKEN, position, Block.getRawIdFromState(state)); } }); return dropCount[0] / 3; }).orElse(0); } private ItemEntity createDrop(ItemStack stack, BlockPos pos, World world, int[] dropCount) { ItemEntity entity = new ItemEntity(world, pos.getX() + world.random.nextFloat(), pos.getY() - 0.5, pos.getZ() + world.random.nextFloat(), stack ); entity.setToDefaultPickupDelay(); world.spawnEntity(entity); dropCount[0]++; return entity; } private Stream buckBlock(TreeType treeType, BlockState treeState, World world, BlockPos position) { if (treeState.getBlock() instanceof Buckable buckable) { return buckable.onBucked((ServerWorld)world, treeState, position).stream(); } BlockPos down = position.down(); BlockState below = world.getBlockState(down); if (below.isAir()) { return Stream.of(treeType.pickRandomStack(world.random, treeState)); } if (below.getBlock() instanceof Buckable buckable) { return buckable.onBucked((ServerWorld)world, below, down).stream(); } return Stream.empty(); } private void affectBlockChange(PlayerEntity player, BlockPos position) { BlockDestructionManager.of(player.getWorld()).damageBlock(position, 4); PosHelper.fastAll(position, p -> { BlockState s = player.getWorld().getBlockState(p); if (s.getBlock() instanceof BeehiveBlock) { if (player.getWorld().getBlockEntity(p) instanceof BeehiveBlockEntity hive) { hive.angerBees(player, s, BeehiveBlockEntity.BeeState.EMERGENCY); } player.getWorld().updateComparators(position, s.getBlock()); Box area = new Box(position).expand(8, 6, 8); List nearbyBees = player.getWorld().getNonSpectatingEntities(BeeEntity.class, area); if (!nearbyBees.isEmpty()) { List nearbyPlayers = player.getWorld().getNonSpectatingEntities(PlayerEntity.class, area); int i = nearbyPlayers.size(); for (BeeEntity bee : nearbyBees) { if (bee.getTarget() == null) { bee.setTarget(nearbyPlayers.get(player.getWorld().random.nextInt(i))); } } } } }, PosHelper.HORIZONTAL); } public interface Buckable { List onBucked(ServerWorld world, BlockState state, BlockPos pos); } }