diff --git a/src/main/java/org/luaj/vm3/lib/StringLib.java b/src/main/java/org/luaj/vm3/lib/StringLib.java index 317d68920..afde5979b 100644 --- a/src/main/java/org/luaj/vm3/lib/StringLib.java +++ b/src/main/java/org/luaj/vm3/lib/StringLib.java @@ -1155,7 +1155,7 @@ public class StringLib extends TwoArgFunction { if ( poff == plen || poff + 1 == plen ) { error( "unbalanced pattern" ); } - if ( s.luaByte( soff ) != p.luaByte( poff ) ) + if ( soff >= s.length() || s.luaByte( soff ) != p.luaByte( poff ) ) return -1; else { int b = p.luaByte( poff ); diff --git a/src/main/resources/assets/opencomputers/lang/de_DE.lang b/src/main/resources/assets/opencomputers/lang/de_DE.lang index c9f1bc053..2b7bc3535 100644 --- a/src/main/resources/assets/opencomputers/lang/de_DE.lang +++ b/src/main/resources/assets/opencomputers/lang/de_DE.lang @@ -12,6 +12,7 @@ oc:tile.Case2.name=Computergehäuse (Stufe 3) oc:tile.Charger.name=Ladestation oc:tile.DiskDrive.name=Diskettenlaufwerk oc:tile.Keyboard.name=Tastatur +oc:tile.Hologram.name=Hologrammprojektor oc:tile.PowerConverter.name=Leistungswandler oc:tile.PowerDistributor.name=Stromverteiler oc:tile.Redstone.name=Redstone-I/O @@ -136,6 +137,7 @@ oc:tooltip.GraphicsCard=Erlaubt es, den angezeigten Inhalt von Bildschirmen zu oc:tooltip.InternetCard=Diese Karte erlaubt es, HTTP-Anfragen zu senden und echte TCP Sockets zu verwenden. oc:tooltip.IronNugget=Ein Nugget, das aus Eisen besteht, darum wird es ja auch Eisennugget genannt, duh... oc:tooltip.Keyboard=Kann an Bildschirmen befestigt werden, um auf ihnen zu tippen. +oc:tooltip.Hologram=Ein Volumendisplay das beliebige, von Computern festgelegte Voxelstrukturen anzeigt.[nl] Auflösung: §f48x32x48§7. [nl] Farbtiefe: §fMonochrom§7. oc:tooltip.Memory=Braucht ein jeder Computer, um zu starten. Je mehr vorhanden, desto komplexere Programme können ausgeführt werden. oc:tooltip.Microchip=Tritt auch unter dem Alias Integrierter Schaltkreis auf. Keine Ahnung, warum das auch mit Redstone klappt, aber es funktioniert. oc:tooltip.NetworkCard=Erlaubt es Computern, die über mehrere Blöcke miteinander verbunden sind (z.B. Kabel), mittels Netzwerknachrichten zu kommunizieren. diff --git a/src/main/resources/assets/opencomputers/lang/en_US.lang b/src/main/resources/assets/opencomputers/lang/en_US.lang index b5f8195d6..a1d56e273 100644 --- a/src/main/resources/assets/opencomputers/lang/en_US.lang +++ b/src/main/resources/assets/opencomputers/lang/en_US.lang @@ -12,6 +12,7 @@ oc:tile.Case2.name=Computer Case (Tier 3) oc:tile.Charger.name=Charger oc:tile.DiskDrive.name=Disk Drive oc:tile.Keyboard.name=Keyboard +oc:tile.Hologram.name=Hologram Projector oc:tile.PowerConverter.name=Power Converter oc:tile.PowerDistributor.name=Power Distributor oc:tile.Redstone.name=Redstone I/O @@ -136,6 +137,7 @@ oc:tooltip.GraphicsCard=Used to change what's displayed on screens.[nl] Maximum oc:tooltip.InternetCard=This card allows making HTTP requests and using real TCP sockets. oc:tooltip.IronNugget=A nugget made of iron, that's why it's called an Iron Nugget, duh... oc:tooltip.Keyboard=Can be attached to screens to allow typing on them. +oc:tooltip.Hologram=A volumetric display that can be controlled by computers to display arbitrary voxel structures.[nl] Resolution: §f48x32x48§7. [nl] Color depth: §fMonochrome§7. oc:tooltip.Memory=Required to get computers to run. The more you have, the more complex the programs you can run. oc:tooltip.Microchip=The chip formerly known as Integrated Circuit. I have no idea why this works with redstone, but it does. oc:tooltip.NetworkCard=Allows distant computers connected by other blocks (such as cable) to communicate by sending messages to each other. diff --git a/src/main/resources/assets/opencomputers/lua/component/internet/bin/irc.lua b/src/main/resources/assets/opencomputers/lua/component/internet/bin/irc.lua index ebba420c7..5eddd65bb 100644 --- a/src/main/resources/assets/opencomputers/lua/component/internet/bin/irc.lua +++ b/src/main/resources/assets/opencomputers/lua/component/internet/bin/irc.lua @@ -41,12 +41,8 @@ end -- utility method for reply tracking tables. function autocreate(table, key) - local value = rawget(table, key) - if not value then - value = {} - rawset(table, key, value) - end - return value + table[key] = {} + return table[key] end -- extract nickname from identity. @@ -172,36 +168,36 @@ local function handleCommand(prefix, command, args, message) print(message) elseif command == commands.RPL_WHOISUSER then local nick = args[2]:lower() - whois[nick].nick = nick + whois[nick].nick = args[2] whois[nick].user = args[3] whois[nick].host = args[4] whois[nick].realName = message elseif command == commands.RPL_WHOISSERVER then - local nick = args[2] + local nick = args[2]:lower() whois[nick].server = args[3] whois[nick].serverInfo = message elseif command == commands.RPL_WHOISOPERATOR then - local nick = args[2] + local nick = args[2]:lower() whois[nick].isOperator = true elseif command == commands.RPL_WHOISIDLE then - local nick = args[2] + local nick = args[2]:lower() whois[nick].idle = tonumber(args[3]) elseif command == commands.RPL_ENDOFWHOIS then - local nick = args[2] + local nick = args[2]:lower() local info = whois[nick] - print("Nick: " .. info.nick) - print("User name: " .. info.user) - print("Real name: " .. info.realName) - print("Host: " .. info.host) - print("Server: " .. info.server .. "(" .. info.serverInfo .. ")") - print("Channels: " .. info.channels) - print("Idle for: " .. info.idle) + if info.nick then print("Nick: " .. info.nick) end + if info.user then print("User name: " .. info.user) end + if info.realName then print("Real name: " .. info.realName) end + if info.host then print("Host: " .. info.host) end + if info.server then print("Server: " .. info.server .. (info.serverInfo and (" (" .. info.serverInfo .. ")") or "")) end + if info.channels then print("Channels: " .. info.channels) end + if info.idle then print("Idle for: " .. info.idle) end whois[nick] = nil elseif command == commands.RPL_WHOISCHANNELS then - local nick = args[1] + local nick = args[2]:lower() whois[nick].channels = message elseif command == commands.RPL_CHANNELMODEIS then - print("Channel mode for " .. args[1] .. ": " .. args[2] .. "(" .. args[3] .. ")") + print("Channel mode for " .. args[1] .. ": " .. args[2] .. " (" .. args[3] .. ")") elseif command == commands.RPL_NOTOPIC then print("No topic is set for " .. args[1] .. ".") elseif command == commands.RPL_TOPIC then @@ -310,7 +306,9 @@ local result, reason = pcall(function() print("[" .. (target or "?") .. "] me: " .. line, true) if line:lower():sub(1, 5) == "/msg " then local user, message = line:sub(6):match("^(%S+) (.+)$") - message = text.trim(message) + if message then + message = text.trim(message) + end if not user or not message or message == "" then print("Invalid use of /msg. Usage: /msg nick|channel message.") line = "" @@ -342,7 +340,7 @@ local result, reason = pcall(function() print("Error: " .. tostring(reason)) elseif type(reason) == "function" then callback = reason - else + elseif reason then line = tostring(reason) end end @@ -368,7 +366,9 @@ if sock then sock:write("QUIT\r\n") sock:close() end -event.cancel(timer) +if timer then + event.cancel(timer) +end if not result then error(reason, 0) diff --git a/src/main/resources/assets/opencomputers/lua/kernel.lua b/src/main/resources/assets/opencomputers/lua/kernel.lua index baec22ae5..cc9946c26 100644 --- a/src/main/resources/assets/opencomputers/lua/kernel.lua +++ b/src/main/resources/assets/opencomputers/lua/kernel.lua @@ -1,5 +1,5 @@ local hookInterval = 100 -local deadline = 0 +local deadline = math.huge local function checkDeadline() if computer.realTime() > deadline then debug.sethook(coroutine.running(), checkDeadline, "", 1) @@ -91,7 +91,11 @@ sandbox = { loadfile = nil, -- in boot/*_base.lua next = next, pairs = pairs, - pcall = pcall, + pcall = function(...) + local result = table.pack(pcall(...)) + checkDeadline() + return table.unpack(result, 1, result.n) + end, print = nil, -- in boot/*_base.lua rawequal = rawequal, rawget = rawget, @@ -103,7 +107,11 @@ sandbox = { tostring = tostring, type = type, _VERSION = "Lua 5.2", - xpcall = xpcall, + xpcall = function(...) + local result = table.pack(xpcall(...)) + checkDeadline() + return table.unpack(result, 1, result.n) + end, coroutine = { create = function(f) diff --git a/src/main/resources/assets/opencomputers/lua/rom/bin/sh.lua b/src/main/resources/assets/opencomputers/lua/rom/bin/sh.lua index 2ec7094d8..342f9f04b 100644 --- a/src/main/resources/assets/opencomputers/lua/rom/bin/sh.lua +++ b/src/main/resources/assets/opencomputers/lua/rom/bin/sh.lua @@ -121,7 +121,7 @@ if #args == 0 and (io.input() == io.stdin or options.i) and not options.c then elseif command ~= "" then local result, reason = execute(command) if not result then - io.stderr:write(reason .. "\n") + io.stderr:write((tostring(reason) or "unknown error").. "\n") elseif term.getCursor() > 1 then term.write("\n") end diff --git a/src/main/resources/assets/opencomputers/lua/rom/init.lua b/src/main/resources/assets/opencomputers/lua/rom/init.lua index 48cc1be29..99afa57f3 100644 --- a/src/main/resources/assets/opencomputers/lua/rom/init.lua +++ b/src/main/resources/assets/opencomputers/lua/rom/init.lua @@ -13,8 +13,9 @@ while true do io.write(_OSVERSION .. " (" .. math.floor(computer.totalMemory() / 1024) .. "k RAM)\n") local result, reason = os.execute(os.getenv("SHELL") .. " -") if not result then - io.stderr:write((reason or "unknown error") .. "\n") + io.stderr:write((tostring(reason) or "unknown error") .. "\n") print("Press any key to continue.") + os.sleep(0.5) event.pull("key") end end \ No newline at end of file diff --git a/src/main/resources/assets/opencomputers/lua/rom/lib/term.lua b/src/main/resources/assets/opencomputers/lua/rom/lib/term.lua index 111097326..67a5f98d8 100644 --- a/src/main/resources/assets/opencomputers/lua/rom/lib/term.lua +++ b/src/main/resources/assets/opencomputers/lua/rom/lib/term.lua @@ -338,7 +338,7 @@ function term.write(value, wrap) end local blink = term.getCursorBlink() term.setCursorBlink(false) - local line, nl = value + local line, nl repeat local wrapAfter, margin = math.huge, math.huge if wrap then @@ -356,7 +356,7 @@ function term.write(value, wrap) component.gpu.fill(1, h, w, 1, " ") cursorY = h end - until not wrap or not value + until not value term.setCursorBlink(blink) end diff --git a/src/main/resources/assets/opencomputers/recipes/default.recipes b/src/main/resources/assets/opencomputers/recipes/default.recipes index e901711bb..8534bb21f 100644 --- a/src/main/resources/assets/opencomputers/recipes/default.recipes +++ b/src/main/resources/assets/opencomputers/recipes/default.recipes @@ -271,7 +271,7 @@ powerDistributor { rack { input: [["oc:circuitTier2", "oc:componentCardWLan", "oc:circuitTier2"] [fenceIron, chest, fenceIron] - ["oc:craftingRouter", "oc:craftingCircuitBoardPrinted","oc:craftingPowerDistributor"]] + ["oc:craftingRouter", "oc:craftingCircuitBoardPrinted", "oc:craftingPowerDistributor"]] } redstone { input: [[ingotIron, blockRedstone, ingotIron] @@ -302,4 +302,9 @@ screen3 { input: [[obsidian, yellowDust, obsidian] [yellowDust, "oc:circuitTier3", glass] [obsidian, yellowDust, obsidian]] +} +hologram { + input: [[obsidian, glass, obsidian] + ["oc:craftingCircuitBoardPrinted", diamond, "oc:craftingCircuitBoardPrinted"] + ["oc:circuitTier3", blazeRod, "oc:circuitTier3"]] } \ No newline at end of file diff --git a/src/main/resources/assets/opencomputers/textures/blocks/hologram_effect.png b/src/main/resources/assets/opencomputers/textures/blocks/hologram_effect.png new file mode 100644 index 000000000..154039406 Binary files /dev/null and b/src/main/resources/assets/opencomputers/textures/blocks/hologram_effect.png differ diff --git a/src/main/resources/assets/opencomputers/textures/blocks/hologram_side.png b/src/main/resources/assets/opencomputers/textures/blocks/hologram_side.png new file mode 100644 index 000000000..99746701c Binary files /dev/null and b/src/main/resources/assets/opencomputers/textures/blocks/hologram_side.png differ diff --git a/src/main/resources/assets/opencomputers/textures/blocks/hologram_top.png b/src/main/resources/assets/opencomputers/textures/blocks/hologram_top.png new file mode 100644 index 000000000..cebbf9d19 Binary files /dev/null and b/src/main/resources/assets/opencomputers/textures/blocks/hologram_top.png differ diff --git a/src/main/resources/reference.conf b/src/main/resources/reference.conf index 0977db7cd..47ebf3c32 100644 --- a/src/main/resources/reference.conf +++ b/src/main/resources/reference.conf @@ -456,6 +456,11 @@ opencomputers { # tick. screen: 0.05 + # The amount of energy a hologram projetor consumes per tick. This + # is the cost if every column is lit. If not a single voxel is + # displayed the hologram projector will not drain energy. + hologram: 0.2 + # Energy it takes read one kilobyte from a file system. Note that non # I/O operations on file systems such as `list` or `getFreeSpace` do # *not* consume power. Note that this very much determines how much diff --git a/src/main/scala/li/cil/oc/Blocks.scala b/src/main/scala/li/cil/oc/Blocks.scala index 6a07c1970..d7dbb5a8f 100644 --- a/src/main/scala/li/cil/oc/Blocks.scala +++ b/src/main/scala/li/cil/oc/Blocks.scala @@ -21,6 +21,7 @@ object Blocks { var diskDrive: DiskDrive = _ var keyboard: Keyboard = _ var keyboardDeprecated: KeyboardDeprecated = _ + var hologram: Hologram = _ var powerConverter: PowerConverter = _ var powerDistributor: PowerDistributor = _ var redstone: Redstone = _ @@ -92,6 +93,7 @@ object Blocks { GameRegistry.registerTileEntity(classOf[tileentity.Charger], Settings.namespace + "charger") GameRegistry.registerTileEntity(classOf[tileentity.DiskDrive], Settings.namespace + "disk_drive") GameRegistry.registerTileEntity(classOf[tileentity.Keyboard], Settings.namespace + "keyboard") + GameRegistry.registerTileEntity(classOf[tileentity.Hologram], Settings.namespace + "hologram") GameRegistry.registerTileEntity(classOf[tileentity.PowerConverter], Settings.namespace + "power_converter") GameRegistry.registerTileEntity(classOf[tileentity.PowerDistributor], Settings.namespace + "power_distributor") GameRegistry.registerTileEntity(classOf[tileentity.Redstone], Settings.namespace + "redstone") @@ -100,6 +102,9 @@ object Blocks { GameRegistry.registerTileEntity(classOf[tileentity.Screen], Settings.namespace + "screen") GameRegistry.registerTileEntity(classOf[tileentity.Rack], Settings.namespace + "serverRack") + // v1.2.2 + hologram = Recipes.addBlockDelegate(new Hologram(blockSpecial), "hologram") + register("oc:craftingCable", cable.createItemStack()) register("oc:craftingCapacitor", capacitor.createItemStack()) register("oc:craftingCaseTier1", case1.createItemStack()) diff --git a/src/main/scala/li/cil/oc/Items.scala b/src/main/scala/li/cil/oc/Items.scala index 5b3582a8f..07a0784d4 100644 --- a/src/main/scala/li/cil/oc/Items.scala +++ b/src/main/scala/li/cil/oc/Items.scala @@ -94,9 +94,6 @@ object Items { upgradeGenerator = Recipes.addItemDelegate(new item.UpgradeGenerator(multi), "generatorUpgrade") ironNugget = new item.IronNugget(multi) - if (OreDictionary.getOres("nuggetIron").exists(ironNugget.createItemStack().isItemEqual)) { - Recipes.addItemDelegate(ironNugget, "nuggetIron") - } cuttingWire = Recipes.addItemDelegate(new item.CuttingWire(multi), "cuttingWire") acid = Recipes.addItemDelegate(new item.Acid(multi), "acid") @@ -181,6 +178,10 @@ object Items { register("oc:craftingAcid", acid.createItemStack()) register("oc:craftingGenerator", upgradeGenerator.createItemStack()) register("oc:craftingSolarGenerator", upgradeSolarGenerator.createItemStack()) + + if (OreDictionary.getOres("nuggetIron").exists(ironNugget.createItemStack().isItemEqual)) { + Recipes.addItemDelegate(ironNugget, "nuggetIron") + } } def register(name: String, item: ItemStack) { diff --git a/src/main/scala/li/cil/oc/Settings.scala b/src/main/scala/li/cil/oc/Settings.scala index 9515877e1..07ca382b4 100644 --- a/src/main/scala/li/cil/oc/Settings.scala +++ b/src/main/scala/li/cil/oc/Settings.scala @@ -111,6 +111,7 @@ class Settings(config: Config) { val robotCost = config.getDouble("power.cost.robot") max 0 val sleepCostFactor = config.getDouble("power.cost.sleepFactor") max 0 val screenCost = config.getDouble("power.cost.screen") max 0 + val hologramCost = config.getDouble("power.cost.hologram") max 0 val hddReadCost = (config.getDouble("power.cost.hddRead") max 0) / 1024 val hddWriteCost = (config.getDouble("power.cost.hddWrite") max 0) / 1024 val gpuSetCost = (config.getDouble("power.cost.gpuSet") max 0) / Settings.basicScreenPixels diff --git a/src/main/scala/li/cil/oc/client/PacketHandler.scala b/src/main/scala/li/cil/oc/client/PacketHandler.scala index 505f9e989..a65b8484f 100644 --- a/src/main/scala/li/cil/oc/client/PacketHandler.scala +++ b/src/main/scala/li/cil/oc/client/PacketHandler.scala @@ -15,21 +15,26 @@ import net.minecraftforge.common.util.ForgeDirection import org.lwjgl.input.Keyboard object PacketHandler extends CommonPacketHandler { + @SubscribeEvent + def onPacket(e: ClientCustomPacketEvent) = + onPacketData(e.packet.payload, Minecraft.getMinecraft.thePlayer) + protected override def world(player: EntityPlayer, dimension: Int) = { val world = player.worldObj if (world.provider.dimensionId == dimension) Some(world) else None } - @SubscribeEvent - def onPacket(e: ClientCustomPacketEvent) { - val p = new PacketParser(e.packet.payload, Minecraft.getMinecraft.thePlayer) + override def dispatch(p: PacketParser) { p.packetType match { case PacketType.AbstractBusState => onAbstractBusState(p) case PacketType.Analyze => onAnalyze(p) case PacketType.ChargerState => onChargerState(p) case PacketType.ComputerState => onComputerState(p) case PacketType.ComputerUserList => onComputerUserList(p) + case PacketType.HologramClear => onHologramClear(p) + case PacketType.HologramScale => onHologramScale(p) + case PacketType.HologramSet => onHologramSet(p) case PacketType.PowerState => onPowerState(p) case PacketType.RedstoneState => onRedstoneState(p) case PacketType.RobotAnimateSwing => onRobotAnimateSwing(p) @@ -104,6 +109,38 @@ object PacketHandler extends CommonPacketHandler { case _ => // Invalid packet. } + def onHologramClear(p: PacketParser) = + p.readTileEntity[Hologram]() match { + case Some(t) => + for (i <- 0 until t.volume.length) t.volume(i) = 0 + t.dirty = true + case _ => // Invalid packet. + } + + def onHologramScale(p: PacketParser) = + p.readTileEntity[Hologram]() match { + case Some(t) => + t.scale = p.readDouble() + t.dirty = true + case _ => // Invalid packet. + } + + def onHologramSet(p: PacketParser) = + p.readTileEntity[Hologram]() match { + case Some(t) => + val fromX = p.readByte(): Int + val untilX = p.readByte(): Int + val fromZ = p.readByte(): Int + val untilZ = p.readByte(): Int + for (x <- fromX until untilX) { + for (z <- fromZ until untilZ) { + t.volume(x + z * t.width) = p.readInt() + } + } + t.dirty = true + case _ => // Invalid packet. + } + def onPowerState(p: PacketParser) = p.readTileEntity[PowerInformation]() match { case Some(t) => diff --git a/src/main/scala/li/cil/oc/client/Proxy.scala b/src/main/scala/li/cil/oc/client/Proxy.scala index caf240e1e..47df2d307 100644 --- a/src/main/scala/li/cil/oc/client/Proxy.scala +++ b/src/main/scala/li/cil/oc/client/Proxy.scala @@ -32,6 +32,7 @@ private[oc] class Proxy extends CommonProxy { ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.Cable], CableRenderer) ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.Case], CaseRenderer) + ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.Hologram], HologramRenderer) ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.PowerDistributor], PowerDistributorRenderer) ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.Rack], RackRenderer) ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.RobotProxy], RobotRenderer) @@ -45,6 +46,7 @@ private[oc] class Proxy extends CommonProxy { override def postInit(e: FMLPostInitializationEvent) { super.postInit(e) + FMLCommonHandler.instance().bus().register(HologramRenderer) FMLCommonHandler.instance().bus().register(ScreenRenderer) if (Settings.get.rTreeDebugRenderer) { MinecraftForge.EVENT_BUS.register(WirelessNetworkDebugRenderer) diff --git a/src/main/scala/li/cil/oc/client/Textures.scala b/src/main/scala/li/cil/oc/client/Textures.scala index fe687c9bb..f088f4fc7 100644 --- a/src/main/scala/li/cil/oc/client/Textures.scala +++ b/src/main/scala/li/cil/oc/client/Textures.scala @@ -21,6 +21,7 @@ object Textures { val blockCable = new ResourceLocation(Settings.resourceDomain, "textures/blocks/cable.png") val blockCaseFrontOn = new ResourceLocation(Settings.resourceDomain, "textures/blocks/case_front_on.png") + val blockHologram = new ResourceLocation(Settings.resourceDomain, "textures/blocks/hologram_effect.png") val blockPowerDistributorOn = new ResourceLocation(Settings.resourceDomain, "textures/blocks/power_distributor_on.png") val blockRackFrontOn = new ResourceLocation(Settings.resourceDomain, "textures/blocks/rack_front_on.png") val blockRobot = new ResourceLocation(Settings.resourceDomain, "textures/blocks/robot.png") diff --git a/src/main/scala/li/cil/oc/client/renderer/tileentity/CableRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/tileentity/CableRenderer.scala index b1359f776..9378c0cba 100644 --- a/src/main/scala/li/cil/oc/client/renderer/tileentity/CableRenderer.scala +++ b/src/main/scala/li/cil/oc/client/renderer/tileentity/CableRenderer.scala @@ -40,6 +40,7 @@ object CableRenderer extends TileEntitySpecialRenderer { for (mask <- 0 to 0xFF >> 2) { GL11.glNewList(displayLists + mask, GL11.GL_COMPILE) + t.startDrawingQuads() for (side <- ForgeDirection.VALID_DIRECTIONS) { val connects = (side.flag & mask) != 0 val z = if (connects) 0 else lb @@ -48,7 +49,6 @@ object CableRenderer extends TileEntitySpecialRenderer { exists(s => (s.flag & mask) != 0)) uo else 0 - t.startDrawingQuads() t.setNormal(side.offsetX, side.offsetY, -side.offsetZ) val (tx, ty, tz, u, v) = side match { case ForgeDirection.WEST => (Array.fill(4)(z), t2, t1, uv1.reverse, uv2) @@ -63,7 +63,6 @@ object CableRenderer extends TileEntitySpecialRenderer { t.addVertexWithUV(tx(1), ty(1), tz(1), u(1) + uc, v(1)) t.addVertexWithUV(tx(2), ty(2), tz(2), u(2) + uc, v(2)) t.addVertexWithUV(tx(3), ty(3), tz(3), u(3) + uc, v(3)) - t.draw() if (connects) { val (axis, sign, uv1, uv2, uv3, uv4) = side match { @@ -79,39 +78,32 @@ object CableRenderer extends TileEntitySpecialRenderer { val o1 = offsets((axis + sign + 3) % 3) val o2 = offsets((axis - sign + 3) % 3) - t.startDrawingQuads() normal(side, 0) t.addVertexWithUV(tx(0) - sign * tl(0), ty(0) - sign * tl(1), tz(0) - sign * tl(2), u(uv(0 + uv1)) + uo, v(uv(0 + uv1)) * vs) t.addVertexWithUV(tx(1) - sign * tl(0), ty(1) - sign * tl(1), tz(1) - sign * tl(2), u(uv(1 + uv1)) + uo, v(uv(1 + uv1)) * vs) t.addVertexWithUV(tx(2) + o1(0), ty(2) + o1(1), tz(2) + o1(2), u(uv(2 + uv1)) + uo, v(uv(2 + uv1)) * vs) t.addVertexWithUV(tx(3) + o1(0), ty(3) + o1(1), tz(3) + o1(2), u(uv(3 + uv1)) + uo, v(uv(3 + uv1)) * vs) - t.draw() - t.startDrawingQuads() normal(side, 1) t.addVertexWithUV(tx(0) - o1(0), ty(0) - o1(1), tz(0) - o1(2), u(uv(0 + uv2)) + uo, v(uv(0 + uv2)) * vs) t.addVertexWithUV(tx(1) - o1(0), ty(1) - o1(1), tz(1) - o1(2), u(uv(1 + uv2)) + uo, v(uv(1 + uv2)) * vs) t.addVertexWithUV(tx(2) - sign * tl(0), ty(2) - sign * tl(1), tz(2) - sign * tl(2), u(uv(2 + uv2)) + uo, v(uv(2 + uv2)) * vs) t.addVertexWithUV(tx(3) - sign * tl(0), ty(3) - sign * tl(1), tz(3) - sign * tl(2), u(uv(3 + uv2)) + uo, v(uv(3 + uv2)) * vs) - t.draw() - t.startDrawingQuads() normal(side, 2) t.addVertexWithUV(tx(0) - sign * tl(0), ty(0) - sign * tl(1), tz(0) - sign * tl(2), u(uv(0 + uv3)) + uo, v(uv(0 + uv3)) * vs) t.addVertexWithUV(tx(1) - o2(0), ty(1) - o2(1), tz(1) - o2(2), u(uv(1 + uv3)) + uo, v(uv(1 + uv3)) * vs) t.addVertexWithUV(tx(2) - o2(0), ty(2) - o2(1), tz(2) - o2(2), u(uv(2 + uv3)) + uo, v(uv(2 + uv3)) * vs) t.addVertexWithUV(tx(3) - sign * tl(0), ty(3) - sign * tl(1), tz(3) - sign * tl(2), u(uv(3 + uv3)) + uo, v(uv(3 + uv3)) * vs) - t.draw() - t.startDrawingQuads() normal(side, 3) t.addVertexWithUV(tx(0) + o2(0), ty(0) + o2(1), tz(0) + o2(2), u(uv(0 + uv4)) + uo, v(uv(0 + uv4)) * vs) t.addVertexWithUV(tx(1) - sign * tl(0), ty(1) - sign * tl(1), tz(1) - sign * tl(2), u(uv(1 + uv4)) + uo, v(uv(1 + uv4)) * vs) t.addVertexWithUV(tx(2) - sign * tl(0), ty(2) - sign * tl(1), tz(2) - sign * tl(2), u(uv(2 + uv4)) + uo, v(uv(2 + uv4)) * vs) t.addVertexWithUV(tx(3) + o2(0), ty(3) + o2(1), tz(3) + o2(2), u(uv(3 + uv4)) + uo, v(uv(3 + uv4)) * vs) - t.draw() } } + t.draw() GL11.glEndList() } diff --git a/src/main/scala/li/cil/oc/client/renderer/tileentity/HologramRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/tileentity/HologramRenderer.scala new file mode 100644 index 000000000..36ef696f6 --- /dev/null +++ b/src/main/scala/li/cil/oc/client/renderer/tileentity/HologramRenderer.scala @@ -0,0 +1,178 @@ +package li.cil.oc.client.renderer.tileentity + +import com.google.common.cache.{RemovalNotification, RemovalListener, CacheBuilder} +import cpw.mods.fml.common.eventhandler.SubscribeEvent +import cpw.mods.fml.common.gameevent.TickEvent.ClientTickEvent +import java.util.concurrent.{Callable, TimeUnit} +import li.cil.oc.common.tileentity.Hologram +import li.cil.oc.util.RenderState +import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer +import net.minecraft.client.renderer.{GLAllocation, Tessellator} +import net.minecraft.tileentity.TileEntity +import org.lwjgl.opengl.GL11 +import scala.util.Random +import li.cil.oc.client.Textures + +object HologramRenderer extends TileEntitySpecialRenderer with Callable[Int] with RemovalListener[TileEntity, Int] { + val random = new Random() + + /** We cache the display lists for the projectors we render for performance. */ + val cache = com.google.common.cache.CacheBuilder.newBuilder(). + expireAfterAccess(2, TimeUnit.SECONDS). + removalListener(this). + asInstanceOf[CacheBuilder[Hologram, Int]]. + build[Hologram, Int]() + + /** Used to pass the current screen along to call(). */ + private var hologram: Hologram = null + + override def renderTileEntityAt(te: TileEntity, x: Double, y: Double, z: Double, f: Float) { + hologram = te.asInstanceOf[Hologram] + if (!hologram.hasPower) return + + GL11.glPushAttrib(0xFFFFFFFF) + + GL11.glPushMatrix() + GL11.glTranslated(x + 0.5, y + 0.5, z + 0.5) + GL11.glScaled(1.001, 1.001, 1.001) // Avoid z-fighting with other blocks. + GL11.glTranslated(-1.5 * hologram.scale, 0, -1.5 * hologram.scale) + + // Do a bit of flickering, because that's what holograms do! + if (random.nextDouble() < 0.025) { + GL11.glScaled(1 + random.nextGaussian() * 0.01, 1 + random.nextGaussian() * 0.001, 1 + random.nextGaussian() * 0.01) + GL11.glTranslated(random.nextGaussian() * 0.01, random.nextGaussian() * 0.01, random.nextGaussian() * 0.01) + } + + // We do two passes here to avoid weird transparency effects: in the first + // pass we find the front-most fragment, in the second we actually draw it. + // TODO proper transparency shader? depth peeling e.g. + GL11.glColorMask(false, false, false, false) + val list = cache.get(hologram, this) + compileOrDraw(list) + GL11.glColorMask(true, true, true, true) + GL11.glDepthFunc(GL11.GL_EQUAL) + compileOrDraw(list) + + GL11.glPopMatrix() + GL11.glPopAttrib() + } + + def compileOrDraw(list: Int) = if (hologram.dirty) { + val doCompile = !RenderState.compilingDisplayList + if (doCompile) { + hologram.dirty = false + GL11.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE) + } + + def isSolid(hx: Int, hy: Int, hz: Int) = { + hx >= 0 && hy >= 0 && hz >= 0 && hx < hologram.width && hy < hologram.height && hz < hologram.width && + (hologram.volume(hx + hz * hologram.width) & (1 << hy)) != 0 + } + + bindTexture(Textures.blockHologram) + val t = Tessellator.instance + t.startDrawingQuads() + t.setColorRGBA_F(1, 1, 1, 0.5f) + + // TODO merge quads for better rendering performance + val s = 1f / 16f * hologram.scale + for (hx <- 0 until hologram.width) { + val wx = hx * s + for (hz <- 0 until hologram.width) { + val wz = hz * s + for (hy <- 0 until hologram.height) { + val wy = hy * s + + if (isSolid(hx, hy, hz)) { + /* + 0---1 + | N | + 0---3---2---1---0 + | W | U | E | D | + 5---6---7---4---5 + | S | + 5---4 + */ + + // South + if (!isSolid(hx, hy, hz + 1)) { + t.setNormal(0, 0, 1) + t.addVertexWithUV(wx + s, wy + s, wz + s, 0, 0) // 5 + t.addVertexWithUV(wx + 0, wy + s, wz + s, 1, 0) // 4 + t.addVertexWithUV(wx + 0, wy + 0, wz + s, 1, 1) // 7 + t.addVertexWithUV(wx + s, wy + 0, wz + s, 0, 1) // 6 + } + // North + if (!isSolid(hx, hy, hz - 1)) { + t.setNormal(0, 0, -1) + t.addVertexWithUV(wx + s, wy + 0, wz + 0, 0, 0) // 3 + t.addVertexWithUV(wx + 0, wy + 0, wz + 0, 1, 0) // 2 + t.addVertexWithUV(wx + 0, wy + s, wz + 0, 1, 1) // 1 + t.addVertexWithUV(wx + s, wy + s, wz + 0, 0, 1) // 0 + } + + // East + if (!isSolid(hx + 1, hy, hz)) { + t.setNormal(1, 0, 0) + t.addVertexWithUV(wx + s, wy + s, wz + s, 1, 0) // 5 + t.addVertexWithUV(wx + s, wy + 0, wz + s, 1, 1) // 6 + t.addVertexWithUV(wx + s, wy + 0, wz + 0, 0, 1) // 3 + t.addVertexWithUV(wx + s, wy + s, wz + 0, 0, 0) // 0 + } + // West + if (!isSolid(hx - 1, hy, hz)) { + t.setNormal(-1, 0, 0) + t.addVertexWithUV(wx + 0, wy + 0, wz + s, 1, 0) // 7 + t.addVertexWithUV(wx + 0, wy + s, wz + s, 1, 1) // 4 + t.addVertexWithUV(wx + 0, wy + s, wz + 0, 0, 1) // 1 + t.addVertexWithUV(wx + 0, wy + 0, wz + 0, 0, 0) // 2 + } + + // Up + if (!isSolid(hx, hy + 1, hz)) { + t.setNormal(0, 1, 0) + t.addVertexWithUV(wx + s, wy + s, wz + 0, 0, 0) // 0 + t.addVertexWithUV(wx + 0, wy + s, wz + 0, 1, 0) // 1 + t.addVertexWithUV(wx + 0, wy + s, wz + s, 1, 1) // 4 + t.addVertexWithUV(wx + s, wy + s, wz + s, 0, 1) // 5 + } + // Down + if (!isSolid(hx, hy - 1, hz)) { + t.setNormal(0, -1, 0) + t.addVertexWithUV(wx + s, wy + 0, wz + s, 0, 0) // 6 + t.addVertexWithUV(wx + 0, wy + 0, wz + s, 1, 0) // 7 + t.addVertexWithUV(wx + 0, wy + 0, wz + 0, 1, 1) // 2 + t.addVertexWithUV(wx + s, wy + 0, wz + 0, 0, 1) // 3 + } + } + } + } + } + + t.draw() + + if (doCompile) { + GL11.glEndList() + } + + true + } + else GL11.glCallList(list) + + // ----------------------------------------------------------------------- // + // Cache + // ----------------------------------------------------------------------- // + + def call = { + val list = GLAllocation.generateDisplayLists(1) + hologram.dirty = true // Force compilation. + list + } + + def onRemoval(e: RemovalNotification[TileEntity, Int]) { + GLAllocation.deleteDisplayLists(e.getValue) + } + + @SubscribeEvent + def onTick(e: ClientTickEvent) = cache.cleanUp() +} diff --git a/src/main/scala/li/cil/oc/client/renderer/tileentity/ScreenRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/tileentity/ScreenRenderer.scala index 1b0170fa0..c7e014c87 100644 --- a/src/main/scala/li/cil/oc/client/renderer/tileentity/ScreenRenderer.scala +++ b/src/main/scala/li/cil/oc/client/renderer/tileentity/ScreenRenderer.scala @@ -74,8 +74,7 @@ object ScreenRenderer extends TileEntitySpecialRenderer with Callable[Int] with if (screen.hasPower) { MonospaceFontRenderer.init(this.field_147501_a.field_147553_e) - val list = cache.get(screen, this) - compileOrDraw(list) + compileOrDraw(cache.get(screen, this)) } GL11.glPopMatrix() diff --git a/src/main/scala/li/cil/oc/common/PacketBuilder.scala b/src/main/scala/li/cil/oc/common/PacketBuilder.scala index b0fe4dc26..80110be45 100644 --- a/src/main/scala/li/cil/oc/common/PacketBuilder.scala +++ b/src/main/scala/li/cil/oc/common/PacketBuilder.scala @@ -3,8 +3,8 @@ package li.cil.oc.common import cpw.mods.fml.common.FMLCommonHandler import cpw.mods.fml.common.network.internal.FMLProxyPacket import io.netty.buffer.Unpooled -import java.io.ByteArrayOutputStream -import java.io.DataOutputStream +import java.io.{OutputStream, ByteArrayOutputStream, DataOutputStream} +import java.util.zip.GZIPOutputStream import li.cil.oc.common.tileentity.TileEntity import li.cil.oc.OpenComputers import net.minecraft.entity.player.EntityPlayerMP @@ -14,9 +14,8 @@ import net.minecraft.world.World import net.minecraftforge.common.util.ForgeDirection import scala.collection.convert.WrapAsScala._ -class PacketBuilder(packetType: PacketType.Value, private val stream: ByteArrayOutputStream = new ByteArrayOutputStream) extends DataOutputStream(stream) { - writeByte(packetType.id) - +// Necessary to keep track of the GZIP stream. +abstract class PacketBuilderBase[T <: OutputStream](protected val stream: T) extends DataOutputStream(stream) { def writeTileEntity(t: TileEntity) = { writeInt(t.world.provider.dimensionId) writeInt(t.x) @@ -57,5 +56,30 @@ class PacketBuilder(packetType: PacketType.Value, private val stream: ByteArrayO def sendToServer() = OpenComputers.channel.sendToServer(packet) - private def packet = new FMLProxyPacket(Unpooled.wrappedBuffer(stream.toByteArray), "OpenComputers") + protected def packet: FMLProxyPacket } + +class PacketBuilder(packetType: PacketType.Value) extends PacketBuilderBase(PacketBuilder.newData(compressed = false)) { + writeByte(packetType.id) + + override protected def packet = { + new FMLProxyPacket(Unpooled.wrappedBuffer(stream.toByteArray), "OpenComputers") + } +} + +class CompressedPacketBuilder(packetType: PacketType.Value, private val data: ByteArrayOutputStream = PacketBuilder.newData(compressed = true)) extends PacketBuilderBase(new GZIPOutputStream(data)) { + writeByte(packetType.id) + + override protected def packet = { + stream.finish() + new FMLProxyPacket(Unpooled.wrappedBuffer(data.toByteArray), "OpenComputers") + } +} + +object PacketBuilder { + def newData(compressed: Boolean) = { + val data = new ByteArrayOutputStream + data.write(if (compressed) 1 else 0) + data + } +} \ No newline at end of file diff --git a/src/main/scala/li/cil/oc/common/PacketHandler.scala b/src/main/scala/li/cil/oc/common/PacketHandler.scala index 04180ebf9..285bb0054 100644 --- a/src/main/scala/li/cil/oc/common/PacketHandler.scala +++ b/src/main/scala/li/cil/oc/common/PacketHandler.scala @@ -1,8 +1,10 @@ package li.cil.oc.common import io.netty.buffer.{ByteBufInputStream, ByteBuf} -import java.io.DataInputStream -import li.cil.oc.Blocks +import java.io.{InputStream, DataInputStream} +import java.util.logging.Level +import java.util.zip.GZIPInputStream +import li.cil.oc.{Blocks, OpenComputers} import net.minecraft.entity.player.EntityPlayer import net.minecraft.item.ItemStack import net.minecraft.nbt.CompressedStreamTools @@ -12,6 +14,21 @@ import scala.reflect.ClassTag import scala.reflect.classTag abstract class PacketHandler { + /** Top level dispatcher based on packet type. */ + protected def onPacketData(data: ByteBuf, player: EntityPlayer) { + // Don't crash on badly formatted packets (may have been altered by a + // malicious client, in which case we don't want to allow it to kill the + // server like this). Just spam the log a bit... ;) + try { + val stream = new ByteBufInputStream(data) + if (stream.read() == 0) dispatch(new PacketParser(stream, player)) + else dispatch(new PacketParser(new GZIPInputStream(stream), player)) + } catch { + case e: Throwable => + OpenComputers.log.log(Level.WARNING, "Received a badly formatted packet.", e) + } + } + /** * Gets the world for the specified dimension. * @@ -21,7 +38,9 @@ abstract class PacketHandler { */ protected def world(player: EntityPlayer, dimension: Int): Option[World] - protected class PacketParser(data: ByteBuf, val player: EntityPlayer) extends DataInputStream(new ByteBufInputStream(data)) { + protected def dispatch(p: PacketParser) + + protected class PacketParser(stream: InputStream, val player: EntityPlayer) extends DataInputStream(stream) { val packetType = PacketType(readByte()) def getTileEntity[T: ClassTag](dimension: Int, x: Int, y: Int, z: Int): Option[T] = { diff --git a/src/main/scala/li/cil/oc/common/PacketType.scala b/src/main/scala/li/cil/oc/common/PacketType.scala index eb98c912c..74f1d21ce 100644 --- a/src/main/scala/li/cil/oc/common/PacketType.scala +++ b/src/main/scala/li/cil/oc/common/PacketType.scala @@ -8,6 +8,9 @@ object PacketType extends Enumeration { ChargerState, ComputerState, ComputerUserList, + HologramClear, + HologramScale, + HologramSet, PowerState, RedstoneState, RobotAnimateSwing, diff --git a/src/main/scala/li/cil/oc/common/block/Delegate.scala b/src/main/scala/li/cil/oc/common/block/Delegate.scala index 9eec7efe7..38bde161d 100644 --- a/src/main/scala/li/cil/oc/common/block/Delegate.scala +++ b/src/main/scala/li/cil/oc/common/block/Delegate.scala @@ -44,11 +44,11 @@ trait Delegate { // items in 1.7 when needed since IDs are no problem anymore. // def canPlaceBlockOnSide(world: World, x: Int, y: Int, z: Int, side: ForgeDirection) = true - def bounds(world: World, x: Int, y: Int, z: Int) = + def bounds(world: IBlockAccess, x: Int, y: Int, z: Int) = AxisAlignedBB.getAABBPool.getAABB(0, 0, 0, 1, 1, 1) def updateBounds(world: IBlockAccess, x: Int, y: Int, z: Int) = - parent.setBlockBounds(0, 0, 0, 1, 1, 1) + parent.setBlockBounds(bounds(world, x, y, z)) def intersect(world: World, x: Int, y: Int, z: Int, origin: Vec3, direction: Vec3) = parent.superCollisionRayTrace(world, x, y, z, origin, direction) diff --git a/src/main/scala/li/cil/oc/common/block/Hologram.scala b/src/main/scala/li/cil/oc/common/block/Hologram.scala new file mode 100644 index 000000000..fe5c5093e --- /dev/null +++ b/src/main/scala/li/cil/oc/common/block/Hologram.scala @@ -0,0 +1,53 @@ +package li.cil.oc.common.block + +import java.util +import li.cil.oc.Settings +import li.cil.oc.common.tileentity +import li.cil.oc.util.Tooltip +import net.minecraft.client.renderer.texture.IIconRegister +import net.minecraft.entity.player.EntityPlayer +import net.minecraft.item.{ItemStack, EnumRarity} +import net.minecraft.world.{World, IBlockAccess} +import net.minecraft.util.{IIcon, AxisAlignedBB} +import net.minecraftforge.common.util.ForgeDirection + +class Hologram(val parent: SpecialDelegator) extends SpecialDelegate { + val unlocalizedName = "Hologram" + + private val icons = Array.fill[IIcon](6)(null) + + override def rarity = EnumRarity.rare + + override def tooltipLines(stack: ItemStack, player: EntityPlayer, tooltip: util.List[String], advanced: Boolean) { + tooltip.addAll(Tooltip.get(unlocalizedName)) + } + + override def icon(side: ForgeDirection) = Some(icons(side.ordinal())) + + override def luminance(world: IBlockAccess, x: Int, y: Int, z: Int) = 15 + + override def isSolid(world: IBlockAccess, x: Int, y: Int, z: Int, side: ForgeDirection) = side == ForgeDirection.DOWN + + override def bounds(world: IBlockAccess, x: Int, y: Int, z: Int) = + AxisAlignedBB.getAABBPool.getAABB(0, 0, 0, 1, 3 / 16f, 1) + + override def itemBounds() { + parent.setBlockBounds(AxisAlignedBB.getAABBPool.getAABB(0, 0, 0, 1, 3 / 16f, 1)) + } + + override def registerIcons(iconRegister: IIconRegister) = { + icons(ForgeDirection.DOWN.ordinal) = iconRegister.registerIcon(Settings.resourceDomain + ":generic_top") + icons(ForgeDirection.UP.ordinal) = iconRegister.registerIcon(Settings.resourceDomain + ":hologram_top") + + icons(ForgeDirection.NORTH.ordinal) = iconRegister.registerIcon(Settings.resourceDomain + ":hologram_side") + icons(ForgeDirection.SOUTH.ordinal) = icons(ForgeDirection.NORTH.ordinal) + icons(ForgeDirection.WEST.ordinal) = icons(ForgeDirection.NORTH.ordinal) + icons(ForgeDirection.EAST.ordinal) = icons(ForgeDirection.NORTH.ordinal) + } + + // ----------------------------------------------------------------------- // + + override def hasTileEntity = true + + override def createTileEntity(world: World) = Some(new tileentity.Hologram()) +} diff --git a/src/main/scala/li/cil/oc/common/tileentity/Hologram.scala b/src/main/scala/li/cil/oc/common/tileentity/Hologram.scala new file mode 100644 index 000000000..e30a04afb --- /dev/null +++ b/src/main/scala/li/cil/oc/common/tileentity/Hologram.scala @@ -0,0 +1,176 @@ +package li.cil.oc.common.tileentity + +import cpw.mods.fml.relauncher.{Side, SideOnly} +import li.cil.oc.api.network._ +import li.cil.oc.server.{PacketSender => ServerPacketSender} +import li.cil.oc.{Settings, api} +import net.minecraft.nbt.NBTTagCompound +import net.minecraft.util.AxisAlignedBB +import net.minecraftforge.common.util.ForgeDirection + +class Hologram extends Environment with SidedEnvironment { + val node = api.Network.newNode(this, Visibility.Network). + withComponent("hologram"). + withConnector(). + create() + + val width = 3 * 16 + + val height = 2 * 16 // 32 bit in an int + + val volume = new Array[Int](width * width) + + // Render scale. + var scale = 1.0 + + // Relative number of lit columns (for energy cost). + var litRatio = -1.0 + + // Whether we need to send an update packet/recompile our display list. + var dirty = false + + // Interval of dirty columns. + var dirtyFromX = Int.MaxValue + var dirtyUntilX = -1 + var dirtyFromZ = Int.MaxValue + var dirtyUntilZ = -1 + + // Time to wait before sending another update packet. + var cooldown = 5 + + var hasPower = true + + def setDirty(x: Int, z: Int) { + dirty = true + dirtyFromX = math.min(dirtyFromX, x) + dirtyUntilX = math.max(dirtyUntilX, x + 1) + dirtyFromZ = math.min(dirtyFromZ, z) + dirtyUntilZ = math.max(dirtyUntilZ, z + 1) + litRatio = -1 + } + + def resetDirtyFlag() { + dirty = false + dirtyFromX = Int.MaxValue + dirtyUntilX = -1 + dirtyFromZ = Int.MaxValue + dirtyUntilZ = -1 + cooldown = 5 + } + + // ----------------------------------------------------------------------- // + + override def canConnect(side: ForgeDirection) = side != ForgeDirection.UP + + override def sidedNode(side: ForgeDirection) = node + + // ----------------------------------------------------------------------- // + + @Callback(doc = """function() -- Clears the hologram.""") + def clear(computer: Context, args: Arguments): Array[AnyRef] = this.synchronized { + for (i <- 0 until volume.length) volume(i) = 0 + ServerPacketSender.sendHologramClear(this) + resetDirtyFlag() + litRatio = 0 + null + } + + @Callback(direct = true, doc = """function(x:number, z:number):number -- Returns the bit mask representing the specified column.""") + def get(computer: Context, args: Arguments): Array[AnyRef] = this.synchronized { + val x = args.checkInteger(0) - 1 + val z = args.checkInteger(1) - 1 + result(volume(x + z * width)) + } + + @Callback(direct = true, limit = 256, doc = """function(x:number, z:number, value:number) -- Set the bit mask for the specified column.""") + def set(computer: Context, args: Arguments): Array[AnyRef] = this.synchronized { + val x = args.checkInteger(0) - 1 + val z = args.checkInteger(1) - 1 + val value = args.checkInteger(2) + volume(x + z * width) = value + setDirty(x, z) + null + } + + @Callback(direct = true, limit = 128, doc = """function(x:number, z:number, height:number) -- Fills a column to the specified height.""") + def fill(computer: Context, args: Arguments): Array[AnyRef] = this.synchronized { + val x = args.checkInteger(0) - 1 + val z = args.checkInteger(1) - 1 + val height = math.min(32, math.max(0, args.checkInteger(2))) + // Bit shifts in the JVM only use the lowest five bits... so we have to + // manually check the height, to avoid the shift being a no-op. + volume(x + z * width) = if (height > 0) 0xFFFFFFFF >>> (32 - height) else 0 + setDirty(x, z) + null + } + + @Callback(doc = """function():number -- Returns the render scale of the hologram.""") + def getScale(computer: Context, args: Arguments): Array[AnyRef] = { + result(scale) + } + + @Callback(doc = """function(value:number) -- Set the render scale. A larger scale consumes more energy.""") + def setScale(computer: Context, args: Arguments): Array[AnyRef] = { + scale = math.max(0.333333, math.min(3, args.checkDouble(0))) + ServerPacketSender.sendHologramScale(this) + null + } + + // ----------------------------------------------------------------------- // + + override def updateEntity() { + super.updateEntity() + if (isServer) { + if (dirty) { + cooldown -= 1 + if (cooldown <= 0) { + ServerPacketSender.sendHologramSet(this) + resetDirtyFlag() + } + } + if (litRatio < 0) { + litRatio = 0 + for (i <- 0 until volume.length) { + if (volume(i) != 0) litRatio += 1 + } + litRatio /= volume.length + } + + hasPower = node.changeBuffer(-Settings.get.hologramCost * litRatio * scale) == 0 + } + } + + // ----------------------------------------------------------------------- // + + override def shouldRenderInPass(pass: Int) = pass == 1 + + override def getRenderBoundingBox = AxisAlignedBB.getAABBPool.getAABB(xCoord + 0.5 - 1.5 * scale, yCoord, zCoord - scale, xCoord + 0.5 + 1.5 * scale, yCoord + 0.25 + 2 * scale, zCoord + 0.5 + 1.5 * scale) + + // ----------------------------------------------------------------------- // + + override def readFromNBT(nbt: NBTTagCompound) { + super.readFromNBT(nbt) + nbt.getIntArray(Settings.namespace + "volume").copyToArray(volume) + scale = nbt.getDouble(Settings.namespace + "scale") + } + + override def writeToNBT(nbt: NBTTagCompound) = this.synchronized { + super.writeToNBT(nbt) + nbt.setIntArray(Settings.namespace + "volume", volume) + nbt.setDouble(Settings.namespace + "scale", scale) + } + + @SideOnly(Side.CLIENT) + override def readFromNBTForClient(nbt: NBTTagCompound) { + super.readFromNBTForClient(nbt) + nbt.getIntArray("volume").copyToArray(volume) + scale = nbt.getDouble("scale") + dirty = true + } + + override def writeToNBTForClient(nbt: NBTTagCompound) { + super.writeToNBTForClient(nbt) + nbt.setIntArray("volume", volume) + nbt.setDouble("scale", scale) + } +} diff --git a/src/main/scala/li/cil/oc/server/PacketHandler.scala b/src/main/scala/li/cil/oc/server/PacketHandler.scala index 0f0ee0437..8be3df353 100644 --- a/src/main/scala/li/cil/oc/server/PacketHandler.scala +++ b/src/main/scala/li/cil/oc/server/PacketHandler.scala @@ -14,12 +14,14 @@ import net.minecraftforge.common.DimensionManager import net.minecraftforge.common.util.ForgeDirection object PacketHandler extends CommonPacketHandler { + @SubscribeEvent + def onPacket(e: ServerCustomPacketEvent) = + onPacketData(e.packet.payload, e.handler.asInstanceOf[NetHandlerPlayServer].playerEntity) + override protected def world(player: EntityPlayer, dimension: Int) = Option(DimensionManager.getWorld(dimension)) - @SubscribeEvent - def onPacket(e: ServerCustomPacketEvent) { - val p = new PacketParser(e.packet.payload, e.handler.asInstanceOf[NetHandlerPlayServer].playerEntity) + override def dispatch(p: PacketParser) { p.packetType match { case PacketType.ComputerPower => onComputerPower(p) case PacketType.KeyDown => onKeyDown(p) diff --git a/src/main/scala/li/cil/oc/server/PacketSender.scala b/src/main/scala/li/cil/oc/server/PacketSender.scala index 8d19dfabe..1e909b0c8 100644 --- a/src/main/scala/li/cil/oc/server/PacketSender.scala +++ b/src/main/scala/li/cil/oc/server/PacketSender.scala @@ -1,9 +1,8 @@ package li.cil.oc.server import li.cil.oc.common -import li.cil.oc.common.PacketBuilder -import li.cil.oc.common.PacketType import li.cil.oc.common.tileentity._ +import li.cil.oc.common.{CompressedPacketBuilder, PacketBuilder, PacketType} import li.cil.oc.util.PackedColor import net.minecraft.entity.player.EntityPlayerMP import net.minecraft.item.ItemStack @@ -55,6 +54,40 @@ object PacketSender { pb.sendToNearbyPlayers(t) } + def sendHologramClear(t: Hologram) { + val pb = new PacketBuilder(PacketType.HologramClear) + + pb.writeTileEntity(t) + + pb.sendToNearbyPlayers(t) + } + + def sendHologramScale(t: Hologram) { + val pb = new PacketBuilder(PacketType.HologramScale) + + pb.writeTileEntity(t) + pb.writeDouble(t.scale) + + pb.sendToNearbyPlayers(t) + } + + def sendHologramSet(t: Hologram) { + val pb = new CompressedPacketBuilder(PacketType.HologramSet) + + pb.writeTileEntity(t) + pb.writeByte(t.dirtyFromX) + pb.writeByte(t.dirtyUntilX) + pb.writeByte(t.dirtyFromZ) + pb.writeByte(t.dirtyUntilZ) + for (x <- t.dirtyFromX until t.dirtyUntilX) { + for (z <- t.dirtyFromZ until t.dirtyUntilZ) { + pb.writeInt(t.volume(x + z * t.width)) + } + } + + pb.sendToNearbyPlayers(t) + } + def sendPowerState(t: PowerInformation) { val pb = new PacketBuilder(PacketType.PowerState) diff --git a/src/main/scala/li/cil/oc/util/LuaStateFactory.scala b/src/main/scala/li/cil/oc/util/LuaStateFactory.scala index 9f7179d76..19722d9cf 100644 --- a/src/main/scala/li/cil/oc/util/LuaStateFactory.scala +++ b/src/main/scala/li/cil/oc/util/LuaStateFactory.scala @@ -217,12 +217,12 @@ object LuaStateFactory { lua.getTop match { case 0 => lua.pushNumber(random.nextDouble()) case 1 => - val u = lua.checkInteger(1) + val u = lua.checkNumber(1).toInt lua.checkArg(1, 1 < u, "interval is empty") lua.pushInteger(1 + random.nextInt(u)) case 2 => - val l = lua.checkInteger(1) - val u = lua.checkInteger(2) + val l = lua.checkNumber(1).toInt + val u = lua.checkNumber(2).toInt lua.checkArg(1, l < u, "interval is empty") lua.pushInteger(l + random.nextInt(u - (l - 1))) case _ => throw new IllegalArgumentException("wrong number of arguments")