/* * This file is part of Industrial Wires. * Copyright (C) 2016-2017 malte0811 * * Industrial Wires 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. * * Industrial Wires 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 Industrial Wires. If not, see . */ package malte0811.industrialWires.blocks.hv; import blusunrize.immersiveengineering.api.ApiUtils; import blusunrize.immersiveengineering.api.IEProperties; import blusunrize.immersiveengineering.api.TargetingInfo; import blusunrize.immersiveengineering.api.energy.wires.IImmersiveConnectable; import blusunrize.immersiveengineering.api.energy.wires.ImmersiveNetHandler; import blusunrize.immersiveengineering.api.energy.wires.WireType; import blusunrize.immersiveengineering.api.energy.wires.redstone.IRedstoneConnector; import blusunrize.immersiveengineering.api.energy.wires.redstone.RedstoneWireNetwork; import blusunrize.immersiveengineering.common.IEContent; import blusunrize.immersiveengineering.common.blocks.BlockTypes_MetalsIE; import blusunrize.immersiveengineering.common.blocks.metal.*; import blusunrize.immersiveengineering.common.util.Utils; import blusunrize.immersiveengineering.common.util.chickenbones.Matrix4; import com.elytradev.mirage.event.GatherLightsEvent; import com.elytradev.mirage.lighting.Light; import malte0811.industrialWires.*; import malte0811.industrialWires.blocks.IBlockBoundsIW; import malte0811.industrialWires.blocks.ISyncReceiver; import malte0811.industrialWires.blocks.IWProperties; import malte0811.industrialWires.blocks.TileEntityIWMultiblock; import malte0811.industrialWires.hv.MarxOreHandler; import malte0811.industrialWires.network.MessageTileSyncIW; import malte0811.industrialWires.util.DualEnergyStorage; import malte0811.industrialWires.util.MiscUtils; import malte0811.industrialWires.wires.IC2Wiretype; import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.Entity; import net.minecraft.entity.item.EntityItem; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.EnumDyeColor; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagDouble; import net.minecraft.nbt.NBTTagList; import net.minecraft.potion.Potion; import net.minecraft.potion.PotionEffect; import net.minecraft.util.EnumFacing; import net.minecraft.util.ITickable; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3i; import net.minecraft.world.World; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Optional; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.*; import static malte0811.industrialWires.blocks.hv.TileEntityMarx.FiringState.FIRE; import static malte0811.industrialWires.util.MiscUtils.getOffset; import static malte0811.industrialWires.util.MiscUtils.offset; import static net.minecraft.item.EnumDyeColor.*; /** * Channel: Purpose * White: Coarse Vcharge * Orange: Coarse bottom cap voltage * Magenta: Coarse top voltage * LBlue: Firing trigger * Yellow: Fine Vcharge * Lime: Fine bottom cap voltage * Pink: Fine top cap voltage */ @Mod.EventBusSubscriber public class TileEntityMarx extends TileEntityIWMultiblock implements ITickable, ISyncReceiver, IBlockBoundsIW, IImmersiveConnectable, IIC2Connector, IRedstoneConnector { //Only relevant client-side. private static final Set FIRING_GENERATORS = Collections.newSetFromMap(new WeakHashMap<>()); private static final String TYPE = "type"; private static final String STAGES = "stages"; private static final String HAS_CONN = "hasConn"; private static final String CAP_VOLTAGES = "capVoltages"; private double rcTimeConst; private double timeFactor; private double timeFactorBottom; private final static double CAPACITANCE = 1.6e-6; private final static double MAX_VOLTAGE = 250e3; public IWProperties.MarxType type = IWProperties.MarxType.NO_MODEL; private int stageCount = 0; public FiringState state = FiringState.CHARGING; public Discharge dischargeData; // Voltage=100*storedEU private DualEnergyStorage storage = new DualEnergyStorage(50_000, 32_000); private boolean hasConnection; private double[] capVoltages; private int voltageControl = 0; private boolean loaded = false; private double leftover; private long lastUpdate = -1; TileEntityMarx(EnumFacing facing, IWProperties.MarxType type, boolean mirrored) { this.facing = facing; this.type = type; this.mirrored = mirrored; } public TileEntityMarx() {} @Override public void writeNBT(NBTTagCompound out, boolean updatePacket) { super.writeNBT(out, updatePacket); MiscUtils.writeConnsToNBT(out, this); out.setInteger(TYPE, type.ordinal()); out.setInteger(STAGES, stageCount); out.setBoolean(HAS_CONN, hasConnection); storage.writeToNbt(out, ENERGY_TAG); NBTTagList voltages = new NBTTagList(); if (capVoltages != null) { for (int i = 0; i < stageCount; i++) { voltages.appendTag(new NBTTagDouble(capVoltages[i])); } } out.setTag(CAP_VOLTAGES, voltages); } @Override public void readNBT(NBTTagCompound in, boolean updatePacket) { super.readNBT(in, updatePacket); MiscUtils.loadConnsFromNBT(in, this); type = IWProperties.MarxType.values()[in.getInteger(TYPE)]; setStageCount(in.getInteger(STAGES)); NBTTagList voltages = in.getTagList(CAP_VOLTAGES, 6);//DOUBLE capVoltages = new double[stageCount]; for (int i = 0;i0) { if ((right==0&&up==0)||(right==1&&up==stageCount-1)) { return IEContent.blockMetalDecoration1.getDefaultState().withProperty(IEContent.blockMetalDecoration1.property, BlockTypes_MetalDecoration1.STEEL_FENCE); } else { return IEContent.blockMetalDecoration2.getDefaultState().withProperty(IEContent.blockMetalDecoration2.property, BlockTypes_MetalDecoration2.STEEL_WALLMOUNT) .withProperty(IEProperties.INT_4, 1-right).withProperty(IEProperties.FACING_ALL, facing.getOpposite()); } } else if (forward==-2) { return IEContent.blockMetalDecoration0.getDefaultState().withProperty(IEContent.blockMetalDecoration0.property, BlockTypes_MetalDecoration0.HEAVY_ENGINEERING); } else if (right==0) { return IEContent.blockConnectors.getDefaultState().withProperty(IEContent.blockConnectors.property, BlockTypes_Connector.CONNECTOR_REDSTONE) .withProperty(IEProperties.FACING_ALL, facing); } else { return IEContent.blockConnectors.getDefaultState().withProperty(IEContent.blockConnectors.property, BlockTypes_Connector.CONNECTOR_HV) .withProperty(IEProperties.FACING_ALL, facing); } } @Override public void update() { ApiUtils.checkForNeedlessTicking(this); FIRING_GENERATORS.remove(this); switch (state) { case NEXT_TICK: if (world.isRemote) { FIRING_GENERATORS.add(this); IndustrialWires.proxy.playMarxBang(this, getMiddle(), (float) getNormedEnergy(dischargeData.energy)); } else { fire(); } state = FIRE; break; case FIRE: state = FiringState.CHARGING; break; } if (!world.isRemote&&type== IWProperties.MarxType.BOTTOM) { if (capVoltages == null || capVoltages.length != stageCount) { capVoltages = new double[stageCount]; } final double oldTopVoltage = capVoltages[stageCount - 1]; final double oldBottomVoltage = capVoltages[0]; for (int i = stageCount - 1; i > 0; i--) { double oldVoltage = capVoltages[i]; double u0 = capVoltages[i - 1]; capVoltages[i] = u0 - (u0 - oldVoltage) * timeFactor; capVoltages[i - 1] -= capVoltages[i] - oldVoltage; } //charge bottom cap from storage double setVoltage = MAX_VOLTAGE * voltageControl / 255F; double u0 = Math.min(setVoltage, 100 * storage.getEnergyStoredEU()); if (u0 < 0) { u0 = 0; } if (u0 < capVoltages[0] && setVoltage > capVoltages[0]) { u0 = capVoltages[0]; } double tmp = u0 - (u0 - oldBottomVoltage) * timeFactorBottom; double energyUsed = .5 * (tmp * tmp - oldBottomVoltage * oldBottomVoltage) * CAPACITANCE; if (energyUsed > 0 && storage.extractEU(energyUsed, false) == energyUsed) {// energyUsed can be negative when discharging the caps storage.extractEU(energyUsed, true); capVoltages[0] = tmp; } else if (energyUsed <= 0) { capVoltages[0] = tmp; } int delta = (int) (lastUpdate+15-world.getTotalWorldTime()); if (Math.abs(getRSSignalFromVoltage(oldBottomVoltage)-getRSSignalFromVoltage(capVoltages[0]))>delta) { net.updateValues(); } else if (Math.abs(getRSSignalFromVoltage(oldTopVoltage)-getRSSignalFromVoltage(capVoltages[stageCount-1]))>delta) { net.updateValues(); } if (capVoltages[0] > MAX_VOLTAGE * 14.5 / 15) { state = FiringState.NEXT_TICK; } } leftover = storage.getMaxInputIF(); } private void fire() { if (!world.isRemote) { //calculate energy double energyStored = 0; boolean failed = capVoltages[0] toBreak = new HashSet<>(stageCount-2); int ores = 0; for (int i = 1;i0) { double energyPerOre = energyStored / ores; for (BlockPos here:toBreak) { IBlockState state = world.getBlockState(here); if (state.getBlockHardness(world, here) < 0) { continue; } if (!world.isAirBlock(here)) { ItemStack[] out = MarxOreHandler.getYield(world, here, energyPerOre); for (ItemStack stack : out) { EntityItem item = new EntityItem(world, here.getX() + .5, here.getY() + .5, here.getZ() + .5, stack); final double maxMotion = .3; item.motionX = 2 * maxMotion * (Utils.RAND.nextDouble() - .5); item.motionY = 2 * maxMotion * (Utils.RAND.nextDouble() - .5); item.motionZ = 2 * maxMotion * (Utils.RAND.nextDouble() - .5); world.spawnEntity(item); } world.setBlockToAir(here); } } } } private void handleEntities(double energyStored) { double damageDistSqu = Math.sqrt(energyStored/50e3); double tinnitusDistSqu = Math.sqrt(energyStored)/50; Vec3d v0 = getMiddle(); AxisAlignedBB aabb = new AxisAlignedBB(v0.x, v0.y, v0.z, v0.x, v0.y, v0.z); aabb = aabb.grow(0, stageCount/2-1,0); aabb = aabb.grow(tinnitusDistSqu); List fools = world.getEntitiesWithinAABB(Entity.class, aabb); damageDistSqu *= damageDistSqu; tinnitusDistSqu *= tinnitusDistSqu; if (IWConfig.HVStuff.marxSoundDamage == 2) { damageDistSqu = tinnitusDistSqu; tinnitusDistSqu = -1; } for (Entity entity : fools) { double y; if (entity.posYpos.getY()+stageCount-2) { y = pos.getY()+stageCount-2; } else { y = entity.posY; } double distSqu = entity.getDistanceSq(v0.x, y, v0.z); if (distSqu<=damageDistSqu) { float dmg = (float) (10*stageCount*(1-distSqu/damageDistSqu)); entity.attackEntityFrom(IWDamageSources.dmg_marx, dmg); } if (distSqu<=tinnitusDistSqu && entity instanceof EntityPlayer) { ItemStack helmet = ((EntityPlayer) entity).inventory.armorInventory.get(3); boolean earMuff = helmet.getItem()==IEContent.itemEarmuffs; if (!earMuff&&helmet.hasTagCompound()) { earMuff = helmet.getTagCompound().hasKey("IE:Earmuffs"); } if (!earMuff) { double multipl = Math.min(5, Math.sqrt(stageCount)); int duration = (int) (20*20*(1+multipl*(1-distSqu/tinnitusDistSqu))); if (IWConfig.HVStuff.marxSoundDamage == 0) { ((EntityPlayer) entity).addPotionEffect(new PotionEffect(IWPotions.tinnitus, duration)); } else { ((EntityPlayer) entity).addPotionEffect(new PotionEffect(Potion.getPotionFromResourceLocation("nausea"), duration)); } } } } } //checks whether the given pos can't be broken because it is part of the generator private boolean canBreak(BlockPos pos) { BlockPos dischargePos = offset(this.pos, facing, mirrored, 1, 3, 0); Vec3i offset = getOffset(dischargePos, facing, mirrored, pos); if (offset.getZ()<1||offset.getZ()>=stageCount-1) { return false; } return Math.abs(offset.getX())>Math.abs(offset.getY()); } private int getRSSignalFromVoltage(double voltage) { return (int) (Math.round(255 * voltage / MAX_VOLTAGE)&0xff); } @Override public Vec3i getSize() { return new Vec3i(stageCount, 8, 2); } @Override public void onSync(NBTTagCompound nbt) { float energy = nbt.getFloat("energy"); if (energy>0) { genDischarge(energy, nbt.getInteger("randSeed")); } else { if (dischargeData==null) { dischargeData = new Discharge(stageCount); } dischargeData.energy = energy; } state = FiringState.NEXT_TICK; } private void genDischarge(float energy, int seed) { if (dischargeData==null) { dischargeData = new Discharge(stageCount); } dischargeData.energy = energy; dischargeData.diameter = (float) getNormedEnergy(dischargeData.energy); dischargeData.genMarxPoint(seed); } private double getNormedEnergy(double total) { return total*2/(stageCount*MAX_VOLTAGE*MAX_VOLTAGE*CAPACITANCE); } private AxisAlignedBB renderAabb = null; @Nonnull @Override public AxisAlignedBB getRenderBoundingBox() { if (renderAabb ==null) { if (type== IWProperties.MarxType.BOTTOM) { renderAabb = new AxisAlignedBB(pos, offset(pos, facing, mirrored, 2, 4, stageCount)); } else { renderAabb = new AxisAlignedBB(pos, pos); } } return renderAabb; } private AxisAlignedBB collisionAabb = null; @Override public AxisAlignedBB getBoundingBox() { if (collisionAabb ==null) { int forward = getForward(); int right = getRight(); int up = offset.getY(); AxisAlignedBB ret = Block.FULL_BLOCK_AABB; switch (forward) { case -3://IO if (right == 1) { ret = new AxisAlignedBB(5 / 16D, 5 / 16D, .25, 11 / 16D, 11 / 16D, 1); } else { ret = new AxisAlignedBB(5 / 16D, 5 / 16D, 7 / 16D, 11 / 16D, 11 / 16D, 1); } break; case -1://charging resistors if (up == 0) { ret = new AxisAlignedBB(.375, 0, 0, .625, 1, 1); } else if (up == stageCount - 1) { ret = new AxisAlignedBB(.375, 0, 9 / 16D, .625, 5 / 16D, 1); } else { ret = new AxisAlignedBB(.375, 0, 9 / 16D, .625, 1, 1); } break; case 1://spark gaps if (right == 0) { if (up!=0) { ret = new AxisAlignedBB(0, 0, 0, 9 / 16D, up == stageCount - 1 ? .5 : 1, 7 / 16D); } else { ret = new AxisAlignedBB(7/16D, 0, 0, 9/16D, 5/16D, 1); } } else { if (stageCount - 1 == up) { ret = new AxisAlignedBB(7 / 16D, 3 / 16D, 0, 9 / 16D, 5 / 16D, 1); } else { ret = new AxisAlignedBB(7 / 16D, 0, 0, 1, 1, 7 / 16D); } } break; case -2://Controller break; case 0://Caps if (up == stageCount - 1) { ret = new AxisAlignedBB(0, 0, 0, 1, .5, 1); } break; default: if (right == 0) { if (forward<4) { ret = new AxisAlignedBB(7/16D, 0, 0, 9/16D, 5/16D, 1); } else { ret = new AxisAlignedBB(0, 0, 0, 9/16D, 5/16D, 9/16D); } } else { if (up==0) { ret = Block.FULL_BLOCK_AABB; } else if (forward < 4) { ret = new AxisAlignedBB(7 / 16D, 3 / 16D, 0, 9 / 16D, 5 / 16D, 1); } else { ret = new AxisAlignedBB(6 / 16D, 1 / 16D, 0, 10 / 16D, 5 / 16D, 10 / 16D); } } } collisionAabb = MiscUtils.apply(getBaseTransform(), ret); } return collisionAabb; } private Matrix4 getBaseTransform() { Matrix4 transform = new Matrix4(); transform.translate(.5, 0, .5); transform.rotate(-facing.getHorizontalAngle() * Math.PI / 180, 0, 1, 0); if (mirrored) { transform.scale(-1, 1, 1); } transform.translate(-.5, 0, -.5); return transform; } //WIRE STUFF @Override public boolean canConnect() { return getForward()==-3; } @Override public boolean isEnergyOutput() { return getForward()==-3&&getRight()==1; } @Override public int outputEnergy(int amount, boolean simulate, int energyType) { TileEntityMarx master = master(this); if (master!=null && amount>0) { double ret = master.storage.insertIF(amount, master.leftover, !simulate); master.leftover -= ret; return (int) ret; } else { return 0; } } @Override public double insertEnergy(double eu, boolean simulate) { TileEntityMarx master = master(this); if (master!=null) { double ret = master.storage.insertEU(eu, master.leftover, !simulate); master.leftover -= ret; return eu-ret; } else { return 0; } } @Override public BlockPos getConnectionMaster(@Nullable WireType cableType, TargetingInfo target) { return pos; } @Override public boolean canConnectCable(WireType cableType, TargetingInfo target) { if (hasConnection) { return false; } if (getRight()==0) { return cableType==WireType.REDSTONE; } else { return cableType==WireType.STEEL||cableType== IC2Wiretype.IC2_TYPES[3]; } } @Override public void connectCable(WireType cableType, TargetingInfo target, IImmersiveConnectable other) { hasConnection = true; } @Override public WireType getCableLimiter(TargetingInfo target) { return getRight()==0?WireType.REDSTONE:IC2Wiretype.IC2_TYPES[3]; } @Override public boolean allowEnergyToPass(ImmersiveNetHandler.Connection con) { return false; } @Override public void onEnergyPassthrough(int amount) { } @Override public void removeCable(ImmersiveNetHandler.Connection connection) { hasConnection = false; if(world != null) { IBlockState state = world.getBlockState(pos); world.notifyBlockUpdate(pos, state,state, 3); } } @Override public Vec3d getRaytraceOffset(IImmersiveConnectable link) { Matrix4 transf = getBaseTransform(); if (getRight()==0) { return transf.apply(new Vec3d(.5, .5, 7/16D)); } else { return transf.apply(new Vec3d(.5, .5, 4/16D)); } } @Override public Vec3d getConnectionOffset(ImmersiveNetHandler.Connection con) { return getRaytraceOffset(null); } @Override public void validate() { super.validate(); ImmersiveNetHandler.INSTANCE.resetCachedIndirectConnections(); } @Override public void invalidate() { super.invalidate(); if (world.isRemote) ImmersiveNetHandler.INSTANCE.clearConnectionsOriginatingFrom(pos, world); } @Override public boolean receiveClientEvent(int id, int type) { return MiscUtils.handleUpdate(id, pos, world)||super.receiveClientEvent(id, type); } // Redstone wire stuff private RedstoneWireNetwork net = new RedstoneWireNetwork(); @Override public void setNetwork(RedstoneWireNetwork net) { masterOr(this, this).net = net; } @Override public RedstoneWireNetwork getNetwork() { TileEntityMarx master = masterOr(this, this); if (!loaded) { master.net.add(this); loaded = true; } return master.net; } @Override public void onChange() { TileEntityMarx master = masterOr(this, this); master.voltageControl = (master.net.channelValues[WHITE.getMetadata()]<<4)|master.net.channelValues[YELLOW.getMetadata()]; if (master.net.channelValues[LIGHT_BLUE.getMetadata()]!=0) { master.tryTriggeredDischarge(); } master.lastUpdate = world.getTotalWorldTime(); } private void tryTriggeredDischarge() { state = FiringState.NEXT_TICK; } @Override public World getConnectorWorld() { return world; } @Override public void updateInput(byte[] signals) { TileEntityMarx master = masterOr(this, this); if (master.capVoltages!=null&&master.capVoltages.length==stageCount) { int signalTop = getRSSignalFromVoltage(master.capVoltages[stageCount-1]); int signalBottom = getRSSignalFromVoltage(master.capVoltages[0]); setSignal(ORANGE, (signalBottom>>4)&0xf, signals); setSignal(MAGENTA, (signalTop>>4)&0xf, signals); setSignal(LIME, signalBottom&0xf, signals); setSignal(PINK, signalTop&0xf, signals); } } private void setSignal(EnumDyeColor channel, int value, byte[] signals) { signals[channel.getMetadata()] = (byte) Math.max(value, signals[channel.getMetadata()]); } public void setStageCount(int stageCount) { this.stageCount = stageCount; rcTimeConst = 5D/stageCount; timeFactor = Math.exp(-1/(20*rcTimeConst)); timeFactorBottom = Math.exp(-1 / (20 * rcTimeConst * 2 / 3)); collisionAabb = null; renderAabb = null; } public int getStageCount() { return stageCount; } private Vec3d getMiddle() { double middleY = pos.getY()+(stageCount)/2D; Vec3i electrodXZ = getBottomElectrode(); return new Vec3d(electrodXZ.getX()+.5, middleY, electrodXZ.getZ()+.5); } private BlockPos getBottomElectrode() { return offset(pos, facing, mirrored, 1, 4, 0); } @Optional.Method(modid="mirage") @SubscribeEvent public static void gatherLights(GatherLightsEvent event) { for (TileEntityMarx te:FIRING_GENERATORS) { Vec3d origin = te.getMiddle().subtract(0, .5*te.stageCount-1,0); Light.Builder builder = Light.builder() .color(1, 1, 1) .radius(5); List toAdd = new ArrayList<>(te.stageCount*2-3); if (te.dischargeData!=null&&te.dischargeData.energy>0) { toAdd.add(builder.pos(origin.addVector(0, 0, 0)).build()); toAdd.add(builder.pos(origin.addVector(0, te.stageCount/2, 0)).build()); toAdd.add(builder.pos(origin.addVector(0, te.stageCount-2, 0)).build()); } origin = new Vec3d(offset(te.pos, te.facing, te.mirrored, 1, 0, 0)) .addVector(0, .75, 0) .add(new Vec3d(te.facing.getDirectionVec()).scale(.25)); builder.radius(.5F); for (int i = 0;i1) { genMarxPoint(min, toGenerate, rand); } if (max-toGenerate>1) { genMarxPoint(toGenerate, max, rand); } } } }