diff --git a/src/main/resources/reference.conf b/src/main/resources/reference.conf index dc7d017d5..293e43d97 100644 --- a/src/main/resources/reference.conf +++ b/src/main/resources/reference.conf @@ -61,6 +61,10 @@ opencomputers { # The volume multiplier applied to sounds from this mod like the computer # running noise. Disable sounds by setting this to zero. soundVolume: 1.0 + + # This is the scaling of the individual chars rendered on screens. This + # is set to slightly overscale per default, to avoid + fontCharScale: 1.01 } # Computer related settings, concerns server performance and security. diff --git a/src/main/scala/li/cil/oc/Settings.scala b/src/main/scala/li/cil/oc/Settings.scala index 3760982af..4bd33e7e6 100644 --- a/src/main/scala/li/cil/oc/Settings.scala +++ b/src/main/scala/li/cil/oc/Settings.scala @@ -29,6 +29,7 @@ class Settings(config: Config) { val pasteShortcut = config.getStringList("client.pasteShortcut").toSet val robotLabels = config.getBoolean("client.robotLabels") val soundVolume = config.getDouble("client.soundVolume").toFloat max 0 min 2 + val fontCharScale = config.getDouble("client.fontCharScale") max 0.5 min 2 val rTreeDebugRenderer = false // *Not* to be configurable via config file. // ----------------------------------------------------------------------- // diff --git a/src/main/scala/li/cil/oc/client/renderer/MonospaceFontRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/MonospaceFontRenderer.scala index 397e2b062..c2ca64c47 100644 --- a/src/main/scala/li/cil/oc/client/renderer/MonospaceFontRenderer.scala +++ b/src/main/scala/li/cil/oc/client/renderer/MonospaceFontRenderer.scala @@ -43,6 +43,9 @@ object MonospaceFontRenderer { // Set up the display lists. { + val s = Settings.get.fontCharScale + val dw = charWidth * s - charWidth + val dh = charHeight * s - charHeight val t = Tessellator.instance // Now create lists for all printable chars. for (index <- 1 until 0xFF) { @@ -52,10 +55,10 @@ object MonospaceFontRenderer { val v = y * vStep GL11.glNewList(charLists + index, GL11.GL_COMPILE) t.startDrawingQuads() - t.addVertexWithUV(0, charHeight, 0, u, v + vSize) - t.addVertexWithUV(charWidth, charHeight, 0, u + uSize, v + vSize) - t.addVertexWithUV(charWidth, 0, 0, u + uSize, v) - t.addVertexWithUV(0, 0, 0, u, v) + t.addVertexWithUV(-dw, charHeight * s, 0, u, v + vSize) + t.addVertexWithUV(charWidth * s, charHeight * s, 0, u + uSize, v + vSize) + t.addVertexWithUV(charWidth * s, -dh, 0, u + uSize, v) + t.addVertexWithUV(-dw, -dh, 0, u, v) t.draw() GL11.glTranslatef(charWidth, 0, 0) GL11.glEndList() diff --git a/src/main/scala/li/cil/oc/common/SaveHandler.scala b/src/main/scala/li/cil/oc/common/SaveHandler.scala index b0e36feae..5a1410ea7 100644 --- a/src/main/scala/li/cil/oc/common/SaveHandler.scala +++ b/src/main/scala/li/cil/oc/common/SaveHandler.scala @@ -19,7 +19,13 @@ object SaveHandler { def scheduleSave(dimension: Int, chunk: ChunkCoordIntPair, name: String, data: Array[Byte]) = saveData.synchronized { if (chunk == null) throw new IllegalArgumentException("chunk is null") - else saveData.getOrElseUpdate(dimension, mutable.Map.empty).getOrElseUpdate(chunk, mutable.Map.empty) += name -> data + else { + val chunks = saveData.getOrElseUpdate(dimension, mutable.Map.empty) + // Make sure we get rid of old versions (e.g. left over by other mods + // triggering a save - this is mostly used for RiM compatibility). + chunks.values.foreach(_ -= name) + chunks.getOrElseUpdate(chunk, mutable.Map.empty) += name -> data + } } def load(dimension: Int, chunk: ChunkCoordIntPair, name: String): Array[Byte] = { diff --git a/src/main/scala/li/cil/oc/common/item/Analyzer.scala b/src/main/scala/li/cil/oc/common/item/Analyzer.scala index c14ffa137..7613a0186 100644 --- a/src/main/scala/li/cil/oc/common/item/Analyzer.scala +++ b/src/main/scala/li/cil/oc/common/item/Analyzer.scala @@ -1,6 +1,5 @@ package li.cil.oc.common.item -import cpw.mods.fml.common.network.Player import java.util import li.cil.oc.Settings import li.cil.oc.api.network._ @@ -70,8 +69,8 @@ class Analyzer(val parent: Delegator) extends Delegate { player.sendChatToPlayer(ChatMessageComponent.createFromTranslationWithSubstitutions( Settings.namespace + "gui.Analyzer.Address", address)) + PacketSender.sendAnalyze(address, player) } - PacketSender.sendAnalyze(address, player) } override def registerIcons(iconRegister: IconRegister) { diff --git a/src/main/scala/li/cil/oc/common/tileentity/Adapter.scala b/src/main/scala/li/cil/oc/common/tileentity/Adapter.scala index 71dbaa8f1..ffca01838 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Adapter.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Adapter.scala @@ -27,7 +27,7 @@ class Adapter extends traits.Environment with Analyzable { override def updateEntity() { super.updateEntity() - if (blocks.nonEmpty) { + if (updatingBlocks.nonEmpty) { for (block <- updatingBlocks) { block.update() } @@ -100,6 +100,13 @@ class Adapter extends traits.Environment with Analyzable { // ----------------------------------------------------------------------- // + override def onDisconnect(node: Node) { + super.onDisconnect(node) + if (node == this.node) { + updatingBlocks.clear() + } + } + override def readFromNBT(nbt: NBTTagCompound) { super.readFromNBT(nbt) diff --git a/src/main/scala/li/cil/oc/common/tileentity/traits/IC2PowerGridAware.scala b/src/main/scala/li/cil/oc/common/tileentity/traits/IC2PowerGridAware.scala new file mode 100644 index 000000000..23e6333d4 --- /dev/null +++ b/src/main/scala/li/cil/oc/common/tileentity/traits/IC2PowerGridAware.scala @@ -0,0 +1,9 @@ +package li.cil.oc.common.tileentity.traits + +import ic2.api.energy.tile.IEnergySink +import cpw.mods.fml.common.Optional + +@Optional.Interface(iface = "ic2.api.energy.tile.IEnergySink", modid = "IC2") +trait IC2PowerGridAware extends IEnergySink { + var addedToPowerGrid = false +} diff --git a/src/main/scala/li/cil/oc/common/tileentity/traits/PowerAcceptor.scala b/src/main/scala/li/cil/oc/common/tileentity/traits/PowerAcceptor.scala index 4b50b5d63..83d160722 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/traits/PowerAcceptor.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/traits/PowerAcceptor.scala @@ -4,21 +4,19 @@ import buildcraft.api.power.{PowerHandler, IPowerReceptor} import cofh.api.energy.IEnergyHandler import cpw.mods.fml.common.{ModAPIManager, Loader, Optional} import cpw.mods.fml.relauncher.{Side, SideOnly} -import ic2.api.energy.event.{EnergyTileUnloadEvent, EnergyTileLoadEvent} -import ic2.api.energy.tile.IEnergySink import li.cil.oc.api.network.Connector +import li.cil.oc.server.TickHandler import li.cil.oc.Settings -import net.minecraftforge.common.{MinecraftForge, ForgeDirection} +import net.minecraftforge.common.ForgeDirection import universalelectricity.api.energy.{IEnergyContainer, IEnergyInterface} @Optional.InterfaceList(Array( new Optional.Interface(iface = "buildcraft.api.power.IPowerReceptor", modid = "BuildCraftAPI|power"), - new Optional.Interface(iface = "ic2.api.energy.tile.IEnergySink", modid = "IC2"), new Optional.Interface(iface = "cofh.api.energy.IEnergyHandler", modid = "ThermalExpansion"), new Optional.Interface(iface = "universalelectricity.api.energy.IEnergyInterface", modid = "UniversalElectricity"), new Optional.Interface(iface = "universalelectricity.api.energy.IEnergyContainer", modid = "UniversalElectricity") )) -trait PowerAcceptor extends TileEntity with IPowerReceptor with IEnergySink with IEnergyHandler with IEnergyInterface with IEnergyContainer { +trait PowerAcceptor extends TileEntity with IPowerReceptor with IC2PowerGridAware with IEnergyHandler with IEnergyInterface with IEnergyContainer { @SideOnly(Side.CLIENT) protected def hasConnector(side: ForgeDirection) = false @@ -99,22 +97,14 @@ trait PowerAcceptor extends TileEntity with IPowerReceptor with IEnergySink with // ----------------------------------------------------------------------- // // IndustrialCraft2 - private var addedToPowerGrid = false - @Optional.Method(modid = "IC2") def loadIC2() { - if (!addedToPowerGrid) { - MinecraftForge.EVENT_BUS.post(new EnergyTileLoadEvent(this)) - addedToPowerGrid = true - } + if (!addedToPowerGrid) TickHandler.scheduleIC2Add(this) } @Optional.Method(modid = "IC2") def unloadIC2() { - if (addedToPowerGrid) { - MinecraftForge.EVENT_BUS.post(new EnergyTileUnloadEvent(this)) - addedToPowerGrid = false - } + if (addedToPowerGrid) TickHandler.scheduleIC2Remove(this) } @Optional.Method(modid = "IC2") diff --git a/src/main/scala/li/cil/oc/server/TickHandler.scala b/src/main/scala/li/cil/oc/server/TickHandler.scala index 492c94634..4ebd52afa 100644 --- a/src/main/scala/li/cil/oc/server/TickHandler.scala +++ b/src/main/scala/li/cil/oc/server/TickHandler.scala @@ -2,35 +2,58 @@ package li.cil.oc.server import codechicken.multipart.TMultiPart import cpw.mods.fml.common.{FMLCommonHandler, Optional, TickType, ITickHandler} +import ic2.api.energy.event.{EnergyTileUnloadEvent, EnergyTileLoadEvent} import java.util import li.cil.oc.api.Network +import li.cil.oc.common.tileentity.traits.IC2PowerGridAware import net.minecraft.tileentity.TileEntity +import net.minecraftforge.common.MinecraftForge import scala.collection.mutable +import li.cil.oc.OpenComputers +import java.util.logging.Level object TickHandler extends ITickHandler { - val pendingAdds = mutable.Buffer.empty[() => Unit] + val pending = mutable.Buffer.empty[() => Unit] def schedule(tileEntity: TileEntity) = - if (FMLCommonHandler.instance.getEffectiveSide.isServer) pendingAdds.synchronized { - pendingAdds += (() => Network.joinOrCreateNetwork(tileEntity)) + if (FMLCommonHandler.instance.getEffectiveSide.isServer) pending.synchronized { + pending += (() => Network.joinOrCreateNetwork(tileEntity)) } @Optional.Method(modid = "ForgeMultipart") def schedule(part: TMultiPart) = - if (FMLCommonHandler.instance.getEffectiveSide.isServer) pendingAdds.synchronized { - pendingAdds += (() => Network.joinOrCreateNetwork(part.tile)) + if (FMLCommonHandler.instance.getEffectiveSide.isServer) pending.synchronized { + pending += (() => Network.joinOrCreateNetwork(part.tile)) } + @Optional.Method(modid = "IC2") + def scheduleIC2Add(tileEntity: TileEntity with IC2PowerGridAware) = pending.synchronized { + pending += (() => if (!tileEntity.addedToPowerGrid && !tileEntity.isInvalid) { + MinecraftForge.EVENT_BUS.post(new EnergyTileLoadEvent(tileEntity)) + tileEntity.addedToPowerGrid = true + }) + } + + @Optional.Method(modid = "IC2") + def scheduleIC2Remove(tileEntity: TileEntity with IC2PowerGridAware) = pending.synchronized { + pending += (() => if (tileEntity.addedToPowerGrid) { + MinecraftForge.EVENT_BUS.post(new EnergyTileUnloadEvent(tileEntity)) + tileEntity.addedToPowerGrid = false + }) + } + override def getLabel = "OpenComputers Network Initialization Ticker" override def ticks() = util.EnumSet.of(TickType.SERVER) override def tickStart(`type`: util.EnumSet[TickType], tickData: AnyRef*) {} - override def tickEnd(`type`: util.EnumSet[TickType], tickData: AnyRef*) = pendingAdds.synchronized { - for (callback <- pendingAdds) { - callback() + override def tickEnd(`type`: util.EnumSet[TickType], tickData: AnyRef*) = pending.synchronized { + for (callback <- pending) { + try callback() catch { + case t: Throwable => OpenComputers.log.log(Level.WARNING, "Error in scheduled tick action.", t) + } } - pendingAdds.clear() + pending.clear() } } diff --git a/src/main/scala/li/cil/oc/server/component/machine/Machine.scala b/src/main/scala/li/cil/oc/server/component/machine/Machine.scala index 56025691f..67a268821 100644 --- a/src/main/scala/li/cil/oc/server/component/machine/Machine.scala +++ b/src/main/scala/li/cil/oc/server/component/machine/Machine.scala @@ -505,10 +505,10 @@ class Machine(val owner: Owner, val rom: Option[ManagedEnvironment], constructor for ((address, name) <- _components) { if (node.network.node(address) == null) { if (name == "filesystem") { - OpenComputers.log.fine("A component of type '%s' disappeared! This usually means that it didn't save its node.".format(name)) + OpenComputers.log.fine(s"A component of type '$name' disappeared ($address)! This usually means that it didn't save its node.") OpenComputers.log.fine("If this was a file system provided by a ComputerCraft peripheral, this is normal.") } - else OpenComputers.log.warning("A component of type '%s' disappeared! This usually means that it didn't save its node.".format(name)) + else OpenComputers.log.warning(s"A component of type '$name' disappeared ($address)! This usually means that it didn't save its node.") signal("component_removed", address, name) invalid += address } diff --git a/src/main/scala/li/cil/oc/server/component/machine/NativeLuaArchitecture.scala b/src/main/scala/li/cil/oc/server/component/machine/NativeLuaArchitecture.scala index c4c3d38ea..02bcd82d6 100644 --- a/src/main/scala/li/cil/oc/server/component/machine/NativeLuaArchitecture.scala +++ b/src/main/scala/li/cil/oc/server/component/machine/NativeLuaArchitecture.scala @@ -596,10 +596,18 @@ class NativeLuaArchitecture(val machine: api.machine.Machine) extends Architectu // on. First, clear the stack, meaning the current kernel. lua.setTop(0) + // Since we have no world yet, we rely on the dimension we were saved in. + // Same goes for the chunk. This also works around issues with computers + // being moved (e.g. Redstone in Motion). val dimension = nbt.getInteger("dimension") + val chunk = + if (nbt.hasKey("chunkX") && nbt.hasKey("chunkZ")) + new ChunkCoordIntPair(nbt.getInteger("chunkX"), nbt.getInteger("chunkZ")) + else + new ChunkCoordIntPair(machine.owner.x >> 4, machine.owner.z >> 4) val kernel = if (nbt.hasKey("kernel")) nbt.getByteArray("kernel") - else SaveHandler.load(dimension, new ChunkCoordIntPair(machine.owner.x >> 4, machine.owner.z >> 4), node.address + "_kernel") + else SaveHandler.load(dimension, chunk, node.address + "_kernel") unpersist(kernel) if (!lua.isThread(1)) { // This shouldn't really happen, but there's a chance it does if @@ -609,7 +617,7 @@ class NativeLuaArchitecture(val machine: api.machine.Machine) extends Architectu if (state.contains(Machine.State.SynchronizedCall) || state.contains(Machine.State.SynchronizedReturn)) { val stack = if (nbt.hasKey("stack")) nbt.getByteArray("stack") - else SaveHandler.load(dimension, new ChunkCoordIntPair(machine.owner.x >> 4, machine.owner.z >> 4), node.address + "_stack") + else SaveHandler.load(dimension, chunk, node.address + "_stack") unpersist(stack) if (!(if (state.contains(Machine.State.SynchronizedCall)) lua.isFunction(2) else lua.isTable(2))) { // Same as with the above, should not really happen normally, but @@ -637,14 +645,20 @@ class NativeLuaArchitecture(val machine: api.machine.Machine) extends Architectu // Try persisting Lua, because that's what all of the rest depends on. // Save the kernel state (which is always at stack index one). assert(lua.isThread(1)) + + // We have to save the dimension and chunk coordinates, because they are + // not available on load / may have changed if the computer was moved. val dimension = machine.owner.world.provider.dimensionId + val chunk = new ChunkCoordIntPair(machine.owner.x >> 4, machine.owner.z >> 4) nbt.setInteger("dimension", dimension) - SaveHandler.scheduleSave(dimension, new ChunkCoordIntPair(machine.owner.x >> 4, machine.owner.z >> 4), node.address + "_kernel", persist(1)) + nbt.setInteger("chunkX", chunk.chunkXPos) + nbt.setInteger("chunkZ", chunk.chunkZPos) + SaveHandler.scheduleSave(dimension, chunk, node.address + "_kernel", persist(1)) // While in a driver call we have one object on the global stack: either // the function to call the driver with, or the result of the call. if (state.contains(Machine.State.SynchronizedCall) || state.contains(Machine.State.SynchronizedReturn)) { assert(if (state.contains(Machine.State.SynchronizedCall)) lua.isFunction(2) else lua.isTable(2)) - SaveHandler.scheduleSave(dimension, new ChunkCoordIntPair(machine.owner.x >> 4, machine.owner.z >> 4), node.address + "_stack", persist(2)) + SaveHandler.scheduleSave(dimension, chunk, node.address + "_stack", persist(2)) } nbt.setInteger("kernelMemory", math.ceil(kernelMemory / ramScale).toInt)