diff --git a/src/main/java/com/minelittlepony/unicopia/client/particle/Particles.java b/src/main/java/com/minelittlepony/unicopia/client/particle/Particles.java index 8f4c841a..82a3ac48 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/particle/Particles.java +++ b/src/main/java/com/minelittlepony/unicopia/client/particle/Particles.java @@ -7,6 +7,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.particle.IParticleFactory; import net.minecraft.client.particle.Particle; import net.minecraft.entity.Entity; +import net.minecraft.util.math.Vec3d; public class Particles { @@ -32,6 +33,10 @@ public class Particles { return -id - 1; } + public Particle spawnParticle(int particleId, boolean ignoreDistance, Vec3d pos, double speedX, double speedY, double speedZ, int ...pars) { + return spawnParticle(particleId, ignoreDistance, pos, speedX, speedY, speedZ, pars); + } + public Particle spawnParticle(int particleId, boolean ignoreDistance, double posX, double posY, double posZ, double speedX, double speedY, double speedZ, int ...pars) { Entity entity = mc.getRenderViewEntity(); diff --git a/src/main/java/com/minelittlepony/unicopia/item/ItemSpell.java b/src/main/java/com/minelittlepony/unicopia/item/ItemSpell.java index f2da1ca4..7f569693 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/ItemSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/item/ItemSpell.java @@ -146,6 +146,20 @@ public class ItemSpell extends Item implements ICastable { return new ActionResult(EnumActionResult.FAIL, stack); } + IMagicEffect effect = SpellRegistry.instance().getSpellFromItemStack(stack); + + if (effect instanceof IUseAction) { + SpellCastResult result = ((IUseAction)effect).onUse(stack, player, world, target); + + if (result != SpellCastResult.NONE) { + if (result == SpellCastResult.PLACE && !player.capabilities.isCreativeMode) { + stack.shrink(1); + } + + return new ActionResult(EnumActionResult.SUCCESS, stack); + } + } + return new ActionResult(EnumActionResult.PASS, stack); } diff --git a/src/main/java/com/minelittlepony/unicopia/spell/ICaster.java b/src/main/java/com/minelittlepony/unicopia/spell/ICaster.java index 49fabe1a..be1699fa 100644 --- a/src/main/java/com/minelittlepony/unicopia/spell/ICaster.java +++ b/src/main/java/com/minelittlepony/unicopia/spell/ICaster.java @@ -1,10 +1,17 @@ package com.minelittlepony.unicopia.spell; +import java.util.Random; +import java.util.function.Consumer; +import java.util.stream.Stream; + import com.minelittlepony.unicopia.player.IOwned; +import com.minelittlepony.util.shape.IShape; +import com.minelittlepony.util.vector.VecHelper; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; public interface ICaster extends IOwned, ILevelled { @@ -30,4 +37,20 @@ public interface ICaster extends IOwned, ILevelle default BlockPos getOrigin() { return getEntity().getPosition(); } + + default void spawnParticles(IShape area, int count, Consumer particleSpawner) { + Random rand = getWorld().rand; + + int x = getOrigin().getX(); + int y = getOrigin().getY(); + int z = getOrigin().getZ(); + + for (int i = 0; i < count; i++) { + particleSpawner.accept(area.computePoint(rand).add(x, y, z)); + } + } + + default Stream findAllEntitiesInRange(double radius) { + return VecHelper.findAllEntitiesInRange(getEntity(), getWorld(), getOrigin(), radius); + } } diff --git a/src/main/java/com/minelittlepony/unicopia/spell/IUseAction.java b/src/main/java/com/minelittlepony/unicopia/spell/IUseAction.java index e9f82cb3..b32d4444 100644 --- a/src/main/java/com/minelittlepony/unicopia/spell/IUseAction.java +++ b/src/main/java/com/minelittlepony/unicopia/spell/IUseAction.java @@ -1,5 +1,7 @@ package com.minelittlepony.unicopia.spell; +import javax.annotation.Nonnull; + import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; @@ -12,10 +14,10 @@ import net.minecraft.world.World; * */ public interface IUseAction { - + /** * Triggered when the player right clicks a block - * + * * @param stack The current itemstack * @param player The player * @param world The player's world @@ -24,20 +26,20 @@ public interface IUseAction { * @param hitX X offset inside the block * @param hitY Y offset inside the block * @param hitZ Z offset inside the block - * + * * @return ActionResult for the type of action to perform */ public SpellCastResult onUse(ItemStack stack, EntityPlayer player, World world, BlockPos pos, EnumFacing side, float hitX, float hitY, float hitZ); - + /** * Triggered when the player right clicks - * - * @param stack The current itemstack + * + * @param stack The current itemstack * @param player The player * @param world The player's world * @param hitEntity The entity in focus, if any - * + * * @return ActionResult for the type of action to perform */ - public SpellCastResult onUse(ItemStack stack, EntityPlayer player, World world, Entity hitEntity); + public SpellCastResult onUse(ItemStack stack, EntityPlayer player, World world, @Nonnull Entity hitEntity); } diff --git a/src/main/java/com/minelittlepony/unicopia/spell/SpellFire.java b/src/main/java/com/minelittlepony/unicopia/spell/SpellFire.java new file mode 100644 index 00000000..9653cb0c --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/spell/SpellFire.java @@ -0,0 +1,262 @@ +package com.minelittlepony.unicopia.spell; + +import javax.annotation.Nonnull; + +import com.minelittlepony.unicopia.Predicates; +import com.minelittlepony.unicopia.entity.IMagicals; +import com.minelittlepony.util.MagicalDamageSource; +import com.minelittlepony.util.PosHelper; +import com.minelittlepony.util.blockstate.IStateMapping; +import com.minelittlepony.util.blockstate.StateMapList; +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.BlockBush; +import net.minecraft.block.BlockDirt; +import net.minecraft.block.BlockFarmland; +import net.minecraft.block.BlockLeaves; +import net.minecraft.block.BlockRedstoneWire; +import net.minecraft.block.BlockSilverfish; +import net.minecraft.block.BlockStoneBrick; +import net.minecraft.block.BlockWall; +import net.minecraft.block.material.Material; +import net.minecraft.block.state.IBlockState; +import net.minecraft.dispenser.IBlockSource; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.item.EntityItem; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.init.Blocks; +import net.minecraft.init.SoundEvents; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.DamageSource; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumParticleTypes; +import net.minecraft.util.SoundCategory; +import net.minecraft.world.World; + +public class SpellFire extends AbstractSpell implements IUseAction, IDispenceable { + + public final StateMapList affected = new StateMapList(); + + private static final IShape visual_effect_region = new Sphere(false, 0.5); + private static final IShape effect_range = new Sphere(false, 4); + + public SpellFire() { + affected.removeBlock(s -> s.getBlock() == Blocks.SNOW_LAYER || s.getBlock() == Blocks.SNOW); + affected.removeBlock(s -> s.getBlock() instanceof BlockBush); + + affected.replaceBlock(Blocks.CLAY, Blocks.HARDENED_CLAY); + affected.replaceBlock(Blocks.OBSIDIAN, Blocks.LAVA); + affected.replaceBlock(Blocks.GRASS, Blocks.DIRT); + affected.replaceBlock(Blocks.MOSSY_COBBLESTONE, Blocks.COBBLESTONE); + + affected.replaceProperty(Blocks.COBBLESTONE_WALL, BlockWall.VARIANT, BlockWall.EnumType.MOSSY, BlockWall.EnumType.NORMAL); + affected.replaceProperty(Blocks.STONEBRICK, BlockStoneBrick.VARIANT, BlockStoneBrick.EnumType.MOSSY, BlockStoneBrick.EnumType.DEFAULT); + affected.replaceProperty(Blocks.MONSTER_EGG, BlockSilverfish.VARIANT, BlockSilverfish.EnumType.MOSSY_STONEBRICK, BlockSilverfish.EnumType.STONEBRICK); + affected.replaceProperty(Blocks.DIRT, BlockDirt.VARIANT, BlockDirt.DirtType.PODZOL, BlockDirt.DirtType.COARSE_DIRT); + + affected.setProperty(Blocks.FARMLAND, BlockFarmland.MOISTURE, 0); + + affected.add(IStateMapping.build( + s -> s.getBlock() == Blocks.DIRT && s.getValue(BlockDirt.VARIANT) == BlockDirt.DirtType.DIRT, + s -> Math.random() <= 0.15 ? + s.withProperty(BlockDirt.VARIANT, BlockDirt.DirtType.COARSE_DIRT) + : s)); + } + + @Override + public int getCurrentLevel() { + return 0; + } + + + @Override + public void setCurrentLevel(int level) { + } + + @Override + public String getName() { + return "fire"; + } + + @Override + public boolean update(ICaster source, int level) { + return false; + } + + @Override + public void render(ICaster source, int level) { + + source.spawnParticles(visual_effect_region, level * 6, pos -> { + source.getWorld().spawnParticle(EnumParticleTypes.SMOKE_LARGE, pos.x, pos.y, pos.z, 0, 0, 0); + }); + } + + @Override + public SpellCastResult onUse(ItemStack stack, EntityPlayer player, World world, BlockPos pos, EnumFacing side, float hitX, float hitY, float hitZ) { + boolean result = false; + + if (player == null || player.isSneaking()) { + result = applyBlocks(player, world, pos); + } else { + + for (BlockPos i : PosHelper.getAllInRegionMutable(pos, effect_range)) { + result |= applyBlocks(player, world, i); + } + } + + if (!result) { + result = applyEntities(player, world, pos); + } + + return result ? SpellCastResult.DEFAULT : SpellCastResult.NONE; + } + + @Override + public SpellCastResult onUse(ItemStack stack, EntityPlayer player, World world, @Nonnull Entity hitEntity) { + return applyEntitySingle(player, world, hitEntity) ? SpellCastResult.DEFAULT : SpellCastResult.NONE; + } + + @Override + public SpellCastResult onDispenced(BlockPos pos, EnumFacing facing, IBlockSource source) { + pos = pos.offset(facing, 4); + + boolean result = false; + + for (BlockPos i : PosHelper.getAllInRegionMutable(pos, effect_range)) { + result |= applyBlocks(null, source.getWorld(), i); + } + + if (!result) { + result = applyEntities(null, source.getWorld(), pos); + } + + return result ? SpellCastResult.NONE : SpellCastResult.DEFAULT; + } + + protected boolean applyBlocks(EntityPlayer owner, World world, BlockPos pos) { + IBlockState state = world.getBlockState(pos); + Block id = state.getBlock(); + + if (id != Blocks.AIR) { + if (id == Blocks.ICE || id == Blocks.PACKED_ICE) { + world.setBlockState(pos, (world.provider.doesWaterVaporize() ? Blocks.AIR : Blocks.WATER).getDefaultState()); + playEffect(world, pos); + + return true; + } else if (id == Blocks.NETHERRACK) { + if (world.getBlockState(pos.up()).getMaterial() == Material.AIR) { + + if (world.rand.nextInt(300) == 0) { + world.setBlockState(pos.up(), Blocks.FIRE.getDefaultState()); + } + + return true; + } + } else if (id == Blocks.REDSTONE_WIRE) { + int power = world.rand.nextInt(5) == 3 ? 15 : 3; + + sendPower(world, pos, power, 3, 0); + + return true; + } else if (id == Blocks.SAND && world.rand.nextInt(10) == 0) { + if (isSurroundedBySand(world, pos)) { + world.setBlockState(pos, Blocks.GLASS.getDefaultState()); + + playEffect(world, pos); + return true; + } + } else if (id instanceof BlockLeaves) { + if (world.getBlockState(pos.up()).getMaterial() == Material.AIR) { + world.setBlockState(pos.up(), Blocks.FIRE.getDefaultState()); + + playEffect(world, pos); + return true; + } + } else { + IBlockState newState = affected.getConverted(state); + + if (!state.equals(newState)) { + world.setBlockState(pos, newState, 3); + + playEffect(world, pos); + return true; + } + } + } + + return false; + } + + protected boolean applyEntities(EntityPlayer owner, World world, BlockPos pos) { + return VecHelper + .findAllEntitiesInRange(owner, world, pos, 3) + .filter(i -> applyEntitySingle(owner, world, i)) + .count() > 0; + } + + protected boolean applyEntitySingle(Entity owner, World world, Entity e) { + if ((!e.equals(owner) || + (owner instanceof EntityPlayer && !Predicates.MAGI.test(owner))) && !(e instanceof EntityItem) + && !(e instanceof IMagicals)) { + e.setFire(60); + e.attackEntityFrom(getDamageCause(e, (EntityLivingBase)owner), 0.1f); + playEffect(world, e.getPosition()); + return true; + } + + return false; + } + + protected DamageSource getDamageCause(Entity target, EntityLivingBase attacker) { + return MagicalDamageSource.causeMobDamage("fire", attacker); + } + + /** + * Transmists power to a piece of redstone + */ + private void sendPower(World w, BlockPos pos, int power, int max, int i) { + IBlockState state = w.getBlockState(pos); + Block id = state.getBlock(); + + if (i < max && id == Blocks.REDSTONE_WIRE) { + i++; + + w.setBlockState(pos, state.withProperty(BlockRedstoneWire.POWER, power)); + + sendPower(w, pos.up(), power, max, i); + sendPower(w, pos.down(), power, max, i); + sendPower(w, pos.north(), power, max, i); + sendPower(w, pos.south(), power, max, i); + sendPower(w, pos.east(), power, max, i); + sendPower(w, pos.west(), power, max, i); + } + } + + protected void playEffect(World world, BlockPos pos) { + int x = pos.getX(); + int y = pos.getY(); + int z = pos.getZ(); + world.playSound((double)((float)x + 0.5F), (double)((float)y + 0.5F), (double)((float)z + 0.5F), SoundEvents.BLOCK_FURNACE_FIRE_CRACKLE, SoundCategory.AMBIENT, 0.5F, 2.6F + (world.rand.nextFloat() - world.rand.nextFloat()) * 0.8F, true); + + + for (int i = 0; i < 8; ++i) { + world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, (double)x + Math.random(), (double)y + Math.random(), (double)z + Math.random(), 0.0D, 0.0D, 0.0D); + } + } + + public static boolean isSurroundedBySand(World w, BlockPos pos) { + return isSand(w, pos.up()) && isSand(w, pos.down()) && + isSand(w, pos.north()) && isSand(w, pos.south()) && + isSand(w, pos.east()) && isSand(w, pos.west()); + } + + public static boolean isSand(World world, BlockPos pos) { + Block id = world.getBlockState(pos).getBlock(); + return id == Blocks.SAND || id == Blocks.GLASS; + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/spell/SpellIce.java b/src/main/java/com/minelittlepony/unicopia/spell/SpellIce.java new file mode 100644 index 00000000..ee5d4394 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/spell/SpellIce.java @@ -0,0 +1,168 @@ +package com.minelittlepony.unicopia.spell; + +import com.minelittlepony.unicopia.UMaterials; +import com.minelittlepony.util.MagicalDamageSource; +import com.minelittlepony.util.PosHelper; +import com.minelittlepony.util.blockstate.IStateMapping; +import com.minelittlepony.util.blockstate.StateMapList; +import com.minelittlepony.util.shape.Sphere; +import com.minelittlepony.util.vector.VecHelper; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockBush; +import net.minecraft.block.BlockLeaves; +import net.minecraft.block.BlockRedstoneWire; +import net.minecraft.block.BlockSnow; +import net.minecraft.block.material.Material; +import net.minecraft.block.state.IBlockState; +import net.minecraft.dispenser.IBlockSource; +import net.minecraft.entity.Entity; +import net.minecraft.entity.item.EntityTNTPrimed; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.init.Blocks; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.DamageSource; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumParticleTypes; +import net.minecraft.world.World; + +public class SpellIce extends AbstractSpell implements IUseAction, IDispenceable { + + public static final StateMapList affected = new StateMapList(); + + static { + affected.add(IStateMapping.build( + s -> s.getMaterial() == Material.WATER, + s -> Blocks.ICE.getDefaultState())); + affected.add(IStateMapping.build( + s -> s.getMaterial() == Material.LAVA, + s -> Blocks.OBSIDIAN.getDefaultState())); + affected.add(IStateMapping.build( + s -> s.getBlock() == Blocks.SNOW_LAYER, + s -> s.cycleProperty(BlockSnow.LAYERS))); + affected.replaceBlock(Blocks.FIRE, Blocks.AIR); + affected.setProperty(Blocks.REDSTONE_WIRE, BlockRedstoneWire.POWER, 0); + } + + protected int rad = 3; + + @Override + public int getCurrentLevel() { + return 0; + } + + @Override + public void setCurrentLevel(int level) { + } + + @Override + public String getName() { + return "ice"; + } + + @Override + public boolean update(ICaster source, int level) { + return false; + } + + @Override + public void render(ICaster source, int level) { + } + + @Override + public SpellCastResult onDispenced(BlockPos pos, EnumFacing facing, IBlockSource source) { + return applyBlocks(null, source.getWorld(), pos.offset(facing, rad)) ? SpellCastResult.NONE : SpellCastResult.DEFAULT; + } + + @Override + public SpellCastResult onUse(ItemStack stack, EntityPlayer player, World world, BlockPos pos, EnumFacing side, float hitX, float hitY, float hitZ) { + if (player != null && player.isSneaking()) { + applyBlockSingle(world, pos); + } else { + applyBlocks(player, world, pos); + } + + return SpellCastResult.DEFAULT; + } + + @Override + public SpellCastResult onUse(ItemStack stack, EntityPlayer player, World world, Entity hitEntity) { + if (hitEntity != null) { + applyEntitySingle(player, hitEntity); + return SpellCastResult.DEFAULT; + } + + return SpellCastResult.NONE; + } + + private boolean applyBlocks(EntityPlayer owner, World world, BlockPos pos) { + + for (BlockPos i : PosHelper.getAllInRegionMutable(pos, new Sphere(false, rad))) { + applyBlockSingle(world, i); + } + + return applyEntities(owner, world, pos); + } + + protected boolean applyEntities(EntityPlayer owner, World world, BlockPos pos) { + return VecHelper.findAllEntitiesInRange(owner, world, pos, 3).filter(i -> { + applyEntitySingle(owner, i); + return true; + }).count() > 0; + } + + protected void applyEntitySingle(EntityPlayer owner, Entity e) { + if (e instanceof EntityTNTPrimed) { + e.setDead(); + e.getEntityWorld().setBlockState(e.getPosition(), Blocks.TNT.getDefaultState()); + } else { + if (e.isBurning()) { + e.extinguish(); + } else { + DamageSource d = MagicalDamageSource.causePlayerDamage("cold", owner); + e.attackEntityFrom(d, 2); + } + } + } + + private void applyBlockSingle(World world, BlockPos pos) { + IBlockState state = world.getBlockState(pos); + Block id = state.getBlock(); + + IBlockState converted = affected.getConverted(state); + + if (!state.equals(converted)) { + world.setBlockState(pos, converted, 3); + } else if (state.getMaterial() != UMaterials.cloud && state.isSideSolid(world, pos, EnumFacing.UP) + || (id == Blocks.SNOW) + || (id instanceof BlockLeaves)) { + incrementIce(world, pos.up()); + } else if (id == Blocks.ICE && world.rand.nextInt(10) == 0) { + if (isSurroundedByIce(world, pos)) { + world.setBlockState(pos, Blocks.PACKED_ICE.getDefaultState()); + } + } + + world.spawnParticle(EnumParticleTypes.WATER_SPLASH, pos.getX() + world.rand.nextFloat(), pos.getY() + 1, pos.getZ() + world.rand.nextFloat(), 0, 0, 0); + } + + public static boolean isSurroundedByIce(World w, BlockPos pos) { + return isIce(w, pos.up()) && isIce(w, pos.down()) && + isIce(w, pos.north()) && isIce(w, pos.south()) && + isIce(w, pos.east()) && isIce(w, pos.west()); + } + + public static boolean isIce(World world, BlockPos pos) { + return world.getBlockState(pos).getMaterial() == Material.ICE; + } + + private void incrementIce(World world, BlockPos pos) { + IBlockState state = world.getBlockState(pos); + Block id = state.getBlock(); + + if (id == Blocks.AIR || (id instanceof BlockBush)) { + world.setBlockState(pos, Blocks.SNOW_LAYER.getDefaultState(), 3); + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/spell/SpellRegistry.java b/src/main/java/com/minelittlepony/unicopia/spell/SpellRegistry.java index 4842ae1f..59a5166c 100644 --- a/src/main/java/com/minelittlepony/unicopia/spell/SpellRegistry.java +++ b/src/main/java/com/minelittlepony/unicopia/spell/SpellRegistry.java @@ -25,8 +25,10 @@ public class SpellRegistry { private final Map entries = new HashMap<>(); private SpellRegistry() { - registerSpell("shield", 0xffff00, SpellShield::new); - registerSpell("charge", 0x0000ff, SpellCharge::new); + registerSpell("shield", 0x66CDAA, SpellShield::new); + registerSpell("charge", 0x0000AA, SpellCharge::new); + registerSpell("fire", 0xFF0000, SpellFire::new); + registerSpell("ice", 0xADD8E6, SpellIce::new); } public IMagicEffect getSpellFromName(String name) { diff --git a/src/main/java/com/minelittlepony/unicopia/spell/SpellShield.java b/src/main/java/com/minelittlepony/unicopia/spell/SpellShield.java index 95b3e989..9f0c026f 100644 --- a/src/main/java/com/minelittlepony/unicopia/spell/SpellShield.java +++ b/src/main/java/com/minelittlepony/unicopia/spell/SpellShield.java @@ -1,7 +1,5 @@ package com.minelittlepony.unicopia.spell; -import java.util.Random; - import com.minelittlepony.unicopia.Predicates; import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.Unicopia; @@ -9,7 +7,6 @@ import com.minelittlepony.unicopia.client.particle.Particles; import com.minelittlepony.unicopia.player.PlayerSpeciesList; import com.minelittlepony.unicopia.power.IPower; import com.minelittlepony.util.ProjectileUtil; -import com.minelittlepony.util.shape.IShape; import com.minelittlepony.util.shape.Sphere; import net.minecraft.entity.Entity; @@ -17,7 +14,6 @@ import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.SoundEvents; import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; @@ -51,20 +47,10 @@ public class SpellShield extends AbstractSpell { } protected void spawnParticles(ICaster source, int strength) { - IShape sphere = new Sphere(true, strength); - Random rand = source.getWorld().rand; - - int x = source.getOrigin().getX(); - int y = source.getOrigin().getY(); - int z = source.getOrigin().getZ(); - - for (int i = 0; i < strength * 6; i++) { - Vec3d pos = sphere.computePoint(rand); - Particles.instance().spawnParticle(Unicopia.MAGIC_PARTICLE, false, - pos.x + x, pos.y + y, pos.z + z, - 0, 0, 0); - } + source.spawnParticles(new Sphere(true, strength), strength * 6, pos -> { + Particles.instance().spawnParticle(Unicopia.MAGIC_PARTICLE, false, pos.x, pos.y, pos.z, 0, 0, 0); + }); } @Override @@ -90,20 +76,10 @@ public class SpellShield extends AbstractSpell { int x = pos.getX(), y = pos.getY(), z = pos.getZ(); - BlockPos begin = pos.add(-radius, -radius, -radius); - BlockPos end = pos.add(radius, radius, radius); - - AxisAlignedBB bb = new AxisAlignedBB(begin, end); - boolean ownerIsValid = Predicates.MAGI.test(owner); - for (Entity i : source.getWorld().getEntitiesInAABBexcluding(source.getEntity(), bb, entity -> !(ownerIsValid && entity.equals(owner)))) { + source.findAllEntitiesInRange(radius).filter(entity -> !(ownerIsValid && entity.equals(owner))).forEach(i -> { double dist = i.getDistance(x, y, z); - double dist2 = i.getDistance(x, y - i.getEyeHeight(), z); - - if (dist > radius && dist2 > radius) { - continue; - } if (ProjectileUtil.isProjectile(i)) { if (!ProjectileUtil.isProjectileThrownBy(i, owner)) { @@ -126,7 +102,7 @@ public class SpellShield extends AbstractSpell { -(y - i.posY) / force + (dist < 1 ? dist : 0), -(z - i.posZ) / force); } - } + }); return true; } diff --git a/src/main/java/com/minelittlepony/util/PosHelper.java b/src/main/java/com/minelittlepony/util/PosHelper.java index 4bbd81c9..15888b06 100644 --- a/src/main/java/com/minelittlepony/util/PosHelper.java +++ b/src/main/java/com/minelittlepony/util/PosHelper.java @@ -1,13 +1,17 @@ package com.minelittlepony.util; +import java.util.Iterator; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Stream; +import com.google.common.collect.AbstractIterator; import com.google.common.collect.Streams; +import com.minelittlepony.util.shape.IShape; import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.BlockPos.MutableBlockPos; public class PosHelper { @@ -27,6 +31,31 @@ public class PosHelper { return false; } + public static Iterable getAllInRegionMutable(BlockPos origin, IShape shape) { + + Iterator iter = BlockPos.getAllInBoxMutable( + origin.add(new BlockPos(shape.getLowerBound())), + origin.add(new BlockPos(shape.getUpperBound())) + ).iterator(); + + return () -> new AbstractIterator() { + @Override + protected MutableBlockPos computeNext() { + while (iter.hasNext()) { + MutableBlockPos pos = iter.next(); + + if (shape.isPointInside(new Vec3d(pos.subtract(origin)))) { + return pos; + } + } + + endOfData(); + + return null; + } + }; + } + /** * Creates a stream of mutable block positions ranging from the beginning position to end. */ diff --git a/src/main/java/com/minelittlepony/util/blockstate/IStateMapping.java b/src/main/java/com/minelittlepony/util/blockstate/IStateMapping.java new file mode 100644 index 00000000..891ff948 --- /dev/null +++ b/src/main/java/com/minelittlepony/util/blockstate/IStateMapping.java @@ -0,0 +1,77 @@ +package com.minelittlepony.util.blockstate; + +import java.util.function.Function; +import java.util.function.Predicate; + +import javax.annotation.Nonnull; + +import net.minecraft.block.Block; +import net.minecraft.block.properties.IProperty; +import net.minecraft.block.state.IBlockState; +import net.minecraft.init.Blocks; + +public interface IStateMapping extends Predicate, Function { + + static IStateMapping removeBlock(Predicate mapper) { + return build( + mapper, + s -> Blocks.AIR.getDefaultState()); + } + + static IStateMapping replaceBlock(Block from, Block to) { + return build( + s -> s.getBlock() == from, + s -> to.getDefaultState()); + } + + static > IStateMapping replaceProperty(Block block, IProperty property, T from, T to) { + return build( + s -> s.getBlock() == block && s.getValue(property) == from, + s -> s.withProperty(property, to)); + } + + static > IStateMapping setProperty(Block block, IProperty property, T to) { + return build( + s -> s.getBlock() == block, + s -> s.withProperty(property, to)); + } + + static IStateMapping build(Predicate predicate, Function converter) { + return new IStateMapping() { + @Override + public boolean test(IBlockState state) { + return predicate.test(state); + } + + @Override + public IBlockState apply(IBlockState state) { + return converter.apply(state); + } + }; + } + + /** + * Checks if this state can be converted by this mapping + * + * @param state State to check + * + * @return True if the state can be converted + */ + @Override + default boolean test(@Nonnull IBlockState state) { + return true; + } + + /** + * Converts the given state based on this mapping + * + * @param state State to convert + * + * @return The converted state + */ + @Nonnull + @Override + default IBlockState apply(@Nonnull IBlockState state) { + return state; + } +} diff --git a/src/main/java/com/minelittlepony/util/blockstate/StateMapList.java b/src/main/java/com/minelittlepony/util/blockstate/StateMapList.java new file mode 100644 index 00000000..be5b6d6f --- /dev/null +++ b/src/main/java/com/minelittlepony/util/blockstate/StateMapList.java @@ -0,0 +1,64 @@ +package com.minelittlepony.util.blockstate; + +import java.util.ArrayList; +import java.util.function.Predicate; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import net.minecraft.block.Block; +import net.minecraft.block.properties.IProperty; +import net.minecraft.block.state.IBlockState; + +/** + * A collection of block-state mappings. + * + */ +public class StateMapList extends ArrayList { + private static final long serialVersionUID = 2602772651960588745L; + + public void removeBlock(Predicate mapper) { + add(IStateMapping.removeBlock(mapper)); + } + + public void replaceBlock(Block from, Block to) { + add(IStateMapping.replaceBlock(from, to)); + } + + public > void replaceProperty(Block block, IProperty property, T from, T to) { + add(IStateMapping.replaceProperty(block, property, from, to)); + } + + public > void setProperty(Block block, IProperty property, T to) { + add(IStateMapping.setProperty(block, property, to)); + } + + /** + * Checks if this collection contains a mapping capable of converting the given state. + * + * @param state State to check + * + * @return True if the state can be converted + */ + public boolean canConvert(@Nullable IBlockState state) { + return state != null && stream().anyMatch(i -> i.test(state)); + } + + /** + * Attempts to convert the given state based on the known mappings in this collection. + * + * @param state State to convert + * + * @return The converted state if there is one, otherwise null + */ + @Nonnull + public IBlockState getConverted(@Nonnull IBlockState state) { + for (IStateMapping i : this) { + if (i.test(state)) { + return i.apply(state); + } + } + + return state; + } +} diff --git a/src/main/java/com/minelittlepony/util/shape/IShape.java b/src/main/java/com/minelittlepony/util/shape/IShape.java index 5e6a5fed..24591c93 100644 --- a/src/main/java/com/minelittlepony/util/shape/IShape.java +++ b/src/main/java/com/minelittlepony/util/shape/IShape.java @@ -5,54 +5,64 @@ import java.util.Random; import net.minecraft.util.math.Vec3d; /** - * + * *Interface for a 3d shape, used for spawning particles in a designated area (or anything else you need shapes for). */ public interface IShape { - + /** * Rotates this shape around it's center. - * + * * @param u Rotate yaw * @param v Rotate pitch - * + * * @return This Shape */ public IShape setRotation(float u, float v); - + /** * Get the volume of space filled by this shape, or the surface area if hollow. - * + * * @return double volume */ public double getVolumeOfSpawnableSpace(); - + /** * X offset from the shape's origin. - * + * * @return X */ public double getXOffset(); - + /** * Y offset from the shape's origin. - * + * * @return Y */ public double getYOffset(); - + /** * Z offset from the shape's origin. - * + * * @return Z */ public double getZOffset(); - + + /** + * Gets the lower bounds of the region occupied by this shape. + */ + public Vec3d getLowerBound(); + + /** + * Gets the upper bound of the region occupied by this shape. + */ + public Vec3d getUpperBound(); + /** * Computes a random coordinate that falls within this shape's designated area. */ public Vec3d computePoint(Random rand); - + /** * Checks if the given point is on the edge, or if not hollow the inside, of this shape. * @return diff --git a/src/main/java/com/minelittlepony/util/shape/Line.java b/src/main/java/com/minelittlepony/util/shape/Line.java index dee56f42..cbb94bc7 100644 --- a/src/main/java/com/minelittlepony/util/shape/Line.java +++ b/src/main/java/com/minelittlepony/util/shape/Line.java @@ -78,7 +78,7 @@ public class Line implements IShape { public Vec3d computePoint(Random rand) { double distance = MathHelper.nextDouble(rand, 0, len); - return (new Vec3d(distance * dX, distance * dY, distance * dZ)).rotateYaw(yaw).rotatePitch(pitch); + return new Vec3d(dX, dY, dZ).scale(distance).add(sX, sY, sZ).rotateYaw(yaw).rotatePitch(pitch); } public Line setRotation(float u, float v) { @@ -89,6 +89,17 @@ public class Line implements IShape { public boolean isPointInside(Vec3d point) { point = point.rotateYaw(-yaw).rotatePitch(-pitch); + return point.x/dX == point.y/dY && point.x/dX == point.z/dZ; } + + @Override + public Vec3d getLowerBound() { + return new Vec3d(sX, sY, sZ).rotateYaw(yaw).rotatePitch(pitch); + } + + @Override + public Vec3d getUpperBound() { + return new Vec3d(sX + dX, sY + dY, sZ + dZ).scale(len).rotateYaw(yaw).rotatePitch(pitch); + } } diff --git a/src/main/java/com/minelittlepony/util/shape/Sphere.java b/src/main/java/com/minelittlepony/util/shape/Sphere.java index 750e76b2..78470009 100644 --- a/src/main/java/com/minelittlepony/util/shape/Sphere.java +++ b/src/main/java/com/minelittlepony/util/shape/Sphere.java @@ -117,7 +117,19 @@ public class Sphere implements IShape { public boolean isPointInside(Vec3d point) { point = point.rotateYaw(-yaw).rotatePitch(-pitch); point = new Vec3d(point.x / stretch.x, point.y / stretch.y, point.z / stretch.z); + double dist = point.length(); + return hollow ? dist == rad : dist <= rad; } + + @Override + public Vec3d getLowerBound() { + return new Vec3d(-rad * stretch.x, -rad * stretch.y, -rad * stretch.z).rotateYaw(yaw).rotatePitch(pitch); + } + + @Override + public Vec3d getUpperBound() { + return new Vec3d(rad * stretch.x, rad * stretch.y, rad * stretch.z).rotateYaw(yaw).rotatePitch(pitch); + } } diff --git a/src/main/java/com/minelittlepony/util/vector/VecHelper.java b/src/main/java/com/minelittlepony/util/vector/VecHelper.java index 405dc075..40d1837a 100644 --- a/src/main/java/com/minelittlepony/util/vector/VecHelper.java +++ b/src/main/java/com/minelittlepony/util/vector/VecHelper.java @@ -1,6 +1,7 @@ package com.minelittlepony.util.vector; import java.util.List; +import java.util.stream.Stream; import javax.annotation.Nullable; @@ -11,8 +12,10 @@ import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.util.EntitySelectors; 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; public class VecHelper { @@ -45,10 +48,25 @@ public class VecHelper { return null; } + public static Stream findAllEntitiesInRange(@Nullable Entity origin, World w, BlockPos pos, double radius) { + + BlockPos begin = pos.add(-radius, -radius, -radius); + BlockPos end = pos.add(radius, radius, radius); + + AxisAlignedBB bb = new AxisAlignedBB(begin, end); + + return w.getEntitiesInAABBexcluding(origin, bb, null).stream().filter(e -> { + double dist = e.getDistance(pos.getX(), pos.getY(), pos.getZ()); + double dist2 = e.getDistance(pos.getX(), pos.getY() - e.getEyeHeight(), pos.getZ()); + + return dist <= radius || dist2 <= radius; + }); + } + /** * Gets all entities within a given range from the player. */ - public static List getWithinRange(EntityPlayer player, double reach, @Nullable Predicate predicate) { + public static List getWithinRange(EntityPlayer player, double reach, @Nullable Predicate predicate) { Vec3d look = player.getLook(0).scale(reach); return player.world.getEntitiesInAABBexcluding(player, player