2025-09-21 10:20:23 -05:00

427 lines
13 KiB
Java

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<BlockPos> getReaSimNodes() {
x = -fluxRange;
z = -fluxRange;
return new Iterator<BlockPos>() {
@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<BlockPos> checkNode(StreamWorld streamWorld) {
List<BlockPos> 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<BlockPos> 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<BlockPos> 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<BlockPos> 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);
}
}
}