diff --git a/src/main/java/WayofTime/alchemicalWizardry/client/nei/IMCForNEI.java b/src/main/java/WayofTime/alchemicalWizardry/client/nei/IMCForNEI.java index 05895f802..ed6d73780 100644 --- a/src/main/java/WayofTime/alchemicalWizardry/client/nei/IMCForNEI.java +++ b/src/main/java/WayofTime/alchemicalWizardry/client/nei/IMCForNEI.java @@ -96,6 +96,7 @@ public static void IMCSender() { sendInfoPage("AWWayofTime masterStone,blockAlchemicCalcinator", "nei.infopage.reagents.1"); sendInfoPage("AWWayofTime masterStone,blockAlchemicCalcinator|", "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"); diff --git a/src/main/java/WayofTime/alchemicalWizardry/common/block/BlockReagentConduit.java b/src/main/java/WayofTime/alchemicalWizardry/common/block/BlockReagentConduit.java index c8680e3bc..322551600 100644 --- a/src/main/java/WayofTime/alchemicalWizardry/common/block/BlockReagentConduit.java +++ b/src/main/java/WayofTime/alchemicalWizardry/common/block/BlockReagentConduit.java @@ -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 boolean canProvidePower() { @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> entry : relay.reagentTargetList.entrySet()) { + Reagent reagent = entry.getKey(); + List 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); } diff --git a/src/main/java/WayofTime/alchemicalWizardry/common/items/energy/ItemAttunedCrystal.java b/src/main/java/WayofTime/alchemicalWizardry/common/items/energy/ItemAttunedCrystal.java index 12bb3b580..3976be96f 100644 --- a/src/main/java/WayofTime/alchemicalWizardry/common/items/energy/ItemAttunedCrystal.java +++ b/src/main/java/WayofTime/alchemicalWizardry/common/items/energy/ItemAttunedCrystal.java @@ -60,6 +60,9 @@ public String getItemStackDisplayName(ItemStack stack) { public void addInformation(ItemStack item, EntityPlayer player, List 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 ItemStack onItemRightClick(ItemStack itemStack, World world, EntityPlayer 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; - } 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); + 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; + } - 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 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 reagentList = getReagents(infos); - - Reagent pastReagent = this.getReagent(itemStack); + 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 (reagentList.isEmpty()) { - 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; + } - int reagentLocation; + 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; + } - reagentLocation = reagentList.indexOf(pastReagent); + Reagent reagent = this.getReagent(itemStack); + if (reagent == null) { + player.addChatComponentMessage(new ChatComponentTranslation("message.attunedcrystal.error.noreagent")); + return itemStack; + } - 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")); - } - } + 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 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 collectReagents(IReagentHandler relay) { + List 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 getReagents(ReagentContainerInfo[] infos) { List reagentList = new LinkedList<>(); for (ReagentContainerInfo info : infos) { @@ -248,6 +285,13 @@ public static List getReagents(ReagentContainerInfo[] infos) { 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()); diff --git a/src/main/java/WayofTime/alchemicalWizardry/common/items/energy/ItemDestinationClearer.java b/src/main/java/WayofTime/alchemicalWizardry/common/items/energy/ItemDestinationClearer.java index 94bb4981e..f80f7ab26 100644 --- a/src/main/java/WayofTime/alchemicalWizardry/common/items/energy/ItemDestinationClearer.java +++ b/src/main/java/WayofTime/alchemicalWizardry/common/items/energy/ItemDestinationClearer.java @@ -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.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; @@ -45,26 +49,66 @@ public ItemStack onItemRightClick(ItemStack itemStack, World world, EntityPlayer } 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>> entries = src.reagentTargetList.entrySet().iterator(); + while (entries.hasNext()) { + Map.Entry> entry = entries.next(); + List coords = entry.getValue(); + if (coords == null) continue; + Iterator 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; + } } diff --git a/src/main/java/WayofTime/alchemicalWizardry/common/tileEntity/TEReagentConduit.java b/src/main/java/WayofTime/alchemicalWizardry/common/tileEntity/TEReagentConduit.java index 4a204a9ea..644828a14 100644 --- a/src/main/java/WayofTime/alchemicalWizardry/common/tileEntity/TEReagentConduit.java +++ b/src/main/java/WayofTime/alchemicalWizardry/common/tileEntity/TEReagentConduit.java @@ -415,17 +415,31 @@ public void onDataPacket(NetworkManager net, S35PacketUpdateTileEntity packet) { readClientNBT(packet.func_148857_g()); } - public boolean addReagentDestinationViaOffset(Reagent reagent, int xOffset, int yOffset, int zOffset) { - int totalConnections = 0; + public int getTotalConnections() { + int total = 0; + for (List list : this.reagentTargetList.values()) { + if (list != null) { + total += list.size(); + } + } + return total; + } - for (Entry> entry : this.reagentTargetList.entrySet()) { - if (entry.getValue() != null) { - totalConnections += entry.getValue().size(); + 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 coords = this.reagentTargetList.get(reagent); + if (coords != null) { + return coords.contains(new Int3(xOffset, yOffset, zOffset)); } } + return false; + } - if (totalConnections >= this.maxConnextions) { - // Send message that it cannot be done? Maybe add a Player instance + public boolean addReagentDestinationViaOffset(Reagent reagent, int xOffset, int yOffset, int zOffset) { + if (getTotalConnections() >= this.maxConnextions) { return false; } @@ -442,6 +456,9 @@ public boolean addReagentDestinationViaOffset(Reagent reagent, int xOffset, int newCoordList.add(newCoord); this.reagentTargetList.put(reagent, newCoordList); } else { + if (coordList.contains(newCoord)) { + return false; + } coordList.add(newCoord); } diff --git a/src/main/resources/assets/alchemicalwizardry/lang/en_US.lang b/src/main/resources/assets/alchemicalwizardry/lang/en_US.lang index dd3f62ec5..b6bfc45ea 100644 --- a/src/main/resources/assets/alchemicalwizardry/lang/en_US.lang +++ b/src/main/resources/assets/alchemicalwizardry/lang/en_US.lang @@ -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,8 @@ 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 incoming links. 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 +551,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 +750,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 transfers AR. Right-click a block that can hold AR to save it as the source. The first reagent in the source is copied automatically. Right-click the source again to cycle through its other reagents. Right-click a different block to link it as a destination. +nei.infopage.reagents.3b=After making a link, the source is cleared, but the reagent stays, so you can quickly make multiple links for the same reagent. Sneak+right-click anywhere before linking to clear the saved source. Sneak+right-click again to clear the saved reagent. Right-clicking a relay with an empty hand sends its current outgoing routing connections in 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 incoming links. An Alchemic Segmenter may be used to limit how many internal AR tanks a single reagent may occupy. Attune it by shift+right-clicking a block with a stored reagent, then right-click a block to cycle how many tanks that reagent can fill. 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.