/**
Copyright (C) <2017> <coolAlias>
This file is part of coolAlias' Zelda Sword Skills Minecraft Mod; as such,
you can redistribute it and/or modify it under the terms of the GNU
General Public License as published by the Free Software Foundation,
either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package zeldaswordskills.util;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import com.google.common.collect.Sets;
import net.minecraft.block.Block;
import net.minecraft.block.BlockButton;
import net.minecraft.block.BlockLever;
import net.minecraft.block.BlockLiquid;
import net.minecraft.block.material.Material;
import net.minecraft.block.properties.IProperty;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.particle.EntityFX;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.item.EntityXPOrb;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.BlockPos;
import net.minecraft.util.MathHelper;
import net.minecraft.util.Vec3;
import net.minecraft.util.WeightedRandom;
import net.minecraft.util.WeightedRandomChestContent;
import net.minecraft.world.World;
import net.minecraftforge.common.ChestGenHooks;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import zeldaswordskills.block.BlockSecretStone;
import zeldaswordskills.block.tileentity.TileEntityDungeonCore;
import zeldaswordskills.entity.passive.EntityFairy;
import zeldaswordskills.ref.Config;
import zeldaswordskills.ref.Sounds;
/**
*
* Collection of methods related to placing objects or entities into the world
*
*/
public class WorldUtils
{
/** Maximum explosionSize within which blocks can be affected, regardless of explosion size */
public static final int MAX_RADIUS = 16;
/**
* Activates a button or toggles a lever at the given position and notifies neighbors
* @param state Must contain either a BlockButton or BlockLever
*/
public static void activateButton(World world, IBlockState state, BlockPos pos) {
Block block = state.getBlock();
if (!(block instanceof BlockButton) && !(block instanceof BlockLever)) {
return;
}
IProperty powered = (block instanceof BlockButton) ? BlockButton.POWERED : BlockLever.POWERED;
boolean setPowered = block instanceof BlockButton || !((Boolean) state.getValue(powered)).booleanValue();
IProperty facing = (block instanceof BlockButton) ? BlockButton.FACING : BlockLever.FACING;
world.setBlockState(pos, state.withProperty(powered, Boolean.valueOf(setPowered)), 3);
world.markBlockRangeForRenderUpdate(pos, pos);
world.playSoundEffect((double)pos.getX() + 0.5D, (double)pos.getY() + 0.5D, (double)pos.getZ() + 0.5D, "random.click", 0.3F, 0.6F);
world.notifyNeighborsOfStateChange(pos, state.getBlock());
world.notifyNeighborsOfStateChange(pos.offset(((BlockLever.EnumOrientation) state.getValue(facing)).getFacing().getOpposite()), state.getBlock());
world.scheduleUpdate(pos, state.getBlock(), state.getBlock().tickRate(world));
world.playSoundEffect((double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, Sounds.CLICK, 0.3F, 0.6F);
}
/**
* Whether the block at the given position can be melted by any of the various fire effects
*/
public static boolean canMeltBlock(World world, Block block, BlockPos pos) {
IBlockState state = world.getBlockState(pos);
int meta = state.getBlock().getMetaFromState(state);
boolean flag = block instanceof BlockSecretStone && (Config.enableFireArrowMelt() ? (meta & ~8) == 5 : meta == 5);
return (flag || block.getMaterial() == Material.ice || block.getMaterial() == Material.packedIce ||
block.getMaterial() == Material.snow || block.getMaterial() == Material.craftedSnow);
}
/**
* Drops the entity's currently held item into the world
*/
public static void dropHeldItem(EntityLivingBase entity) {
if (!entity.worldObj.isRemote && entity.getHeldItem() != null) {
EntityItem drop = new EntityItem(entity.worldObj, entity.posX,
entity.posY - 0.30000001192092896D + (double) entity.getEyeHeight(),
entity.posZ, entity.getHeldItem().copy());
float f = 0.3F;
float f1 = entity.worldObj.rand.nextFloat() * (float) Math.PI * 2.0F;
drop.motionX = (double)(-MathHelper.sin(entity.rotationYaw / 180.0F * (float) Math.PI) * MathHelper.cos(entity.rotationPitch / 180.0F * (float) Math.PI) * f);
drop.motionZ = (double)(MathHelper.cos(entity.rotationYaw / 180.0F * (float) Math.PI) * MathHelper.cos(entity.rotationPitch / 180.0F * (float) Math.PI) * f);
drop.motionY = (double)(-MathHelper.sin(entity.rotationPitch / 180.0F * (float) Math.PI) * f + 0.1F);
f = 0.02F * entity.worldObj.rand.nextFloat();
drop.motionX += Math.cos((double) f1) * (double) f;
drop.motionY += (double)((entity.worldObj.rand.nextFloat() - entity.worldObj.rand.nextFloat()) * 0.1F);
drop.motionZ += Math.sin((double) f1) * (double) f;
drop.setPickupDelay(40);
entity.worldObj.spawnEntityInWorld(drop);
entity.setCurrentItemOrArmor(0, (ItemStack) null);
}
}
/**
* Call when a container block is broken to drop the entire inv into the world
*/
public static void dropContainerBlockInventory(World world, BlockPos pos) {
TileEntity tileEntity = world.getTileEntity(pos);
if (!(tileEntity instanceof IInventory)) { return; }
IInventory inv = (IInventory) tileEntity;
for (int i = 0; i < inv.getSizeInventory(); ++i) {
spawnItemWithRandom(world, inv.removeStackFromSlot(i), pos.getX(), pos.getY(), pos.getZ());
}
}
/**
* Undoes whatever acceleration has been applied by materials such as flowing water (see {@link World#handleMaterialAcceleration})
*/
public static boolean reverseMaterialAcceleration(World world, AxisAlignedBB aabb, Material material, Entity entity) {
int i = MathHelper.floor_double(aabb.minX);
int j = MathHelper.floor_double(aabb.maxX + 1.0D);
int k = MathHelper.floor_double(aabb.minY);
int l = MathHelper.floor_double(aabb.maxY + 1.0D);
int i1 = MathHelper.floor_double(aabb.minZ);
int j1 = MathHelper.floor_double(aabb.maxZ + 1.0D);
if (!WorldUtils.isAreaLoaded(world, i, k, i1, j, l, j1, true)) {
return false;
} else {
boolean flag = false;
Vec3 vec3 = new Vec3(0.0D, 0.0D, 0.0D);
for (int k1 = i; k1 < j; ++k1) {
for (int l1 = k; l1 < l; ++l1) {
for (int i2 = i1; i2 < j1; ++i2) {
BlockPos pos = new BlockPos(k1, l1, i2);
IBlockState state = world.getBlockState(pos);
Block block = state.getBlock();
if (block.getMaterial() == material) {
double liquidLevel = (double)((float)(l1 + 1) - BlockLiquid.getLiquidHeightPercent(((Integer) state.getValue(BlockLiquid.LEVEL)).intValue()));
if ((double)l >= liquidLevel) {
flag = true;
vec3 = block.modifyAcceleration(world, pos, entity, vec3);
}
}
}
}
}
if (vec3.lengthVector() > 0.0D && entity.isPushedByWater()) {
vec3 = vec3.normalize();
double d1 = 0.014D;
entity.motionX -= vec3.xCoord * d1;
entity.motionY -= vec3.yCoord * d1;
entity.motionZ -= vec3.zCoord * d1;
entity.motionX *= 0.85D;
entity.motionY *= 0.85D;
entity.motionZ *= 0.85D;
}
return flag;
}
}
/**
* Copied from {@link World#isAreaLoaded}
*/
public static boolean isAreaLoaded(World world, int xStart, int yStart, int zStart, int xEnd, int yEnd, int zEnd, boolean allowEmpty) {
if (yEnd >= 0 && yStart < 256) {
xStart >>= 4;
zStart >>= 4;
xEnd >>= 4;
zEnd >>= 4;
for (int k1 = xStart; k1 <= xEnd; ++k1) {
for (int l1 = zStart; l1 <= zEnd; ++l1) {
if (!WorldUtils.isChunkLoaded(world, k1, l1, allowEmpty)) {
return false;
}
}
}
return true;
}
return false;
}
/**
* Copied from {@link World#isChunkLoaded}
*/
public static boolean isChunkLoaded(World world, int x, int z, boolean allowEmpty) {
return world.getChunkProvider().chunkExists(x, z) && (allowEmpty || !world.getChunkProvider().provideChunk(x, z).isEmpty());
}
/**
* Generates a number of random chest contents, placing them in the chest either completely
* at random or only in empty slots, as designated by the parameters
*/
public static void generateRandomChestContents(Random rand, List<WeightedRandomChestContent> weightedContents, IInventory chest, int numItems, boolean atRandom) {
for (int i = 0; i < numItems; ++i) {
WeightedRandomChestContent weightedChest = (WeightedRandomChestContent) WeightedRandom.getRandomItem(rand, weightedContents);
ItemStack[] stacks = ChestGenHooks.generateStacks(rand, weightedChest.theItemId, weightedChest.minStackSize, weightedChest.maxStackSize);
for (ItemStack item : stacks) {
if (atRandom) {
chest.setInventorySlotContents(rand.nextInt(chest.getSizeInventory()), item);
} else {
addItemToInventoryAtRandom(rand, item, chest, 3);
}
}
}
}
/**
* Attempts to add the itemstack to a random slot in the inventory; failing that,
* it will add to the first available slot
* @param numAttempts number of times to attempt random placement
* @return the number of items remaining in the stack, zero if all were added
*/
public static int addItemToInventoryAtRandom(Random rand, ItemStack stack, IInventory inv, int numAttempts) {
for (int i = 0; i < numAttempts; ++i) {
int slot = rand.nextInt(inv.getSizeInventory());
if (inv.getStackInSlot(slot) == null) {
inv.setInventorySlotContents(slot, stack);
return 0;
}
}
return addItemToInventory(stack, inv);
}
/**
* Adds an ItemStack to the first available slot in the provided IInventory, continuing
* to distribute the stack as necessary until the stacksize reaches 0, if possible
* @return the number of items remaining in the stack, zero if all were added
*/
public static int addItemToInventory(ItemStack stack, IInventory inv) {
int remaining = stack.stackSize;
for (int i = 0; i < inv.getSizeInventory() && remaining > 0; ++i) {
ItemStack slotstack = inv.getStackInSlot(i);
if (slotstack == null && inv.isItemValidForSlot(i, stack)) {
remaining -= inv.getInventoryStackLimit();
stack.stackSize = (remaining > 0 ? inv.getInventoryStackLimit() : stack.stackSize);
inv.setInventorySlotContents(i, stack);
inv.markDirty();
} else if (slotstack != null && stack.isStackable() && inv.isItemValidForSlot(i, stack)) {
if (slotstack.getItem() == stack.getItem() && (!stack.getHasSubtypes() ||
stack.getItemDamage() == slotstack.getItemDamage()) && ItemStack.areItemStackTagsEqual(stack, slotstack))
{
int l = slotstack.stackSize + remaining;
if (l <= stack.getMaxStackSize() && l <= inv.getInventoryStackLimit()) {
remaining = 0;
slotstack.stackSize = l;
inv.markDirty();
} else if (slotstack.stackSize < stack.getMaxStackSize() && stack.getMaxStackSize() <= inv.getInventoryStackLimit()) {
remaining -= stack.getMaxStackSize() - slotstack.stackSize;
slotstack.stackSize = stack.getMaxStackSize();
inv.markDirty();
}
}
}
}
return remaining;
}
/**
* Attempts to add all items in the list to the IInventory at the given
* position using {@link #addItemToInventoryAtRandom}. Items are added
* beginning at the first available slot.
* @return true if all items were successfully added
*/
public static boolean addInventoryContents(World world, BlockPos pos, List<ItemStack> items) {
TileEntity te = world.getTileEntity(pos);
if (te instanceof IInventory) {
IInventory inv = (IInventory) te;
for (ItemStack stack : items) {
if (addItemToInventory(stack, inv) > 0) {
return false;
}
}
return true;
}
return false;
}
/**
* Attempts to add all items in the list to the IInventory at the given
* position using {@link #addItemToInventoryAtRandom}
* @return true if all items were successfully added
*/
public static boolean addInventoryContentsRandomly(World world, BlockPos pos, List<ItemStack> items) {
TileEntity te = world.getTileEntity(pos);
if (te instanceof IInventory) {
IInventory inv = (IInventory) te;
boolean flag = true;
for (ItemStack stack : items) {
if (addItemToInventoryAtRandom(world.rand, stack, inv, 3) > 0) {
flag = false;
}
}
return flag;
}
return false;
}
/**
* Populates a list with blocks that can be affected within the given radius
* @param targetBlock the block to target, or null if all blocks may be targeted
*/
public static HashSet<BlockPos> getAffectedBlocksList(World world, Random rand, float radius, double posX, double posY, double posZ, Block targetBlock) {
HashSet<BlockPos> hashset = Sets.newHashSet();
for (int i = 0; i < MAX_RADIUS; ++i) {
for (int j = 0; j < MAX_RADIUS; ++j) {
for (int k = 0; k < MAX_RADIUS; ++k) {
if (i == 0 || i == MAX_RADIUS - 1 || j == 0 || j == MAX_RADIUS - 1 || k == 0 || k == MAX_RADIUS - 1)
{
double d3 = (double)((float)i / ((float) MAX_RADIUS - 1.0F) * 2.0F - 1.0F);
double d4 = (double)((float)j / ((float) MAX_RADIUS - 1.0F) * 2.0F - 1.0F);
double d5 = (double)((float)k / ((float) MAX_RADIUS - 1.0F) * 2.0F - 1.0F);
double d6 = Math.sqrt(d3 * d3 + d4 * d4 + d5 * d5);
d3 /= d6;
d4 /= d6;
d5 /= d6;
float f1 = radius * (0.7F + rand.nextFloat() * 0.6F);
double d0 = posX;
double d1 = posY;
double d2 = posZ;
for (float f2 = 0.3F; f1 > 0.0F; f1 -= 0.22500001F)
{
int l = MathHelper.floor_double(d0);
int i1 = MathHelper.floor_double(d1);
int j1 = MathHelper.floor_double(d2);
BlockPos blockpos = new BlockPos(l, i1, j1);
Block block = world.getBlockState(blockpos).getBlock();
if (block.getMaterial() != Material.air) {
f1 -= 1.3F * f2;
}
if (f1 > 0.0F && (targetBlock == null || block == targetBlock)) {
hashset.add(blockpos);
}
d0 += d3 * (double)f2;
d1 += d4 * (double)f2;
d2 += d5 * (double)f2;
}
}
}
}
}
return hashset;
}
/**
* Returns a list of all Tile Entities matching the class given within the bounding box
*/
public static <T extends TileEntity> List<T> getTileEntitiesWithinAABB(World world, Class<T> clazz, AxisAlignedBB aabb) {
List<T> list = new ArrayList<T>();
int minX = MathHelper.floor_double(aabb.minX - World.MAX_ENTITY_RADIUS);
int maxX = MathHelper.floor_double(aabb.maxX + World.MAX_ENTITY_RADIUS);
int minY = MathHelper.floor_double(aabb.minY - World.MAX_ENTITY_RADIUS);
int maxY = MathHelper.floor_double(aabb.maxY + World.MAX_ENTITY_RADIUS);
int minZ = MathHelper.floor_double(aabb.minZ - World.MAX_ENTITY_RADIUS);
int maxZ = MathHelper.floor_double(aabb.maxZ + World.MAX_ENTITY_RADIUS);
if (!world.isAreaLoaded(new BlockPos(minX, minY, minZ), new BlockPos(maxX, maxY, maxZ))) {
return list;
}
for (int i = minX; i <= maxX; ++i) {
for (int j = minY; j <= maxY; ++j) {
for (int k = minZ; k <= maxZ; ++k) {
TileEntity te = world.getTileEntity(new BlockPos(i, j, k));
if (te != null && clazz.isAssignableFrom(te.getClass())) {
list.add((T) te);
}
}
}
}
return list;
}
/**
* Returns an entity by UUID, possibly traversing every single loaded entity
*/
public static Entity getEntityByUUID(World world, UUID uuid) {
for (Object o : world.loadedEntityList) {
if (((Entity) o).getUniqueID().compareTo(uuid) == 0) {
return (Entity) o;
}
}
return null;
}
/**
* Returns the nearest fairy spawner dungeon core to the position, or null if none exists
* @param requiresFairy if true, only returns a spawner with at least one fairy nearby
*/
public static TileEntityDungeonCore getNearbyFairySpawner(World world, double x, double y, double z, boolean requiresFairy) {
List<TileEntityDungeonCore> list = WorldUtils.getTileEntitiesWithinAABB(world, TileEntityDungeonCore.class, new AxisAlignedBB(x - 3.0D, y - 2.0D, z - 3.0D, x + 3.0D, y + 2.0D, z + 3.0D));
for (TileEntityDungeonCore core : list) {
BlockPos pos = core.getPos();
if (core.isSpawner() && (!requiresFairy || world.getEntitiesWithinAABB(EntityFairy.class, new AxisAlignedBB((double) pos.getX() - 5D, (double) pos.getY() - 1, (double) pos.getZ() - 5D, (double) pos.getX() + 5D, (double) pos.getY() + 5.0D, (double) pos.getZ() + 5D)).size() > 0)) {
return core;
}
}
return null;
}
/**
* Plays a sound on the server with randomized volume and pitch; no effect if called on client
* @param f Volume: nextFloat() * f + add
* @param add Pitch: 1.0F / (nextFloat() * f + add)
*/
public static void playSoundAtEntity(Entity entity, String sound, float f, float add) {
playSoundAt(entity.worldObj, entity.posX, entity.posY, entity.posZ, sound, f, add);
}
/**
* Plays a sound on the server with randomized volume and pitch; no effect if called on client
* @param f Volume: nextFloat() * f + add
* @param add Pitch: 1.0F / (nextFloat() * f + add)
*/
public static void playSoundAt(World world, double x, double y, double z, String sound, float f, float add) {
float volume = world.rand.nextFloat() * f + add;
float pitch = 1.0F / (world.rand.nextFloat() * f + add);
world.playSoundEffect(x, y, z, sound, volume, pitch);
}
/**
* Spawns the provided ItemStack as an EntityItem with randomized position and motion,
* with default motion factor used by blocks to scatter items when the block is broken
*/
public static void spawnItemWithRandom(World world, ItemStack stack, double x, double y, double z) {
spawnItemWithRandom(world, stack, x, y, z, 0.05F);
}
/**
* Spawns the provided ItemStack as an EntityItem with randomized position and motion
* @param motionFactor EntityItem's motion is multiplied by this amount; clamped between 0.0F and 1.0F
*/
public static void spawnItemWithRandom(World world, ItemStack stack, double x, double y, double z, float motionFactor) {
if (!world.isRemote && stack != null) {
double spawnX = x + world.rand.nextFloat();
double spawnY = y + world.rand.nextFloat();
double spawnZ = z + world.rand.nextFloat();
float f = MathHelper.clamp_float(motionFactor, 0.0F, 1.0F);
EntityItem entityitem = new EntityItem(world, spawnX, spawnY, spawnZ, stack);
entityitem.motionX = (-0.5F + world.rand.nextGaussian()) * f;
entityitem.motionY = (4 + world.rand.nextGaussian()) * f;
entityitem.motionZ = (-0.5F + world.rand.nextGaussian()) * f;
entityitem.setDefaultPickupDelay();
world.spawnEntityInWorld(entityitem);
}
}
/**
* Adds previously instantiated particle effect to the effect renderer depending on minecraft game settings
*/
@SideOnly(Side.CLIENT)
public static void spawnWorldParticles(World world, EntityFX particle) {
Minecraft mc = Minecraft.getMinecraft();
if (particle != null && mc != null && mc.getRenderViewEntity() != null && mc.effectRenderer != null) {
int particleSetting = mc.gameSettings.particleSetting;
if (particleSetting == 2 || (particleSetting == 1 && world.rand.nextInt(3) == 0)) {
return;
}
double dx = mc.getRenderViewEntity().posX - particle.posX;
double dy = mc.getRenderViewEntity().posY - particle.posY;
double dz = mc.getRenderViewEntity().posZ - particle.posZ;
if (dx * dx + dy * dy + dz * dz < 256) {
Minecraft.getMinecraft().effectRenderer.addEffect(particle);
}
}
}
/**
* Spawns XP Orbs for the amount given with randomized position and motion
*/
public static void spawnXPOrbsWithRandom(World world, Random rand, int x, int y, int z, int xpAmount) {
if (!world.isRemote) {
while (xpAmount > 0) {
int xp = (xpAmount > 50 ? 50 : EntityXPOrb.getXPSplit(xpAmount));
xpAmount -= xp;
float spawnX = x + rand.nextFloat();
float spawnY = y + rand.nextFloat();
float spawnZ = z + rand.nextFloat();
EntityXPOrb xpOrb = new EntityXPOrb(world, spawnX, spawnY, spawnZ, xp);
xpOrb.motionY += (4 + rand.nextGaussian()) * 0.05F;
world.spawnEntityInWorld(xpOrb);
}
}
}
/**
* Sets an entity's location near x/y/z so that it doesn't spawn inside of walls.
* @return false if no suitable location found
*/
public static boolean setEntityInStructure(World world, Entity entity, BlockPos pos) {
if (entity == null) { return false; }
int i = 0;
entity.setLocationAndAngles(pos.getX(), pos.getY(), pos.getZ(), 0.0F, 0.0F);
while (entity.isEntityInsideOpaqueBlock() && i < 8) {
if (i == 4) { entity.setPosition(pos.getX() + 1, pos.getY(), pos.getZ() + 1); }
switch(i % 4) {
case 0: entity.setPosition(entity.posX + 0.5D, entity.posY, entity.posZ + 0.5D); break;
case 1: entity.setPosition(entity.posX, entity.posY, entity.posZ - 1.0D); break;
case 2: entity.setPosition(entity.posX - 1.0D, entity.posY, entity.posZ); break;
case 3: entity.setPosition(entity.posX, entity.posY, entity.posZ + 1.0D); break;
}
++i;
}
if (entity.isEntityInsideOpaqueBlock()) {
entity.setPosition(entity.posX + 0.5D, entity.posY, entity.posZ + 0.5D);
return false;
}
return true;
}
}