Conflicts:
	src/main/scala/li/cil/oc/Blocks.scala
	src/main/scala/li/cil/oc/Items.scala
	src/main/scala/li/cil/oc/client/Proxy.scala
	src/main/scala/li/cil/oc/client/renderer/tileentity/ScreenRenderer.scala
	src/main/scala/li/cil/oc/common/PacketBuilder.scala
	src/main/scala/li/cil/oc/common/PacketHandler.scala
This commit is contained in:
Florian Nücke 2014-02-27 11:41:02 +01:00
commit 44798834c1
31 changed files with 618 additions and 69 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 B

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,6 +8,9 @@ object PacketType extends Enumeration {
ChargerState,
ComputerState,
ComputerUserList,
HologramClear,
HologramScale,
HologramSet,
PowerState,
RedstoneState,
RobotAnimateSwing,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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