diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index 83e60eb2b..bd8ef92c1 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -1510,9 +1510,11 @@ opencomputers { # Enable debug card functionality. This may also be of use for custom # maps, so it is enabled by default. If you run a server where people # may cheat in items but should not have op/admin-like rights, you may - # want to set this to false. This will *not* remove the card, it will - # just make all functions it provides error out. - enableDebugCard: true + # want to set this to false or `deny`. Set this to `whitelist` if you + # want to enable whitelisting of debug card users (managed by command + # /oc_debugWhitelist). This will *not* remove the card, it will just + # make all functions it provides error out. + debugCardAccess: allow # Whether to always register the LuaJ architecture - even if the native # library is available. In that case it is possible to switch between diff --git a/src/main/scala/li/cil/oc/Settings.scala b/src/main/scala/li/cil/oc/Settings.scala index bb38fce98..bd8897fdb 100644 --- a/src/main/scala/li/cil/oc/Settings.scala +++ b/src/main/scala/li/cil/oc/Settings.scala @@ -3,6 +3,8 @@ package li.cil.oc import java.io._ import java.net.Inet4Address import java.net.InetAddress +import java.nio.charset.StandardCharsets +import java.security.SecureRandom import java.util.UUID import com.google.common.net.InetAddresses @@ -11,11 +13,16 @@ import com.typesafe.config._ import cpw.mods.fml.common.Loader import cpw.mods.fml.common.versioning.DefaultArtifactVersion import cpw.mods.fml.common.versioning.VersionRange +import li.cil.oc.Settings.DebugCardAccess import li.cil.oc.common.Tier import li.cil.oc.integration.Mods +import li.cil.oc.server.component.DebugCard +import li.cil.oc.server.component.DebugCard.AccessContext +import org.apache.commons.codec.binary.Hex import org.apache.commons.lang3.StringEscapeUtils import scala.collection.convert.WrapAsScala._ +import scala.collection.mutable import scala.io.Codec import scala.io.Source import scala.util.matching.Regex @@ -413,7 +420,21 @@ class Settings(val config: Config) { val nativeInTmpDir = config.getBoolean("debug.nativeInTmpDir") val periodicallyForceLightUpdate = config.getBoolean("debug.periodicallyForceLightUpdate") val insertIdsInConverters = config.getBoolean("debug.insertIdsInConverters") - val enableDebugCard = config.getBoolean("debug.enableDebugCard") + + val debugCardAccess = config.getValue("debug.debugCardAccess").unwrapped() match { + case "true" | "allow" | java.lang.Boolean.TRUE => DebugCardAccess.Allowed + case "false" | "deny" | java.lang.Boolean.FALSE => DebugCardAccess.Forbidden + case "whitelist" => + val wlFile = new File(Loader.instance.getConfigDir + File.separator + "opencomputers" + File.separator + + "debug_card_whitelist.txt") + + DebugCardAccess.Whitelist(wlFile) + + case _ => // Fallback to most secure configuration + OpenComputers.log.warn("Unknown debug card access type, falling back to `deny`. Allowed values: `allow`, `deny`, `whitelist`.") + DebugCardAccess.Forbidden + } + val registerLuaJArchitecture = config.getBoolean("debug.registerLuaJArchitecture") val disableLocaleChanging = config.getBoolean("debug.disableLocaleChanging") } @@ -557,4 +578,95 @@ object Settings { def apply(inetAddress: InetAddress, host: String) = validator(inetAddress, host) } + sealed trait DebugCardAccess { + def checkAccess(ctx: Option[DebugCard.AccessContext]): Option[String] + } + + object DebugCardAccess { + case object Forbidden extends DebugCardAccess { + override def checkAccess(ctx: Option[AccessContext]): Option[String] = + Some("debug card is disabled") + } + + case object Allowed extends DebugCardAccess { + override def checkAccess(ctx: Option[AccessContext]): Option[String] = None + } + + case class Whitelist(noncesFile: File) extends DebugCardAccess { + private val values = mutable.Map.empty[String, String] + private val rng = SecureRandom.getInstance("SHA1PRNG") + + load() + + def save(): Unit = { + val noncesDir = noncesFile.getParentFile + if (!noncesDir.exists() && !noncesDir.mkdirs()) + throw new IOException(s"Cannot create nonces directory: ${noncesDir.getCanonicalPath}") + + val writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(noncesFile), StandardCharsets.UTF_8), false) + try { + for ((p, n) <- values) + writer.println(s"$p $n") + } finally writer.close() + } + + def load(): Unit = { + values.clear() + + if (!noncesFile.exists()) + return + + val reader = new BufferedReader(new InputStreamReader(new FileInputStream(noncesFile), StandardCharsets.UTF_8)) + Iterator.continually(reader.readLine()) + .takeWhile(_ != null) + .map(_.split(" ", 2)) + .flatMap { + case Array(p, n) => Seq(p -> n) + case _ => Nil + }.foreach(values += _) + } + + private def generateNonce(): String = { + val buf = new Array[Byte](16) + rng.nextBytes(buf) + new String(Hex.encodeHex(buf, true)) + } + + def nonce(player: String) = values.get(player.toLowerCase) + + def isWhitelisted(player: String) = values.contains(player.toLowerCase) + + def whitelist: collection.Set[String] = values.keySet + + def add(player: String): Unit = { + if (!values.contains(player.toLowerCase)) { + values.put(player.toLowerCase, generateNonce()) + save() + } + } + + def remove(player: String): Unit = { + if (values.remove(player.toLowerCase).isDefined) + save() + } + + def invalidate(player: String): Unit = { + if (values.contains(player.toLowerCase)) { + values.put(player.toLowerCase, generateNonce()) + save() + } + } + + def checkAccess(ctxOpt: Option[DebugCard.AccessContext]): Option[String] = ctxOpt match { + case Some(ctx) => values.get(ctx.player) match { + case Some(x) => + if (x == ctx.nonce) None + else Some("debug card is invalidated, please re-bind it to yourself") + case None => Some("you are not whitelisted to use debug card") + } + + case None => Some("debug card is whitelisted, Shift+Click with it to bind card to yourself") + } + } + } } diff --git a/src/main/scala/li/cil/oc/common/item/DebugCard.scala b/src/main/scala/li/cil/oc/common/item/DebugCard.scala index 837793bbb..7aaf1c669 100644 --- a/src/main/scala/li/cil/oc/common/item/DebugCard.scala +++ b/src/main/scala/li/cil/oc/common/item/DebugCard.scala @@ -2,23 +2,41 @@ package li.cil.oc.common.item import java.util +import li.cil.oc.Settings +import li.cil.oc.Settings.DebugCardAccess import li.cil.oc.common.item.data.DebugCardData import net.minecraft.entity.player.EntityPlayer import net.minecraft.item.ItemStack import net.minecraft.world.World +import li.cil.oc.server.command.string2text +import li.cil.oc.server.component.{DebugCard => CDebugCard} class DebugCard(val parent: Delegator) extends traits.Delegate { override protected def tooltipExtended(stack: ItemStack, tooltip: util.List[String]): Unit = { super.tooltipExtended(stack, tooltip) val data = new DebugCardData(stack) - data.player.foreach(name => tooltip.add(s"§8$name§r")) + data.access.foreach(access => tooltip.add(s"§8${access.player}§r")) } override def onItemRightClick(stack: ItemStack, world: World, player: EntityPlayer): ItemStack = { if (player.isSneaking) { val data = new DebugCardData(stack) - if (data.player.contains(player.getCommandSenderName)) data.player = None - else data.player = Option(player.getCommandSenderName) + val name = player.getCommandSenderName + + if (data.access.exists(_.player == name)) data.access = None + else data.access = + Some(CDebugCard.AccessContext(name, Settings.get.debugCardAccess match { + case wl: DebugCardAccess.Whitelist => wl.nonce(name) match { + case Some(n) => n + case None => + player.addChatComponentMessage("§cYou are not whitelisted to use debug card") + player.swingItem() + return stack + } + + case _ => "" + })) + data.save(stack) player.swingItem() } diff --git a/src/main/scala/li/cil/oc/common/item/data/DebugCardData.scala b/src/main/scala/li/cil/oc/common/item/data/DebugCardData.scala index 1b6b339ff..839ef99e2 100644 --- a/src/main/scala/li/cil/oc/common/item/data/DebugCardData.scala +++ b/src/main/scala/li/cil/oc/common/item/data/DebugCardData.scala @@ -1,7 +1,7 @@ package li.cil.oc.common.item.data -import li.cil.oc.Constants -import li.cil.oc.Settings +import li.cil.oc.{Constants, Settings} +import li.cil.oc.server.component.DebugCard.AccessContext import net.minecraft.item.ItemStack import net.minecraft.nbt.NBTTagCompound @@ -11,19 +11,16 @@ class DebugCardData extends ItemData(Constants.ItemName.DebugCard) { load(stack) } - var player: Option[String] = None + var access: Option[AccessContext] = None - override def load(nbt: NBTTagCompound) { - val tag = dataTag(nbt) - if (tag.hasKey(Settings.namespace + "player")) { - player = Option(tag.getString(Settings.namespace + "player")) - } + override def load(nbt: NBTTagCompound): Unit = { + access = AccessContext.load(dataTag(nbt)) } - override def save(nbt: NBTTagCompound) { + override def save(nbt: NBTTagCompound): Unit = { val tag = dataTag(nbt) - tag.removeTag(Settings.namespace + "player") - player.foreach(tag.setString(Settings.namespace + "player", _)) + AccessContext.remove(tag) + access.foreach(_.save(tag)) } private def dataTag(nbt: NBTTagCompound) = { diff --git a/src/main/scala/li/cil/oc/server/command/CommandHandler.scala b/src/main/scala/li/cil/oc/server/command/CommandHandler.scala index 0207cc5a1..509e7d3a8 100644 --- a/src/main/scala/li/cil/oc/server/command/CommandHandler.scala +++ b/src/main/scala/li/cil/oc/server/command/CommandHandler.scala @@ -10,5 +10,6 @@ object CommandHandler { e.registerServerCommand(NonDisassemblyAgreementCommand) e.registerServerCommand(WirelessRenderingCommand) e.registerServerCommand(SpawnComputerCommand) + e.registerServerCommand(DebugWhitelistCommand) } } diff --git a/src/main/scala/li/cil/oc/server/command/DebugWhitelistCommand.scala b/src/main/scala/li/cil/oc/server/command/DebugWhitelistCommand.scala new file mode 100644 index 000000000..0bf8305d9 --- /dev/null +++ b/src/main/scala/li/cil/oc/server/command/DebugWhitelistCommand.scala @@ -0,0 +1,52 @@ +package li.cil.oc.server.command + +import li.cil.oc.Settings +import li.cil.oc.Settings.DebugCardAccess +import li.cil.oc.common.command.SimpleCommand +import net.minecraft.command.{ICommandSender, WrongUsageException} + +object DebugWhitelistCommand extends SimpleCommand("oc_debugWhitelist") { + // Required OP levels: + // to revoke your cards - 0 + // to do other whitelist manipulation - 2 + + override def getRequiredPermissionLevel = 0 + private def isOp(sender: ICommandSender) = getOpLevel(sender) >= 2 + + override def getCommandUsage(sender: ICommandSender): String = + if (isOp(sender)) name + " [revoke|add|remove] OR " + name + " [revoke|list]" + else name + " revoke" + + override def processCommand(sender: ICommandSender, args: Array[String]): Unit = { + val wl = Settings.get.debugCardAccess match { + case w: DebugCardAccess.Whitelist => w + case _ => throw new WrongUsageException("§cDebug card whitelisting is not enabled.") + } + + def revokeUser(player: String): Unit = { + if (wl.isWhitelisted(player)) { + wl.invalidate(player) + sender.addChatMessage("§aAll your debug cards were invalidated.") + } else sender.addChatMessage("§cYou are not whitelisted to use debug card.") + } + + args match { + case Array("revoke") => revokeUser(sender.getCommandSenderName) + case Array("revoke", player) if isOp(sender) => revokeUser(player) + case Array("list") if isOp(sender) => + val players = wl.whitelist + if (players.nonEmpty) + sender.addChatMessage("§aCurrently whitelisted players: §e" + players.mkString(", ")) + else + sender.addChatMessage("§cThere is no currently whitelisted players.") + case Array("add", player) if isOp(sender) => + wl.add(player) + sender.addChatMessage("§aPlayer was added to whitelist.") + case Array("remove", player) if isOp(sender) => + wl.remove(player) + sender.addChatMessage("§aPlayer was removed from whitelist") + case _ => + sender.addChatMessage("§e" + getCommandUsage(sender)) + } + } +} diff --git a/src/main/scala/li/cil/oc/server/command/package.scala b/src/main/scala/li/cil/oc/server/command/package.scala new file mode 100644 index 000000000..304ca02d3 --- /dev/null +++ b/src/main/scala/li/cil/oc/server/command/package.scala @@ -0,0 +1,32 @@ +package li.cil.oc.server + +import java.util.logging.Level + +import cpw.mods.fml.common.FMLLog +import net.minecraft.command.ICommandSender +import net.minecraft.entity.player.EntityPlayerMP +import net.minecraft.server.MinecraftServer +import net.minecraft.server.management.UserListOpsEntry +import net.minecraft.util.{ChatComponentText, IChatComponent} + +import scala.language.implicitConversions + +package object command { + implicit def string2text(s: String): IChatComponent = new ChatComponentText(s) + + def getOpLevel(sender: ICommandSender): Int = { + // Shitty minecraft server logic & shitty minecraft server code. + val srv = MinecraftServer.getServer + if (srv.isSinglePlayer && srv.worldServers.head.getWorldInfo.areCommandsAllowed && + srv.getServerOwner.equalsIgnoreCase(sender.getCommandSenderName) /* || srv.commandsAllowedForAll */ ) + return 4 + + sender match { + case _: MinecraftServer => 4 + case p: EntityPlayerMP => + val e = srv.getConfigurationManager.func_152603_m.func_152683_b(p.getGameProfile) + if (e == null) 0 else e.asInstanceOf[UserListOpsEntry].func_152644_a() + case _ => 0 + } + } +} diff --git a/src/main/scala/li/cil/oc/server/component/DebugCard.scala b/src/main/scala/li/cil/oc/server/component/DebugCard.scala index fd5b71965..99ade4b53 100644 --- a/src/main/scala/li/cil/oc/server/component/DebugCard.scala +++ b/src/main/scala/li/cil/oc/server/component/DebugCard.scala @@ -16,7 +16,7 @@ import li.cil.oc.api.network.SidedEnvironment import li.cil.oc.api.network.Visibility import li.cil.oc.api.prefab import li.cil.oc.api.prefab.AbstractValue -import li.cil.oc.server.component.DebugCard.CommandSender +import li.cil.oc.server.component.DebugCard.{AccessContext, CommandSender} import li.cil.oc.util.BlockPosition import li.cil.oc.util.ExtendedArguments._ import li.cil.oc.util.ExtendedNBT._ @@ -57,7 +57,9 @@ class DebugCard(host: EnvironmentHost) extends prefab.ManagedEnvironment { private var remoteNodePosition: Option[(Int, Int, Int)] = None // Player this card is bound to (if any) to use for permissions. - var player: Option[String] = None + implicit var access: Option[AccessContext] = None + + def player = access.map(_.player) private lazy val CommandSender = { def defaultFakePlayer = FakePlayerFactory.get(host.world.asInstanceOf[WorldServer], Settings.get.fakePlayerProfile) @@ -72,67 +74,67 @@ class DebugCard(host: EnvironmentHost) extends prefab.ManagedEnvironment { // ----------------------------------------------------------------------- // - import li.cil.oc.server.component.DebugCard.checkEnabled + import li.cil.oc.server.component.DebugCard.checkAccess @Callback(doc = """function(value:number):number -- Changes the component network's energy buffer by the specified delta.""") def changeBuffer(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() result(node.changeBuffer(args.checkDouble(0))) } @Callback(doc = """function():number -- Get the container's X position in the world.""") def getX(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() result(host.xPosition) } @Callback(doc = """function():number -- Get the container's Y position in the world.""") def getY(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() result(host.yPosition) } @Callback(doc = """function():number -- Get the container's Z position in the world.""") def getZ(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() result(host.zPosition) } @Callback(doc = """function([id:number]):userdata -- Get the world object for the specified dimension ID, or the container's.""") def getWorld(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() if (args.count() > 0) result(new DebugCard.WorldValue(DimensionManager.getWorld(args.checkInteger(0)))) else result(new DebugCard.WorldValue(host.world)) } @Callback(doc = """function():table -- Get a list of all world IDs, loaded and unloaded.""") def getWorlds(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() result(DimensionManager.getStaticDimensionIDs) } @Callback(doc = """function(name:string):userdata -- Get the entity of a player.""") def getPlayer(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() result(new DebugCard.PlayerValue(args.checkString(0))) } @Callback(doc = """function():table -- Get a list of currently logged-in players.""") def getPlayers(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() result(MinecraftServer.getServer.getAllUsernames) } @Callback(doc = """function(name:string):boolean -- Get whether a mod or API is loaded.""") def isModLoaded(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() val name = args.checkString(0) result(Loader.isModLoaded(name) || ModAPIManager.INSTANCE.hasAPI(name)) } @Callback(doc = """function(command:string):number -- Runs an arbitrary command using a fake player.""") def runCommand(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() val commands = if (args.isTable(0)) collectionAsScalaIterable(args.checkTable(0).values()) else Iterable(args.checkString(0)) @@ -149,7 +151,7 @@ class DebugCard(host: EnvironmentHost) extends prefab.ManagedEnvironment { @Callback(doc = """function(x:number, y:number, z:number):boolean -- Connect the debug card to the block at the specified coordinates.""") def connectToBlock(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() val x = args.checkInteger(0) val y = args.checkInteger(1) val z = args.checkInteger(2) @@ -177,7 +179,7 @@ class DebugCard(host: EnvironmentHost) extends prefab.ManagedEnvironment { @Callback(doc = """function():userdata -- Test method for user-data and general value conversion.""") def test(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() val v1 = mutable.Map("a" -> true, "b" -> "test") val v2 = Map(10 -> "zxc", false -> v1) @@ -215,42 +217,63 @@ class DebugCard(host: EnvironmentHost) extends prefab.ManagedEnvironment { override def load(nbt: NBTTagCompound): Unit = { super.load(nbt) + access = AccessContext.load(nbt) if (nbt.hasKey(Settings.namespace + "remoteX")) { val x = nbt.getInteger(Settings.namespace + "remoteX") val y = nbt.getInteger(Settings.namespace + "remoteY") val z = nbt.getInteger(Settings.namespace + "remoteZ") remoteNodePosition = Some((x, y, z)) } - if (nbt.hasKey(Settings.namespace + "player")) { - player = Option(nbt.getString(Settings.namespace + "player")) - } } override def save(nbt: NBTTagCompound): Unit = { super.save(nbt) + access.foreach(_.save(nbt)) remoteNodePosition.foreach { case (x, y, z) => nbt.setInteger(Settings.namespace + "remoteX", x) nbt.setInteger(Settings.namespace + "remoteY", y) nbt.setInteger(Settings.namespace + "remoteZ", z) } - player.foreach(nbt.setString(Settings.namespace + "player", _)) } } object DebugCard { + def checkAccess()(implicit ctx: Option[AccessContext]) = + for (msg <- Settings.get.debugCardAccess.checkAccess(ctx)) + throw new Exception(msg) - import li.cil.oc.util.ResultWrapper.result + object AccessContext { + def remove(nbt: NBTTagCompound): Unit = { + nbt.removeTag(Settings.namespace + "player") + nbt.removeTag(Settings.namespace + "accessNonce") + } - def checkEnabled() = if (!Settings.get.enableDebugCard) throw new Exception("debug card functionality is disabled") + def load(nbt: NBTTagCompound): Option[AccessContext] = { + if (nbt.hasKey(Settings.namespace + "player")) + Some(AccessContext( + nbt.getString(Settings.namespace + "player"), + nbt.getString(Settings.namespace + "accessNonce") + )) + else + None + } + } - class PlayerValue(var name: String) extends prefab.AbstractValue { - def this() = this("") // For loading. + case class AccessContext(player: String, nonce: String) { + def save(nbt: NBTTagCompound): Unit = { + nbt.setString(Settings.namespace + "player", player) + nbt.setString(Settings.namespace + "accessNonce", nonce) + } + } + + class PlayerValue(var name: String)(implicit var ctx: Option[AccessContext]) extends prefab.AbstractValue { + def this() = this("")(None) // For loading. // ----------------------------------------------------------------------- // def withPlayer(f: (EntityPlayerMP) => Array[AnyRef]) = { - checkEnabled() + checkAccess() MinecraftServer.getServer.getConfigurationManager.func_152612_a(name) match { case player: EntityPlayerMP => f(player) case _ => result(Unit, "player is offline") @@ -304,93 +327,95 @@ object DebugCard { override def load(nbt: NBTTagCompound) { super.load(nbt) + ctx = AccessContext.load(nbt) name = nbt.getString("name") } override def save(nbt: NBTTagCompound) { super.save(nbt) + ctx.foreach(_.save(nbt)) nbt.setString("name", name) } } - class WorldValue(var world: World) extends prefab.AbstractValue { - def this() = this(null) // For loading. + class WorldValue(var world: World)(implicit var ctx: Option[AccessContext]) extends prefab.AbstractValue { + def this() = this(null)(None) // For loading. // ----------------------------------------------------------------------- // @Callback(doc = """function():number -- Gets the numeric id of the current dimension.""") def getDimensionId(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() result(world.provider.dimensionId) } @Callback(doc = """function():string -- Gets the name of the current dimension.""") def getDimensionName(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() result(world.provider.getDimensionName) } @Callback(doc = """function():number -- Gets the seed of the world.""") def getSeed(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() result(world.getSeed) } @Callback(doc = """function():boolean -- Returns whether it is currently raining.""") def isRaining(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() result(world.isRaining) } @Callback(doc = """function(value:boolean) -- Sets whether it is currently raining.""") def setRaining(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() world.getWorldInfo.setRaining(args.checkBoolean(0)) null } @Callback(doc = """function():boolean -- Returns whether it is currently thundering.""") def isThundering(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() result(world.isThundering) } @Callback(doc = """function(value:boolean) -- Sets whether it is currently thundering.""") def setThundering(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() world.getWorldInfo.setThundering(args.checkBoolean(0)) null } @Callback(doc = """function():number -- Get the current world time.""") def getTime(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() result(world.getWorldTime) } @Callback(doc = """function(value:number) -- Set the current world time.""") def setTime(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() world.setWorldTime(args.checkDouble(0).toLong) null } @Callback(doc = """function():number, number, number -- Get the current spawn point coordinates.""") def getSpawnPoint(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() result(world.getWorldInfo.getSpawnX, world.getWorldInfo.getSpawnY, world.getWorldInfo.getSpawnZ) } @Callback(doc = """function(x:number, y:number, z:number) -- Set the spawn point coordinates.""") def setSpawnPoint(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() world.getWorldInfo.setSpawnPosition(args.checkInteger(0), args.checkInteger(1), args.checkInteger(2)) null } @Callback(doc = """function(x:number, y:number, z:number, sound:string, range:number) -- Play a sound at the specified coordinates.""") def playSoundAt(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() val (x, y, z) = (args.checkInteger(0), args.checkInteger(1), args.checkInteger(2)) val sound = args.checkString(3) val range = args.checkInteger(4) @@ -402,25 +427,25 @@ object DebugCard { @Callback(doc = """function(x:number, y:number, z:number):number -- Get the ID of the block at the specified coordinates.""") def getBlockId(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() result(Block.getIdFromBlock(world.getBlock(args.checkInteger(0), args.checkInteger(1), args.checkInteger(2)))) } @Callback(doc = """function(x:number, y:number, z:number):number -- Get the metadata of the block at the specified coordinates.""") def getMetadata(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() result(world.getBlockMetadata(args.checkInteger(0), args.checkInteger(1), args.checkInteger(2))) } @Callback(doc = """function(x:number, y:number, z:number):number -- Check whether the block at the specified coordinates is loaded.""") def isLoaded(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() result(world.blockExists(args.checkInteger(0), args.checkInteger(1), args.checkInteger(2))) } @Callback(doc = """function(x:number, y:number, z:number):number -- Check whether the block at the specified coordinates has a tile entity.""") def hasTileEntity(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() val (x, y, z) = (args.checkInteger(0), args.checkInteger(1), args.checkInteger(2)) val block = world.getBlock(x, y, z) result(block != null && block.hasTileEntity(world.getBlockMetadata(x, y, z))) @@ -428,7 +453,7 @@ object DebugCard { @Callback(doc = """function(x:number, y:number, z:number):table -- Get the NBT of the block at the specified coordinates.""") def getTileNBT(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() val (x, y, z) = (args.checkInteger(0), args.checkInteger(1), args.checkInteger(2)) world.getTileEntity(x, y, z) match { case tileEntity: TileEntity => result(toNbt(tileEntity.writeToNBT _).toTypedMap) @@ -438,7 +463,7 @@ object DebugCard { @Callback(doc = """function(x:number, y:number, z:number, nbt:table):boolean -- Set the NBT of the block at the specified coordinates.""") def setTileNBT(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() val (x, y, z) = (args.checkInteger(0), args.checkInteger(1), args.checkInteger(2)) world.getTileEntity(x, y, z) match { case tileEntity: TileEntity => @@ -456,25 +481,25 @@ object DebugCard { @Callback(doc = """function(x:number, y:number, z:number):number -- Get the light opacity of the block at the specified coordinates.""") def getLightOpacity(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() result(world.getBlockLightOpacity(args.checkInteger(0), args.checkInteger(1), args.checkInteger(2))) } @Callback(doc = """function(x:number, y:number, z:number):number -- Get the light value (emission) of the block at the specified coordinates.""") def getLightValue(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() result(world.getBlockLightValue(args.checkInteger(0), args.checkInteger(1), args.checkInteger(2))) } @Callback(doc = """function(x:number, y:number, z:number):number -- Get whether the block at the specified coordinates is directly under the sky.""") def canSeeSky(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() result(world.canBlockSeeTheSky(args.checkInteger(0), args.checkInteger(1), args.checkInteger(2))) } @Callback(doc = """function(x:number, y:number, z:number, id:number or string, meta:number):number -- Set the block at the specified coordinates.""") def setBlock(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() val block = if (args.isInteger(3)) Block.getBlockById(args.checkInteger(3)) else Block.getBlockFromName(args.checkString(3)) val metadata = args.checkInteger(4) result(world.setBlock(args.checkInteger(0), args.checkInteger(1), args.checkInteger(2), block, metadata, 3)) @@ -482,7 +507,7 @@ object DebugCard { @Callback(doc = """function(x1:number, y1:number, z1:number, x2:number, y2:number, z2:number, id:number or string, meta:number):number -- Set all blocks in the area defined by the two corner points (x1, y1, z1) and (x2, y2, z2).""") def setBlocks(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() val (xMin, yMin, zMin) = (args.checkInteger(0), args.checkInteger(1), args.checkInteger(2)) val (xMax, yMax, zMax) = (args.checkInteger(3), args.checkInteger(4), args.checkInteger(5)) val block = if (args.isInteger(6)) Block.getBlockById(args.checkInteger(6)) else Block.getBlockFromName(args.checkString(6)) @@ -501,7 +526,7 @@ object DebugCard { @Callback(doc = """function(id:string, count:number, damage:number, nbt:string, x:number, y:number, z:number, side:number):boolean - Insert an item stack into the inventory at the specified location. NBT tag is expected in JSON format.""") def insertItem(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() val item = Item.itemRegistry.getObject(args.checkString(0)).asInstanceOf[Item] if (item == null) { throw new IllegalArgumentException("invalid item id") @@ -523,7 +548,7 @@ object DebugCard { @Callback(doc = """function(x:number, y:number, z:number, slot:number[, count:number]):number - Reduce the size of an item stack in the inventory at the specified location.""") def removeItem(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() val position = BlockPosition(args.checkDouble(0), args.checkDouble(1), args.checkDouble(2), world) InventoryUtils.inventoryAt(position) match { case Some(inventory) => @@ -538,7 +563,7 @@ object DebugCard { @Callback(doc = """function(id:string, amount:number, x:number, y:number, z:number, side:number):boolean - Insert some fluid into the tank at the specified location.""") def insertFluid(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() val fluid = FluidRegistry.getFluid(args.checkString(0)) if (fluid == null) { throw new IllegalArgumentException("invalid fluid id") @@ -554,7 +579,7 @@ object DebugCard { @Callback(doc = """function(amount:number, x:number, y:number, z:number, side:number):boolean - Remove some fluid from a tank at the specified location.""") def removeFluid(context: Context, args: Arguments): Array[AnyRef] = { - checkEnabled() + checkAccess() val amount = args.checkInteger(0) val position = BlockPosition(args.checkDouble(1), args.checkDouble(2), args.checkDouble(3), world) val side = args.checkSideAny(4) @@ -568,11 +593,13 @@ object DebugCard { override def load(nbt: NBTTagCompound) { super.load(nbt) + ctx = AccessContext.load(nbt) world = DimensionManager.getWorld(nbt.getInteger("dimension")) } override def save(nbt: NBTTagCompound) { super.save(nbt) + ctx.foreach(_.save(nbt)) nbt.setInteger("dimension", world.provider.dimensionId) } }