diff --git a/li/cil/oc/api/network/Arguments.java b/li/cil/oc/api/network/Arguments.java index ad15fb21f..1db862dbb 100644 --- a/li/cil/oc/api/network/Arguments.java +++ b/li/cil/oc/api/network/Arguments.java @@ -5,6 +5,10 @@ package li.cil.oc.api.network; *

* It allows checking for the presence of arguments in a uniform manner, taking * care of proper type checking based on what can be passed along by Lua. + *

+ * Note that integer values fetched this way are actually double values that + * have been truncated. So if a Lua program passes 1.9 and you do a + * checkInteger you'll get a 1. */ public interface Arguments extends Iterable { /** @@ -28,16 +32,125 @@ public interface Arguments extends Iterable { * * @param index the index from which to get the argument. * @return the raw value at that index. + * @throws IllegalArgumentException if there is no argument at that index. */ Object checkAny(int index); + /** + * Try to get a boolean value at the specified index. + *

+ * Throws an error if there are too few arguments. + * + * @param index the index from which to get the argument. + * @return the boolean value at the specified index. + * @throws IllegalArgumentException if there is no argument at that index, + * or if the argument is not a boolean. + */ boolean checkBoolean(int index); - double checkDouble(int index); - + /** + * Try to get an integer value at the specified index. + *

+ * Throws an error if there are too few arguments. + * + * @param index the index from which to get the argument. + * @return the integer value at the specified index. + * @throws IllegalArgumentException if there is no argument at that index, + * or if the argument is not a number. + */ int checkInteger(int index); + /** + * Try to get a double value at the specified index. + *

+ * Throws an error if there are too few arguments. + * + * @param index the index from which to get the argument. + * @return the double value at the specified index. + * @throws IllegalArgumentException if there is no argument at that index, + * or if the argument is not a number. + */ + double checkDouble(int index); + + /** + * Try to get a string value at the specified index. + *

+ * Throws an error if there are too few arguments. + *

+ * This will actually check for a byte array and convert it to a string + * using UTF-8 encoding. + * + * @param index the index from which to get the argument. + * @return the boolean value at the specified index. + * @throws IllegalArgumentException if there is no argument at that index, + * or if the argument is not a string. + */ String checkString(int index); + /** + * Try to get a byte array at the specified index. + *

+ * Throws an error if there are too few arguments. + * + * @param index the index from which to get the argument. + * @return the byte array at the specified index. + * @throws IllegalArgumentException if there is no argument at that index, + * or if the argument is not a byte array. + */ byte[] checkByteArray(int index); + + /** + * Tests whether the argument at the specified index is a boolean value. + *

+ * This will return true if there is no argument at the specified + * index, i.e. if there are too few arguments. + * + * @param index the index to check. + * @return true if the argument is a boolean; false otherwise. + */ + boolean isBoolean(int index); + + /** + * Tests whether the argument at the specified index is an integer value. + *

+ * This will return true if there is no argument at the specified + * index, i.e. if there are too few arguments. + * + * @param index the index to check. + * @return true if the argument is an integer; false otherwise. + */ + boolean isInteger(int index); + + /** + * Tests whether the argument at the specified index is a double value. + *

+ * This will return true if there is no argument at the specified + * index, i.e. if there are too few arguments. + * + * @param index the index to check. + * @return true if the argument is a double; false otherwise. + */ + boolean isDouble(int index); + + /** + * Tests whether the argument at the specified index is a string value. + *

+ * This will return true if there is no argument at the specified + * index, i.e. if there are too few arguments. + * + * @param index the index to check. + * @return true if the argument is a string; false otherwise. + */ + boolean isString(int index); + + /** + * Tests whether the argument at the specified index is a byte array. + *

+ * This will return true if there is no argument at the specified + * index, i.e. if there are too few arguments. + * + * @param index the index to check. + * @return true if the argument is a byte array; false otherwise. + */ + boolean isByteArray(int index); } diff --git a/li/cil/oc/common/component/Screen.scala b/li/cil/oc/common/component/Screen.scala index ee0ecd171..bc84864ce 100644 --- a/li/cil/oc/common/component/Screen.scala +++ b/li/cil/oc/common/component/Screen.scala @@ -74,12 +74,12 @@ object Screen { trait Environment extends tileentity.Environment with util.Persistable { val node = api.Network.newNode(this, Visibility.Network). withComponent("screen"). - withConnector(Config.bufferScreen). + withConnector(Config.bufferScreen * (tier + 1)). create() - final val instance = new component.Screen(this, maxResolution) + final val instance = new component.Screen(this, Config.screenResolutionsByTier(tier)) - protected def maxResolution: (Int, Int) + protected def tier: Int // ----------------------------------------------------------------------- // diff --git a/li/cil/oc/common/tileentity/Screen.scala b/li/cil/oc/common/tileentity/Screen.scala index def9b5d36..59ed6d4b2 100644 --- a/li/cil/oc/common/tileentity/Screen.scala +++ b/li/cil/oc/common/tileentity/Screen.scala @@ -14,15 +14,15 @@ import scala.collection.mutable import net.minecraft.client.Minecraft class ScreenTier1 extends Screen { - protected def maxResolution = Config.screenResolutionsByTier(0) + protected def tier = 0 } class ScreenTier2 extends Screen { - protected def maxResolution = Config.screenResolutionsByTier(1) + protected def tier = 1 } class ScreenTier3 extends Screen { - protected def maxResolution = Config.screenResolutionsByTier(2) + protected def tier = 2 } abstract class Screen extends Rotatable with ScreenEnvironment { @@ -165,7 +165,7 @@ abstract class Screen extends Rotatable with ScreenEnvironment { def tryMergeTowards(dx: Int, dy: Int) = { val (nx, ny, nz) = unproject(x + dx, y + dy, z) worldObj.getBlockTileEntity(nx, ny, nz) match { - case s: Screen if s.maxResolution == maxResolution && s.pitch == pitch && s.yaw == yaw && !screens.contains(s) => + case s: Screen if s.tier == tier && s.pitch == pitch && s.yaw == yaw && !screens.contains(s) => val (sx, sy, _) = project(s.origin) val canMergeAlongX = sy == y && s.height == height && s.width + width <= Config.maxScreenWidth val canMergeAlongY = sx == x && s.width == width && s.height + height <= Config.maxScreenHeight diff --git a/li/cil/oc/server/component/Carriage.scala b/li/cil/oc/server/component/Carriage.scala index 52fff5377..bafb632c3 100644 --- a/li/cil/oc/server/component/Carriage.scala +++ b/li/cil/oc/server/component/Carriage.scala @@ -1,49 +1,71 @@ package li.cil.oc.server.component -import java.lang.reflect.InvocationTargetException import li.cil.oc.api import li.cil.oc.api.network._ +import li.cil.oc.util.RedstoneInMotion import net.minecraft.nbt.NBTTagCompound -import scala.Some -import scala.language.existentials -class Carriage(controller: Object) extends ManagedComponent { +class Carriage(controller: AnyRef) extends ManagedComponent { val node = api.Network.newNode(this, Visibility.Network). withComponent("carriage"). create() - private val (directions, setup, move) = try { - val directions = Class.forName("JAKJ.RedstoneInMotion.Directions").getEnumConstants - val clazz = Class.forName("JAKJ.RedstoneInMotion.CarriageControllerEntity") - val methods = clazz.getDeclaredMethods - val setup = methods.find(_.getName == "SetupMotion").orNull - val move = methods.find(_.getName == "Move").orNull - (directions, setup, move) - } catch { - case _: Throwable => (null, null, null) - } + private val names = Map( + "negy" -> 0, "posy" -> 1, "negz" -> 2, "posz" -> 3, "negx" -> 4, "posx" -> 5, + "down" -> 0, "up" -> 1, "north" -> 2, "south" -> 3, "west" -> 4, "east" -> 5) - private var shouldMove = false + private var anchored = false private var direction = 0 private var simulating = false - private var anchored = false + + private var shouldMove = false private var moving = false // ----------------------------------------------------------------------- // @LuaCallback("move") def move(context: Context, args: Arguments): Array[Object] = { - if (directions == null || setup == null || move == null) + direction = checkDirection(args) + simulating = if (args.count > 1) args.checkBoolean(1) else false + shouldMove = true + result(true) + } + + @LuaCallback("simulate") + def simulate(context: Context, args: Arguments): Array[Object] = { + direction = checkDirection(args) + simulating = true + shouldMove = true + result(true) + } + + @LuaCallback(value = "getAnchored", direct = true) + def getAnchored(context: Context, args: Arguments): Array[Object] = + result(anchored) + + @LuaCallback("setAnchored") + def setAnchored(context: Context, args: Arguments): Array[Object] = { + anchored = args.checkBoolean(0) + result(anchored) + } + + private def checkDirection(args: Arguments) = { + if (!RedstoneInMotion.available) throw new Exception("Redstone in Motion not found") if (shouldMove || moving) throw new Exception("already moving") - direction = args.checkInteger(0) - if (direction < 0 || direction > directions.length) - throw new ArrayIndexOutOfBoundsException("invalid direction") - simulating = args.checkBoolean(1) - anchored = args.checkBoolean(2) - shouldMove = true - result(true) + if (args.isString(0)) { + val name = args.checkString(0).toLowerCase + if (!names.contains(name)) + throw new IllegalArgumentException("invalid direction") + names(name) + } + else { + val index = args.checkInteger(0) + if (index < 0 || index > 5) + throw new ArrayIndexOutOfBoundsException("invalid direction") + index + } } // ----------------------------------------------------------------------- // @@ -53,18 +75,19 @@ class Carriage(controller: Object) extends ManagedComponent { if (shouldMove) { shouldMove = false moving = true - var error: Option[Throwable] = None try { - setup.invoke(controller, directions(direction), Boolean.box(simulating), Boolean.box(anchored)) - move.invoke(controller) - } catch { - case e: InvocationTargetException => error = Some(e.getCause) - case e: Throwable => error = Some(e) + RedstoneInMotion.move(controller, direction, simulating, anchored) + if (simulating || anchored) { + // We won't get re-connected, so we won't send in onConnect. Do it here. + node.sendToReachable("computer.signal", "carriage_moved", Boolean.box(true)) + } } - moving = false - error match { - case Some(e) => node.sendToReachable("computer.signal", "carriage_moved", Unit, Option(e.getMessage).getOrElse(e.toString)) - case _ => if (simulating || anchored) node.sendToReachable("computer.signal", "carriage_moved", Boolean.box(true)) + catch { + case e: Throwable => + node.sendToReachable("computer.signal", "carriage_moved", Unit, Option(e.getMessage).getOrElse(e.toString)) + } + finally { + moving = false } } } @@ -84,10 +107,12 @@ class Carriage(controller: Object) extends ManagedComponent { override def save(nbt: NBTTagCompound) { super.save(nbt) nbt.setBoolean("moving", moving) + nbt.setBoolean("anchored", anchored) } override def load(nbt: NBTTagCompound) { super.load(nbt) moving = nbt.getBoolean("moving") + anchored = nbt.getBoolean("anchored") } } diff --git a/li/cil/oc/server/driver/Carriage.scala b/li/cil/oc/server/driver/Carriage.scala index b77acaaf9..6d405e973 100644 --- a/li/cil/oc/server/driver/Carriage.scala +++ b/li/cil/oc/server/driver/Carriage.scala @@ -2,28 +2,19 @@ package li.cil.oc.server.driver import li.cil.oc.api.driver import li.cil.oc.server.component +import li.cil.oc.util.RedstoneInMotion import net.minecraft.world.World object Carriage extends driver.Block { - private val (carriageControllerClass) = try { - Class.forName("JAKJ.RedstoneInMotion.CarriageControllerEntity") - } catch { - case _: Throwable => null - } - def worksWith(world: World, x: Int, y: Int, z: Int) = Option(world.getBlockTileEntity(x, y, z)) match { - case Some(entity) if checkClass(entity) => true + case Some(entity) if RedstoneInMotion.isCarriageController(entity) => true case _ => false } def createEnvironment(world: World, x: Int, y: Int, z: Int) = world.getBlockTileEntity(x, y, z) match { - case controller if checkClass(controller) => - new component.Carriage(controller) + case entity if RedstoneInMotion.isCarriageController(entity) => new component.Carriage(entity) case _ => null } - - private def checkClass(value: Object) = - carriageControllerClass != null && carriageControllerClass.isAssignableFrom(value.getClass) } diff --git a/li/cil/oc/server/network/Component.scala b/li/cil/oc/server/network/Component.scala index db63e7175..4c116392f 100644 --- a/li/cil/oc/server/network/Component.scala +++ b/li/cil/oc/server/network/Component.scala @@ -146,7 +146,7 @@ object Component { args(index) } - def checkBoolean(index: Int): Boolean = { + def checkBoolean(index: Int) = { checkIndex(index, "boolean") args(index) match { case value: java.lang.Boolean => value @@ -154,7 +154,7 @@ object Component { } } - def checkDouble(index: Int): Double = { + def checkDouble(index: Int) = { checkIndex(index, "number") args(index) match { case value: java.lang.Double => value @@ -162,7 +162,7 @@ object Component { } } - def checkInteger(index: Int): Int = { + def checkInteger(index: Int) = { checkIndex(index, "number") args(index) match { case value: java.lang.Double => value.intValue @@ -170,10 +170,16 @@ object Component { } } - def checkString(index: Int) = - new String(checkByteArray(index), "UTF-8") + def checkString(index: Int) = { + checkIndex(index, "string") + args(index) match { + case value: java.lang.String => value + case value: Array[Byte] => new String(value, "UTF-8") + case value => throw typeError(index, value, "string") + } + } - def checkByteArray(index: Int): Array[Byte] = { + def checkByteArray(index: Int) = { checkIndex(index, "string") args(index) match { case value: Array[Byte] => value @@ -181,6 +187,37 @@ object Component { } } + def isBoolean(index: Int) = + index >= 0 && index < count && (args(index) match { + case value: java.lang.Boolean => true + case _ => false + }) + + def isDouble(index: Int) = + index >= 0 && index < count && (args(index) match { + case value: java.lang.Double => true + case _ => false + }) + + def isInteger(index: Int) = + index >= 0 && index < count && (args(index) match { + case value: java.lang.Integer => true + case _ => false + }) + + def isString(index: Int) = + index >= 0 && index < count && (args(index) match { + case value: java.lang.String => true + case value: Array[Byte] => true + case _ => false + }) + + def isByteArray(index: Int) = + index >= 0 && index < count && (args(index) match { + case value: Array[Byte] => true + case _ => false + }) + private def checkIndex(index: Int, name: String) = if (index < 0) throw new IndexOutOfBoundsException() else if (args.length <= index) throw new IllegalArgumentException( diff --git a/li/cil/oc/util/RedstoneInMotion.scala b/li/cil/oc/util/RedstoneInMotion.scala new file mode 100644 index 000000000..a12098211 --- /dev/null +++ b/li/cil/oc/util/RedstoneInMotion.scala @@ -0,0 +1,36 @@ +package li.cil.oc.util + +import java.lang.reflect.InvocationTargetException +import scala.language.existentials + +object RedstoneInMotion { + private val (controller, setup, move, directions) = try { + val controller = Class.forName("JAKJ.RedstoneInMotion.CarriageControllerEntity") + val methods = controller.getDeclaredMethods + val setup = methods.find(_.getName == "SetupMotion").get + val move = methods.find(_.getName == "Move").get + val directions = Class.forName("JAKJ.RedstoneInMotion.Directions").getEnumConstants + (Option(controller), setup, move, directions) + } catch { + case _: Throwable => (None, null, null, null) + } + + def available = controller.isDefined + + def isCarriageController(value: AnyRef) = controller match { + case Some(clazz) => clazz.isAssignableFrom(value.getClass) + case _ => false + } + + def move(controller: AnyRef, direction: Int, simulating: Boolean, anchored: Boolean) { + if (!isCarriageController(controller)) + throw new IllegalArgumentException("Not a carriage controller.") + + try { + setup.invoke(controller, directions(direction), Boolean.box(simulating), Boolean.box(anchored)) + move.invoke(controller) + } catch { + case e: InvocationTargetException => throw e.getCause + } + } +}