Merge remote-tracking branch 'origin/alchemic-router-not-bad' into dev

This commit is contained in:
Martin Robertz committed 2026-05-16 14:27:35 +02:00
commit 325f545ad9
6 files changed
+278 -118

No files matched your search

@@ -96,6 +96,7 @@ public class IMCForNEI {
sendInfoPage("AWWayofTime masterStone,blockAlchemicCalcinator", "nei.infopage.reagents.1");
sendInfoPage("AWWayofTime masterStone,blockAlchemicCalcinator|<AWWayofTime:terrae>", "nei.infopage.reagents.2");
sendInfoPage("AWWayofTime masterStone,blockAlchemicCalcinator,itemAttunedCrystal", "nei.infopage.reagents.3");
sendInfoPage("AWWayofTime masterStone,blockAlchemicCalcinator,itemAttunedCrystal", "nei.infopage.reagents.3b");
sendInfoPage(
"AWWayofTime masterStone,blockAlchemicCalcinator,itemTankSegmenter,itemDestinationClearer",
"nei.infopage.reagents.4");
@@ -1,13 +1,21 @@
package WayofTime.alchemicalWizardry.common.block;
import java.util.List;
import java.util.Map;
import net.minecraft.block.BlockContainer;
import net.minecraft.block.material.Material;
import net.minecraft.client.renderer.texture.IIconRegister;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ChatComponentTranslation;
import net.minecraft.world.World;
import WayofTime.alchemicalWizardry.AlchemicalWizardry;
import WayofTime.alchemicalWizardry.api.Int3;
import WayofTime.alchemicalWizardry.api.alchemy.energy.Reagent;
import WayofTime.alchemicalWizardry.api.items.interfaces.IReagentManipulator;
import WayofTime.alchemicalWizardry.common.tileEntity.TEReagentConduit;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
@@ -41,6 +49,36 @@ public class BlockReagentConduit extends BlockContainer {
@Override
public boolean onBlockActivated(World world, int x, int y, int z, EntityPlayer player, int side, float what,
float these, float are) {
if (!world.isRemote) {
ItemStack held = player.getHeldItem();
if (held == null || !(held.getItem() instanceof IReagentManipulator)) {
TileEntity tile = world.getTileEntity(x, y, z);
if (tile instanceof TEReagentConduit relay) {
if (relay.reagentTargetList.isEmpty()) {
player.addChatComponentMessage(
new ChatComponentTranslation("message.reagentconduit.noconnections"));
} else {
player.addChatComponentMessage(
new ChatComponentTranslation("message.reagentconduit.connections"));
for (Map.Entry<Reagent, List<Int3>> entry : relay.reagentTargetList.entrySet()) {
Reagent reagent = entry.getKey();
List<Int3> offsets = entry.getValue();
if (offsets == null) continue;
for (Int3 offset : offsets) {
player.addChatComponentMessage(
new ChatComponentTranslation(
"message.reagentconduit.connection.entry",
reagent.name(),
x + offset.x(),
y + offset.y(),
z + offset.z()));
}
}
}
return true;
}
}
}
return super.onBlockActivated(world, x, y, z, player, side, what, these, are);
}
@@ -60,6 +60,9 @@ public class ItemAttunedCrystal extends Item implements IReagentManipulator {
public void addInformation(ItemStack item, EntityPlayer player, List<String> tooltip, boolean adv) {
tooltip.add(StatCollector.translateToLocal("tooltip.attunedcrystal.desc1"));
tooltip.add(StatCollector.translateToLocal("tooltip.attunedcrystal.desc2"));
tooltip.add(StatCollector.translateToLocal("tooltip.attunedcrystal.desc3"));
tooltip.add(StatCollector.translateToLocal("tooltip.attunedcrystal.desc4"));
tooltip.add(StatCollector.translateToLocal("tooltip.attunedcrystal.desc5"));
if (item.getTagCompound() == null) {
return;
@@ -131,107 +134,141 @@ public class ItemAttunedCrystal extends Item implements IReagentManipulator {
return itemStack;
}
MovingObjectPosition movingobjectposition = this.getMovingObjectPositionFromPlayer(world, player, false);
if (movingobjectposition == null) {
if (player.isSneaking()) {
// Sneak wipes the source first, then the reagent — regardless of where the cursor points.
if (player.isSneaking()) {
if (this.getHasSavedCoordinates(itemStack)) {
this.setHasSavedCoordinates(itemStack, false);
player.addChatComponentMessage(new ChatComponentTranslation("message.attunedcrystal.clearing"));
} else if (this.getReagent(itemStack) != null) {
this.clearReagent(itemStack);
player.addChatComponentMessage(new ChatComponentTranslation("message.attunedcrystal.reagentcleared"));
}
return itemStack;
}
MovingObjectPosition movingobjectposition = this.getMovingObjectPositionFromPlayer(world, player, false);
if (movingobjectposition == null
|| movingobjectposition.typeOfHit != MovingObjectPosition.MovingObjectType.BLOCK) {
return itemStack;
}
int x = movingobjectposition.blockX;
int y = movingobjectposition.blockY;
int z = movingobjectposition.blockZ;
TileEntity tile = world.getTileEntity(x, y, z);
if (!(tile instanceof IReagentHandler relay)) {
return itemStack;
}
if (this.getHasSavedCoordinates(itemStack)) {
Int3 coords = this.getCoordinates(itemStack);
int dimension = this.getDimension(itemStack);
if (coords == null) {
return itemStack;
}
return itemStack;
} else {
if (movingobjectposition.typeOfHit == MovingObjectPosition.MovingObjectType.BLOCK) {
int x = movingobjectposition.blockX;
int y = movingobjectposition.blockY;
int z = movingobjectposition.blockZ;
TileEntity tile = world.getTileEntity(x, y, z);
if (!(tile instanceof IReagentHandler relay)) {
// Re-clicking the saved source cycles its reagents, never tries to self-link.
if (dimension == world.provider.dimensionId && coords.x() == x && coords.y() == y && coords.z() == z) {
List<Reagent> reagentList = collectReagents(relay);
if (reagentList.size() <= 1) {
player.addChatComponentMessage(
new ChatComponentTranslation("message.attunedcrystal.error.cannotlinktoself"));
return itemStack;
}
if (player.isSneaking()) {
ReagentContainerInfo[] infos = relay.getContainerInfo(ForgeDirection.UNKNOWN);
if (infos != null) {
List<Reagent> reagentList = getReagents(infos);
Reagent pastReagent = this.getReagent(itemStack);
if (reagentList.isEmpty()) {
return itemStack;
}
int reagentLocation;
reagentLocation = reagentList.indexOf(pastReagent);
if (reagentLocation == -1 || reagentLocation + 1 >= reagentList.size()) {
this.setReagentWithNotification(itemStack, reagentList.getFirst(), player);
} else {
this.setReagentWithNotification(itemStack, reagentList.get(reagentLocation + 1), player);
}
}
} else {
if (this.getHasSavedCoordinates(itemStack)) {
Int3 coords = this.getCoordinates(itemStack);
int dimension = this.getDimension(itemStack);
if (coords == null) {
return itemStack;
}
if (dimension != world.provider.dimensionId || Math.abs(coords.x() - x) > maxDistance
|| Math.abs(coords.y() - y) > maxDistance
|| Math.abs(coords.z() - z) > maxDistance) {
player.addChatComponentMessage(
new ChatComponentTranslation("message.attunedcrystal.error.toofar"));
return itemStack;
}
TileEntity pastTile = world.getTileEntity(coords.x(), coords.y(), coords.z());
if (!(pastTile instanceof TEReagentConduit pastRelay)) {
player.addChatComponentMessage(
new ChatComponentTranslation("message.attunedcrystal.error.cannotfind"));
return itemStack;
}
Reagent reagent = this.getReagent(itemStack);
if (reagent == null) {
return itemStack;
}
if (player.isSneaking()) {
pastRelay.removeReagentDestinationViaActual(reagent, x, y, z);
} else {
if (pastRelay.addReagentDestinationViaActual(reagent, x, y, z)) {
player.addChatComponentMessage(
new ChatComponentText(
StatCollector.translateToLocal("message.attunedcrystal.linked") + " "
+ reagent.name()));
} else {
player.addChatComponentMessage(
new ChatComponentTranslation("message.attunedcrystal.error.noconnections"));
}
}
world.markBlockForUpdate(coords.x(), coords.y(), coords.z());
} else {
int dimension = world.provider.dimensionId;
this.setDimension(itemStack, dimension);
this.setCoordinates(itemStack, new Int3(x, y, z));
player.addChatComponentMessage(new ChatComponentTranslation("message.attunedcrystal.linking"));
}
}
Reagent pastReagent = this.getReagent(itemStack);
int reagentLocation = reagentList.indexOf(pastReagent);
Reagent next = (reagentLocation == -1 || reagentLocation + 1 >= reagentList.size())
? reagentList.getFirst()
: reagentList.get(reagentLocation + 1);
this.setReagentWithNotification(itemStack, next, player);
return itemStack;
}
if (dimension != world.provider.dimensionId || Math.abs(coords.x() - x) > maxDistance
|| Math.abs(coords.y() - y) > maxDistance
|| Math.abs(coords.z() - z) > maxDistance) {
player.addChatComponentMessage(new ChatComponentTranslation("message.attunedcrystal.error.toofar"));
return itemStack;
}
TileEntity pastTile = world.getTileEntity(coords.x(), coords.y(), coords.z());
if (!(pastTile instanceof TEReagentConduit pastRelay)) {
// Saved source is gone — clear it so the player can start over without a manual reset.
this.setHasSavedCoordinates(itemStack, false);
player.addChatComponentMessage(new ChatComponentTranslation("message.attunedcrystal.error.cannotfind"));
return itemStack;
}
Reagent reagent = this.getReagent(itemStack);
if (reagent == null) {
player.addChatComponentMessage(new ChatComponentTranslation("message.attunedcrystal.error.noreagent"));
return itemStack;
}
if (pastRelay.hasReagentDestination(reagent, x, y, z)) {
player.addChatComponentMessage(new ChatComponentTranslation("message.attunedcrystal.error.duplicate"));
} else if (pastRelay.addReagentDestinationViaActual(reagent, x, y, z)) {
int used = pastRelay.getTotalConnections();
int max = pastRelay.maxConnextions;
player.addChatComponentMessage(
new ChatComponentTranslation("message.attunedcrystal.linked", reagent.name(), used, max));
// Successful link: clear the source so the next click starts a fresh link, but
// keep the reagent so the player can fan out to multiple destinations quickly.
this.setHasSavedCoordinates(itemStack, false);
} else {
player.addChatComponentMessage(
new ChatComponentTranslation("message.attunedcrystal.error.noconnections"));
}
world.markBlockForUpdate(coords.x(), coords.y(), coords.z());
return itemStack;
}
// No saved source — this click saves one and (when useful) copies its reagent.
if (tile instanceof TEReagentConduit conduit && conduit.getTotalConnections() >= conduit.maxConnextions) {
player.addChatComponentMessage(new ChatComponentTranslation("message.attunedcrystal.error.sourcefull"));
return itemStack;
}
List<Reagent> reagentList = collectReagents(relay);
Reagent currentReagent = this.getReagent(itemStack);
if (reagentList.isEmpty() && currentReagent == null) {
player.addChatComponentMessage(new ChatComponentTranslation("message.attunedcrystal.error.nothingtocopy"));
return itemStack;
}
// Keep the crystal's existing reagent if the source has it (or if the source is empty);
// otherwise replace with the source's first tank.
if (!reagentList.isEmpty() && (currentReagent == null || !reagentList.contains(currentReagent))) {
this.setReagentWithNotification(itemStack, reagentList.getFirst(), player);
}
this.setDimension(itemStack, world.provider.dimensionId);
this.setCoordinates(itemStack, new Int3(x, y, z));
player.addChatComponentMessage(new ChatComponentTranslation("message.attunedcrystal.linking", x, y, z));
return itemStack;
}
private static List<Reagent> collectReagents(IReagentHandler relay) {
List<Reagent> reagentList = new LinkedList<>();
ReagentContainerInfo[] infos = relay.getContainerInfo(ForgeDirection.UNKNOWN);
if (infos == null) {
return reagentList;
}
for (ReagentContainerInfo info : infos) {
if (info == null) continue;
ReagentStack reagentStack = info.reagent;
if (reagentStack == null) continue;
Reagent reagent = reagentStack.reagent;
if (reagent != null && !reagentList.contains(reagent)) {
reagentList.add(reagent);
}
}
return reagentList;
}
public static List<Reagent> getReagents(ReagentContainerInfo[] infos) {
List<Reagent> reagentList = new LinkedList<>();
for (ReagentContainerInfo info : infos) {
@@ -248,6 +285,13 @@ public class ItemAttunedCrystal extends Item implements IReagentManipulator {
return reagentList;
}
public void clearReagent(ItemStack stack) {
if (!stack.hasTagCompound()) {
stack.setTagCompound(new NBTTagCompound());
}
stack.getTagCompound().setString("reagent", "");
}
public void setCoordinates(ItemStack stack, Int3 coords) {
if (!stack.hasTagCompound()) {
stack.setTagCompound(new NBTTagCompound());
@@ -1,6 +1,8 @@
package WayofTime.alchemicalWizardry.common.items.energy;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import net.minecraft.client.renderer.texture.IIconRegister;
import net.minecraft.entity.player.EntityPlayer;
@@ -13,6 +15,8 @@ import net.minecraft.util.StatCollector;
import net.minecraft.world.World;
import WayofTime.alchemicalWizardry.AlchemicalWizardry;
import WayofTime.alchemicalWizardry.api.Int3;
import WayofTime.alchemicalWizardry.api.alchemy.energy.Reagent;
import WayofTime.alchemicalWizardry.api.items.interfaces.IReagentManipulator;
import WayofTime.alchemicalWizardry.common.tileEntity.TEReagentConduit;
import cpw.mods.fml.relauncher.Side;
@@ -36,6 +40,7 @@ public class ItemDestinationClearer extends Item implements IReagentManipulator
public void addInformation(ItemStack item, EntityPlayer player, List<String> tooltip, boolean adv) {
tooltip.add(StatCollector.translateToLocal("tooltip.destclearer.desc1"));
tooltip.add(StatCollector.translateToLocal("tooltip.destclearer.desc2"));
tooltip.add(StatCollector.translateToLocal("tooltip.destclearer.desc3"));
}
@Override
@@ -45,26 +50,66 @@ public class ItemDestinationClearer extends Item implements IReagentManipulator
}
MovingObjectPosition movingobjectposition = this.getMovingObjectPositionFromPlayer(world, player, false);
if (movingobjectposition == null) {
if (movingobjectposition == null
|| movingobjectposition.typeOfHit != MovingObjectPosition.MovingObjectType.BLOCK) {
return itemStack;
}
if (movingobjectposition.typeOfHit == MovingObjectPosition.MovingObjectType.BLOCK) {
int x = movingobjectposition.blockX;
int y = movingobjectposition.blockY;
int z = movingobjectposition.blockZ;
TileEntity tile = world.getTileEntity(x, y, z);
int x = movingobjectposition.blockX;
int y = movingobjectposition.blockY;
int z = movingobjectposition.blockZ;
if (!(tile instanceof TEReagentConduit relay)) {
return itemStack;
}
TileEntity tile = world.getTileEntity(x, y, z);
if (!(tile instanceof TEReagentConduit relay)) {
return itemStack;
}
if (player.isSneaking()) {
int removed = clearIncomingConnections(world, x, y, z);
player.addChatComponentMessage(
new ChatComponentTranslation("message.destinationclearer.incoming.cleared", removed));
} else {
relay.reagentTargetList.clear();
world.markBlockForUpdate(x, y, z);
player.addChatComponentMessage(new ChatComponentTranslation("message.destinationclearer.cleared"));
}
return itemStack;
}
private static int clearIncomingConnections(World world, int x, int y, int z) {
int range = ItemAttunedCrystal.maxDistance;
int totalRemoved = 0;
for (int dx = -range; dx <= range; dx++) {
for (int dy = -range; dy <= range; dy++) {
for (int dz = -range; dz <= range; dz++) {
if (dx == 0 && dy == 0 && dz == 0) continue;
TileEntity neighbor = world.getTileEntity(x + dx, y + dy, z + dz);
if (!(neighbor instanceof TEReagentConduit src)) continue;
boolean changed = false;
Iterator<Map.Entry<Reagent, List<Int3>>> entries = src.reagentTargetList.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry<Reagent, List<Int3>> entry = entries.next();
List<Int3> coords = entry.getValue();
if (coords == null) continue;
Iterator<Int3> it = coords.iterator();
while (it.hasNext()) {
Int3 off = it.next();
if (src.xCoord + off.x() == x && src.yCoord + off.y() == y && src.zCoord + off.z() == z) {
it.remove();
changed = true;
totalRemoved++;
}
}
if (coords.isEmpty()) entries.remove();
}
if (changed) {
world.markBlockForUpdate(src.xCoord, src.yCoord, src.zCoord);
}
}
}
}
return totalRemoved;
}
}
@@ -415,17 +415,31 @@ public class TEReagentConduit extends TileSegmentedReagentHandler {
readClientNBT(packet.func_148857_g());
}
public boolean addReagentDestinationViaOffset(Reagent reagent, int xOffset, int yOffset, int zOffset) {
int totalConnections = 0;
for (Entry<Reagent, List<Int3>> entry : this.reagentTargetList.entrySet()) {
if (entry.getValue() != null) {
totalConnections += entry.getValue().size();
public int getTotalConnections() {
int total = 0;
for (List<Int3> list : this.reagentTargetList.values()) {
if (list != null) {
total += list.size();
}
}
return total;
}
if (totalConnections >= this.maxConnextions) {
// Send message that it cannot be done? Maybe add a Player instance
public boolean hasReagentDestination(Reagent reagent, int x, int y, int z) {
int xOffset = x - this.xCoord;
int yOffset = y - this.yCoord;
int zOffset = z - this.zCoord;
if (this.reagentTargetList.containsKey(reagent)) {
List<Int3> coords = this.reagentTargetList.get(reagent);
if (coords != null) {
return coords.contains(new Int3(xOffset, yOffset, zOffset));
}
}
return false;
}
public boolean addReagentDestinationViaOffset(Reagent reagent, int xOffset, int yOffset, int zOffset) {
if (getTotalConnections() >= this.maxConnextions) {
return false;
}
@@ -442,6 +456,9 @@ public class TEReagentConduit extends TileSegmentedReagentHandler {
newCoordList.add(newCoord);
this.reagentTargetList.put(reagent, newCoordList);
} else {
if (coordList.contains(newCoord)) {
return false;
}
coordList.add(newCoord);
}
@@ -434,8 +434,11 @@ tooltip.alchemyflask.caution=CAUTION: Contents are throwable
tooltip.alchemyflask.swigsleft=Swigs Left:
tooltip.armorinhibitor.desc1=Used to suppress a soul's
tooltip.armorinhibitor.desc2=unnatural abilities.
tooltip.attunedcrystal.desc1=A tool to tune alchemy
tooltip.attunedcrystal.desc2=reagent transmission
tooltip.attunedcrystal.desc1=1) Right-click a source relay to save it and copy its reagent.
tooltip.attunedcrystal.desc2=2) Right-click the same source again to cycle its reagent.
tooltip.attunedcrystal.desc3=3) Right-click a destination relay to link.
tooltip.attunedcrystal.desc4=Sneak+right-click to clear the saved source.
tooltip.attunedcrystal.desc5=Sneak+right-click again to clear the held reagent.
tooltip.blankspell.desc=Crystal of infinite possibilities.
tooltip.bloodframe.desc=Stirs bees into a frenzy.
tooltip.bloodletterpack.desc=This pack really chafes...
@@ -455,8 +458,9 @@ tooltip.crystalbelljar.empty=- Empty
tooltip.demonictelepfocus.desc1=A stronger version of the focus,
tooltip.demonictelepfocus.desc2=using a demonic shard
tooltip.demonplacer.desc=Used to spawn demons.
tooltip.destclearer.desc1=Used to clear the destination
tooltip.destclearer.desc2=list for an alchemy container
tooltip.destclearer.desc1=Right-click a relay to clear its outgoing links.
tooltip.destclearer.desc2=Sneak+right-click a relay to clear links
tooltip.destclearer.desc3=pointing AT it from nearby relays.
tooltip.diablokey.desc=Binds other items to the owner's network
tooltip.divinationsigil.desc1=Peer into the soul to
tooltip.divinationsigil.desc2=get the current essence
@@ -548,16 +552,26 @@ message.altar.progress=Altar's Progress: %,d LP / %,d LP
message.altar.inputtank= Input Tank: %,d LP
message.altar.outputtank= Output Tank: %,d LP
message.altar.hunger=[BM] Your high regeneration rate has caused you to become hungry...
message.attunedcrystal.clearing=Clearing saved container...
message.attunedcrystal.error.cannotfind=Can no longer find linked container.
message.attunedcrystal.clearing=Cleared saved source.
message.attunedcrystal.reagentcleared=Cleared held reagent.
message.attunedcrystal.error.cannotfind=Can no longer find saved source.
message.attunedcrystal.error.noconnections=Linked container has no connections remaining!
message.attunedcrystal.error.toofar=Linked container is either too far or is in a different dimension.
message.attunedcrystal.linked=Container is now linked. Transmitting:
message.attunedcrystal.linking=Linking to selected container.
message.attunedcrystal.error.toofar=Saved source is either too far or is in a different dimension.
message.attunedcrystal.error.noreagent=No reagent selected! Right-click a source relay to copy one.
message.attunedcrystal.error.duplicate=Already connected to that destination!
message.attunedcrystal.error.sourcefull=Source has no more open outgoing links.
message.attunedcrystal.error.cannotlinktoself=Cannot link a source to itself.
message.attunedcrystal.error.nothingtocopy=Source has no reagents to copy.
message.attunedcrystal.linked=Linked! Transmitting %s. (%s/%s connections used)
message.attunedcrystal.linking=Source relay saved at %s, %s, %s. Right-click a destination to link.
message.attunedcrystal.setto=Attuned Crystal now set to:
message.reagentconduit.noconnections=This relay has no routing connections.
message.reagentconduit.connections=Routing connections:
message.reagentconduit.connection.entry= %s -> (%s, %s, %s)
message.demon.shallfollow=I shall follow and protect you!
message.demon.willstay=I will stay here for now, Master.
message.destinationclearer.cleared=Destination list now cleared.
message.destinationclearer.incoming.cleared=Cleared %s incoming connection(s) from nearby relays.
message.divinationsigil.amount=Amount: %,d AR
message.divinationsigil.currentessence=Current Essence: %,d LP
message.divinationsigil.reagent=Reagent: %s
@@ -737,6 +751,7 @@ nei.infopage.rituals.2=To build a ritual, place a Master Ritual Stone surrounded
nei.infopage.rituals.3=Once the ritual is assembled, right-click the Master Ritual Stone with a bound Activation Crystal. If everything is in place and your Soul Network contains sufficient LP, the ritual will commence! Each ritual has a unique effect. Experimentation is encouraged to discover the full potential of the Master Ritual Stone.
nei.infopage.reagents.1=Some rituals can be modified by providing reagents to the Master Ritual Stone. However, raw reagents are too impure to be used directly. They must first be refined in an Alchemic Calcinator into AR (Alchemy Reagent), a pure essence usable by the ritual.
nei.infopage.reagents.2=To use an Alchemic Calcinator, right-click it with a bound Blood Orb, and then right-click it with the reagent item. Reagents may also be inserted via hoppers or pipes. The reagent will be consumed and converted into AR, which the Calcinator will store internally.
nei.infopage.reagents.3=An Alchemic Router is required to transfer AR. Shift-right-click a block containing AR to attune the Router to that reagent. Then right-click the source block, followed by the destination block, to create a link. Shift-right-click in the air to remove the currently saved source.
nei.infopage.reagents.4=An Alchemic Cleanser may be used to remove AR links by using it on the source block. An Alchemic Segmenter may be used to limit how many internal AR tanks a single reagent may occupy; it is attuned in the same way as the Router.
nei.infopage.reagents.3=An Alchemic Router (Attuned Crystal) transfers AR. Right-click a block holding AR to save it as the source — the Router copies that source's AR automatically. Right-click the saved source again to cycle through its remaining AR types. Right-click a different block to link it as a destination; the source clears but the AR stays, so you can fan out to many destinations quickly.
nei.infopage.reagents.3b=Sneak+right-click anywhere to clear the saved source; sneak+right-click again to clear the held AR. Right-clicking a relay with an empty hand prints its current outgoing routing connections to chat.
nei.infopage.reagents.4=An Alchemic Cleanser clears AR routing connections — right-click a relay to wipe its outgoing links, or sneak+right-click to wipe nearby relays' links pointing AT it. An Alchemic Segmenter may be used to limit how many internal AR tanks a single reagent may occupy; it is attuned in the same way as the Router.
nei.infopage.reagents.5=The range of AR links is limited to a few blocks. To extend the transfer distance, use Alchemic Relays. Crystal Belljars may be placed to store AR, and they will retain their contents when broken.