diff --git a/src/main/java/li/cil/oc/api/machine/MachineHost.java b/src/main/java/li/cil/oc/api/machine/MachineHost.java index 67c7eae4a..d79124702 100644 --- a/src/main/java/li/cil/oc/api/machine/MachineHost.java +++ b/src/main/java/li/cil/oc/api/machine/MachineHost.java @@ -55,4 +55,13 @@ public interface MachineHost extends EnvironmentHost { * @param node the node that was disconnected from the network. */ void onMachineDisconnect(Node node); + + /** Helper method for printing the machine position in error messages and debug statements. */ + default String machinePosition() + { + if (world() != null && world().provider != null) + return String.format("(%g, %g, %g, %d)", xPosition(), yPosition(), zPosition(), world().provider.getDimension()); + else + return String.format("(%g, %g, %g)", xPosition(), yPosition(), zPosition()); + } } diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/sh.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/sh.lua index d4e4dd9db..de7a7b1c4 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/sh.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/sh.lua @@ -41,5 +41,5 @@ if #args == 0 then end else -- execute command. - return sh.execute(...) + return sh.execute(_ENV, ...) end diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_shell.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_shell.lua index 2576b07ee..7c6701715 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_shell.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_shell.lua @@ -10,8 +10,8 @@ function shell.execute(command, env, ...) if not sh then return false, reason end - local proc = process.load(sh, nil, nil, command) - local result = table.pack(process.internal.continue(proc, env, command, ...)) + local proc = process.load(sh, env, nil, command) + local result = table.pack(process.internal.continue(proc, command, ...)) if result.n == 0 then return true end return table.unpack(result, 1, result.n) end diff --git a/src/main/resources/assets/opencomputers/robot.names b/src/main/resources/assets/opencomputers/robot.names index 3d862d926..d3e64e7bc 100644 --- a/src/main/resources/assets/opencomputers/robot.names +++ b/src/main/resources/assets/opencomputers/robot.names @@ -34,6 +34,7 @@ Claptrap # Borderlands Codsworth # Fallout 4 Continuity # Mona Lisa Overdrive Cortana # Halo +Crow # Mystery Science Theater 3000 Crypto # Kodos Curiosity # Mars Rover Daedalus # Deus Ex @@ -60,6 +61,7 @@ Ghost # I, Robot quote "There have always been ghosts in the machin GLaDOS # Portal Gipsy Danger # Pacific Rim G.U.N.T.E.R. # Lost in Space +Gypsy # Mystery Science Theater 3000 HAL 9000 # Space Odyssey Harbinger # Mass Effect Harkness # Fallout 3 @@ -117,6 +119,7 @@ T-1000 # Terminator Tachikoma # Ghost in the Shell TARA UH # Perry Rhodan Terminator # Terminator +Tom Servo # Mystery Science Theater 3000 Rinzler # Tron Twiki # Buck Rodgers Uniblab # The Jetsons @@ -132,3 +135,4 @@ Wheatley # Portal Wintermute # Neuromancer Wobbo # Contributor Yui-MHCP001 # AKA Yui (Mental Health Counseling Program 001) - Sword Art Online +Zone of Endless # Ace Combat series diff --git a/src/main/scala/li/cil/oc/integration/appeng/NetworkControl.scala b/src/main/scala/li/cil/oc/integration/appeng/NetworkControl.scala index b131af852..9ab47f3db 100644 --- a/src/main/scala/li/cil/oc/integration/appeng/NetworkControl.scala +++ b/src/main/scala/li/cil/oc/integration/appeng/NetworkControl.scala @@ -131,7 +131,7 @@ trait NetworkControl[AETile >: Null <: TileEntity with IActionHost with IGridHos case (key, value) => hash += key -> value } hash.update("isCraftable", Boolean.box(aeItem.isCraftable)) - hash.update("size", Int.box(aeItem.getStackSize.toInt)) + hash.update("size", Long.box(aeItem.getStackSize)) hash } diff --git a/src/main/scala/li/cil/oc/server/component/DiskDriveMountable.scala b/src/main/scala/li/cil/oc/server/component/DiskDriveMountable.scala index fd6edaa7a..930df534f 100644 --- a/src/main/scala/li/cil/oc/server/component/DiskDriveMountable.scala +++ b/src/main/scala/li/cil/oc/server/component/DiskDriveMountable.scala @@ -83,6 +83,14 @@ class DiskDriveMountable(val rack: api.internal.Rack, val slot: Int) extends Abs else result(false) } + @Callback(doc = "function(): string -- Return the internal floppy disk address") + def media(context: Context, args: Arguments): Array[AnyRef] = { + if (filesystemNode.isEmpty) + result(Unit, "drive is empty") + else + result(filesystemNode.head.address) + } + // ----------------------------------------------------------------------- // // Analyzable diff --git a/src/main/scala/li/cil/oc/server/component/traits/InventoryWorldControl.scala b/src/main/scala/li/cil/oc/server/component/traits/InventoryWorldControl.scala index c1c7f8efd..008e58aed 100644 --- a/src/main/scala/li/cil/oc/server/component/traits/InventoryWorldControl.scala +++ b/src/main/scala/li/cil/oc/server/component/traits/InventoryWorldControl.scala @@ -44,9 +44,9 @@ trait InventoryWorldControl extends InventoryAware with WorldAware with SideRest val stack = inventory.getStackInSlot(selectedSlot) if (!stack.isEmpty && stack.getCount > 0) { val blockPos = position.offset(facing) - InventoryUtils.inventoryAt(blockPos, facing.getOpposite) match { - case Some(inv) if mayInteract(blockPos, facing.getOpposite, inv) => - if (!InventoryUtils.insertIntoInventory(stack, inv, count)) { + InventoryUtils.inventorySourceAt(blockPos, facing.getOpposite) match { + case Some(inv) if mayInteract(inv) => + if (!InventoryUtils.insertIntoInventory(stack, inv.inventory, count)) { // Cannot drop into that inventory. return result(false, "inventory full") } @@ -103,9 +103,9 @@ trait InventoryWorldControl extends InventoryAware with WorldAware with SideRest val count = args.optItemCount(1) val blockPos = position.offset(facing) - var extracted: Int = InventoryUtils.inventoryAt(blockPos, facing.getOpposite) match { - case Some(inventory) => mayInteract(blockPos, facing.getOpposite) - InventoryUtils.extractAnyFromInventory((is, sim) => InventoryUtils.insertIntoInventory(is, InventoryUtils.asItemHandler(this.inventory), slots = Option(insertionSlots), simulate = sim), inventory, count) + var extracted: Int = InventoryUtils.inventorySourceAt(blockPos, facing.getOpposite) match { + case Some(inventory) => mayInteract(inventory) + InventoryUtils.extractAnyFromInventory((is, sim) => InventoryUtils.insertIntoInventory(is, InventoryUtils.asItemHandler(this.inventory), slots = Option(insertionSlots), simulate = sim), inventory.inventory, count) case _ => 0 } if (extracted <= 0) { diff --git a/src/main/scala/li/cil/oc/server/component/traits/InventoryWorldControlMk2.scala b/src/main/scala/li/cil/oc/server/component/traits/InventoryWorldControlMk2.scala index 5df2854a9..0e858c9f1 100644 --- a/src/main/scala/li/cil/oc/server/component/traits/InventoryWorldControlMk2.scala +++ b/src/main/scala/li/cil/oc/server/component/traits/InventoryWorldControlMk2.scala @@ -60,8 +60,8 @@ trait InventoryWorldControlMk2 extends InventoryAware with WorldAware with SideR } private def withInventory(blockPos: BlockPosition, fromSide: EnumFacing, f: IItemHandler => Array[AnyRef]) = - InventoryUtils.inventoryAt(blockPos, fromSide) match { - case Some(inventory) if mayInteract(blockPos, fromSide) => f(inventory) + InventoryUtils.inventorySourceAt(blockPos, fromSide) match { + case Some(inventory) if mayInteract(inventory) => f(inventory.inventory) case _ => result(Unit, "no inventory") } } diff --git a/src/main/scala/li/cil/oc/server/component/traits/WorldAware.scala b/src/main/scala/li/cil/oc/server/component/traits/WorldAware.scala index eaf94a5f4..b63dc7b0f 100644 --- a/src/main/scala/li/cil/oc/server/component/traits/WorldAware.scala +++ b/src/main/scala/li/cil/oc/server/component/traits/WorldAware.scala @@ -2,15 +2,14 @@ package li.cil.oc.server.component.traits import li.cil.oc.OpenComputers import li.cil.oc.Settings -import li.cil.oc.util.BlockPosition +import li.cil.oc.util.{BlockInventorySource, BlockPosition, EntityInventorySource, InventorySource} import li.cil.oc.util.ExtendedBlock._ import li.cil.oc.util.ExtendedWorld._ import net.minecraft.entity.Entity import net.minecraft.entity.EntityLivingBase import net.minecraft.entity.item.EntityMinecart import net.minecraft.entity.player.EntityPlayer -import net.minecraft.util.EnumFacing -import net.minecraft.util.EnumHand +import net.minecraft.util.{EnumActionResult, EnumFacing, EnumHand} import net.minecraft.util.math.AxisAlignedBB import net.minecraft.world.WorldServer import net.minecraftforge.common.MinecraftForge @@ -35,7 +34,7 @@ trait WorldAware { player } - def mayInteract(blockPos: BlockPosition, face: EnumFacing): Boolean = { + private def mayInteract(blockPos: BlockPosition, face: EnumFacing): Boolean = { try { val event = new PlayerInteractEvent.RightClickBlock(fakePlayer, EnumHand.MAIN_HAND, blockPos.toBlockPos, face, null) MinecraftForge.EVENT_BUS.post(event) @@ -47,9 +46,25 @@ trait WorldAware { } } - def mayInteract(blockPos: BlockPosition, side: EnumFacing, inventory: IItemHandler): Boolean = mayInteract(blockPos, side) && (inventory match { + private def mayInteract(entity: Entity): Boolean = { + try { + val event = new PlayerInteractEvent.EntityInteract(fakePlayer, EnumHand.MAIN_HAND, entity) + MinecraftForge.EVENT_BUS.post(event) + !event.isCanceled + } catch { + case t: Throwable => + OpenComputers.log.warn("Some event handler threw up while checking for permission to access an entity.", t) + true + } + } + + def mayInteract(inv: InventorySource): Boolean = (inv.inventory match { case inv: InvWrapper if inv.getInv != null => inv.getInv.isUsableByPlayer(fakePlayer) case _ => true + }) && (inv match { + case inv: BlockInventorySource => mayInteract(inv.position, inv.side) + case inv: EntityInventorySource => mayInteract(inv.entity) + case _ => true }) def entitiesInBounds[Type <: Entity](clazz: Class[Type], bounds: AxisAlignedBB) = { diff --git a/src/main/scala/li/cil/oc/server/component/traits/WorldInventoryAnalytics.scala b/src/main/scala/li/cil/oc/server/component/traits/WorldInventoryAnalytics.scala index ee46da2f1..d896e8fe8 100644 --- a/src/main/scala/li/cil/oc/server/component/traits/WorldInventoryAnalytics.scala +++ b/src/main/scala/li/cil/oc/server/component/traits/WorldInventoryAnalytics.scala @@ -6,13 +6,13 @@ import li.cil.oc.api.machine.Callback import li.cil.oc.api.machine.Context import li.cil.oc.api.prefab.ItemStackArrayValue import li.cil.oc.server.component.result -import li.cil.oc.util.{BlockPosition, DatabaseAccess, InventoryUtils} +import li.cil.oc.util.{BlockInventorySource, BlockPosition, DatabaseAccess, EntityInventorySource, InventorySource, InventoryUtils, StackOption} import li.cil.oc.util.ExtendedWorld._ import li.cil.oc.util.ExtendedArguments._ import net.minecraft.block.Block -import li.cil.oc.util.StackOption import net.minecraft.item.ItemStack import net.minecraft.util.EnumFacing +import net.minecraftforge.fml.common.registry.EntityRegistry import net.minecraftforge.items.IItemHandler import net.minecraftforge.oredict.OreDictionary @@ -102,8 +102,12 @@ trait WorldInventoryAnalytics extends WorldAware with SideRestricted with Networ } case _ => None } - withInventory(facing, inventory => blockAt(position.offset(facing)) match { - case Some(block) => result(block.getRegistryName) + withInventorySource(facing, { + case BlockInventorySource(position, _, _) => blockAt(position) match { + case Some(block) => result(block.getRegistryName) + case _ => result(Unit, "Unknown") + } + case EntityInventorySource(entity, _, _) => result(EntityRegistry.getEntry(entity.getClass).getRegistryName) case _ => result(Unit, "Unknown") }) } @@ -122,9 +126,12 @@ trait WorldInventoryAnalytics extends WorldAware with SideRestricted with Networ withInventory(facing, inventory => store(inventory.getStackInSlot(args.checkSlot(inventory, 1)))) } - private def withInventory(side: EnumFacing, f: IItemHandler => Array[AnyRef]) = - InventoryUtils.inventoryAt(position.offset(side), side.getOpposite) match { - case Some(inventory) if mayInteract(position.offset(side), side.getOpposite, inventory) => f(inventory) + private def withInventorySource(side: EnumFacing, f: InventorySource => Array[AnyRef]) = + InventoryUtils.inventorySourceAt(position.offset(side), side.getOpposite) match { + case Some(inventory) if mayInteract(inventory) => f(inventory) case _ => result(Unit, "no inventory") } + + private def withInventory(side: EnumFacing, f: IItemHandler => Array[AnyRef]) = + withInventorySource(side, is => f(is.inventory)) } diff --git a/src/main/scala/li/cil/oc/server/machine/Machine.scala b/src/main/scala/li/cil/oc/server/machine/Machine.scala index 40b707cc7..60cc85116 100644 --- a/src/main/scala/li/cil/oc/server/machine/Machine.scala +++ b/src/main/scala/li/cil/oc/server/machine/Machine.scala @@ -799,7 +799,7 @@ class Machine(val host: MachineHost) extends AbstractManagedEnvironment with mac catch { case t: Throwable => OpenComputers.log.error( - s"""Unexpected error loading a state of computer at (${host.xPosition}, ${host.yPosition}, ${host.zPosition}). """ + + s"""Unexpected error loading a state of computer at ${host.machinePosition()}. """ + s"""State: ${state.headOption.fold("no state")(_.toString)}. Unless you're upgrading/downgrading across a major version, please report this! Thank you.""", t) close() } @@ -882,7 +882,7 @@ class Machine(val host: MachineHost) extends AbstractManagedEnvironment with mac catch { case t: Throwable => OpenComputers.log.error( - s"""Unexpected error saving a state of computer at (${host.xPosition}, ${host.yPosition}, ${host.zPosition}). """ + + s"""Unexpected error saving a state of computer at ${host.machinePosition()}. """ + s"""State: ${state.headOption.fold("no state")(_.toString)}. Unless you're upgrading/downgrading across a major version, please report this! Thank you.""", t) } }) diff --git a/src/main/scala/li/cil/oc/server/machine/luac/NativeLuaArchitecture.scala b/src/main/scala/li/cil/oc/server/machine/luac/NativeLuaArchitecture.scala index f87d313d1..ed13eb4e3 100644 --- a/src/main/scala/li/cil/oc/server/machine/luac/NativeLuaArchitecture.scala +++ b/src/main/scala/li/cil/oc/server/machine/luac/NativeLuaArchitecture.scala @@ -376,7 +376,7 @@ abstract class NativeLuaArchitecture(val machine: api.machine.Machine) extends A try lua.gc(LuaState.GcAction.COLLECT, 0) catch { case t: Throwable => - OpenComputers.log.warn(s"Error cleaning up loaded computer @ (${machine.host.xPosition}, ${machine.host.yPosition}, ${machine.host.zPosition}). This either means the server is badly overloaded or a user created an evil __gc method, accidentally or not.") + OpenComputers.log.warn(s"Error cleaning up loaded computer @ ${machine.host().machinePosition()}. This either means the server is badly overloaded or a user created an evil __gc method, accidentally or not.") machine.crash("error in garbage collector, most likely __gc method timed out") } } catch { @@ -414,15 +414,15 @@ abstract class NativeLuaArchitecture(val machine: api.machine.Machine) extends A try lua.gc(LuaState.GcAction.COLLECT, 0) catch { case t: Throwable => - OpenComputers.log.warn(s"Error cleaning up loaded computer @ (${machine.host.xPosition}, ${machine.host.yPosition}, ${machine.host.zPosition}). This either means the server is badly overloaded or a user created an evil __gc method, accidentally or not.") + OpenComputers.log.warn(s"Error cleaning up loaded computer @ ${machine.host().machinePosition()}. This either means the server is badly overloaded or a user created an evil __gc method, accidentally or not.") machine.crash("error in garbage collector, most likely __gc method timed out") } } catch { case e: LuaRuntimeException => - OpenComputers.log.warn(s"Could not persist computer @ (${machine.host.xPosition}, ${machine.host.yPosition}, ${machine.host.zPosition}).\n${e.toString}" + (if (e.getLuaStackTrace.isEmpty) "" else "\tat " + e.getLuaStackTrace.mkString("\n\tat "))) + OpenComputers.log.warn(s"Could not persist computer @ ${machine.host().machinePosition()}.\n${e.toString}" + (if (e.getLuaStackTrace.isEmpty) "" else "\tat " + e.getLuaStackTrace.mkString("\n\tat "))) nbt.removeTag("state") case e: LuaGcMetamethodException => - OpenComputers.log.warn(s"Could not persist computer @ (${machine.host.xPosition}, ${machine.host.yPosition}, ${machine.host.zPosition}).\n${e.toString}") + OpenComputers.log.warn(s"Could not persist computer @ ${machine.host().machinePosition()}.\n${e.toString}") nbt.removeTag("state") } diff --git a/src/main/scala/li/cil/oc/util/InventoryUtils.scala b/src/main/scala/li/cil/oc/util/InventoryUtils.scala index d01c7714d..92fddbcd1 100644 --- a/src/main/scala/li/cil/oc/util/InventoryUtils.scala +++ b/src/main/scala/li/cil/oc/util/InventoryUtils.scala @@ -40,33 +40,40 @@ object InventoryUtils { (!checkNBT || ItemStack.areItemStackTagsEqual(stackA, stackB)) /** - * Retrieves an actual inventory implementation for a specified world coordinate. - *
- * This performs special handling for (double-)chests and also checks for - * mine carts with chests. + * Retrieves an actual inventory implementation for a specified world coordinate, + * complete with a reference to the source of said implementation. */ - def inventoryAt(position: BlockPosition, side: EnumFacing): Option[IItemHandler] = position.world match { + def inventorySourceAt(position: BlockPosition, side: EnumFacing): Option[InventorySource] = position.world match { case Some(world) if world.blockExists(position) => world.getTileEntity(position) match { - case tile: TileEntity if tile.hasCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, side) => Option(tile.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, side)) - case tile: IInventory => Option(asItemHandler(tile, side)) + case tile: TileEntity if tile.hasCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, side) => Option(BlockInventorySource(position, side, tile.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, side))) + case tile: IInventory => Option(BlockInventorySource(position, side, asItemHandler(tile, side))) case _ => world.getEntitiesWithinAABB(classOf[Entity], position.bounds) .filter(e => !e.isDead && e.hasCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, side)) - .map(_.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, side)) - .find(_ != null) + .map(a => EntityInventorySource(a, side, a.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, side))) + .find(a => a != null && a.inventory != null) } case _ => None } - def anyInventoryAt(position: BlockPosition): Option[IItemHandler] = { + /** + * Retrieves an actual inventory implementation for a specified world coordinate. + */ + def inventoryAt(position: BlockPosition, side: EnumFacing): Option[IItemHandler] = inventorySourceAt(position, side) + .map(a => a.inventory) + + def anyInventorySourceAt(position: BlockPosition): Option[InventorySource] = { for(side <- null :: EnumFacing.VALUES.toList) { - inventoryAt(position, side) match { - case inv: Some[IItemHandler] => return inv + inventorySourceAt(position, side) match { + case inv: Some[InventorySource] => return inv case _ => } } None } + def anyInventoryAt(position: BlockPosition): Option[IItemHandler] = anyInventorySourceAt(position) + .map(a => a.inventory) + /** * Inserts a stack into an inventory. *
@@ -397,3 +404,10 @@ object InventoryUtils { case _ => null } } + +sealed trait InventorySource { + def side: EnumFacing + def inventory: IItemHandler +} +final case class BlockInventorySource(position: BlockPosition, side: EnumFacing, inventory: IItemHandler) extends InventorySource +final case class EntityInventorySource(entity: Entity, side: EnumFacing, inventory: IItemHandler) extends InventorySource \ No newline at end of file