diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/ps.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/ps.lua index e47180519..9a31a8abd 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/ps.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/ps.lua @@ -1,6 +1,7 @@ local process = require("process") local unicode = require("unicode") local event = require("event") +local thread = require("thread") local event_mt = getmetatable(event.handlers) -- WARNING this code does not use official kernel API and is likely to change @@ -42,33 +43,38 @@ local cols = return count == 0 and "-" or tostring(count) end}, {"THREADS", function(_,p) - -- threads are handles with mt.close + -- threads are handles with mt.close == thread.waitForAll local count = 0 - for _,h in pairs(p.data.handles or {}) do + for h in pairs(p.data.handles) do local mt = getmetatable(h) - if mt and mt.__index and mt.__index.close then - count = count + #h - break -- there is only one thread handle manager + if mt and mt.__status then + count = count + 1 end end return count == 0 and "-" or tostring(count) end}, {"PARENT", function(_,p) for _,process_info in pairs(process.list) do - for _,handle in pairs(process_info.data.handles or {}) do + for handle in pairs(process_info.data.handles) do local mt = getmetatable(handle) - if mt and mt.__index and mt.__index.close then - for _,ht in ipairs(handle) do - local ht_mt = getmetatable(ht) - if ht_mt.process == p then - return thread_id(nil,process_info) - end + if mt and mt.__status then + if mt.process == p then + return thread_id(nil, process_info) end - break end end end - return thread_id(nil,p.parent) + return thread_id(nil, p.parent) + end}, + {"HANDLES", function(_, p) + local count = 0 + for stream,closure in pairs(p.data.handles) do + cprint(string.format("%s %s", stream, closure)) + if closure then + count = count + 1 + end + end + return count == 0 and "-" or tostring(count) end}, {"CMD", function(_,p) return p.command end}, } diff --git a/src/main/resources/assets/opencomputers/loot/openos/boot/01_process.lua b/src/main/resources/assets/opencomputers/loot/openos/boot/01_process.lua index a85e3af2d..9c2e9808b 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/boot/01_process.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/boot/01_process.lua @@ -1,4 +1,5 @@ local process = require("process") +local fs = require("filesystem") --Initialize coroutine library-- local _coroutine = coroutine -- real coroutine backend @@ -62,8 +63,20 @@ process.list[init_thread] = { data = { vars={}, + handles={}, io={}, --init will populate this coroutine_handler = _coroutine }, instances = setmetatable({}, {__mode="v"}) } + +-- intercept fs open +local fs_open = fs.open +fs.open = function(...) + local fs_open_result = table.pack(fs_open(...)) + if fs_open_result[1] then + process.closeOnExit(fs_open_result[1]) + end + return table.unpack(fs_open_result, 1, fs_open_result.n) +end + diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/buffer.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/buffer.lua index 2d128df54..369ca9a85 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/buffer.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/buffer.lua @@ -17,21 +17,32 @@ function buffer.new(mode, stream) bufferWrite = "", bufferSize = math.max(512, math.min(8 * 1024, computer.freeMemory() / 8)), bufferMode = "full", - readTimeout = math.huge + readTimeout = math.huge, } mode = mode or "r" for i = 1, unicode.len(mode) do result.mode[unicode.sub(mode, i, i)] = true end + -- when stream closes, result should close first + -- when result closes, stream should close after + -- when stream closes, it is removed from the proc + stream.close = setmetatable({close = stream.close,parent = result},{__call = buffer.close}) return setmetatable(result, metatable) end function buffer:close() - if self.mode.w or self.mode.a then - self:flush() + -- self is either the buffer, or the stream.close callable + local meta = getmetatable(self) + if meta == metatable.__metatable then + return self.stream:close() end - self.closed = true - return self.stream:close() + local parent = self.parent + + if parent.mode.w or parent.mode.a then + parent:flush() + end + parent.closed = true + return self.close(parent.stream) end function buffer:flush() diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_sh.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_sh.lua index 3bd48a95c..2a7334737 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_sh.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_sh.lua @@ -78,7 +78,7 @@ end -- redirects as built by buildCommentRedirects function sh.internal.openCommandRedirects(redirects) local data = process.info().data - local ios, handles = data.io, data.handles + local ios = data.io for _,rjob in ipairs(redirects) do local from_io, to_io, mode = table.unpack(rjob) @@ -93,7 +93,6 @@ function sh.internal.openCommandRedirects(redirects) io.stderr:write("could not open '" .. to_io .. "': " .. reason .. "\n") os.exit(1) end - table.insert(handles, file) ios[from_io] = file end end diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/pipe.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/pipe.lua index 80904f055..4b551b241 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/pipe.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/pipe.lua @@ -134,16 +134,16 @@ function pipe.buildPipeChain(progs) -- B needs to be a stack in case any thread in B calls read pipe.createCoroutineStack(thread) chain[i] = thread - local data = process.info(thread).data - local pio = data.io + local proc = process.info(thread) + local pio = proc.data.io local piped_stream if i < #progs then local handle = setmetatable({redirect = {rawget(pio, 1)},buffer = ""}, {__index = pipe_stream}) + process.closeOnExit(handle, proc) piped_stream = buffer.new("rw", handle) piped_stream:setvbuf("no", 1024) pio[1] = piped_stream - table.insert(data.handles, piped_stream) end if prev_piped_stream then @@ -194,7 +194,7 @@ function pipe.popen(prog, mode, env) local chain = {} -- to simplify the code - shell.execute is run within a function to pass (prog, env) - -- if cmd_proc where to come second (mode=="w") then the pipe_proc would have to pass + -- if cmd_proc were to come second (mode=="w") then the pipe_proc would have to pass -- the starting args. which is possible, just more complicated local cmd_proc = process.load(function() return shell.execute(prog, env) end, nil, nil, prog) diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/process.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/process.lua index de7d99428..42a669111 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/process.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/process.lua @@ -125,7 +125,11 @@ function process.internal.close(thread, result) checkArg(1,thread,"thread") local pdata = process.info(thread).data pdata.result = result - for _,v in pairs(pdata.handles) do + local handles = {} + for s,_ in pairs(pdata.handles) do + table.insert(handles, s) + end + for _,v in ipairs(handles) do pcall(v.close, v) end process.list[thread] = nil @@ -146,6 +150,20 @@ function process.internal.continue(co, ...) return table.unpack(result, 2, result.n) end +function process.closeOnExit(stream, proc) + local handles = (proc or process.info()).data.handles + if not handles[stream] then + handles[stream] = stream.close + stream.close = function(...) + local close = handles[stream] + handles[stream] = nil + if close then + return close(...) + end + end + end +end + function process.running(level) -- kept for backwards compat, prefer process.info local info = process.info(level) if info then diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/thread.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/thread.lua index 2e155a423..8bd36800b 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/thread.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/thread.lua @@ -60,22 +60,6 @@ function thread.waitForAll(threads, timeout) end local box_thread = {} -local box_thread_list = {close = thread.waitForAll} - -local function get_process_threads(proc, bCreate) - local handles = proc.data.handles - for _,next_handle in ipairs(handles) do - local handle_mt = getmetatable(next_handle) - if handle_mt and handle_mt.__index == box_thread_list then - return next_handle - end - end - if bCreate then - local btm = setmetatable({}, {__index = box_thread_list}) - table.insert(handles, btm) - return btm - end -end function box_thread:resume() local mt = getmetatable(self) @@ -120,31 +104,25 @@ function box_thread:detach() end function box_thread:attach(parent) - checkArg(1, parent, "thread", "number", "nil") - local mt = assert(getmetatable(self), "thread panic: no metadata") local proc = process.info(parent) + local mt = assert(getmetatable(self), "thread panic: no metadata") if not proc then return nil, "thread failed to attach, process not found" end if mt.attached == proc then return self end -- already attached + -- remove from old parent + local waiting_handler if mt.attached then - local prev_threads = assert(get_process_threads(mt.attached), "thread panic: no thread handle") - for index,t_in_list in ipairs(prev_threads) do - if t_in_list == self then - table.remove(prev_threads, index) - break - end - end + -- registration happens on the attached proc, unregister before reparenting + waiting_handler = mt.unregister() + mt.attached.data.handles[self] = nil end - -- registration happens on the attached proc, unregister before reparenting - local waiting_handler = mt.unregister() + -- fix close + self.close = self.join -- attach to parent or the current process mt.attached = proc - - -- this process may not have a box_thread list - local threads = get_process_threads(proc, true) - table.insert(threads, self) + process.closeOnExit(self, proc) -- register on the new parent if waiting_handler then -- event-waiting @@ -159,9 +137,9 @@ function thread.current() local thread_root while proc do if thread_root then - for _,bt in ipairs(get_process_threads(proc) or {}) do - if bt.pco.root == thread_root then - return bt + for handle in pairs(proc.data.handles) do + if handle.pco and handle.pco.root == thread_root then + return handle end end else @@ -174,9 +152,8 @@ end function thread.create(fp, ...) checkArg(1, fp, "function") - local t = {} local mt = {__status="suspended",__index=box_thread} - setmetatable(t, mt) + local t = setmetatable({}, mt) t.pco = pipe.createCoroutineStack(function(...) mt.__status = "running" local fp_co = t.pco.create(fp) @@ -246,7 +223,7 @@ function thread.create(fp, ...) mt.id = nil mt.reg = nil -- before just removing a handler, make sure it is still ours - if id and mt.attached and mt.attached.data.handlers[id] == reg then + if id and mt.attached.data.handlers[id] == reg then mt.attached.data.handlers[id] = nil return reg end @@ -285,18 +262,12 @@ function thread.create(fp, ...) end function mt.close() - if t:status() == "dead" then - return - end - local threads = get_process_threads(mt.attached) - for index,t_in_list in ipairs(threads) do - if t_in_list == t then - table.remove(threads, index) - break - end - end + local old_status = t:status() mt.__status = "dead" - event.push("thread_exit") + mt.attached.data.handles[t] = nil + if old_status ~= "dead" then + event.push("thread_exit") + end end t:attach() -- the current process @@ -321,8 +292,6 @@ do end assert(init_thread, "thread library panic: no init thread") handlers_mt.threaded = true - -- handles might be optimized out for memory - root_data.handles = root_data.handles or {} -- if we don't separate root handlers from thread handlers we see double dispatch -- because the thread calls dispatch on pull as well root_data.handlers = {} -- root handlers diff --git a/src/main/resources/assets/opencomputers/lua/machine.lua b/src/main/resources/assets/opencomputers/lua/machine.lua index 06f07a70f..d8c3eb540 100644 --- a/src/main/resources/assets/opencomputers/lua/machine.lua +++ b/src/main/resources/assets/opencomputers/lua/machine.lua @@ -938,7 +938,14 @@ sandbox = { } end end, - traceback = debug.traceback + traceback = debug.traceback, + -- using () to wrap the return of debug methods because in Lua doing this + -- causes only the first return value to be selected + -- e.g. (1, 2) is only (1), the 2 is not returned + -- this is critically important here because the 2nd return value from these + -- debug methods is the value itself, which opens a door to exploit the sandbox + getlocal = function(...) return (debug.getlocal(...)) end, + getupvalue = function(...) return (debug.getupvalue(...)) end, }, -- Lua 5.3. diff --git a/src/main/scala/li/cil/oc/common/container/Player.scala b/src/main/scala/li/cil/oc/common/container/Player.scala index 8f65d3696..e532e8f49 100644 --- a/src/main/scala/li/cil/oc/common/container/Player.scala +++ b/src/main/scala/li/cil/oc/common/container/Player.scala @@ -51,54 +51,64 @@ abstract class Player(val playerInventory: InventoryPlayer, val otherInventory: ItemStack.EMPTY } - protected def tryTransferStackInSlot(from: Slot, intoPlayerInventory: Boolean) { + // return true if all items have been moved or no more work to do + protected def tryMoveAllSlotToSlot(from: Slot, to: Slot): Boolean = { + if (to == null) + return false // nowhere to move it + + if (from == null || + !from.getHasStack || + from.getStack.isEmpty) + return true // all moved because nothing to move + + if (to.inventory == from.inventory) + return false // not intended for moving in the same inventory + + // for ghost slots we don't care about stack size val fromStack = from.getStack - var somethingChanged = false + val toStack = if (to.getHasStack) to.getStack else null + val toStackSize = if (!toStack.isEmpty) toStack.getCount else 0 - val step = if (intoPlayerInventory) -1 else 1 - val (begin, end) = - if (intoPlayerInventory) (inventorySlots.size - 1, 0) - else (0, inventorySlots.size - 1) + val maxStackSize = math.min(fromStack.getMaxStackSize, to.getSlotStackLimit) + val itemsMoved = math.min(maxStackSize - toStackSize, fromStack.getCount) - if (fromStack.getMaxStackSize > 1) for (i <- begin to end by step if i >= 0 && i < inventorySlots.size && from.getHasStack && from.getStack.getCount > 0) { - val intoSlot = inventorySlots.get(i) - if (intoSlot.inventory != from.inventory && intoSlot.getHasStack) { - val intoStack = intoSlot.getStack - val itemsAreEqual = fromStack.isItemEqual(intoStack) && ItemStack.areItemStackTagsEqual(fromStack, intoStack) - val maxStackSize = math.min(fromStack.getMaxStackSize, intoSlot.getSlotStackLimit) - val slotHasCapacity = intoStack.getCount < maxStackSize - if (itemsAreEqual && slotHasCapacity) { - val itemsMoved = math.min(maxStackSize - intoStack.getCount, fromStack.getCount) - if (itemsMoved > 0) { - intoStack.grow(from.decrStackSize(itemsMoved).getCount) - intoSlot.onSlotChanged() - somethingChanged = true - } - } + if (toStack != null) { + if (toStackSize < maxStackSize && + fromStack.isItemEqual(toStack) && + ItemStack.areItemStackTagsEqual(fromStack, toStack) && + itemsMoved > 0) { + toStack.grow(from.decrStackSize(itemsMoved).getCount) + } else return false + } else if (to.isItemValid(fromStack)) { + to.putStack(from.decrStackSize(itemsMoved)) + if (maxStackSize == 0) { + // Special case: we have an inventory with "phantom/ghost stacks", i.e. + // zero size stacks, usually used for configuring machinery. In that + // case we stop early if whatever we're shift clicking is already in a + // slot of the target inventory. This workaround can be problematic if + // an inventory has both real and phantom slots, but we don't have + // something like that, yet, so hey. + return true } - } + } else return false - for (i <- begin to end by step if i >= 0 && i < inventorySlots.size && from.getHasStack && from.getStack.getCount > 0) { - val intoSlot = inventorySlots.get(i) - if (intoSlot.inventory != from.inventory && !intoSlot.getHasStack && intoSlot.isItemValid(fromStack)) { - val maxStackSize = math.min(fromStack.getMaxStackSize, intoSlot.getSlotStackLimit) - val itemsMoved = math.min(maxStackSize, fromStack.getCount) - intoSlot.putStack(from.decrStackSize(itemsMoved)) - if (maxStackSize == 0) { - // Special case: we have an inventory with "phantom/ghost stacks", i.e. - // zero size stacks, usually used for configuring machinery. In that - // case we stop early if whatever we're shift clicking is already in a - // slot of the target inventory. This workaround can be problematic if - // an inventory has both real and phantom slots, but we don't have - // something like that, yet, so hey. - return - } - somethingChanged = true - } - } + to.onSlotChanged() + from.onSlotChanged() + false + } - if (somethingChanged) { - from.onSlotChanged() + protected def fillOrder(backFill: Boolean): Seq[Int] = { + (if (backFill) inventorySlots.indices.reverse else inventorySlots.indices).sortBy(i => inventorySlots(i) match { + case s: Slot if s.getHasStack => -1 + case s: ComponentSlot => s.tier + case _ => 99 + }) + } + + protected def tryTransferStackInSlot(from: Slot, intoPlayerInventory: Boolean) { + for (i <- fillOrder(intoPlayerInventory)) { + if (inventorySlots.get(i) match { case slot: Slot => tryMoveAllSlotToSlot(from, slot) case _ => false }) + return } } diff --git a/src/main/scala/li/cil/oc/common/init/Items.scala b/src/main/scala/li/cil/oc/common/init/Items.scala index cfeb7410e..9d19641e5 100644 --- a/src/main/scala/li/cil/oc/common/init/Items.scala +++ b/src/main/scala/li/cil/oc/common/init/Items.scala @@ -249,6 +249,7 @@ object Items extends ItemAPI { data.components = Array( safeGetStack(Constants.BlockName.ScreenTier1), safeGetStack(Constants.BlockName.Keyboard), + safeGetStack(Constants.BlockName.Geolyzer), safeGetStack(Constants.ItemName.InventoryUpgrade), safeGetStack(Constants.ItemName.InventoryUpgrade), safeGetStack(Constants.ItemName.InventoryUpgrade), @@ -257,6 +258,7 @@ object Items extends ItemAPI { safeGetStack(Constants.ItemName.TankUpgrade), safeGetStack(Constants.ItemName.TankControllerUpgrade), safeGetStack(Constants.ItemName.CraftingUpgrade), + safeGetStack(Constants.ItemName.HoverUpgradeTier2), safeGetStack(Constants.ItemName.GraphicsCardTier3), safeGetStack(Constants.ItemName.RedstoneCardTier2), diff --git a/src/main/scala/li/cil/oc/common/inventory/ComponentInventory.scala b/src/main/scala/li/cil/oc/common/inventory/ComponentInventory.scala index f79cd2d86..7fbdf4be9 100644 --- a/src/main/scala/li/cil/oc/common/inventory/ComponentInventory.scala +++ b/src/main/scala/li/cil/oc/common/inventory/ComponentInventory.scala @@ -130,7 +130,7 @@ trait ComponentInventory extends Inventory with network.Environment { override def getInventoryStackLimit = 1 - override protected def onItemAdded(slot: Int, stack: ItemStack) = if (isComponentSlot(slot, stack)) { + override protected def onItemAdded(slot: Int, stack: ItemStack) = if (slot >= 0 && slot < components.length && isComponentSlot(slot, stack)) { Option(Driver.driverFor(stack)).foreach(driver => Option(driver.createEnvironment(stack, host)) match { case Some(component) => this.synchronized { @@ -154,7 +154,7 @@ trait ComponentInventory extends Inventory with network.Environment { }) } - override protected def onItemRemoved(slot: Int, stack: ItemStack) { + override protected def onItemRemoved(slot: Int, stack: ItemStack): Unit = if (slot >= 0 && slot < components.length) { // Uninstall component previously in that slot. components(slot) match { case Some(component) => this.synchronized { diff --git a/src/main/scala/li/cil/oc/common/tileentity/NetSplitter.scala b/src/main/scala/li/cil/oc/common/tileentity/NetSplitter.scala index 90a720913..48551bfaf 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/NetSplitter.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/NetSplitter.scala @@ -4,6 +4,7 @@ import li.cil.oc.Settings import li.cil.oc.api import li.cil.oc.api.network.Visibility import li.cil.oc.common.EventHandler +import li.cil.oc.common.tileentity.traits.RedstoneChangedEventArgs import li.cil.oc.server.{PacketSender => ServerPacketSender} import net.minecraft.init.SoundEvents import net.minecraft.nbt.NBTTagCompound @@ -52,10 +53,10 @@ class NetSplitter extends traits.Environment with traits.OpenSides with traits.R // ----------------------------------------------------------------------- // - override protected def onRedstoneInputChanged(side: EnumFacing, oldMaxValue: Int, newMaxValue: Int): Unit = { - super.onRedstoneInputChanged(side, oldMaxValue, newMaxValue) + override protected def onRedstoneInputChanged(args: RedstoneChangedEventArgs): Unit = { + super.onRedstoneInputChanged(args) val oldIsInverted = isInverted - isInverted = newMaxValue > 0 + isInverted = args.newValue > 0 if (isInverted != oldIsInverted) { if (isServer) { node.remove() diff --git a/src/main/scala/li/cil/oc/common/tileentity/Print.scala b/src/main/scala/li/cil/oc/common/tileentity/Print.scala index 5e97ed300..391fd379a 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Print.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Print.scala @@ -7,6 +7,7 @@ import li.cil.oc.Constants import li.cil.oc.Settings import li.cil.oc.api import li.cil.oc.common.item.data.PrintData +import li.cil.oc.common.tileentity.traits.RedstoneChangedEventArgs import li.cil.oc.util.ExtendedAABB import li.cil.oc.util.ExtendedAABB._ import li.cil.oc.util.ExtendedNBT._ @@ -145,9 +146,8 @@ class Print(val canToggle: Option[() => Boolean], val scheduleUpdate: Option[Int } } - override protected def onRedstoneInputChanged(side: EnumFacing, oldMaxValue: Int, newMaxValue: Int): Unit = { - super.onRedstoneInputChanged(side, oldMaxValue, newMaxValue) - val newState = newMaxValue > 0 + override protected def onRedstoneInputChanged(args: RedstoneChangedEventArgs): Unit = { + val newState = args.newValue > 0 if (!data.emitRedstone && data.hasActiveState && state != newState) { toggleState() } diff --git a/src/main/scala/li/cil/oc/common/tileentity/Rack.scala b/src/main/scala/li/cil/oc/common/tileentity/Rack.scala index 99510eefc..d923d851f 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Rack.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Rack.scala @@ -16,6 +16,7 @@ import li.cil.oc.api.network.Packet import li.cil.oc.api.network.Visibility import li.cil.oc.api.util.StateAware import li.cil.oc.common.Slot +import li.cil.oc.common.tileentity.traits.RedstoneChangedEventArgs import li.cil.oc.integration.opencomputers.DriverRedstoneCard import li.cil.oc.server.{PacketSender => ServerPacketSender} import li.cil.oc.util.ExtendedInventory._ @@ -290,11 +291,11 @@ class Rack extends traits.PowerAcceptor with traits.Hub with traits.PowerBalance // ----------------------------------------------------------------------- // // RedstoneAware - override protected def onRedstoneInputChanged(side: EnumFacing, oldMaxValue: Int, newMaxValue: Int) { - super.onRedstoneInputChanged(side, oldMaxValue, newMaxValue) + override protected def onRedstoneInputChanged(args: RedstoneChangedEventArgs) { + super.onRedstoneInputChanged(args) components.collect { case Some(mountable: RackMountable) if mountable.node != null => - mountable.node.sendToNeighbors("redstone.changed", toLocal(side), int2Integer(oldMaxValue), int2Integer(newMaxValue)) + mountable.node.sendToNeighbors("redstone.changed", args) } } diff --git a/src/main/scala/li/cil/oc/common/tileentity/Redstone.scala b/src/main/scala/li/cil/oc/common/tileentity/Redstone.scala index f44dc9063..19acb84f6 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Redstone.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Redstone.scala @@ -5,6 +5,7 @@ import li.cil.oc.api import li.cil.oc.api.network.Component import li.cil.oc.api.network.Node import li.cil.oc.api.network.Visibility +import li.cil.oc.common.tileentity.traits.RedstoneChangedEventArgs import li.cil.oc.integration.util.BundledRedstone import li.cil.oc.server.component import li.cil.oc.server.component.RedstoneVanilla @@ -43,11 +44,11 @@ class Redstone extends traits.Environment with traits.BundledRedstoneAware with // ----------------------------------------------------------------------- // - override protected def onRedstoneInputChanged(side: EnumFacing, oldMaxValue: Int, newMaxValue: Int) { - super.onRedstoneInputChanged(side, oldMaxValue, newMaxValue) + override protected def onRedstoneInputChanged(args: RedstoneChangedEventArgs) { + super.onRedstoneInputChanged(args) if (node != null && node.network != null) { node.connect(dummyNode) - dummyNode.sendToNeighbors("redstone.changed", side, Int.box(oldMaxValue), Int.box(newMaxValue)) + dummyNode.sendToNeighbors("redstone.changed", args) } } } diff --git a/src/main/scala/li/cil/oc/common/tileentity/Screen.scala b/src/main/scala/li/cil/oc/common/tileentity/Screen.scala index 3d8983055..28233912a 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Screen.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Screen.scala @@ -6,6 +6,7 @@ import li.cil.oc.api.network._ import li.cil.oc.client.gui import li.cil.oc.common.component.TextBuffer import li.cil.oc.util.BlockPosition +import li.cil.oc.common.tileentity.traits.RedstoneChangedEventArgs import li.cil.oc.util.Color import li.cil.oc.util.ExtendedWorld._ import net.minecraft.client.Minecraft @@ -344,8 +345,8 @@ class Screen(var tier: Int) extends traits.TextBuffer with SidedEnvironment with override def onAnalyze(player: EntityPlayer, side: EnumFacing, hitX: Float, hitY: Float, hitZ: Float) = Array(origin.node) - override protected def onRedstoneInputChanged(side: EnumFacing, oldMaxValue: Int, newMaxValue: Int) { - super.onRedstoneInputChanged(side, oldMaxValue, newMaxValue) + override protected def onRedstoneInputChanged(args: RedstoneChangedEventArgs) { + super.onRedstoneInputChanged(args) val hasRedstoneInput = screens.map(_.maxInput).max > 0 if (hasRedstoneInput != hadRedstoneInput) { hadRedstoneInput = hasRedstoneInput diff --git a/src/main/scala/li/cil/oc/common/tileentity/traits/BundledRedstoneAware.scala b/src/main/scala/li/cil/oc/common/tileentity/traits/BundledRedstoneAware.scala index 27eadcd82..71f2b02ce 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/traits/BundledRedstoneAware.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/traits/BundledRedstoneAware.scala @@ -32,39 +32,31 @@ trait BundledRedstoneAware extends RedstoneAware { super.isOutputEnabled_=(value) } + def bundledInput(side: EnumFacing, color: Int): Int = + math.max(_bundledInput(side.ordinal())(color), _rednetInput(side.ordinal())(color)) + def bundledInput(side: EnumFacing): Array[Int] = (_bundledInput(side.ordinal()), _rednetInput(side.ordinal())).zipped.map(math.max) def bundledInput(side: EnumFacing, newBundledInput: Array[Int]): Unit = { - val ownBundledInput = _bundledInput(side.ordinal()) - val oldMaxValue = ownBundledInput.max - var changed = false - if (newBundledInput != null) for (color <- 0 until 16) { - changed = changed || (ownBundledInput(color) >= 0 && ownBundledInput(color) != newBundledInput(color)) - ownBundledInput(color) = newBundledInput(color) - } - else for (color <- 0 until 16) { - changed = changed || ownBundledInput(color) > 0 - ownBundledInput(color) = 0 - } - if (changed) { - onRedstoneInputChanged(side, oldMaxValue, ownBundledInput.max) + for (color <- 0 until 16) { + updateInput(_bundledInput, side, color, if (newBundledInput == null) 0 else newBundledInput(color)) } } - def bundledInput(side: EnumFacing, color: Int): Int = - math.max(_bundledInput(side.ordinal())(color), _rednetInput(side.ordinal())(color)) + def rednetInput(side: EnumFacing, color: Int, value: Int): Unit = updateInput(_rednetInput, side, color, value) - def rednetInput(side: EnumFacing, color: Int, value: Int): Unit = { - val oldValue = _rednetInput(side.ordinal())(color) - if (oldValue != value) { + def updateInput(inputs: Array[Array[Int]], side: EnumFacing, color: Int, newValue: Int): Unit = { + val oldValue = inputs(side.ordinal())(color) + if (oldValue != newValue) { if (oldValue != -1) { - onRedstoneInputChanged(side, oldValue, value) + onRedstoneInputChanged(RedstoneChangedEventArgs(side, oldValue, newValue, color)) } - _rednetInput(side.ordinal())(color) = value + inputs(side.ordinal())(color) = newValue } } + def bundledOutput(side: EnumFacing): Array[Int] = _bundledOutput(toLocal(side).ordinal()) def bundledOutput(side: EnumFacing, color: Int): Int = bundledOutput(side)(color) 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 38e5cbf39..61129870f 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 @@ -203,9 +203,9 @@ trait Computer extends Environment with ComponentInventory with Rotatable with B checkRedstoneInputChanged() } - override protected def onRedstoneInputChanged(side: EnumFacing, oldMaxValue: Int, newMaxValue: Int) { - super.onRedstoneInputChanged(side, oldMaxValue, newMaxValue) - machine.node.sendToNeighbors("redstone.changed", toLocal(side), Int.box(oldMaxValue), Int.box(newMaxValue)) + override protected def onRedstoneInputChanged(args: RedstoneChangedEventArgs) { + super.onRedstoneInputChanged(args) + machine.node.sendToNeighbors("redstone.changed", args) } // ----------------------------------------------------------------------- // diff --git a/src/main/scala/li/cil/oc/common/tileentity/traits/Hub.scala b/src/main/scala/li/cil/oc/common/tileentity/traits/Hub.scala index 3dc84b8a1..24ef31f41 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/traits/Hub.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/traits/Hub.scala @@ -17,7 +17,11 @@ import scala.collection.mutable trait Hub extends traits.Environment with SidedEnvironment with Tickable { override def node: Node = null - override protected def isConnected = plugs.exists(plug => plug.node.address != null && plug.node.network != null) + override protected def isConnected = plugs.exists(plug => + plug != null && + plug.node != null && + plug.node.address != null && + plug.node.network != null) protected val plugs = EnumFacing.values.map(side => createPlug(side)) @@ -127,7 +131,8 @@ trait Hub extends traits.Environment with SidedEnvironment with Tickable { if (isServer) { nbt.setNewTagList(PlugsTag, plugs.map(plug => { val plugNbt = new NBTTagCompound() - plug.node.save(plugNbt) + if (plug.node != null) + plug.node.save(plugNbt) plugNbt })) nbt.setNewTagList(QueueTag, queue.map { diff --git a/src/main/scala/li/cil/oc/common/tileentity/traits/RedstoneAware.scala b/src/main/scala/li/cil/oc/common/tileentity/traits/RedstoneAware.scala index cd886160f..d2b674b22 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/traits/RedstoneAware.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/traits/RedstoneAware.scala @@ -9,6 +9,8 @@ import net.minecraft.util.EnumFacing import net.minecraftforge.fml.relauncher.Side import net.minecraftforge.fml.relauncher.SideOnly +case class RedstoneChangedEventArgs (side: EnumFacing, oldValue: Int, newValue: Int, color: Int = -1) + trait RedstoneAware extends RotationAware { protected[tileentity] val _input: Array[Int] = Array.fill(6)(-1) @@ -39,7 +41,7 @@ trait RedstoneAware extends RotationAware { val oldInput = _input(side.ordinal()) _input(side.ordinal()) = newInput if (oldInput >= 0 && newInput != oldInput) { - onRedstoneInputChanged(side, oldInput, newInput) + onRedstoneInputChanged(RedstoneChangedEventArgs(side, oldInput, newInput)) } } @@ -117,7 +119,7 @@ trait RedstoneAware extends RotationAware { // ----------------------------------------------------------------------- // - protected def onRedstoneInputChanged(side: EnumFacing, oldMaxValue: Int, newMaxValue: Int) {} + protected def onRedstoneInputChanged(args: RedstoneChangedEventArgs) {} protected def onRedstoneOutputEnabledChanged() { if (getWorld != null) { diff --git a/src/main/scala/li/cil/oc/server/component/Agent.scala b/src/main/scala/li/cil/oc/server/component/Agent.scala index 3535e306d..4d14b2d94 100644 --- a/src/main/scala/li/cil/oc/server/component/Agent.scala +++ b/src/main/scala/li/cil/oc/server/component/Agent.scala @@ -155,6 +155,17 @@ trait Agent extends traits.WorldControl with traits.InventoryControl with traits reason = reason.orElse(Option(what)) } + // all side attempts failed - but there could be a partial block that is hard to "see" + val (hasBlock, _) = blockContent(facing) + if (hasBlock) { + val blockPos = position.offset(facing) + val player = rotatedPlayer(facing, facing) + player.setSneaking(sneaky) + val (ok, why) = click(player, blockPos.toBlockPos, facing) + player.setSneaking(false) + return result(ok, why) + } + result(false, reason.orNull) } diff --git a/src/main/scala/li/cil/oc/server/component/Geolyzer.scala b/src/main/scala/li/cil/oc/server/component/Geolyzer.scala index 3d004cf6a..7ab5b08ba 100644 --- a/src/main/scala/li/cil/oc/server/component/Geolyzer.scala +++ b/src/main/scala/li/cil/oc/server/component/Geolyzer.scala @@ -19,6 +19,9 @@ import li.cil.oc.api.network.Message import li.cil.oc.api.network.Visibility import li.cil.oc.api.prefab import li.cil.oc.api.prefab.AbstractManagedEnvironment +import li.cil.oc.common.tileentity.{Robot => EntityRobot, Microcontroller} +import li.cil.oc.common.entity.{Drone => EntityDrone} +import li.cil.oc.common.item.TabletWrapper import li.cil.oc.util.BlockPosition import li.cil.oc.util.DatabaseAccess import li.cil.oc.util.ExtendedArguments._ @@ -34,7 +37,7 @@ import scala.collection.convert.WrapAsJava._ import scala.collection.convert.WrapAsScala._ import scala.language.existentials -class Geolyzer(val host: EnvironmentHost) extends AbstractManagedEnvironment with DeviceInfo { +class Geolyzer(val host: EnvironmentHost) extends AbstractManagedEnvironment with traits.WorldControl with DeviceInfo { override val node = api.Network.newNode(this, Visibility.Network). withComponent("geolyzer"). withConnector(). @@ -52,6 +55,45 @@ class Geolyzer(val host: EnvironmentHost) extends AbstractManagedEnvironment wit // ----------------------------------------------------------------------- // + override protected def checkSideForAction(args: Arguments, n: Int): EnumFacing = { + val side = args.checkSideAny(n) + val is_uc = host.isInstanceOf[Microcontroller] + host match { + case robot: EntityRobot => robot.proxy.toGlobal(side) + case drone: EntityDrone => drone.toGlobal(side) + case uc: Microcontroller => uc.toLocal(side) // not really sure what it is reversed for microcontrollers + case tablet: TabletWrapper => tablet.toGlobal(side) + case _ => side + } + } + + override def position: BlockPosition = host match { + case robot: EntityRobot => robot.proxy.position + case drone: EntityDrone => BlockPosition(drone.getPosition, drone.getEntityWorld) + case uc: Microcontroller => uc.position + case tablet: TabletWrapper => BlockPosition(tablet.xPosition, tablet.yPosition, tablet.zPosition, tablet.world) + case _ => BlockPosition(host) + } + + private def canSeeSky: Boolean = { + val blockPos = position.offset(EnumFacing.UP) + !host.world.provider.isNether && host.world.canBlockSeeSky(blockPos.toBlockPos) + } + + @Callback(doc = """function():boolean -- Returns whether there is a clear line of sight to the sky directly above.""") + def canSeeSky(computer: Context, args: Arguments): Array[AnyRef] = { + result(canSeeSky) + } + + @Callback(doc = """function():boolean -- Return whether the sun is currently visible directly above.""") + def isSunVisible(computer: Context, args: Arguments): Array[AnyRef] = { + val blockPos = BlockPosition(host).offset(EnumFacing.UP) + result( + host.world.isDaytime && + canSeeSky && + !host.world.getBiome(blockPos.toBlockPos).canRain || (!host.world.isRaining && !host.world.isThundering)) + } + @Callback(doc = """function(x:number, z:number[, y:number, w:number, d:number, h:number][, ignoreReplaceable:boolean|options:table]):table -- Analyzes the density of the column at the specified relative coordinates.""") def scan(computer: Context, args: Arguments): Array[AnyRef] = { val (minX, minY, minZ, maxX, maxY, maxZ, optIndex) = getScanArgs(args) diff --git a/src/main/scala/li/cil/oc/server/component/RedstoneSignaller.scala b/src/main/scala/li/cil/oc/server/component/RedstoneSignaller.scala index 26cfc2e82..117dab582 100644 --- a/src/main/scala/li/cil/oc/server/component/RedstoneSignaller.scala +++ b/src/main/scala/li/cil/oc/server/component/RedstoneSignaller.scala @@ -7,8 +7,11 @@ import li.cil.oc.api.machine.Context import li.cil.oc.api.network.Visibility import li.cil.oc.api.prefab import li.cil.oc.api.prefab.AbstractManagedEnvironment +import li.cil.oc.common.tileentity.traits.RedstoneChangedEventArgs import net.minecraft.nbt.NBTTagCompound +import scala.collection.mutable.ArrayBuffer + trait RedstoneSignaller extends AbstractManagedEnvironment { override val node = Network.newNode(this, Visibility.Network). withComponent("redstone", Visibility.Neighbors). @@ -32,9 +35,13 @@ trait RedstoneSignaller extends AbstractManagedEnvironment { // ----------------------------------------------------------------------- // - def onRedstoneChanged(side: AnyRef, oldMaxValue: Int, newMaxValue: Int): Unit = { - node.sendToReachable("computer.signal", "redstone_changed", side, Int.box(oldMaxValue), Int.box(newMaxValue)) - if (oldMaxValue < wakeThreshold && newMaxValue >= wakeThreshold) { + def onRedstoneChanged(args: RedstoneChangedEventArgs): Unit = { + val side: AnyRef = if (args.side == null) "wireless" else Int.box(args.side.ordinal) + val flatArgs = ArrayBuffer[Object]("redstone_changed", side, Int.box(args.oldValue), Int.box(args.newValue)) + if (args.color >= 0) + flatArgs += Int.box(args.color) + node.sendToReachable("computer.signal", flatArgs: _*) + if (args.oldValue < wakeThreshold && args.newValue >= wakeThreshold) { if (wakeNeighborsOnly) node.sendToNeighbors("computer.start") else diff --git a/src/main/scala/li/cil/oc/server/component/RedstoneVanilla.scala b/src/main/scala/li/cil/oc/server/component/RedstoneVanilla.scala index f4c08e169..0c3be8047 100644 --- a/src/main/scala/li/cil/oc/server/component/RedstoneVanilla.scala +++ b/src/main/scala/li/cil/oc/server/component/RedstoneVanilla.scala @@ -12,7 +12,7 @@ import li.cil.oc.api.machine.Arguments import li.cil.oc.api.machine.Callback import li.cil.oc.api.machine.Context import li.cil.oc.api.network._ -import li.cil.oc.common.tileentity.traits.RedstoneAware +import li.cil.oc.common.tileentity.traits.{RedstoneAware, RedstoneChangedEventArgs} import li.cil.oc.util.BlockPosition import li.cil.oc.util.ExtendedBlock._ import li.cil.oc.util.ExtendedWorld._ @@ -79,8 +79,8 @@ trait RedstoneVanilla extends RedstoneSignaller with DeviceInfo { override def onMessage(message: Message): Unit = { super.onMessage(message) if (message.name == "redstone.changed") message.data match { - case Array(side: EnumFacing, oldMaxValue: Number, newMaxValue: Number) => - onRedstoneChanged(Int.box(side.ordinal()), oldMaxValue.intValue(), newMaxValue.intValue()) + case Array(args: RedstoneChangedEventArgs) => + onRedstoneChanged(args) case _ => } } diff --git a/src/main/scala/li/cil/oc/server/component/RedstoneWireless.scala b/src/main/scala/li/cil/oc/server/component/RedstoneWireless.scala new file mode 100644 index 000000000..71d1f24cb --- /dev/null +++ b/src/main/scala/li/cil/oc/server/component/RedstoneWireless.scala @@ -0,0 +1,163 @@ +package li.cil.oc.server.component + +import codechicken.lib.vec.Vector3 +import codechicken.wirelessredstone.api.WirelessReceivingDevice +import codechicken.wirelessredstone.api.WirelessTransmittingDevice +import li.cil.oc.Constants +import li.cil.oc.api.driver.DeviceInfo.DeviceAttribute +import li.cil.oc.api.driver.DeviceInfo.DeviceClass +import li.cil.oc.Settings +import li.cil.oc.api.driver.DeviceInfo +import li.cil.oc.api.network.EnvironmentHost +import li.cil.oc.api.machine.Arguments +import li.cil.oc.api.machine.Callback +import li.cil.oc.api.machine.Context +import li.cil.oc.api.network._ +import li.cil.oc.common.EventHandler +import li.cil.oc.common.tileentity.traits.RedstoneChangedEventArgs +import li.cil.oc.integration.Mods +import li.cil.oc.integration.util +import net.minecraft.nbt.NBTTagCompound +import net.minecraftforge.fml.common.Optional + +import scala.collection.convert.WrapAsJava._ + +@Optional.InterfaceList(Array( + new Optional.Interface(iface = "codechicken.wirelessredstone.api.WirelessReceivingDevice", modid = Mods.IDs.WirelessRedstoneCBE), + new Optional.Interface(iface = "codechicken.wirelessredstone.api.WirelessTransmittingDevice", modid = Mods.IDs.WirelessRedstoneCBE) +)) +trait RedstoneWireless extends RedstoneSignaller with WirelessReceivingDevice with WirelessTransmittingDevice with DeviceInfo { + def redstone: EnvironmentHost + + var wirelessFrequency = 0 + + var wirelessInput = false + + var wirelessOutput = false + + // ----------------------------------------------------------------------- // + + private final lazy val deviceInfo = Map( + DeviceAttribute.Class -> DeviceClass.Communication, + DeviceAttribute.Description -> "Wireless redstone controller", + DeviceAttribute.Vendor -> Constants.DeviceInfo.DefaultVendor, + DeviceAttribute.Product -> "Rw400-M", + DeviceAttribute.Capacity -> "1", + DeviceAttribute.Width -> "1" + ) + + override def getDeviceInfo: java.util.Map[String, String] = deviceInfo + + // ----------------------------------------------------------------------- // + + @Callback(doc = """function():number -- Get the wireless redstone input.""") + def getWirelessInput(context: Context, args: Arguments): Array[AnyRef] = { + wirelessInput = util.WirelessRedstone.getInput(this) + result(wirelessInput) + } + + @Callback(direct = true, doc = """function():boolean -- Get the wireless redstone output.""") + def getWirelessOutput(context: Context, args: Arguments): Array[AnyRef] = result(wirelessOutput) + + @Callback(doc = """function(value:boolean):boolean -- Set the wireless redstone output.""") + def setWirelessOutput(context: Context, args: Arguments): Array[AnyRef] = { + val oldValue = wirelessOutput + val newValue = args.checkBoolean(0) + + if (oldValue != newValue) { + wirelessOutput = newValue + + util.WirelessRedstone.updateOutput(this) + + if (Settings.get.redstoneDelay > 0) + context.pause(Settings.get.redstoneDelay) + } + + result(oldValue) + } + + @Callback(direct = true, doc = """function():number -- Get the currently set wireless redstone frequency.""") + def getWirelessFrequency(context: Context, args: Arguments): Array[AnyRef] = result(wirelessFrequency) + + @Callback(doc = """function(frequency:number):number -- Set the wireless redstone frequency to use.""") + def setWirelessFrequency(context: Context, args: Arguments): Array[AnyRef] = { + val oldValue = wirelessFrequency + val newValue = args.checkInteger(0) + + if (oldValue != newValue) { + util.WirelessRedstone.removeReceiver(this) + util.WirelessRedstone.removeTransmitter(this) + + wirelessFrequency = newValue + wirelessInput = false + wirelessOutput = false + + util.WirelessRedstone.addReceiver(this) + + context.pause(0.5) + } + + result(oldValue) + } + + // ----------------------------------------------------------------------- // + + @Optional.Method(modid = Mods.IDs.WirelessRedstoneCBE) + override def updateDevice(frequency: Int, on: Boolean) { + if (frequency == wirelessFrequency && on != wirelessInput) { + wirelessInput = on + onRedstoneChanged(RedstoneChangedEventArgs(null, if (on) 0 else 1, if (on) 1 else 0)) + } + } + + @Optional.Method(modid = Mods.IDs.WirelessRedstoneCBE) + override def getTransmitPos = new Vector3(redstone.xPosition, redstone.yPosition, redstone.zPosition) + + @Optional.Method(modid = Mods.IDs.WirelessRedstoneCBE) + override def getDimension = redstone.world.provider.getDimension + + @Optional.Method(modid = Mods.IDs.WirelessRedstoneCBE) + override def getFreq = wirelessFrequency + + @Optional.Method(modid = Mods.IDs.WirelessRedstoneCBE) + override def getAttachedEntity = null + + // ----------------------------------------------------------------------- // + + override def onConnect(node: Node) { + super.onConnect(node) + if (node == this.node) { + EventHandler.scheduleWirelessRedstone(this) + } + } + + override def onDisconnect(node: Node) { + super.onDisconnect(node) + if (node == this.node) { + util.WirelessRedstone.removeReceiver(this) + util.WirelessRedstone.removeTransmitter(this) + wirelessOutput = false + wirelessFrequency = 0 + } + } + + // ----------------------------------------------------------------------- // + + private final val WirelessFrequencyTag = "wirelessFrequency" + private final val WirelessInputTag = "wirelessInput" + private final val WirelessOutputTag = "wirelessOutput" + + override def load(nbt: NBTTagCompound) { + super.load(nbt) + wirelessFrequency = nbt.getInteger(WirelessFrequencyTag) + wirelessInput = nbt.getBoolean(WirelessInputTag) + wirelessOutput = nbt.getBoolean(WirelessOutputTag) + } + + override def save(nbt: NBTTagCompound) { + super.save(nbt) + nbt.setInteger(WirelessFrequencyTag, wirelessFrequency) + nbt.setBoolean(WirelessInputTag, wirelessInput) + nbt.setBoolean(WirelessOutputTag, wirelessOutput) + } +}