Merge branch 'master-MC1.11' into master-MC1.12

This commit is contained in:
payonel 2018-03-18 23:07:06 -07:00
commit ac400a5485
25 changed files with 430 additions and 169 deletions

View File

@ -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},
}

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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
}
}

View File

@ -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),

View File

@ -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 {

View File

@ -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()

View File

@ -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()
}

View File

@ -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)
}
}

View File

@ -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)
}
}
}

View File

@ -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

View File

@ -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)

View File

@ -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)
}
// ----------------------------------------------------------------------- //

View File

@ -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 {

View File

@ -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) {

View File

@ -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)
}

View File

@ -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)

View File

@ -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

View File

@ -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 _ =>
}
}

View File

@ -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)
}
}