/* * This file is part of Matter Overdrive * Copyright (c) 2015., Simeon Radivoev, All rights reserved. * * Matter Overdrive is free software: 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. * * Matter Overdrive 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 Matter Overdrive. If not, see <http://www.gnu.org/licenses>. */ package matteroverdrive.tile; import cpw.mods.fml.common.Optional; import cpw.mods.fml.common.gameevent.TickEvent; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import matteroverdrive.MatterOverdrive; import matteroverdrive.api.inventory.UpgradeTypes; import matteroverdrive.api.network.*; import matteroverdrive.blocks.BlockReplicator; import matteroverdrive.compat.modules.waila.IWailaBodyProvider; import matteroverdrive.data.BlockPos; import matteroverdrive.data.Inventory; import matteroverdrive.data.ItemPattern; import matteroverdrive.data.inventory.DatabaseSlot; import matteroverdrive.data.inventory.RemoveOnlySlot; import matteroverdrive.data.inventory.ShieldingSlot; import matteroverdrive.fx.ReplicatorParticle; import matteroverdrive.handler.GoogleAnalyticsCommon; import matteroverdrive.handler.SoundHandler; import matteroverdrive.init.MatterOverdriveItems; import matteroverdrive.machines.MachineNBTCategory; import matteroverdrive.machines.components.ComponentMatterNetworkConfigs; import matteroverdrive.matter_network.MatterNetworkPacket; import matteroverdrive.matter_network.MatterNetworkPacketQueue; import matteroverdrive.matter_network.MatterNetworkTaskQueue; import matteroverdrive.matter_network.components.MatterNetworkComponentReplicator; import matteroverdrive.matter_network.tasks.MatterNetworkTaskReplicatePattern; import matteroverdrive.network.packet.client.PacketReplicationComplete; import matteroverdrive.util.MatterHelper; import matteroverdrive.util.MatterNetworkHelper; import matteroverdrive.util.TimeTracker; import matteroverdrive.util.math.MOMathHelper; import mcp.mobius.waila.api.IWailaConfigHandler; import mcp.mobius.waila.api.IWailaDataAccessor; import net.minecraft.client.Minecraft; import net.minecraft.entity.EntityLivingBase; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.potion.PotionEffect; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.Vec3; import net.minecraft.world.World; import net.minecraftforge.common.util.ForgeDirection; import net.minecraftforge.fluids.Fluid; import org.lwjgl.util.vector.Vector3f; import java.util.EnumSet; import java.util.List; import static matteroverdrive.util.MOBlockHelper.*; public class TileEntityMachineReplicator extends MOTileEntityMachineMatter implements IMatterNetworkClient, IMatterNetworkHandler, IMatterNetworkDispatcher<MatterNetworkTaskReplicatePattern>,IMatterNetworkBroadcaster,IWailaBodyProvider { public static int MATTER_STORAGE = 1024; public static int ENERGY_STORAGE = 512000; public static final int MATTER_TRANSFER = 128; public static final int PATTERN_SEARCH_DELAY = 60; public static final int REPLICATION_ANIMATION_TIME = 60; public int OUTPUT_SLOT_ID = 0; public int SECOND_OUTPUT_SLOT_ID = 1; public int DATABASE_SLOT_ID = 2; public int SHIELDING_SLOT_ID = 3; public static int REPLICATE_SPEED_PER_MATTER = 120; public static int REPLICATE_ENERGY_PER_MATTER = 16000; public static final int RADIATION_DAMAGE_DELAY = 5; public static final int RADIATION_RANGE = 8; public static final double FAIL_CHANCE = 0.005; @SideOnly(Side.CLIENT) private boolean isPlayingReplicateAnimation; @SideOnly(Side.CLIENT) private int replicateAnimationCounter; public int replicateTime; private float replicateProgress; private MatterNetworkComponentReplicator networkComponent; private ComponentMatterNetworkConfigs componentMatterNetworkConfigs; private final MatterNetworkTaskQueue<MatterNetworkTaskReplicatePattern> taskQueueProcessing; private final TimeTracker timeTracker; private ItemPattern internalPatternStorage; public TileEntityMachineReplicator() { super(4); this.energyStorage.setCapacity(ENERGY_STORAGE); this.energyStorage.setMaxExtract(ENERGY_STORAGE); this.energyStorage.setMaxReceive(ENERGY_STORAGE); this.matterStorage.setCapacity(MATTER_STORAGE); this.matterStorage.setMaxReceive(MATTER_TRANSFER); this.matterStorage.setMaxExtract(MATTER_TRANSFER); taskQueueProcessing = new MatterNetworkTaskQueue<>(this,1); timeTracker = new TimeTracker(); playerSlotsMain = true; playerSlotsHotbar = true; } protected void RegisterSlots(Inventory inventory) { OUTPUT_SLOT_ID = inventory.AddSlot(new RemoveOnlySlot(false).setSendToClient(true)); SECOND_OUTPUT_SLOT_ID = inventory.AddSlot(new RemoveOnlySlot(false)); DATABASE_SLOT_ID = inventory.AddSlot(new DatabaseSlot(true)); SHIELDING_SLOT_ID = inventory.AddSlot(new ShieldingSlot(true)); super.RegisterSlots(inventory); } @Override protected void registerComponents() { super.registerComponents(); componentMatterNetworkConfigs = new ComponentMatterNetworkConfigs(this); networkComponent = new MatterNetworkComponentReplicator(this); addComponent(componentMatterNetworkConfigs); addComponent(networkComponent); } @Override public void updateEntity() { super.updateEntity(); this.manageReplicate(); if (worldObj.isRemote) { manageSpawnParticles(); } } protected void manageReplicate() { if (this.isActive()) { ItemStack newItem = internalPatternStorage.toItemStack(false); int time = getSpeed(newItem); if (!worldObj.isRemote) { if (taskQueueProcessing.peek().isValid(worldObj)) { if (energyStorage.getEnergyStored() >= getEnergyDrainPerTick()) { taskQueueProcessing.peek().setState(MatterNetworkTaskState.PROCESSING); this.replicateTime++; this.extractEnergy(ForgeDirection.DOWN, getEnergyDrainPerTick(), false); if (this.replicateTime >= time) { this.replicateTime = 0; this.replicateItem(internalPatternStorage, newItem); MatterOverdrive.packetPipeline.sendToDimention(new PacketReplicationComplete(this), worldObj); SoundHandler.PlaySoundAt(worldObj, "replicate_success", this.xCoord, this.yCoord, this.zCoord, 0.25F * getBlockType(BlockReplicator.class).replication_volume, 1.0F, 0.2F, 0.8F); } if (timeTracker.hasDelayPassed(worldObj, RADIATION_DAMAGE_DELAY)) { manageRadiation(); } replicateProgress = (float) replicateTime / (float) time; } } else { taskQueueProcessing.dequeue(); } } else { if (getBlockType(BlockReplicator.class).hasVentParticles) { SpawnVentParticles(0.05f, ForgeDirection.getOrientation(getLeftSide(worldObj.getBlockMetadata(xCoord, yCoord, zCoord))), 1); SpawnVentParticles(0.05f, ForgeDirection.getOrientation(getRightSide(worldObj.getBlockMetadata(xCoord, yCoord, zCoord))), 1); } } } else { this.replicateTime = 0; replicateProgress = 0; //internalPatternStorage = null; } } @SideOnly(Side.CLIENT) public void beginSpawnParticles() { replicateAnimationCounter = REPLICATION_ANIMATION_TIME; } @SideOnly(Side.CLIENT) public void manageSpawnParticles() { if (replicateAnimationCounter > 0) { isPlayingReplicateAnimation = true; SpawnReplicateParticles(REPLICATION_ANIMATION_TIME - replicateAnimationCounter); replicateAnimationCounter--; } else { if (isPlayingReplicateAnimation) { //sync with server so that the replicated item will be seen isPlayingReplicateAnimation = false; forceSync(); } } } private void replicateItem(ItemPattern itemPattern,ItemStack newItem) { if(isActive()) { int matterAmount = MatterHelper.getMatterAmountFromItem(newItem); float chance = random.nextFloat(); if(chance < getFailChance(itemPattern)) { if(failReplicate(MatterHelper.getMatterAmountFromItem(newItem))) { int matter = this.matterStorage.getMatterStored(); setMatterStored(matter - matterAmount); return; } } else { if(putInOutput(newItem)) { MatterOverdrive.proxy.getGoogleAnalytics().sendEventHit(GoogleAnalyticsCommon.EVENT_CATEGORY_MACHINES, GoogleAnalyticsCommon.EVENT_ACTION_REPLICATE,newItem.getUnlocalizedName(),null); int matter = this.matterStorage.getMatterStored(); setMatterStored(matter - matterAmount); MatterNetworkTaskReplicatePattern task = taskQueueProcessing.peek(); task.getPattern().setCount(task.getPattern().getCount()-1); if (task.getPattern().getCount() <= 0) { task.setState(MatterNetworkTaskState.FINISHED); taskQueueProcessing.dequeue(); } } } } } private boolean putInOutput(ItemStack item) { if(getStackInSlot(OUTPUT_SLOT_ID) == null) { setInventorySlotContents(OUTPUT_SLOT_ID, item); return true; } else { if(getStackInSlot(OUTPUT_SLOT_ID).isStackable() && getStackInSlot(OUTPUT_SLOT_ID).getItemDamage() == item.getItemDamage() && getStackInSlot(OUTPUT_SLOT_ID).getItem() == item.getItem()) { int newStackSize = getStackInSlot(OUTPUT_SLOT_ID).stackSize + 1; if(newStackSize <= getStackInSlot(OUTPUT_SLOT_ID).getMaxStackSize()) { getStackInSlot(OUTPUT_SLOT_ID).stackSize = newStackSize; return true; } } } return false; } private boolean failReplicate(int amount) { ItemStack stack = getStackInSlot(SECOND_OUTPUT_SLOT_ID); if(stack == null) { stack = new ItemStack(MatterOverdriveItems.matter_dust); MatterOverdriveItems.matter_dust.setMatter(stack,amount); setInventorySlotContents(SECOND_OUTPUT_SLOT_ID, stack); return true; } else { if (canReplicateIntoSecoundOutput(amount)) { stack.stackSize++; return true; } } return false; } @SideOnly(Side.CLIENT) public void SpawnReplicateParticles(int startTime) { double time = (double)(startTime) / (double)(REPLICATION_ANIMATION_TIME); double gravity = MOMathHelper.easeIn(time, 0.02, 0.2, 1); int age = (int)Math.round(MOMathHelper.easeIn(time, 2, 10, 1)); int count = (int)Math.round(MOMathHelper.easeIn(time, 1, 20, 1)); for(int i = 0;i < count;i++) { float speed = 0.05f; Vector3f pos = MOMathHelper.randomSpherePoint(this.xCoord + 0.5D, this.yCoord + 0.5D, this.zCoord + 0.5D, Vec3.createVectorHelper(0.5,0.5,0.5), this.worldObj.rand); Vector3f dir = new Vector3f(random.nextFloat() * 2 - 1,(random.nextFloat()* 2 - 1) * 0.05f,random.nextFloat()* 2 - 1); dir.scale(speed); ReplicatorParticle replicatorParticle = new ReplicatorParticle(this.worldObj,pos.getX(),pos.getY() ,pos.getZ(),dir.getX(), dir.getY(), dir.getZ()); replicatorParticle.setCenter(this.xCoord + 0.5D, this.yCoord + 0.5D, this.zCoord + 0.5D); replicatorParticle.setParticleAge(age); replicatorParticle.setPointGravityScale(gravity); Minecraft.getMinecraft().effectRenderer.addEffect(replicatorParticle); } } public boolean canCompleteTask() { MatterNetworkTaskReplicatePattern task = taskQueueProcessing.peek(); return task != null && internalPatternStorage != null && task.getPattern().equals(getInternalPatternStorage()); } @Override public boolean getServerActive() { if(getRedstoneActive() && taskQueueProcessing.size() > 0 && getInternalPatternStorage() != null && canCompleteTask()) { ItemStack item = getInternalPatternStorage().toItemStack(false); int matter = MatterHelper.getMatterAmountFromItem(item); return this.getMatterStored() >= matter && canReplicateIntoOutput(item) && canReplicateIntoSecoundOutput(matter); } return false; } public void manageRadiation() { int shielding = getShielding(); if(shielding >= 5) return; //has full shielding AxisAlignedBB bb = AxisAlignedBB.getBoundingBox(xCoord - RADIATION_RANGE,yCoord - RADIATION_RANGE,zCoord - RADIATION_RANGE,xCoord + RADIATION_RANGE,yCoord + RADIATION_RANGE,zCoord + RADIATION_RANGE); List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class,bb); for (Object e : entities) { if (e instanceof EntityLivingBase) { EntityLivingBase l = (EntityLivingBase) e; double distance = l.getDistance(xCoord,yCoord,zCoord) / RADIATION_RANGE; distance = net.minecraft.util.MathHelper.clamp_double(distance,0,1); distance = 1.0 - distance; distance *= 5 - shielding; PotionEffect[] effects = new PotionEffect[4]; //confusion effects[0] = new PotionEffect(9, (int) Math.round(Math.pow(5,distance)), 0); //weakness effects[1] = new PotionEffect(18, (int)Math.round(Math.pow(10,distance)), 0); //hunger effects[2] = new PotionEffect(17, (int)Math.round(Math.pow(12,distance)), 0); //poison effects[3] = new PotionEffect(19, (int)Math.round(Math.pow(5,distance)), 0); for (PotionEffect effect : effects) { if(effect.getDuration() > 0) l.addPotionEffect(effect); } } } } private boolean canReplicateIntoOutput(ItemStack itemStack) { if (itemStack == null) return false; if(getStackInSlot(OUTPUT_SLOT_ID) == null) { return true; } else { if(itemStack.isItemEqual(getStackInSlot(OUTPUT_SLOT_ID)) && ItemStack.areItemStackTagsEqual(itemStack,getStackInSlot(OUTPUT_SLOT_ID)) && getStackInSlot(OUTPUT_SLOT_ID).stackSize < getStackInSlot(OUTPUT_SLOT_ID).getMaxStackSize()) { return true; } } return false; } private boolean canReplicateIntoSecoundOutput(int matter) { ItemStack stack = getStackInSlot(SECOND_OUTPUT_SLOT_ID); if (stack == null) { return true; }else { if (stack.getItem() == MatterOverdriveItems.matter_dust && stack.getItemDamage() == matter && stack.stackSize < stack.getMaxStackSize()) { return true; } } return false; } @Override public boolean isAffectedByUpgrade(UpgradeTypes type) { return type == UpgradeTypes.PowerStorage || type == UpgradeTypes.Speed || type == UpgradeTypes.Fail || type == UpgradeTypes.PowerUsage || type == UpgradeTypes.MatterStorage; } //region NBT @Override public void readCustomNBT(NBTTagCompound nbt, EnumSet<MachineNBTCategory> categories) { super.readCustomNBT(nbt, categories); if (categories.contains(MachineNBTCategory.DATA)) { this.replicateTime = nbt.getShort("ReplicateTime"); taskQueueProcessing.readFromNBT(nbt); if (nbt.hasKey("InternalPattern")) internalPatternStorage = new ItemPattern(nbt.getCompoundTag("InternalPattern")); } } @Override public void writeCustomNBT(NBTTagCompound nbt, EnumSet<MachineNBTCategory> categories, boolean toDisk) { super.writeCustomNBT(nbt, categories, toDisk); if (categories.contains(MachineNBTCategory.DATA)) { nbt.setShort("ReplicateTime", (short) this.replicateTime); taskQueueProcessing.writeToNBT(nbt); if (internalPatternStorage != null) { NBTTagCompound patternNBT = new NBTTagCompound(); internalPatternStorage.writeToNBT(patternNBT); nbt.setTag("InternalPattern", patternNBT); } } } //endregion //region Inventory Functions @Override public int[] getAccessibleSlotsFromSide(int side) { return new int[]{OUTPUT_SLOT_ID, SECOND_OUTPUT_SLOT_ID}; } @Override public boolean canExtractItem(int slot, ItemStack item, int side) { return true; } @Override public ItemStack decrStackSize(int slot, int size) { ItemStack s = super.decrStackSize(slot, size); forceSync(); return s; } //endregion //region Matter Network functions @Override public boolean canPreform(MatterNetworkPacket packet) { return networkComponent.canPreform(packet); } @Override public void queuePacket(MatterNetworkPacket packet,ForgeDirection from) { networkComponent.queuePacket(packet, from); } @Override public MatterNetworkPacketQueue getPacketQueue(int queueID) { return networkComponent.getPacketQueue(queueID); } @Override public int getPacketQueueCount() { return networkComponent.getPacketQueueCount(); } @Override public BlockPos getPosition() { return new BlockPos(this); } @Override public boolean canConnectFromSide(ForgeDirection side) { int meta = worldObj.getBlockMetadata(xCoord, yCoord, zCoord); return getOppositeSide(meta) == side.ordinal(); } @Override public int onNetworkTick(World world,TickEvent.Phase phase) { return networkComponent.onNetworkTick(world, phase); } @Override public MatterNetworkTaskQueue<MatterNetworkTaskReplicatePattern> getTaskQueue(int queueID) { return taskQueueProcessing; } @Override public int getTaskQueueCount() { return 1; } //endregion //region Events @Override public void onAdded(World world, int x, int y, int z) { } @Override public void onPlaced(World world, EntityLivingBase entityLiving) { } @Override public void onDestroyed() { } @Override protected void onAwake(Side side) { if (side.isServer()) { MatterNetworkHelper.broadcastConnection(worldObj, this); } } @Override protected void onActiveChange() { } //endregion //region Waila @Override @Optional.Method(modid = "Waila") public List<String> getWailaBody(ItemStack itemStack, List<String> currenttip, IWailaDataAccessor accessor, IWailaConfigHandler config) { List<String> list = super.getWailaBody(itemStack, currenttip, accessor, config); if (accessor.getTileEntity() instanceof TileEntityMachineReplicator) { MatterNetworkTaskReplicatePattern task = ((TileEntityMachineReplicator) accessor.getTileEntity()).getTaskQueue(0).peek(); if (task != null) { ItemStack pattern = task.getPattern().toItemStack(false); list.add(EnumChatFormatting.YELLOW + String.format("Replicating %s", pattern.getDisplayName())); } } return list; } //endregion //region Getters and Setters public ItemPattern getInternalPatternStorage() { return internalPatternStorage; } public void setInternalPatternStorage(ItemPattern internalPatternStorage){this.internalPatternStorage = internalPatternStorage;} private int getShielding() { if(getStackInSlot(SHIELDING_SLOT_ID) != null && getStackInSlot(SHIELDING_SLOT_ID).getItem() == MatterOverdriveItems.tritanium_plate) { return getStackInSlot(SHIELDING_SLOT_ID).stackSize; } return 0; } @Override public String getSound() { return "machine"; } @Override public boolean hasSound() { return true; } @Override public float soundVolume() { return 1;} public int getSpeed(ItemStack itemStack) { double matter = Math.log1p(MatterHelper.getMatterAmountFromItem(itemStack)); matter *= matter; return (int) Math.round(((REPLICATE_SPEED_PER_MATTER * matter) - 60) * getUpgradeMultiply(UpgradeTypes.Speed)) + 60; } public double getFailChance(ItemPattern itemPattern) { double progressChance = 1f - itemPattern.getProgressF(); double upgradeMultiply = getUpgradeMultiply(UpgradeTypes.Fail); //this does not negate all fail chance if item is not fully scanned return FAIL_CHANCE * upgradeMultiply + progressChance * 0.5 + (progressChance * 0.5) * upgradeMultiply; } public int getEnergyDrainPerTick() { int maxEnergy = getEnergyDrainMax(); return maxEnergy / getSpeed(internalPatternStorage.toItemStack(false)); } public int getEnergyDrainMax() { int matter = MatterHelper.getMatterAmountFromItem(internalPatternStorage.toItemStack(false)); double upgradeMultiply = getUpgradeMultiply(UpgradeTypes.PowerUsage); return (int) Math.round((matter * REPLICATE_ENERGY_PER_MATTER) * upgradeMultiply); } public boolean canCompleteTask(MatterNetworkTaskReplicatePattern taskReplicatePattern) { return taskReplicatePattern != null && internalPatternStorage != null && taskReplicatePattern.getPattern().equals(getInternalPatternStorage()) && taskReplicatePattern.isValid(worldObj); } @Override public NBTTagCompound getFilter() { return componentMatterNetworkConfigs.getFilter(); } @Override public float getProgress() { return replicateProgress; } @Override public boolean canDrain(ForgeDirection from, Fluid fluid) { return false; } public int getTaskReplicateCount() { if (taskQueueProcessing.peek() != null) { return taskQueueProcessing.peek().getPattern().getCount(); } return 0; } //endregion }