diff --git a/src/main/java/li/cil/oc/api/prefab/TileEntityEnvironment.java b/src/main/java/li/cil/oc/api/prefab/TileEntityEnvironment.java index 1f467dde0..e1fb6fca8 100644 --- a/src/main/java/li/cil/oc/api/prefab/TileEntityEnvironment.java +++ b/src/main/java/li/cil/oc/api/prefab/TileEntityEnvironment.java @@ -6,7 +6,6 @@ import li.cil.oc.api.network.Message; import li.cil.oc.api.network.Node; import li.cil.oc.api.network.Visibility; import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.server.gui.IUpdatePlayerListBox; import net.minecraft.tileentity.TileEntity; /** @@ -18,7 +17,7 @@ import net.minecraft.tileentity.TileEntity; * network as an index structure to find other nodes connected to them. */ @SuppressWarnings("UnusedDeclaration") -public abstract class TileEntityEnvironment extends TileEntity implements Environment, IUpdatePlayerListBox { +public abstract class TileEntityEnvironment extends TileEntity implements Environment { /** * This must be set in subclasses to the node that is used to represent * this tile entity. @@ -97,7 +96,7 @@ public abstract class TileEntityEnvironment extends TileEntity implements Enviro // ----------------------------------------------------------------------- // @Override - public void update() { + public void updateEntity() { // On the first update, try to add our node to nearby networks. We do // this in the update logic, not in validate() because we need to access // neighboring tile entities, which isn't possible in validate(). diff --git a/src/main/resources/assets/opencomputers/loot/OpenLoader/bin/opl-flash.lua b/src/main/resources/assets/opencomputers/loot/OpenLoader/bin/opl-flash.lua index d13f30063..a8de2204e 100644 --- a/src/main/resources/assets/opencomputers/loot/OpenLoader/bin/opl-flash.lua +++ b/src/main/resources/assets/opencomputers/loot/OpenLoader/bin/opl-flash.lua @@ -1,9 +1,18 @@ +local version = "OpenLoader 0.2EE" + local eeprom = [[ -_G._OSVERSION = "OpenLoader 0.1EE" +_G._OSVERSION = "]] .. version .. [[" local component = component or require('component') local computer = computer or require('computer') local unicode = unicode or require('unicode') +local eeprom = component.list("eeprom")() +computer.getBootAddress = function() + return component.invoke(eeprom, "getData") +end +computer.setBootAddress = function(address) + return component.invoke(eeprom, "setData", address) +end local gpu = component.list("gpu")() local w, h @@ -147,7 +156,7 @@ say ("Do you really want to flash openloader to EEPROM("..tostring(#eeprom).." b if options.q or options.quiet or io.read():lower() == "y" then say("Flashing... Do not reboot now!") component.eeprom.set(eeprom) - component.eeprom.setLabel((type(options.label) == "string" and options.label) or "OpenLoader") + component.eeprom.setLabel((type(options.label) == "string" and options.label) or version) if options.r or options.reboot then computer.shutdown(true) else diff --git a/src/main/resources/assets/opencomputers/loot/OpenLoader/init.lua b/src/main/resources/assets/opencomputers/loot/OpenLoader/init.lua index 2f70d5a3b..037fde921 100644 --- a/src/main/resources/assets/opencomputers/loot/OpenLoader/init.lua +++ b/src/main/resources/assets/opencomputers/loot/OpenLoader/init.lua @@ -1,8 +1,15 @@ -_G._OSVERSION = "OpenLoader 0.1" +_G._OSVERSION = "OpenLoader 0.2" local component = component or require('component') local computer = computer or require('computer') local unicode = unicode or require('unicode') +local eeprom = component.list("eeprom")() +computer.getBootAddress = function() + return component.invoke(eeprom, "getData") +end +computer.setBootAddress = function(address) + return component.invoke(eeprom, "setData", address) +end local gpu = component.list("gpu")() local w, h diff --git a/src/main/resources/assets/opencomputers/loot/OpenOS/bin/dmesg.lua b/src/main/resources/assets/opencomputers/loot/OpenOS/bin/dmesg.lua new file mode 100644 index 000000000..ab3bb4a0e --- /dev/null +++ b/src/main/resources/assets/opencomputers/loot/OpenOS/bin/dmesg.lua @@ -0,0 +1,33 @@ +local event = require "event" +local component = require "component" +local keyboard = require "keyboard" + +local interactive = io.output() == io.stdout +local color, isPal, evt +if interactive then + color, isPal = component.gpu.getForeground() +end +io.write("Press 'q' to exit\n") +pcall(function() + repeat + evt = table.pack(event.pull()) + if interactive then component.gpu.setForeground(0xCC2200) end + io.write("[" .. os.date("%T") .. "] ") + if interactive then component.gpu.setForeground(0x44CC00) end + io.write(tostring(evt[1]) .. string.rep(" ", math.max(10 - #tostring(evt[1]), 0) + 1)) + if interactive then component.gpu.setForeground(0xB0B00F) end + io.write(tostring(evt[2]) .. string.rep(" ", 37 - #tostring(evt[2]))) + if interactive then component.gpu.setForeground(0xFFFFFF) end + if evt.n > 2 then + for i = 3, evt.n do + io.write(" " .. tostring(evt[i])) + end + end + + io.write("\n") + until evt[1] == "key_down" and evt[4] == keyboard.keys.q +end) +if interactive then + component.gpu.setForeground(color, isPal) +end + diff --git a/src/main/resources/assets/opencomputers/loot/OpenOS/bin/hostname.lua b/src/main/resources/assets/opencomputers/loot/OpenOS/bin/hostname.lua new file mode 100644 index 000000000..f8ff68205 --- /dev/null +++ b/src/main/resources/assets/opencomputers/loot/OpenOS/bin/hostname.lua @@ -0,0 +1,20 @@ +local args = {...} +if args[1] then + local file, reason = io.open("/etc/hostname", "w") + if not file then + io.stderr:write(reason .. "\n") + else + file:write(args[1]) + file:close() + os.setenv("HOSTNAME", args[1]) + os.setenv("PS1", "$HOSTNAME:$PWD# ") + end +else + local file = io.open("/etc/hostname") + if file then + io.write(file:read("*l"), "\n") + file:close() + else + io.stderr:write("Hostname not set\n") + end +end diff --git a/src/main/resources/assets/opencomputers/loot/OpenOS/boot/94_shell.lua b/src/main/resources/assets/opencomputers/loot/OpenOS/boot/94_shell.lua index e754143d7..865ad6612 100644 --- a/src/main/resources/assets/opencomputers/loot/OpenOS/boot/94_shell.lua +++ b/src/main/resources/assets/opencomputers/loot/OpenOS/boot/94_shell.lua @@ -13,4 +13,13 @@ shell.setAlias("rs", "redstone") shell.setAlias("view", "edit -r") shell.setAlias("help", "man") shell.setAlias("?", "man") -shell.setAlias("cp", "cp -i") \ No newline at end of file +shell.setAlias("cp", "cp -i") + +require("event").listen("init", function() + local file = io.open("/etc/hostname") + if file then + os.setenv("HOSTNAME", file:read("*l")) + os.setenv("PS1", "$HOSTNAME:$PWD# ") + file:close() + end +end) diff --git a/src/main/resources/assets/opencomputers/loot/OpenOS/usr/man/dmesg b/src/main/resources/assets/opencomputers/loot/OpenOS/usr/man/dmesg new file mode 100644 index 000000000..89f1f7e10 --- /dev/null +++ b/src/main/resources/assets/opencomputers/loot/OpenOS/usr/man/dmesg @@ -0,0 +1,6 @@ +NAME + dmesg - display messages(events) + +SYNOPIS + dmesg + diff --git a/src/main/resources/assets/opencomputers/loot/OpenOS/usr/man/hostname b/src/main/resources/assets/opencomputers/loot/OpenOS/usr/man/hostname new file mode 100644 index 000000000..ae853e58a --- /dev/null +++ b/src/main/resources/assets/opencomputers/loot/OpenOS/usr/man/hostname @@ -0,0 +1,12 @@ +NAME + hostname - Display and modify hostname + +SYNOPIS + hostname [NEW NAME] + +EXAMPLES + hostname + Prints currently set hostname + + hostname test + Sets hostname of this computer to test diff --git a/src/main/resources/assets/opencomputers/robot.names b/src/main/resources/assets/opencomputers/robot.names index 2587ce8c5..a14f0a57c 100644 --- a/src/main/resources/assets/opencomputers/robot.names +++ b/src/main/resources/assets/opencomputers/robot.names @@ -51,6 +51,7 @@ Kilobyte # Contributor KITT # Knight Rider Kodos # Contributor Laire # Perry Rhodan +Loader 1340 # Borderlands 2 LordFokas # Contributor Marvin # Hitchhiker's Guide to the Galaxy Michiyo # Contributor diff --git a/src/main/scala/li/cil/oc/common/EventHandler.scala b/src/main/scala/li/cil/oc/common/EventHandler.scala index fb206a832..bad067260 100644 --- a/src/main/scala/li/cil/oc/common/EventHandler.scala +++ b/src/main/scala/li/cil/oc/common/EventHandler.scala @@ -6,6 +6,7 @@ import cpw.mods.fml.common.Optional import cpw.mods.fml.common.eventhandler.SubscribeEvent import cpw.mods.fml.common.gameevent.PlayerEvent._ import cpw.mods.fml.common.gameevent.TickEvent +import cpw.mods.fml.common.gameevent.TickEvent.ClientTickEvent import cpw.mods.fml.common.gameevent.TickEvent.ServerTickEvent import cpw.mods.fml.common.network.FMLNetworkEvent.ClientConnectedToServerEvent import li.cil.oc._ @@ -97,7 +98,7 @@ object EventHandler { } @SubscribeEvent - def onTick(e: ServerTickEvent) = if (e.phase == TickEvent.Phase.START) { + def onServerTick(e: ServerTickEvent) = if (e.phase == TickEvent.Phase.START) { pending.synchronized { val adds = pending.toArray pending.clear() @@ -107,6 +108,10 @@ object EventHandler { case t: Throwable => OpenComputers.log.warn("Error in scheduled tick action.", t) } }) + } + + @SubscribeEvent + def onClientTick(e: ClientTickEvent) = if (e.phase == TickEvent.Phase.START) { totalWorldTicks += 1 } diff --git a/src/main/scala/li/cil/oc/common/block/Raid.scala b/src/main/scala/li/cil/oc/common/block/Raid.scala index 7c32128e3..e23b1a959 100644 --- a/src/main/scala/li/cil/oc/common/block/Raid.scala +++ b/src/main/scala/li/cil/oc/common/block/Raid.scala @@ -29,7 +29,7 @@ class Raid(protected implicit val tileTag: ClassTag[tileentity.Raid]) extends Si super.tooltipTail(metadata, stack, player, tooltip, advanced) if (KeyBindings.showExtendedTooltips) { val data = new RaidData(stack) - for (disk <- data.disks) { + for (disk <- data.disks if disk != null) { tooltip.add("- " + disk.getDisplayName) } } diff --git a/src/main/scala/li/cil/oc/common/block/RobotProxy.scala b/src/main/scala/li/cil/oc/common/block/RobotProxy.scala index 7bf14710e..987dfee1e 100644 --- a/src/main/scala/li/cil/oc/common/block/RobotProxy.scala +++ b/src/main/scala/li/cil/oc/common/block/RobotProxy.scala @@ -88,7 +88,7 @@ class RobotProxy extends RedstoneAware with traits.SpecialBlock with traits.Stat val components = info.containers ++ info.components if (components.length > 0) { tooltip.addAll(Tooltip.get("Server.Components")) - for (component <- components) { + for (component <- components if component != null) { tooltip.add("- " + component.getDisplayName) } } diff --git a/src/main/scala/li/cil/oc/common/tileentity/Raid.scala b/src/main/scala/li/cil/oc/common/tileentity/Raid.scala index 296617ecb..def714fda 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Raid.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Raid.scala @@ -77,7 +77,8 @@ class Raid extends traits.Environment with traits.Inventory with traits.Rotatabl } def tryCreateRaid(id: String) { - if (items.count(_.isDefined) == items.length) { + if (items.count(_.isDefined) == items.length && filesystem.fold(true)(fs => fs.node == null || fs.node.address != id)) { + filesystem.foreach(fs => if (fs.node != null) fs.node.remove()) val fs = api.FileSystem.asManagedEnvironment( api.FileSystem.fromSaveDirectory(id, wipeDisksAndComputeSpace, Settings.get.bufferChanges), label, this, Settings.resourceDomain + ":hdd_access"). diff --git a/src/main/scala/li/cil/oc/common/tileentity/Robot.scala b/src/main/scala/li/cil/oc/common/tileentity/Robot.scala index 31f0db864..f5ce170df 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Robot.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Robot.scala @@ -447,6 +447,11 @@ class Robot extends traits.Computer with traits.PowerInformation with IFluidHand swingingTool = nbt.getBoolean(Settings.namespace + "swingingTool") turnAxis = nbt.getByte(Settings.namespace + "turnAxis") } + + // Normally set in superclass, but that's not called directly, only in the + // robot's proxy instance. + _isOutputEnabled = hasRedstoneCard + _isAbstractBusAvailable = hasAbstractBusCard } // Side check for Waila (and other mods that may call this client side). @@ -692,8 +697,8 @@ class Robot extends traits.Computer with traits.PowerInformation with IFluidHand player().inventory.addItemStackToInventory(stack) spawnStackInWorld(stack, Option(facing)) } + setSelectedSlot(oldSelected) } // else: save is screwed and we potentially lose items. Life is hard. - setSelectedSlot(oldSelected) } } finally { diff --git a/src/main/scala/li/cil/oc/common/tileentity/RobotProxy.scala b/src/main/scala/li/cil/oc/common/tileentity/RobotProxy.scala index 269184479..b97004f74 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/RobotProxy.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/RobotProxy.scala @@ -64,10 +64,8 @@ class RobotProxy(val robot: Robot) extends traits.Computer with traits.PowerInfo override def disconnectComponents() {} - @SideOnly(Side.CLIENT) override def isRunning = robot.isRunning - @SideOnly(Side.CLIENT) override def setRunning(value: Boolean) = robot.setRunning(value) // ----------------------------------------------------------------------- // diff --git a/src/main/scala/li/cil/oc/common/tileentity/traits/Computer.scala b/src/main/scala/li/cil/oc/common/tileentity/traits/Computer.scala index 56df3653a..65f34d1a6 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/traits/Computer.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/traits/Computer.scala @@ -50,14 +50,15 @@ trait Computer extends Environment with ComponentInventory with Rotatable with B def isRunning = _isRunning - @SideOnly(Side.CLIENT) def setRunning(value: Boolean): Unit = if (value != _isRunning) { _isRunning = value - world.markBlockForUpdate(x, y, z) - runSound.foreach(sound => - if (_isRunning) Sound.startLoop(this, sound, 0.5f, 50 + world.rand.nextInt(50)) - else Sound.stopLoop(this) - ) + if (world != null) { + world.markBlockForUpdate(x, y, z) + runSound.foreach(sound => + if (_isRunning) Sound.startLoop(this, sound, 0.5f, 50 + world.rand.nextInt(50)) + else Sound.stopLoop(this) + ) + } } @SideOnly(Side.CLIENT) @@ -137,7 +138,7 @@ trait Computer extends Environment with ComponentInventory with Rotatable with B // Kickstart initialization to avoid values getting overwritten by // readFromNBTForClient if that packet is handled after a manual // initialization / state change packet. - _isRunning = machine.isRunning + setRunning(machine.isRunning) _isOutputEnabled = hasRedstoneCard _isAbstractBusAvailable = hasAbstractBusCard } @@ -155,7 +156,7 @@ trait Computer extends Environment with ComponentInventory with Rotatable with B @SideOnly(Side.CLIENT) override def readFromNBTForClient(nbt: NBTTagCompound) { super.readFromNBTForClient(nbt) - _isRunning = nbt.getBoolean("isRunning") + setRunning(nbt.getBoolean("isRunning")) _users.clear() _users ++= nbt.getTagList("users", NBT.TAG_STRING).map((tag: NBTTagString) => tag.func_150285_a_()) if (_isRunning) runSound.foreach(sound => Sound.startLoop(this, sound, 0.5f, 1000 + world.rand.nextInt(2000))) diff --git a/src/main/scala/li/cil/oc/integration/opencomputers/DriverMemory.scala b/src/main/scala/li/cil/oc/integration/opencomputers/DriverMemory.scala index bb655ecd1..ea7bf66b1 100644 --- a/src/main/scala/li/cil/oc/integration/opencomputers/DriverMemory.scala +++ b/src/main/scala/li/cil/oc/integration/opencomputers/DriverMemory.scala @@ -10,7 +10,7 @@ import net.minecraft.item.ItemStack object DriverMemory extends Item with driver.item.Memory { override def amount(stack: ItemStack) = Items.multi.subItem(stack) match { - case Some(memory: item.Memory) => memory.tier + case Some(memory: item.Memory) => memory.tier + 1 case _ => 0.0 } diff --git a/src/main/scala/li/cil/oc/server/component/DebugCard.scala b/src/main/scala/li/cil/oc/server/component/DebugCard.scala index ee94b8f4d..c0c965246 100644 --- a/src/main/scala/li/cil/oc/server/component/DebugCard.scala +++ b/src/main/scala/li/cil/oc/server/component/DebugCard.scala @@ -20,7 +20,6 @@ import li.cil.oc.util.ExtendedArguments._ import li.cil.oc.util.ExtendedWorld._ import li.cil.oc.util.InventoryUtils import net.minecraft.block.Block -import net.minecraft.command.ICommandSender import net.minecraft.entity.player.EntityPlayerMP import net.minecraft.item.Item import net.minecraft.item.ItemStack @@ -33,12 +32,14 @@ import net.minecraft.world.World import net.minecraft.world.WorldServer import net.minecraft.world.WorldSettings.GameType import net.minecraftforge.common.DimensionManager +import net.minecraftforge.common.util.FakePlayer import net.minecraftforge.common.util.FakePlayerFactory import net.minecraftforge.common.util.ForgeDirection import net.minecraftforge.fluids.FluidRegistry import net.minecraftforge.fluids.FluidStack import net.minecraftforge.fluids.IFluidHandler +import scala.collection.convert.WrapAsScala._ import scala.collection.mutable class DebugCard(host: EnvironmentHost) extends prefab.ManagedEnvironment { @@ -56,6 +57,17 @@ class DebugCard(host: EnvironmentHost) extends prefab.ManagedEnvironment { // Player this card is bound to (if any) to use for permissions. var player: Option[String] = None + private lazy val CommandSender = { + def defaultFakePlayer = FakePlayerFactory.get(host.world.asInstanceOf[WorldServer], Settings.get.fakePlayerProfile) + new CommandSender(host, player match { + case Some(name) => Option(MinecraftServer.getServer.getConfigurationManager.func_152612_a(name)) match { + case Some(playerEntity) => playerEntity + case _ => defaultFakePlayer + } + case _ => defaultFakePlayer + }) + } + // ----------------------------------------------------------------------- // import li.cil.oc.server.component.DebugCard.checkEnabled @@ -99,10 +111,18 @@ class DebugCard(host: EnvironmentHost) extends prefab.ManagedEnvironment { @Callback(doc = """function(command:string):number -- Runs an arbitrary command using a fake player.""") def runCommand(context: Context, args: Arguments): Array[AnyRef] = { checkEnabled() - val command = args.checkString(0) - val sender = new CommandSender(host, player) - val value = MinecraftServer.getServer.getCommandManager.executeCommand(sender, command) - result(value, sender.messages.orNull) + val commands = + if (args.isTable(0)) collectionAsScalaIterable(args.checkTable(0).values()) + else Iterable (args.checkString(0)) + + CommandSender.synchronized { + CommandSender.prepare() + var value = 0 + for (command <- commands) { + value = MinecraftServer.getServer.getCommandManager.executeCommand(CommandSender, command.toString) + } + result(value, CommandSender.messages.orNull) + } } @Callback(doc = """function(x:number, y:number, z:number):boolean -- Connect the debug card to the block at the specified coordinates.""") @@ -496,31 +516,28 @@ object DebugCard { } } - class CommandSender(val host: EnvironmentHost, val playerName: Option[String]) extends ICommandSender { - val fakePlayer = { - def defaultFakePlayer = FakePlayerFactory.get(host.world.asInstanceOf[WorldServer], Settings.get.fakePlayerProfile) - playerName match { - case Some(name) => Option(MinecraftServer.getServer.getConfigurationManager.func_152612_a(name)) match { - case Some(player) => player - case _ => defaultFakePlayer - } - case _ => defaultFakePlayer - } - } - + class CommandSender(val host: EnvironmentHost, val underlying: EntityPlayerMP) extends FakePlayer(underlying.getEntityWorld.asInstanceOf[WorldServer], underlying.getGameProfile) { var messages: Option[String] = None - override def getCommandSenderName = fakePlayer.getCommandSenderName + def prepare(): Unit = { + val blockPos = BlockPosition(host) + posX = blockPos.x + posY = blockPos.y + posZ = blockPos.z + messages = None + } + + override def getCommandSenderName = underlying.getCommandSenderName override def getEntityWorld = host.world override def addChatMessage(message: IChatComponent) { - messages = Option(messages.getOrElse("") + message.getUnformattedText) + messages = Option(messages.fold("")(_ + "\n") + message.getUnformattedText) } override def canCommandSenderUseCommand(level: Int, command: String) = { - val profile = fakePlayer.getGameProfile - val server = fakePlayer.mcServer + val profile = underlying.getGameProfile + val server = underlying.mcServer val config = server.getConfigurationManager server.isSinglePlayer || (config.func_152596_g(profile) && (config.func_152603_m.func_152683_b(profile) match { case entry: UserListOpsEntry => entry.func_152644_a >= level @@ -530,7 +547,7 @@ object DebugCard { override def getPlayerCoordinates = BlockPosition(host).toChunkCoordinates - override def func_145748_c_() = fakePlayer.func_145748_c_() + override def func_145748_c_() = underlying.func_145748_c_() } class TestValue extends AbstractValue { diff --git a/src/main/scala/li/cil/oc/server/network/Network.scala b/src/main/scala/li/cil/oc/server/network/Network.scala index 17dd7fa3a..e6a1c487f 100644 --- a/src/main/scala/li/cil/oc/server/network/Network.scala +++ b/src/main/scala/li/cil/oc/server/network/Network.scala @@ -9,7 +9,8 @@ import li.cil.oc.OpenComputers import li.cil.oc.Settings import li.cil.oc.api import li.cil.oc.api.network -import li.cil.oc.api.network.{Node => ImmutableNode, _} +import li.cil.oc.api.network._ +import li.cil.oc.api.network.{Node => ImmutableNode} import li.cil.oc.common.block.Cable import li.cil.oc.common.tileentity import li.cil.oc.integration.Mods @@ -21,8 +22,8 @@ import net.minecraft.nbt._ import net.minecraft.tileentity.TileEntity import net.minecraftforge.common.util.ForgeDirection -import scala.collection.convert.WrapAsScala._ import scala.collection.JavaConverters._ +import scala.collection.convert.WrapAsScala._ import scala.collection.mutable import scala.collection.mutable.ArrayBuffer @@ -50,13 +51,21 @@ private class Network private(private val data: mutable.Map[String, Network.Vert node.data.network = wrapper }) - // Called by nodes when they may have changed address from loading. - def remap(node: MutableNode) { - data.find(_._2.data == node) match { - case Some((address, vertex)) => - data -= address - data += node.address -> vertex - case _ => // Eh? + // Called by nodes when they want to change address from loading. + def remap(remappedNode: MutableNode, newAddress: String) { + data.get(remappedNode.address) match { + case Some(node) => + val neighbors = node.edges.map(_.other(node)) + node.data.remove() + node.data.address = newAddress + while (data.contains(node.data.address)) { + node.data.address = java.util.UUID.randomUUID().toString + } + if (neighbors.isEmpty) + addNew(node.data) + else + neighbors.foreach(_.data.connect(node.data)) + case _ => throw new AssertionError("Node believes it belongs to a network it doesn't.") } } @@ -217,7 +226,7 @@ private class Network private(private val data: mutable.Map[String, Network.Vert newNode } - private def add(oldNode: Network.Vertex, addedNode: MutableNode) = { + private def add(oldNode: Network.Vertex, addedNode: MutableNode): Boolean = { // Queue onConnect calls to avoid side effects from callbacks. val connects = mutable.Buffer.empty[(ImmutableNode, Iterable[ImmutableNode])] // Check if the other node is new or if we have to merge networks. @@ -248,44 +257,52 @@ private class Network private(private val data: mutable.Map[String, Network.Vert // never happen in normal operation anyway. It *can* happen when NBT // editing stuff or using mods to clone blocks (e.g. WorldEdit). otherNetwork.data.filter(entry => data.contains(entry._1)).toArray.foreach { - case (address, node: Network.Vertex) => + case (_, node: Network.Vertex) => val neighbors = node.edges.map(_.other(node)) node.data.remove() - node.data.address = java.util.UUID.randomUUID().toString + do { + node.data.address = java.util.UUID.randomUUID().toString + } while (data.contains(node.data.address) || otherNetwork.data.contains(node.data.address)) if (neighbors.isEmpty) otherNetwork.addNew(node.data) else neighbors.foreach(_.data.connect(node.data)) } - if (addedNode.reachability == Visibility.Neighbors) - connects += ((addedNode, Iterable(oldNode.data))) - if (oldNode.data.reachability == Visibility.Neighbors) - connects += ((oldNode.data, Iterable(addedNode))) + // The address change can theoretically cause the node to be kicked from + // its old network (via onConnect callbacks), so we make sure it's still + // in the same network. If it isn't we start over. + if (addedNode.network != null && addedNode.network.asInstanceOf[Network.Wrapper].network == otherNetwork) { + if (addedNode.reachability == Visibility.Neighbors) + connects += ((addedNode, Iterable(oldNode.data))) + if (oldNode.data.reachability == Visibility.Neighbors) + connects += ((oldNode.data, Iterable(addedNode))) - val oldNodes = nodes - val newNodes = otherNetwork.nodes - val oldVisibleNodes = oldNodes.filter(_.reachability == Visibility.Network) - val newVisibleNodes = newNodes.filter(_.reachability == Visibility.Network) + val oldNodes = nodes + val newNodes = otherNetwork.nodes + val oldVisibleNodes = oldNodes.filter(_.reachability == Visibility.Network) + val newVisibleNodes = newNodes.filter(_.reachability == Visibility.Network) - newVisibleNodes.foreach(node => connects += ((node, oldNodes))) - oldVisibleNodes.foreach(node => connects += ((node, newNodes))) + newVisibleNodes.foreach(node => connects += ((node, oldNodes))) + oldVisibleNodes.foreach(node => connects += ((node, newNodes))) - data ++= otherNetwork.data - connectors ++= otherNetwork.connectors - globalBuffer += otherNetwork.globalBuffer - globalBufferSize += otherNetwork.globalBufferSize - otherNetwork.data.values.foreach(node => { - node.data match { - case connector: Connector => connector.distributor = Some(wrapper) - case _ => - } - node.data.network = wrapper - }) - otherNetwork.data.clear() - otherNetwork.connectors.clear() + data ++= otherNetwork.data + connectors ++= otherNetwork.connectors + globalBuffer += otherNetwork.globalBuffer + globalBufferSize += otherNetwork.globalBufferSize + otherNetwork.data.values.foreach(node => { + node.data match { + case connector: Connector => connector.distributor = Some(wrapper) + case _ => + } + node.data.network = wrapper + }) + otherNetwork.data.clear() + otherNetwork.connectors.clear() - Network.Edge(oldNode, node(addedNode)) + Network.Edge(oldNode, node(addedNode)) + } + else add(oldNode, addedNode) } for ((node, nodes) <- connects) nodes.foreach(_.asInstanceOf[MutableNode].onConnect(node)) diff --git a/src/main/scala/li/cil/oc/server/network/Node.scala b/src/main/scala/li/cil/oc/server/network/Node.scala index bae0e6b08..e9e98da6c 100644 --- a/src/main/scala/li/cil/oc/server/network/Node.scala +++ b/src/main/scala/li/cil/oc/server/network/Node.scala @@ -66,11 +66,10 @@ trait Node extends ImmutableNode { def load(nbt: NBTTagCompound) = { if (nbt.hasKey("address")) { - val oldAddress = address - address = nbt.getString("address") - if (address != oldAddress) network match { - case wrapper: Network.Wrapper => wrapper.network.remap(this) - case _ => + val newAddress = nbt.getString("address") + if (newAddress != address) network match { + case wrapper: Network.Wrapper => wrapper.network.remap(this, newAddress) + case _ => address = newAddress } } }