From a28d16f5d9b1012946edd491cad30d0b5049889e Mon Sep 17 00:00:00 2001 From: payonel Date: Sun, 11 Mar 2018 22:18:49 -0700 Subject: [PATCH 01/10] close handles on process exit --- .../opencomputers/loot/openos/bin/ps.lua | 34 +++++---- .../loot/openos/boot/01_process.lua | 13 ++++ .../opencomputers/loot/openos/lib/buffer.lua | 21 ++++-- .../loot/openos/lib/core/full_sh.lua | 3 +- .../opencomputers/loot/openos/lib/pipe.lua | 8 +-- .../opencomputers/loot/openos/lib/process.lua | 20 +++++- .../opencomputers/loot/openos/lib/thread.lua | 69 +++++-------------- 7 files changed, 92 insertions(+), 76 deletions(-) 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 From 54bf2c21e5da873b27e34713b4ac93ffc265837e Mon Sep 17 00:00:00 2001 From: payonel Date: Sun, 11 Mar 2018 22:36:49 -0700 Subject: [PATCH 02/10] check slot index is in bounds closes #1991 in some cases, the other cases are undefined/no repro --- .../scala/li/cil/oc/common/inventory/ComponentInventory.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 8caf654c8..b4c19f0c0 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 { From f6f067ef29d6a55e1709d19caf15fa4f044204c1 Mon Sep 17 00:00:00 2001 From: payonel Date: Sun, 11 Mar 2018 22:40:55 -0700 Subject: [PATCH 03/10] protect from missing nodes in hub plugs closes #2445 --- .../scala/li/cil/oc/common/tileentity/traits/Hub.scala | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 4770c6c70..b2b1377f4 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 { 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 = ForgeDirection.VALID_DIRECTIONS.map(side => createPlug(side)) @@ -120,7 +124,8 @@ trait Hub extends traits.Environment with SidedEnvironment { if (isServer) { nbt.setNewTagList(Settings.namespace + "plugs", plugs.map(plug => { val plugNbt = new NBTTagCompound() - plug.node.save(plugNbt) + if (plug.node != null) + plug.node.save(plugNbt) plugNbt })) nbt.setNewTagList(Settings.namespace + "queue", queue.map { From d020e0d9413242bb8ce5b7f565195a64f96aaced Mon Sep 17 00:00:00 2001 From: payonel Date: Wed, 14 Mar 2018 17:29:36 -0700 Subject: [PATCH 04/10] add new redstone_changed color parameter for bundled input bundled inputs now include the color of the input that changed also, the redstone_change signal for bundled input no longer sends the MAX old and MAX new values, but instead, only the old and new values of the specific color that changed closes #1385 --- .../oc/common/tileentity/NetSplitter.scala | 7 ++-- .../li/cil/oc/common/tileentity/Print.scala | 7 ++-- .../li/cil/oc/common/tileentity/Rack.scala | 7 ++-- .../cil/oc/common/tileentity/Redstone.scala | 7 ++-- .../li/cil/oc/common/tileentity/Screen.scala | 5 +-- .../traits/BundledRedstoneAware.scala | 35 +++++++------------ .../common/tileentity/traits/Computer.scala | 6 ++-- .../tileentity/traits/RedstoneAware.scala | 6 ++-- .../server/component/RedstoneSignaller.scala | 14 ++++++-- .../oc/server/component/RedstoneVanilla.scala | 6 ++-- .../server/component/RedstoneWireless.scala | 4 ++- 11 files changed, 56 insertions(+), 48 deletions(-) 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 e212409cf..56de3dcad 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 cpw.mods.fml.relauncher.{Side, SideOnly} import li.cil.oc.{Settings, 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.nbt.NBTTagCompound import net.minecraftforge.common.util.ForgeDirection @@ -51,10 +52,10 @@ class NetSplitter extends traits.Environment with traits.OpenSides with traits.R // ----------------------------------------------------------------------- // - override protected def onRedstoneInputChanged(side: ForgeDirection, 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 58c94f823..79f1efb81 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Print.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Print.scala @@ -3,6 +3,7 @@ package li.cil.oc.common.tileentity import cpw.mods.fml.relauncher.Side import cpw.mods.fml.relauncher.SideOnly 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._ @@ -42,10 +43,10 @@ class Print extends traits.TileEntity with traits.RedstoneAware with traits.Rota override def canUpdate = false - override protected def onRedstoneInputChanged(side: ForgeDirection, oldMaxValue: Int, newMaxValue: Int): Unit = { - super.onRedstoneInputChanged(side, oldMaxValue, newMaxValue) + override protected def onRedstoneInputChanged(args: RedstoneChangedEventArgs): Unit = { + super.onRedstoneInputChanged(args) if (!data.emitRedstone && data.hasActiveState) { - state = newMaxValue > 0 + state = args.newValue > 0 world.playSoundEffect(x + 0.5, y + 0.5, z + 0.5, "random.click", 0.3F, if (state) 0.6F else 0.5F) world.markBlockForUpdate(x, y, z) if (state && data.isButtonMode) { 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 bf7ba7f26..6dfee65f9 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Rack.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Rack.scala @@ -20,6 +20,7 @@ import li.cil.oc.api.network.Node import li.cil.oc.api.network.Packet import li.cil.oc.api.network.Visibility import li.cil.oc.common.Slot +import li.cil.oc.common.tileentity.traits.RedstoneChangedEventArgs import li.cil.oc.integration.Mods import li.cil.oc.integration.opencomputers.DriverRedstoneCard import li.cil.oc.integration.stargatetech2.DriverAbstractBusCard @@ -315,11 +316,11 @@ class Rack extends traits.PowerAcceptor with traits.Hub with traits.PowerBalance // ----------------------------------------------------------------------- // // RedstoneAware - override protected def onRedstoneInputChanged(side: ForgeDirection, 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 26c677ebf..c8d75205b 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Redstone.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Redstone.scala @@ -3,6 +3,7 @@ package li.cil.oc.common.tileentity import li.cil.oc.Settings import li.cil.oc.api 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.util.ExtendedNBT._ @@ -40,11 +41,11 @@ class Redstone extends traits.Environment with traits.BundledRedstoneAware { // ----------------------------------------------------------------------- // - override protected def onRedstoneInputChanged(side: ForgeDirection, 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 db2321902..8347a17be 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Screen.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Screen.scala @@ -7,6 +7,7 @@ import li.cil.oc.api.network.Analyzable import li.cil.oc.api.network._ import li.cil.oc.client.gui import li.cil.oc.common.component.TextBuffer +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 @@ -333,8 +334,8 @@ class Screen(var tier: Int) extends traits.TextBuffer with SidedEnvironment with override def onAnalyze(player: EntityPlayer, side: Int, hitX: Float, hitY: Float, hitZ: Float) = Array(origin.node) - override protected def onRedstoneInputChanged(side: ForgeDirection, 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 8f4138d1b..d3ac67d7d 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 @@ -48,35 +48,26 @@ trait BundledRedstoneAware extends RedstoneAware with IBundledEmitter with IBund (_bundledInput(side.ordinal()), _rednetInput(side.ordinal())).zipped.map(math.max) def bundledInput(side: ForgeDirection, 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) + for (color <- 0 until 16) { + updateInput(_bundledInput, side, color, if (newBundledInput == null) 0 else newBundledInput(color)) } - else for (color <- 0 until 16) { - changed = changed || ownBundledInput(color) > 0 - ownBundledInput(color) = 0 - } - if (changed) { - onRedstoneInputChanged(side, oldMaxValue, ownBundledInput.max) + } + + def rednetInput(side: ForgeDirection, color: Int, value: Int): Unit = updateInput(_rednetInput, side, color, value) + + def updateInput(inputs: Array[Array[Int]], side: ForgeDirection, color: Int, newValue: Int): Unit = { + val oldValue = inputs(side.ordinal())(color) + if (oldValue != newValue) { + if (oldValue != -1) { + onRedstoneInputChanged(RedstoneChangedEventArgs(side, oldValue, newValue, color)) + } + inputs(side.ordinal())(color) = newValue } } def bundledInput(side: ForgeDirection, color: Int) = math.max(_bundledInput(side.ordinal())(color), _rednetInput(side.ordinal())(color)) - def rednetInput(side: ForgeDirection, color: Int, value: Int): Unit = { - val oldValue = _rednetInput(side.ordinal())(color) - if (oldValue != value) { - if (oldValue != -1) { - onRedstoneInputChanged(side, oldValue, value) - } - _rednetInput(side.ordinal())(color) = value - } - } - def bundledOutput(side: ForgeDirection) = _bundledOutput(toLocal(side).ordinal()) def bundledOutput(side: ForgeDirection, 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 1f09602f4..fa2cc2b06 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 @@ -214,9 +214,9 @@ trait Computer extends Environment with ComponentInventory with Rotatable with B checkRedstoneInputChanged() } - override protected def onRedstoneInputChanged(side: ForgeDirection, 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/RedstoneAware.scala b/src/main/scala/li/cil/oc/common/tileentity/traits/RedstoneAware.scala index 45de5b606..0ea9baf28 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 @@ -16,6 +16,8 @@ import mods.immibis.redlogic.api.wiring.IWire import net.minecraft.nbt.NBTTagCompound import net.minecraftforge.common.util.ForgeDirection +case class RedstoneChangedEventArgs (side: ForgeDirection, oldValue: Int, newValue: Int, color: Int = -1) + @Optional.InterfaceList(Array( new Optional.Interface(iface = "mods.immibis.redlogic.api.wiring.IConnectable", modid = Mods.IDs.RedLogic), new Optional.Interface(iface = "mods.immibis.redlogic.api.wiring.IRedstoneEmitter", modid = Mods.IDs.RedLogic), @@ -51,7 +53,7 @@ trait RedstoneAware extends RotationAware with IConnectable with IRedstoneEmitte val oldInput = _input(side.ordinal()) _input(side.ordinal()) = newInput if (oldInput >= 0 && newInput != oldInput) { - onRedstoneInputChanged(side, oldInput, newInput) + onRedstoneInputChanged(RedstoneChangedEventArgs(side, oldInput, newInput)) } } @@ -125,7 +127,7 @@ trait RedstoneAware extends RotationAware with IConnectable with IRedstoneEmitte // ----------------------------------------------------------------------- // - protected def onRedstoneInputChanged(side: ForgeDirection, oldMaxValue: Int, newMaxValue: Int) {} + protected def onRedstoneInputChanged(args: RedstoneChangedEventArgs) {} protected def onRedstoneOutputEnabledChanged() { world.notifyBlocksOfNeighborChange(position, block) 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 11d3ad3ee..44c564767 100644 --- a/src/main/scala/li/cil/oc/server/component/RedstoneSignaller.scala +++ b/src/main/scala/li/cil/oc/server/component/RedstoneSignaller.scala @@ -6,7 +6,11 @@ import li.cil.oc.api.machine.Callback import li.cil.oc.api.machine.Context import li.cil.oc.api.network.Visibility import li.cil.oc.api.prefab +import li.cil.oc.common.tileentity.traits.RedstoneChangedEventArgs import net.minecraft.nbt.NBTTagCompound +import net.minecraftforge.common.util.ForgeDirection + +import scala.collection.mutable.ArrayBuffer trait RedstoneSignaller extends prefab.ManagedEnvironment { override val node = Network.newNode(this, Visibility.Network). @@ -31,9 +35,13 @@ trait RedstoneSignaller extends prefab.ManagedEnvironment { // ----------------------------------------------------------------------- // - 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 == ForgeDirection.UNKNOWN) "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 b3a5c86ba..ac2e6dc1c 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.ExtendedWorld._ import li.cil.oc.util.ExtendedBlock._ @@ -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: ForgeDirection, 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 index 3b4f04597..48cd3096a 100644 --- a/src/main/scala/li/cil/oc/server/component/RedstoneWireless.scala +++ b/src/main/scala/li/cil/oc/server/component/RedstoneWireless.scala @@ -15,9 +15,11 @@ 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.common.util.ForgeDirection import scala.collection.convert.WrapAsJava._ @@ -105,7 +107,7 @@ trait RedstoneWireless extends RedstoneSignaller with WirelessReceivingDevice wi override def updateDevice(frequency: Int, on: Boolean) { if (frequency == wirelessFrequency && on != wirelessInput) { wirelessInput = on - onRedstoneChanged("wireless", if (on) 0 else 1, if (on) 1 else 0) + onRedstoneChanged(RedstoneChangedEventArgs(ForgeDirection.UNKNOWN, if (on) 0 else 1, if (on) 1 else 0)) } } From c69c9e674855bb55ca8b9d8b10a9ce8fc33a6e88 Mon Sep 17 00:00:00 2001 From: payonel Date: Wed, 14 Mar 2018 23:31:43 -0700 Subject: [PATCH 05/10] returning just the first value of debug getlocal and getupvalue the suggested access was to return nil always as the value, but these values aren't nil, and I felt it reasonable to return at least the variable name and not mislead that the value was nil closes #1607 --- src/main/resources/assets/opencomputers/lua/machine.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/resources/assets/opencomputers/lua/machine.lua b/src/main/resources/assets/opencomputers/lua/machine.lua index 06f07a70f..54945e52c 100644 --- a/src/main/resources/assets/opencomputers/lua/machine.lua +++ b/src/main/resources/assets/opencomputers/lua/machine.lua @@ -938,7 +938,9 @@ sandbox = { } end end, - traceback = debug.traceback + traceback = debug.traceback, + getlocal = function(...) return (debug.getlocal(...)) end, + getupvalue = function(...) return (debug.getupvalue(...)) end, }, -- Lua 5.3. From 37c3ddaf91403033b533bd8b93647d421d6504e0 Mon Sep 17 00:00:00 2001 From: payonel Date: Fri, 16 Mar 2018 19:45:39 -0700 Subject: [PATCH 06/10] slot tier weighted fill closes #1658 --- .../li/cil/oc/common/container/Player.scala | 95 +++++++++++-------- 1 file changed, 53 insertions(+), 42 deletions(-) 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 9fae2d985..54de6a02d 100644 --- a/src/main/scala/li/cil/oc/common/container/Player.scala +++ b/src/main/scala/li/cil/oc/common/container/Player.scala @@ -53,54 +53,65 @@ abstract class Player(val playerInventory: InventoryPlayer, val otherInventory: null } - 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 == null || + from.getStack.stackSize == 0) + 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 != null) toStack.stackSize 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.stackSize) - if (fromStack.getMaxStackSize > 1) for (i <- begin to end by step if i >= 0 && i < inventorySlots.size && from.getHasStack && from.getStack.stackSize > 0) { - val intoSlot = inventorySlots.get(i).asInstanceOf[Slot] - 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.stackSize < maxStackSize - if (itemsAreEqual && slotHasCapacity) { - val itemsMoved = math.min(maxStackSize - intoStack.stackSize, fromStack.stackSize) - if (itemsMoved > 0) { - intoStack.stackSize += from.decrStackSize(itemsMoved).stackSize - intoSlot.onSlotChanged() - somethingChanged = true - } - } + if (toStack != null) { + if (toStackSize < maxStackSize && + fromStack.isItemEqual(toStack) && + ItemStack.areItemStackTagsEqual(fromStack, toStack) && + itemsMoved > 0) { + toStack.stackSize += from.decrStackSize(itemsMoved).stackSize + } 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.stackSize > 0) { - val intoSlot = inventorySlots.get(i).asInstanceOf[Slot] - if (intoSlot.inventory != from.inventory && !intoSlot.getHasStack && intoSlot.isItemValid(fromStack)) { - val maxStackSize = math.min(fromStack.getMaxStackSize, intoSlot.getSlotStackLimit) - val itemsMoved = math.min(maxStackSize, fromStack.stackSize) - 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 } } From 0095f3352221ac9687e3a7c8676260b7a99b4f45 Mon Sep 17 00:00:00 2001 From: payonel Date: Fri, 16 Mar 2018 19:45:45 -0700 Subject: [PATCH 07/10] cleanup --- src/main/resources/assets/opencomputers/lua/machine.lua | 5 +++++ src/main/scala/li/cil/oc/common/tileentity/Redstone.scala | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/resources/assets/opencomputers/lua/machine.lua b/src/main/resources/assets/opencomputers/lua/machine.lua index 54945e52c..d8c3eb540 100644 --- a/src/main/resources/assets/opencomputers/lua/machine.lua +++ b/src/main/resources/assets/opencomputers/lua/machine.lua @@ -939,6 +939,11 @@ sandbox = { end end, 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, }, 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 c8d75205b..350b30ff0 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Redstone.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Redstone.scala @@ -8,7 +8,6 @@ import li.cil.oc.integration.util.BundledRedstone import li.cil.oc.server.component import li.cil.oc.util.ExtendedNBT._ import net.minecraft.nbt.NBTTagCompound -import net.minecraftforge.common.util.ForgeDirection class Redstone extends traits.Environment with traits.BundledRedstoneAware { val instance = From 4a83d4426dc032a58b7b080f8a9533343af83f8b Mon Sep 17 00:00:00 2001 From: payonel Date: Sat, 17 Mar 2018 19:58:23 -0700 Subject: [PATCH 08/10] allow the robot to swing at anything that would block its movement closes #1840 --- src/main/scala/li/cil/oc/server/component/Agent.scala | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 74a6a3960..88be38d19 100644 --- a/src/main/scala/li/cil/oc/server/component/Agent.scala +++ b/src/main/scala/li/cil/oc/server/component/Agent.scala @@ -150,6 +150,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.x, blockPos.y, blockPos.z, facing.ordinal()) + player.setSneaking(false) + return result(ok, why) + } + result(false, reason.orNull) } From 8cac5dfcd315d1489428c0eabb8c1908ebafd610 Mon Sep 17 00:00:00 2001 From: payonel Date: Sat, 17 Mar 2018 20:46:30 -0700 Subject: [PATCH 09/10] add isSunVisible and canSeeSky to the geolyzer this is a potential solution to robots that need to know if their solar panels are able to charge if a geolyzer is also added to a robot, it can use the geolyzer component to check for charge options issue #1778 specifically asked for similar api on the solar upgrade, but we didn't want to make the solar upgrade a component. Doing so would increase the component load of existing robots, which could cause existing robots to simply fail. thus this is a compromise to that request closes #1778 --- .../li/cil/oc/server/component/Geolyzer.scala | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) 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 2f5c0f2f1..5d2cd9fde 100644 --- a/src/main/scala/li/cil/oc/server/component/Geolyzer.scala +++ b/src/main/scala/li/cil/oc/server/component/Geolyzer.scala @@ -26,6 +26,7 @@ import net.minecraft.entity.player.EntityPlayer import net.minecraft.item.Item import net.minecraft.item.ItemStack import net.minecraft.nbt.NBTTagCompound +import net.minecraft.world.biome.BiomeGenDesert import net.minecraftforge.common.MinecraftForge import net.minecraftforge.common.util.ForgeDirection @@ -51,6 +52,25 @@ class Geolyzer(val host: EnvironmentHost) extends prefab.ManagedEnvironment with // ----------------------------------------------------------------------- // + private def canSeeSky: Boolean = { + val blockPos = BlockPosition(host).offset(ForgeDirection.UP) + !host.world.provider.hasNoSky && host.world.canBlockSeeTheSky(blockPos.x, blockPos.y, blockPos.z) + } + + @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(ForgeDirection.UP) + result( + host.world.isDaytime && + canSeeSky && + (host.world.getWorldChunkManager.getBiomeGenAt(blockPos.x, blockPos.z).isInstanceOf[BiomeGenDesert] || (!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) From abe18ad535adc1f5d691e4f777da5c97606b6e7a Mon Sep 17 00:00:00 2001 From: payonel Date: Sun, 18 Mar 2018 01:09:45 -0700 Subject: [PATCH 10/10] add detect to geolyzer this commit also fixes some positional issues with geolyzer and canSeeSky when used as an internal component with tablets and drones this commit also adds the hover tier 2 upgrade to the creatix robot closes #1863 --- .../scala/li/cil/oc/common/init/Items.scala | 2 ++ .../li/cil/oc/server/component/Geolyzer.scala | 27 +++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) 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 aa10481e5..abd0aa29c 100644 --- a/src/main/scala/li/cil/oc/common/init/Items.scala +++ b/src/main/scala/li/cil/oc/common/init/Items.scala @@ -237,6 +237,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), @@ -245,6 +246,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/server/component/Geolyzer.scala b/src/main/scala/li/cil/oc/server/component/Geolyzer.scala index 5d2cd9fde..71308416c 100644 --- a/src/main/scala/li/cil/oc/server/component/Geolyzer.scala +++ b/src/main/scala/li/cil/oc/server/component/Geolyzer.scala @@ -18,6 +18,9 @@ import li.cil.oc.api.machine.Context import li.cil.oc.api.network.Message import li.cil.oc.api.network.Visibility import li.cil.oc.api.prefab +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 prefab.ManagedEnvironment with DeviceInfo { +class Geolyzer(val host: EnvironmentHost) extends prefab.ManagedEnvironment with traits.WorldControl with DeviceInfo { override val node = api.Network.newNode(this, Visibility.Network). withComponent("geolyzer"). withConnector(). @@ -52,8 +55,28 @@ class Geolyzer(val host: EnvironmentHost) extends prefab.ManagedEnvironment with // ----------------------------------------------------------------------- // + override protected def checkSideForAction(args: Arguments, n: Int): ForgeDirection = { + 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.posX, drone.posY, drone.posZ, drone.world) + 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 = BlockPosition(host).offset(ForgeDirection.UP) + val blockPos = position.offset(ForgeDirection.UP) !host.world.provider.hasNoSky && host.world.canBlockSeeTheSky(blockPos.x, blockPos.y, blockPos.z) }