package electroblob.wizardry.tileentity; import electroblob.wizardry.block.BlockReceptacle; import electroblob.wizardry.constants.Element; import electroblob.wizardry.event.ImbuementActivateEvent; import electroblob.wizardry.item.IManaStoringItem; import electroblob.wizardry.item.ItemWizardArmour; import electroblob.wizardry.registry.*; import electroblob.wizardry.util.GeometryUtils; import electroblob.wizardry.util.ParticleBuilder; import electroblob.wizardry.util.ParticleBuilder.Type; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.network.NetworkManager; import net.minecraft.network.play.server.SPacketUpdateTileEntity; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.EnumFacing; import net.minecraft.util.ITickable; import net.minecraft.util.SoundCategory; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; import net.minecraft.world.WorldServer; import net.minecraft.world.storage.loot.LootContext; import net.minecraft.world.storage.loot.LootTable; import net.minecraftforge.common.MinecraftForge; import org.apache.commons.lang3.ArrayUtils; import javax.annotation.Nullable; import java.util.Arrays; import java.util.List; import java.util.UUID; public class TileEntityImbuementAltar extends TileEntity implements ITickable { private static final int IMBUEMENT_DURATION = 140; private ItemStack stack; private int imbuementTimer; private Element displayElement; private EntityPlayer lastUser; /** For loading purposes only. This does not get updated once loaded! */ private UUID lastUserUUID; public TileEntityImbuementAltar(){ stack = ItemStack.EMPTY; } public void setStack(ItemStack stack){ this.stack = stack; checkRecipe(); } public void setLastUser(EntityPlayer player){ this.lastUser = player; } public void checkRecipe(){ if(getResult().isEmpty()){ imbuementTimer = 0; }else if(imbuementTimer == 0){ imbuementTimer = 1; }else{ return; // Don't sync if nothing changed } world.notifyBlockUpdate(pos, world.getBlockState(pos), world.getBlockState(pos), 3); // Sync } public ItemStack getStack(){ return stack; } @Override public void update(){ if(lastUserUUID != null && lastUser == null) lastUser = world.getPlayerEntityByUUID(lastUserUUID); if(imbuementTimer > 0){ if(imbuementTimer == 1){ // Has to be done here because of syncing world.playSound(pos.getX() + 0.5, pos.getY() + 1, pos.getZ() + 0.5, WizardrySounds.BLOCK_IMBUEMENT_ALTAR_IMBUE, SoundCategory.BLOCKS, 1, 1, false); } ItemStack result = getResult(); if(result.isEmpty()){ imbuementTimer = 0; }else{ if(imbuementTimer++ >= IMBUEMENT_DURATION){ this.stack = result; consumeReceptacleContents(); imbuementTimer = 0; displayElement = null; if(lastUser instanceof EntityPlayerMP){ WizardryAdvancementTriggers.imbuement_altar.trigger((EntityPlayerMP)lastUser, this.stack); } } if(world.isRemote && world.rand.nextInt(2) == 0){ Element[] elements = getReceptacleElements(); Vec3d centre = GeometryUtils.getCentre(this.pos.up()); for(int i = 0; i < elements.length; i++){ if(elements[i] == null) continue; Vec3d offset = new Vec3d(EnumFacing.byHorizontalIndex(i).getDirectionVec()); Vec3d vec = GeometryUtils.getCentre(this.pos).add(0, 0.3, 0).add(offset.scale(0.7)); int[] colours = BlockReceptacle.PARTICLE_COLOURS.get(elements[i]); ParticleBuilder.create(Type.DUST, world.rand, vec.x, vec.y, vec.z, 0.1, false) .vel(centre.subtract(vec).scale(0.02)).clr(colours[1]).fade(colours[2]).time(50).spawn(world); } } } } } /** Returns the element to use for the visual ray effect colours, or null if they should not be displayed. */ public Element getDisplayElement(){ return displayElement; } /** Returns how complete the current action is (from 0 to 1), or 0 if no action is being performed. */ public float getImbuementProgress(){ return (float)imbuementTimer / IMBUEMENT_DURATION; } private ItemStack getResult(){ boolean actuallyCrafting = imbuementTimer >= IMBUEMENT_DURATION - 1 && world instanceof WorldServer; Element[] elements = getReceptacleElements(); ItemStack result = getImbuementResult(stack, elements, actuallyCrafting, world, lastUser); if(result.isEmpty()){ displayElement = null; }else if(Arrays.stream(elements).distinct().count() == 1){ // All the same element displayElement = elements[0]; }else{ displayElement = Element.MAGIC; } return result; } /** Returns the elements of the 4 adjacent receptacles, in SWNE order. Null means an empty or missing receptacle. */ private Element[] getReceptacleElements(){ Element[] elements = new Element[4]; for(EnumFacing side : EnumFacing.HORIZONTALS){ TileEntity tileEntity = world.getTileEntity(pos.offset(side)); if(tileEntity instanceof TileEntityReceptacle){ elements[side.getHorizontalIndex()] = ((TileEntityReceptacle)tileEntity).getElement(); }else{ elements[side.getHorizontalIndex()] = null; } } return elements; } /** Empties the 4 adjacent receptacles. */ private void consumeReceptacleContents(){ for(EnumFacing side : EnumFacing.HORIZONTALS){ TileEntity tileEntity = world.getTileEntity(pos.offset(side)); if(tileEntity instanceof TileEntityReceptacle){ ((TileEntityReceptacle)tileEntity).setElement(null); } } } @Override public NBTTagCompound writeToNBT(NBTTagCompound nbt){ super.writeToNBT(nbt); NBTTagCompound itemTag = new NBTTagCompound(); stack.writeToNBT(itemTag); nbt.setTag("item", itemTag); nbt.setInteger("imbuementTimer", imbuementTimer); if(lastUser != null) nbt.setUniqueId("lastUser", lastUser.getUniqueID()); return nbt; } @Override public void readFromNBT(NBTTagCompound nbt){ super.readFromNBT(nbt); NBTTagCompound itemTag = nbt.getCompoundTag("item"); this.stack = new ItemStack(itemTag); this.imbuementTimer = nbt.getInteger("imbuementTimer"); this.lastUserUUID = nbt.getUniqueId("lastUser"); } @Override public NBTTagCompound getUpdateTag(){ return this.writeToNBT(new NBTTagCompound()); } @Nullable @Override public SPacketUpdateTileEntity getUpdatePacket(){ return new SPacketUpdateTileEntity(pos, 0, this.getUpdateTag()); } @Override public void onDataPacket(NetworkManager net, SPacketUpdateTileEntity pkt){ readFromNBT(pkt.getNbtCompound()); } /** * Returns the stack that results from imbuing the given input with the given elements. * @param input The item stack being imbued * @param receptacleElements The elements of the four receptacles * @param fullLootGen True to perform full loot generation from loot tables, false if this is just being queried for * visuals or other purposes. * @param world A reference to the current world object (may be null if {@code fullLootGen} is false) * @param lastUser The player that last interacted with the imbuement altar, or null if there isn't one (or if this * is being queried for other reasons, e.g. JEI) * @return The resulting item stack, or an empty stack if the given combination is not a valid imbuement */ public static ItemStack getImbuementResult(ItemStack input, Element[] receptacleElements, boolean fullLootGen, World world, EntityPlayer lastUser){ ImbuementActivateEvent event = new ImbuementActivateEvent(input, receptacleElements, world, lastUser, ItemStack.EMPTY); if (world != null && MinecraftForge.EVENT_BUS.post(event)) { // Event was cancelled; return whatever result the handler set (may be empty to suppress all recipes). return event.result; } if(input.getItem() instanceof ItemWizardArmour && ((ItemWizardArmour)input.getItem()).element == null){ if(Arrays.stream(receptacleElements).distinct().count() == 1 && receptacleElements[0] != null){ // All the same element ItemStack result = new ItemStack(ItemWizardArmour.getArmour(receptacleElements[0], ((ItemWizardArmour)input.getItem()).armourClass, ((ItemWizardArmour)input.getItem()).armorType)); result.setTagCompound(input.getTagCompound()); ((IManaStoringItem)result.getItem()).setMana(result, ((ItemWizardArmour)input.getItem()).getMana(input)); return result; } } if((input.getItem() == WizardryItems.magic_crystal || input.getItem() == Item.getItemFromBlock(WizardryBlocks.crystal_block)) && input.getMetadata() == 0){ if(Arrays.stream(receptacleElements).distinct().count() == 1 && receptacleElements[0] != null){ // All the same element return new ItemStack(input.getItem(), input.getCount(), receptacleElements[0].ordinal()); } } if(input.getItem() == WizardryItems.ruined_spell_book){ if(!ArrayUtils.contains(receptacleElements, null)){ // All receptacles filled (any element) if(fullLootGen){ // Pick a random element out of the receptacles and use its loot table // This is an elegant way of having the dust elements affect the spell outcome without hardcoding // any actual numbers, thus allowing packmakers as much control as possible over the weighting // The probabilities are a little complicated but work out quite nicely at 57% chance with 4 of the // same element of spectral dust Element element = receptacleElements[world.rand.nextInt(receptacleElements.length)]; LootTable table = world.getLootTableManager().getLootTableFromLocation( WizardryLoot.RUINED_SPELL_BOOK_LOOT_TABLES[element.ordinal() - 1]); LootContext context = new LootContext.Builder((WorldServer)world).withPlayer(lastUser) .withLuck(lastUser == null ? 0 : lastUser.getLuck()).build(); List stacks = table.generateLootForPools(world.rand, context); return stacks.isEmpty() ? ItemStack.EMPTY : stacks.get(0); } return new ItemStack(WizardryItems.spell_book); // No point generating loot every tick just to check the recipe } } return ItemStack.EMPTY; } }