package com.hbm.handler.neutron; import com.hbm.blocks.machine.rbmk.RBMKBase; import com.hbm.handler.neutron.NeutronNodeWorld.StreamWorld; import com.hbm.handler.radiation.ChunkRadiationManager; import com.hbm.tileentity.machine.rbmk.*; import com.hbm.util.Compat; import com.hbm.util.fauxpointtwelve.BlockPos; import java.util.ArrayList; import java.util.List; import java.util.Iterator; import net.minecraft.block.Block; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.Vec3; import net.minecraft.world.World; import net.minecraftforge.common.util.ForgeDirection; public class RBMKNeutronHandler { static double moderatorEfficiency; static double reflectorEfficiency; static double absorberEfficiency; static int columnHeight; static int fluxRange; public enum RBMKType { ROD, MODERATOR, CONTROL_ROD, REFLECTOR, ABSORBER, OUTGASSER, OTHER // why do neutron calculations on them if they won't change anything? } private static TileEntity blockPosToTE(World worldObj, BlockPos pos) { return Compat.getTileStandard(worldObj, pos.getX(), pos.getY(), pos.getZ()); } public static RBMKNeutronNode makeNode(StreamWorld streamWorld, TileEntityRBMKBase tile) { BlockPos pos = new BlockPos(tile); RBMKNeutronNode node = (RBMKNeutronNode) streamWorld.getNode(pos); return node != null ? node : new RBMKNeutronNode(tile, tile.getRBMKType(), tile.hasLid()); } public static class RBMKNeutronNode extends NeutronNode { public RBMKNeutronNode(TileEntityRBMKBase tile, RBMKType type, boolean hasLid) { super(tile, NeutronStream.NeutronType.RBMK); this.data.put("hasLid", hasLid); this.data.put("type", type); posInstance = new BlockPos(tile); } public void addLid() { this.data.replace("hasLid", true); } public void removeLid() { this.data.replace("hasLid", false); } protected BlockPos posInstance; private int x; private int z; public Iterator getReaSimNodes() { x = -fluxRange; z = -fluxRange; return new Iterator() { @Override public boolean hasNext() { return (fluxRange + x) * (fluxRange * 2 + 1) + z + fluxRange + 1 < (fluxRange * 2 + 1) * (fluxRange * 2 + 1); } @Override public BlockPos next() { if(Math.pow(x, 2) + Math.pow(z, 2) <= fluxRange * fluxRange) { z++; if(z > fluxRange) { z = -fluxRange; x++; } return posInstance.mutate(tile.xCoord + x, tile.yCoord, tile.zCoord + z); } else { z++; if(z > fluxRange) { z = -fluxRange; x++; } return null; } } }; } public List checkNode(StreamWorld streamWorld) { List list = new ArrayList<>(); BlockPos pos = new BlockPos(this.tile); RBMKNeutronStream[] streams = new RBMKNeutronStream[TileEntityRBMKRod.fluxDirs.length]; // Simulate streams coming out of the RBMK rod. ForgeDirection[] fluxDirs = TileEntityRBMKRod.fluxDirs; for(int i = 0; i < fluxDirs.length; i++) { streams[i] = (new RBMKNeutronStream(this, Vec3.createVectorHelper(fluxDirs[i].offsetX, 0, fluxDirs[i].offsetZ))); } // Check if the rod should uncache nodes. if(tile instanceof TileEntityRBMKRod && !(tile instanceof TileEntityRBMKRodReaSim)) { TileEntityRBMKRod rod = (TileEntityRBMKRod) tile; if(!rod.hasRod || rod.lastFluxQuantity == 0) { for(RBMKNeutronStream stream : streams) { for(NeutronNode node : stream.getNodes(streamWorld, false)) if(node != null) list.add(new BlockPos(node.tile)); } return list; } } { Iterator reaSimNodes = getReaSimNodes(); // Check if the ReaSim rod should be culled from the cache due to no rod or no flux. if(tile instanceof TileEntityRBMKRodReaSim) { // fuckkkkkkk TileEntityRBMKRodReaSim rod = (TileEntityRBMKRodReaSim) tile; if(!rod.hasRod || rod.lastFluxQuantity == 0) { reaSimNodes.forEachRemaining((a) -> { if(a != null) list.add(a.clone()); // ae The RAM usage will be really high here but hopefully the GC can take care of it :pray: }); return list; } } } // Check if non-rod nodes should be uncached... but now with ReaSim! { // Yeah, I don't want to contaminate the surrounding scope. Iterator reaSimNodes = getReaSimNodes(); boolean hasRod = false; while(reaSimNodes.hasNext()) { BlockPos nodePos = reaSimNodes.next(); if(nodePos == null) continue; NeutronNode node = streamWorld.getNode(nodePos); if(node != null && node.tile instanceof TileEntityRBMKRod) { TileEntityRBMKRod rod = (TileEntityRBMKRod) node.tile; if(rod.hasRod && rod.lastFluxQuantity > 0) { hasRod = true; break; } } } if(!hasRod) { list.add(pos); return list; } } // Check if non-rod nodes should be uncached due to no rod in range. for(RBMKNeutronStream stream : streams) { NeutronNode[] nodes = stream.getNodes(streamWorld, false); for(NeutronNode node : nodes) { if(!(node == null) && node.tile instanceof TileEntityRBMKRod) return list; } } // If we get here, then no rods were found along this stream's path! // This, most of the time, means we can just uncache all the nodes inside the stream's path. // That other part of the time, streams will be crossing paths. // This is fine though, we can just uncache them anyway and the streams later on (next tick) will recache them. // /\ idk what this guy was on about but this is just plain wrong. /\ list.add(pos); return list; } } public static class RBMKNeutronStream extends NeutronStream { public RBMKNeutronStream(NeutronNode origin, Vec3 vector) { super(origin, vector); } public RBMKNeutronStream(NeutronNode origin, Vec3 vector, double flux, double ratio) { super(origin, vector, flux, ratio, NeutronType.RBMK); } // Does NOT include the origin node // USES THE CACHE!!! public NeutronNode[] getNodes(StreamWorld streamWorld, boolean addNode) { NeutronNode[] positions = new RBMKNeutronNode[fluxRange]; BlockPos pos = new BlockPos(origin.tile); World world = origin.tile.getWorldObj(); for(int i = 1; i <= fluxRange; i++) { int x = (int) Math.floor(0.5 + vector.xCoord * i); int z = (int) Math.floor(0.5 + vector.zCoord * i); pos.mutate(origin.tile.xCoord + x, origin.tile.yCoord, origin.tile.zCoord + z); NeutronNode node = streamWorld.getNode(pos); if(node instanceof RBMKNeutronNode) { positions[i - 1] = node; } else if(this.origin.tile.getBlockType() instanceof RBMKBase) { TileEntity te = blockPosToTE(world, pos); if(te instanceof TileEntityRBMKBase) { TileEntityRBMKBase rbmkBase = (TileEntityRBMKBase) te; node = makeNode(streamWorld, rbmkBase); positions[i - 1] = node; if(addNode) streamWorld.addNode(node); } } } return positions; } // The... small one? whatever it's still pretty big, runs the interaction for the stream. public void runStreamInteraction(World worldObj, StreamWorld streamWorld) { // do nothing if there's nothing to do lmao if(fluxQuantity == 0D) return; BlockPos pos = new BlockPos(origin.tile); TileEntityRBMKBase originTE; NeutronNode node = streamWorld.getNode(pos); if(node != null) { originTE = (TileEntityRBMKBase) node.tile; } else { originTE = (TileEntityRBMKBase) blockPosToTE(worldObj, pos); if(originTE == null) return; // Doesn't exist anymore! streamWorld.addNode(new RBMKNeutronNode(originTE, originTE.getRBMKType(), originTE.hasLid())); } int moderatedCount = 0; Iterator iterator = getBlocks(fluxRange); while(iterator.hasNext()) { BlockPos targetPos = iterator.next(); if(fluxQuantity == 0D) // Whoops, used it all up! return; NeutronNode targetNode = streamWorld.getNode(targetPos); if(targetNode == null) { TileEntity te = blockPosToTE(worldObj, targetPos); // ok, maybe it didn't get added to the list somehow?? if(te instanceof TileEntityRBMKBase) { targetNode = makeNode(streamWorld, (TileEntityRBMKBase) te); streamWorld.addNode(targetNode); // whoops! } else { int hits = getHits(targetPos); // Get the amount of hits on blocks. if(hits == columnHeight) // If stream is fully blocked. return; else if(hits > 0) { // If stream is partially blocked. irradiateFromFlux(pos, hits); fluxQuantity *= 1 - ((double) hits / columnHeight); // Inverse to get partial blocking by blocks. continue; } else { // Nothing hit! irradiateFromFlux(pos, 0); continue; } } } RBMKType type = (RBMKType) targetNode.data.get("type"); if(type == RBMKType.OTHER || type == null) // pass right on by! continue; // we established earlier during `getNodes()` that they should all be RBMKBase TEs // no issue with casting here! TileEntityRBMKBase nodeTE = (TileEntityRBMKBase) targetNode.tile; if(!(boolean) targetNode.data.get("hasLid")) ChunkRadiationManager.proxy.incrementRad(worldObj, targetPos.getX(), targetPos.getY(), targetPos.getZ(), (float) (this.fluxQuantity * 0.05F)); if(type == RBMKType.MODERATOR || nodeTE.isModerated()) { moderatedCount++; moderateStream(); } if(nodeTE instanceof IRBMKFluxReceiver) { IRBMKFluxReceiver column = (IRBMKFluxReceiver) nodeTE; if(type == RBMKType.ROD) { TileEntityRBMKRod rod = (TileEntityRBMKRod) column; if(rod.hasRod) { rod.receiveFlux(this); return; } } else if(type == RBMKType.OUTGASSER) { TileEntityRBMKOutgasser outgasser = ((TileEntityRBMKOutgasser) column); if(outgasser.canProcess()) { column.receiveFlux(this); return; } } } else if(type == RBMKType.CONTROL_ROD) { TileEntityRBMKControl rod = (TileEntityRBMKControl) nodeTE; if(rod.level > 0.0D) { this.fluxQuantity *= rod.getMult(); continue; } return; } else if(type == RBMKType.REFLECTOR) { if(((TileEntityRBMKBase) this.origin.tile).isModerated()) moderatedCount++; if(this.fluxRatio > 0 && moderatedCount > 0) for(int i = 0; i < moderatedCount; i++) moderateStream(); if(reflectorEfficiency != 1.0D) { this.fluxQuantity *= reflectorEfficiency; continue; } ((TileEntityRBMKRod) originTE).receiveFlux(this); return; } else if(type == RBMKType.ABSORBER) { if(absorberEfficiency == 1) return; this.fluxQuantity *= absorberEfficiency; } } NeutronNode[] nodes = getNodes(streamWorld, true); NeutronNode lastNode = nodes[(nodes.length - 1)]; if(lastNode == null) { // This implies that there was *no* last node, meaning either way it was never caught. // There is really no good way to figure out where exactly it should irradiate, so just irradiate at the origin tile. irradiateFromFlux(new BlockPos(origin.tile.xCoord + this.vector.xCoord, origin.tile.yCoord, origin.tile.zCoord + this.vector.zCoord)); return; } RBMKType lastNodeType = (RBMKType) lastNode.data.get("type"); if(lastNodeType == RBMKType.CONTROL_ROD) { TileEntityRBMKControl rod = (TileEntityRBMKControl) lastNode.tile; if(rod.getMult() > 0.0D) { this.fluxQuantity *= rod.getMult(); BlockPos posAfter = new BlockPos(lastNode.tile.xCoord + this.vector.xCoord, lastNode.tile.yCoord, lastNode.tile.zCoord + this.vector.zCoord); // The below code checks if the block after the control rod is actually a block or if it's an RBMK rod. // Resolves GitHub issue #1933. if(NeutronNodeWorld.getNode(worldObj, pos) == null) { TileEntity te = blockPosToTE(worldObj, posAfter); if (te instanceof TileEntityRBMKBase) { RBMKNeutronNode nodeAfter = makeNode(NeutronNodeWorld.getOrAddWorld(worldObj), (TileEntityRBMKBase) te); NeutronNodeWorld.getOrAddWorld(worldObj).addNode(nodeAfter); } else { irradiateFromFlux(posAfter); // I'm so mad about this... } } } } } public int getHits(BlockPos pos) { int hits = 0; for(int h = 0; h < columnHeight; h++) { // holy fucking shit // I have had this one line cause me like tens of problems // I FUCKING HATE THIS // total count of bugs fixed attributed to this function: 14 Block block = origin.tile.getWorldObj().getBlock(pos.getX(), pos.getY() + h, pos.getZ()); if(block.isOpaqueCube()) hits += 1; } return hits; } public void irradiateFromFlux(BlockPos pos) { ChunkRadiationManager.proxy.incrementRad(origin.tile.getWorldObj(), pos.getX(), pos.getY(), pos.getZ(), (float) (fluxQuantity * 0.05F * (1 - (double) getHits(pos) / columnHeight))); } public void irradiateFromFlux(BlockPos pos, int hits) { ChunkRadiationManager.proxy.incrementRad(origin.tile.getWorldObj(), pos.getX(), pos.getY(), pos.getZ(), (float) (fluxQuantity * 0.05F * (1 - (double) hits / columnHeight))); } public void moderateStream() { fluxRatio *= (1 - moderatorEfficiency); } } }