package com.minelittlepony.unicopia.power; import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; import org.lwjgl.input.Keyboard; import com.google.common.collect.Lists; import com.google.gson.annotations.Expose; import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.item.ItemApple; import com.minelittlepony.unicopia.particle.Particles; import com.minelittlepony.unicopia.player.IPlayer; import com.minelittlepony.unicopia.player.PlayerSpeciesList; import com.minelittlepony.unicopia.power.data.Location; import com.minelittlepony.unicopia.world.UWorld; import com.minelittlepony.util.MagicalDamageSource; import com.minelittlepony.util.PosHelper; import com.minelittlepony.util.WorldEvent; import com.minelittlepony.util.shape.IShape; import com.minelittlepony.util.shape.Sphere; import com.minelittlepony.util.vector.VecHelper; import net.minecraft.block.Block; import net.minecraft.block.BlockLeaves; import net.minecraft.block.BlockLog; import net.minecraft.block.material.Material; import net.minecraft.block.properties.IProperty; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.SharedMonsterAttributes; import net.minecraft.entity.item.EntityItem; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Blocks; import net.minecraft.item.ItemStack; import net.minecraft.util.DamageSource; import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.RayTraceResult; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; import static net.minecraft.util.EnumFacing.*; public class PowerStomp implements IPower { private final double rad = 4; private final AxisAlignedBB areaOfEffect = new AxisAlignedBB( -rad, -rad, -rad, rad, rad, rad ); @Override public String getKeyName() { return "unicopia.power.earth"; } @Override public int getKeyCode() { return Keyboard.KEY_M; } @Override public int getWarmupTime(IPlayer player) { return 3; } @Override public int getCooldownTime(IPlayer player) { return 50; } @Override public boolean canUse(Race playerSpecies) { return playerSpecies.canUseEarth(); } @Override public PowerStomp.Data tryActivate(IPlayer player) { RayTraceResult mop = VecHelper.getObjectMouseOver(player.getOwner(), 6, 1); if (mop != null && mop.typeOfHit == RayTraceResult.Type.BLOCK) { BlockPos pos = mop.getBlockPos(); IBlockState state = player.getWorld().getBlockState(pos); if (state.getBlock() instanceof BlockLog) { pos = getBaseOfTree(player.getWorld(), state, pos); if (measureTree(player.getWorld(), state, pos) > 0) { return new Data(pos.getX(), pos.getY(), pos.getZ(), 1); } } } if (!player.getOwner().onGround && !player.getOwner().capabilities.isFlying) { player.getOwner().addVelocity(0, -6, 0); return new Data(0, 0, 0, 0); } return null; } @Override public Class getPackageType() { return PowerStomp.Data.class; } public static BlockPos getSolidBlockBelow(BlockPos pos, World w) { while (w.isValid(pos)) { pos = pos.down(); if (w.getBlockState(pos).isSideSolid(w, pos, EnumFacing.UP)) { return pos; } } return pos; } @Override public void apply(IPlayer iplayer, Data data) { EntityPlayer player = iplayer.getOwner(); if (data.hitType == 0) { BlockPos ppos = player.getPosition(); BlockPos pos = getSolidBlockBelow(ppos, player.getEntityWorld()); player.addVelocity(0, -(ppos.distanceSq(pos)), 0); iplayer.getWorld().getEntitiesWithinAABBExcludingEntity(player, areaOfEffect.offset(iplayer.getOriginVector())).forEach(i -> { double dist = Math.sqrt(i.getDistanceSq(pos)); if (dist <= rad + 3) { double force = dist / 5; i.addVelocity( -(player.posX - i.posX) / force, -(player.posY - i.posY - 2) / force + (dist < 1 ? dist : 0), -(player.posZ - i.posZ) / force); DamageSource damage = MagicalDamageSource.causePlayerDamage("smash", player); double amount = (4 * player.getEntityAttribute(SharedMonsterAttributes.ATTACK_DAMAGE).getAttributeValue()) / (float)dist; if (i instanceof EntityPlayer) { Race race = PlayerSpeciesList.instance().getPlayer((EntityPlayer)i).getPlayerSpecies(); if (race.canUseEarth()) { amount /= 3; } if (race.canFly()) { amount *= 4; } } i.attackEntityFrom(damage, (float)amount); } }); BlockPos.getAllInBoxMutable(pos.add(-rad, -rad, -rad), pos.add(rad, rad, rad)).forEach(i -> { if (i.distanceSqToCenter(player.posX, player.posY, player.posZ) <= rad*rad) { spawnEffect(player.world, i); } }); for (int i = 1; i < 202; i+= 2) { spawnParticleRing(player, i); } iplayer.subtractEnergyCost(rad); } else if (data.hitType == 1) { boolean harmed = player.getHealth() < player.getMaxHealth(); if (harmed && player.world.rand.nextInt(30) == 0) { iplayer.subtractEnergyCost(3); return; } if (harmed || player.world.rand.nextInt(5) == 0) { if (!harmed || player.world.rand.nextInt(30) == 0) { UWorld.enqueueTask(w -> removeTree(w, data.pos())); } iplayer.subtractEnergyCost(3); } else { int cost = dropApples(player.world, data.pos()); if (cost > 0) { iplayer.subtractEnergyCost(cost * 3); } } } } private void spawnEffect(World w, BlockPos pos) { IBlockState state = w.getBlockState(pos); if (state.getBlock() != Blocks.AIR) { if (w.getBlockState(pos.up()).getBlock() == Blocks.AIR) { WorldEvent.DESTROY_BLOCK.play(w, pos, state); } } } @Override public void preApply(IPlayer player) { player.addExertion(40); player.getOwner().spawnRunningParticles(); } @Override public void postApply(IPlayer player) { int timeDiff = getCooldownTime(player) - player.getAbilities().getRemainingCooldown(); if (player.getOwner().getEntityWorld().getWorldTime() % 1 == 0 || timeDiff == 0) { spawnParticleRing(player.getOwner(), timeDiff, 1); } } private void spawnParticleRing(EntityPlayer player, int timeDiff) { spawnParticleRing(player, timeDiff, 0); } private void spawnParticleRing(EntityPlayer player, int timeDiff, double yVel) { int animationTicks = (int)(timeDiff / 10); if (animationTicks < 6) { IShape shape = new Sphere(true, animationTicks, 1, 0, 1); double y = 0.5 + (Math.sin(animationTicks) * 1.5); yVel *= y * 5; for (int i = 0; i < shape.getVolumeOfSpawnableSpace(); i++) { Vec3d point = shape.computePoint(player.getEntityWorld().rand); Particles.instance().spawnParticle(EnumParticleTypes.BLOCK_CRACK.getParticleID(), false, player.posX + point.x, player.posY + y + point.y, player.posZ + point.z, 0, yVel, 0, Block.getStateId(Blocks.DIRT.getDefaultState())); } } } private void removeTree(World w, BlockPos pos) { IBlockState log = w.getBlockState(pos); int size = measureTree(w, log, pos); if (size > 0) { pos = ascendTrunk(new ArrayList(), w, pos, log, 0); removeTreePart( w, log, pos, 0); } } private BlockPos ascendTrunk(List done, World w, BlockPos pos, IBlockState log, int level) { if (level < 3 && !done.contains(pos)) { done.add(pos); BlockPos result = ascendTree(w, log, pos, true); if (variantAndBlockEquals(w.getBlockState(pos.east()), log)) { result = ascendTrunk(done, w, pos.east(), log, level + 1); } if (variantAndBlockEquals(w.getBlockState(pos.west()), log)) { result = ascendTrunk(done, w, pos.west(), log, level + 1); } if (variantAndBlockEquals(w.getBlockState(pos.north()), log)) { result = ascendTrunk(done, w, pos.north(), log, level + 1); } if (variantAndBlockEquals(w.getBlockState(pos.south()), log)) { result = ascendTrunk(done, w, pos.south(), log, level + 1); } return result; } return pos; } private void removeTreePart(World w, IBlockState log, BlockPos pos, int level) { if (level < 10 && isWoodOrLeaf(w, log, pos)) { if (level < 5) { w.destroyBlock(pos, true); } else { IBlockState state = w.getBlockState(pos); state.getBlock().dropBlockAsItem(w, pos, state, 0); w.setBlockState(pos, Blocks.AIR.getDefaultState(), 3); } PosHelper.all(pos, p -> { removeTreePart(w, log, p, level + 1); }, UP, NORTH, SOUTH, EAST, WEST); } } private BlockPos ascendTree(World w, IBlockState log, BlockPos pos, boolean remove) { int breaks = 0; IBlockState state; while (variantAndBlockEquals(w.getBlockState(pos.up()), log)) { if (PosHelper.some(pos, p -> isLeaves(w.getBlockState(p), log), HORIZONTALS)) { break; } if (remove) { if (breaks < 10) { w.destroyBlock(pos, true); } else { state = w.getBlockState(pos); state.getBlock().dropBlockAsItem(w, pos, state, 0); w.setBlockState(pos, Blocks.AIR.getDefaultState(), 3); } breaks++; } pos = pos.up(); } return pos; } private int dropApples(World w, BlockPos pos) { IBlockState log = w.getBlockState(pos); int size = measureTree(w, log, pos); if (size > 0) { List capturedDrops = Lists.newArrayList(); dropApplesPart(capturedDrops, new ArrayList(), w, log, pos, 0); UWorld.enqueueTask(wo -> { capturedDrops.forEach(item -> { item.setNoPickupDelay(); wo.spawnEntity(item); }); }); return capturedDrops.size() / 3; } return 0; } private void dropApplesPart(List drops, List done, World w, IBlockState log, BlockPos pos, int level) { if (!done.contains(pos)) { done.add(pos); pos = ascendTree(w, log, pos, false); if (level < 10 && isWoodOrLeaf(w, log, pos)) { IBlockState state = w.getBlockState(pos); if (state.getBlock() instanceof BlockLeaves && w.getBlockState(pos.down()).getMaterial() == Material.AIR) { WorldEvent.DESTROY_BLOCK.play(w, pos, state); EntityItem item = new EntityItem(w); item.setPosition(pos.getX() + w.rand.nextFloat(), pos.getY() - 0.5, pos.getZ() + w.rand.nextFloat()); item.setItem(getApple(w, log)); drops.add(item); } PosHelper.all(pos, p -> { dropApplesPart(drops, done, w, log, p, level + 1); }, UP, NORTH, SOUTH, EAST, WEST); } } } private ItemStack getApple(World w, IBlockState log) { return ItemApple.getRandomItemStack(getVariant(log)); } private int measureTree(World w, IBlockState log, BlockPos pos) { List logs = new ArrayList(); List leaves = new ArrayList(); countParts(logs, leaves, w, log, pos); return logs.size() <= (leaves.size() / 2) ? logs.size() + leaves.size() : 0; } private BlockPos getBaseOfTree(World w, IBlockState log, BlockPos pos) { return getBaseOfTreePart(new ArrayList(), w, log, pos); } private BlockPos getBaseOfTreePart(List done, World w, IBlockState log, BlockPos pos) { if (done.contains(pos) || !variantAndBlockEquals(w.getBlockState(pos), log)) { return null; } done.add(pos); while (variantAndBlockEquals(w.getBlockState(pos.down()), log)) { pos = pos.down(); done.add(pos); } BlockPos adjacent = getBaseOfTreePart(done, w, log, pos.north()); if (adjacent != null && adjacent.getY() < pos.getY()) { pos = adjacent; } adjacent = getBaseOfTreePart(done, w, log, pos.south()); if (adjacent != null && adjacent.getY() < pos.getY()) { pos = adjacent; } adjacent = getBaseOfTreePart(done, w, log, pos.east()); if (adjacent != null && adjacent.getY() < pos.getY()) { pos = adjacent; } adjacent = getBaseOfTreePart(done, w, log, pos.west()); if (adjacent != null && adjacent.getY() < pos.getY()) { pos = adjacent; } if (!done.contains(pos)) { done.add(pos); } return pos; } private boolean isWoodOrLeaf(World w, IBlockState log, BlockPos pos) { IBlockState state = w.getBlockState(pos); return variantAndBlockEquals(state, log) || (isLeaves(state, log) && ((Boolean)state.getValue(BlockLeaves.DECAYABLE)).booleanValue()); } private void countParts(List logs, List leaves, World w, IBlockState log, BlockPos pos) { if (logs.contains(pos) || leaves.contains(pos)) { return; } IBlockState state = w.getBlockState(pos); boolean yay = false; if (state.getBlock() instanceof BlockLeaves && ((Boolean)state.getValue(BlockLeaves.DECAYABLE)).booleanValue() && variantEquals(state, log)) { leaves.add(pos); yay = true; } else if (variantAndBlockEquals(state, log)) { logs.add(pos); yay = true; } if (yay) { PosHelper.all(pos, p -> { countParts(logs, leaves, w, log, p); }, UP, NORTH, SOUTH, EAST, WEST); } } private boolean isLeaves(IBlockState state, IBlockState log) { return state.getBlock() instanceof BlockLeaves && variantEquals(state, log); } private boolean variantAndBlockEquals(IBlockState one, IBlockState two) { return (one.getBlock() == two.getBlock()) && variantEquals(one, two); } private boolean variantEquals(IBlockState one, IBlockState two) { return getVariant(one) == getVariant(two); } private Object getVariant(IBlockState state) { if (state.getBlock() instanceof BlockLeaves) { return ((BlockLeaves)state.getBlock()).getWoodType(state.getBlock().getMetaFromState(state)); } for (Entry, ?> i : state.getProperties().entrySet()) { if (i.getKey().getName().contentEquals("variant")) { return i.getValue(); } } return null; } protected static class Data extends Location { @Expose public int hitType; public Data(int x, int y, int z, int hit) { super(x, y, z); hitType = hit; } } }