diff --git a/assets/opencomputers/lua/kernel.lua b/assets/opencomputers/lua/kernel.lua index 8cac4b510..a215fae03 100644 --- a/assets/opencomputers/lua/kernel.lua +++ b/assets/opencomputers/lua/kernel.lua @@ -214,7 +214,11 @@ sandbox = { os = { clock = os.clock, - date = os.date, + date = function(format, time) + checkArg(1, format, "string", "nil") + checkArg(2, time, "number", "nil") + return os.date(format, time) + end, difftime = function(t2, t1) return t2 - t1 end, diff --git a/assets/opencomputers/lua/rom/bin/time.lua b/assets/opencomputers/lua/rom/bin/time.lua new file mode 100644 index 000000000..31bb0f7ff --- /dev/null +++ b/assets/opencomputers/lua/rom/bin/time.lua @@ -0,0 +1 @@ +print(os.date("%F %T")) diff --git a/assets/opencomputers/lua/rom/lib/event.lua b/assets/opencomputers/lua/rom/lib/event.lua index b37e91c38..944ff844c 100644 --- a/assets/opencomputers/lua/rom/lib/event.lua +++ b/assets/opencomputers/lua/rom/lib/event.lua @@ -45,7 +45,7 @@ local function tick() if timer.times <= 0 then timers[id] = nil else - timer.after = timer.after + timer.interval + timer.after = os.uptime() + timer.interval end end end diff --git a/li/cil/oc/common/tileentity/Computer.scala b/li/cil/oc/common/tileentity/Computer.scala index 87d956581..ff6fd8c08 100644 --- a/li/cil/oc/common/tileentity/Computer.scala +++ b/li/cil/oc/common/tileentity/Computer.scala @@ -72,6 +72,7 @@ class Computer(isClient: Boolean) extends Rotatable with ComputerEnvironment wit super.updateEntity() if (node != null && node.network == null) { Network.joinOrCreateNetwork(worldObj, xCoord, yCoord, zCoord) + this.synchronized(powerConsumed = 0.0) } else if (!worldObj.isRemote) { // If we just joined a network we were just loaded from disk. We skip the diff --git a/li/cil/oc/server/component/Computer.scala b/li/cil/oc/server/component/Computer.scala index 0c9d01396..e55bd5275 100644 --- a/li/cil/oc/server/component/Computer.scala +++ b/li/cil/oc/server/component/Computer.scala @@ -13,7 +13,7 @@ import li.cil.oc.api.network._ import li.cil.oc.common.tileentity import li.cil.oc.server import li.cil.oc.util.ExtendedLuaState.extendLuaState -import li.cil.oc.util.LuaStateFactory +import li.cil.oc.util.{GameTimeFormatter, LuaStateFactory} import li.cil.oc.{OpenComputers, Config} import net.minecraft.nbt._ import net.minecraft.tileentity.TileEntity @@ -112,7 +112,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl switchTo(Computer.State.Starting) // Remember when we started, for os.clock(). - timeStarted = owner.world.getWorldInfo.getWorldTotalTime + timeStarted = owner.world.getWorldTime // Mark state change in owner, to send it to clients. owner.markAsChanged(8) // Initial power required to start. @@ -214,12 +214,12 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl // pause. lastUpdate = System.currentTimeMillis - // TODO This seems to be the "run time", not the elapsed ingame time. For example, when doing /time set 0 the game - // should jump to the next day, but this value does not jump. Is this just Forge or do we have to find some other - // way around this? CC seems to use getWorldTime, which is really odd, since that should be only within the range - // of a single day (0 to 24000), which it *is*... perhaps vanilla Minecraft (not re-compiled) behaves different? - // Update world time for computer threads. - worldTime = owner.world.getWorldInfo.getWorldTotalTime + // Update world time for time(). + worldTime = owner.world.getWorldTime + if (isRunning) { + // We can have rollbacks from '/time set'. Avoid getting negative uptimes. + timeStarted = timeStarted min worldTime + } // Check if we should switch states. state.synchronized(state.top match { @@ -231,7 +231,6 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl // Resuming after being loaded. case Computer.State.Resuming if System.currentTimeMillis() >= sleepUntil => { verifyComponents() - owner.markAsChanged(Double.NegativeInfinity) state.pop() switchTo(state.top) // Trigger execution if necessary. } @@ -622,13 +621,57 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl }) lua.setField(-2, "clock") + // Date formatting function. + lua.pushScalaFunction(lua => { + val format = + if (lua.getTop > 0 && lua.isString(1)) lua.toString(1) + else "%d/%m/%y %H:%M:%S" + val time = + if (lua.getTop > 1 && lua.isNumber(2)) lua.toNumber(2) * 1000 / 60 / 60 + else worldTime + 6000 + + val dt = GameTimeFormatter.parse(time) + def fmt(format: String) { + if (format == "*t") { + lua.newTable(0, 8) + lua.pushInteger(dt.year) + lua.setField(-2, "year") + lua.pushInteger(dt.month) + lua.setField(-2, "month") + lua.pushInteger(dt.day) + lua.setField(-2, "day") + lua.pushInteger(dt.hour) + lua.setField(-2, "hour") + lua.pushInteger(dt.minute) + lua.setField(-2, "min") + lua.pushInteger(dt.second) + lua.setField(-2, "sec") + lua.pushInteger(dt.weekDay) + lua.setField(-2, "wday") + lua.pushInteger(dt.yearDay) + lua.setField(-2, "yday") + } + else { + lua.pushString(GameTimeFormatter.format(format, dt)) + } + } + + // Just ignore the allowed leading '!', Minecraft has no time zones... + if (format.startsWith("!")) + fmt(format.substring(1)) + else + fmt(format) + 1 + }) + lua.setField(-2, "date") + // Return ingame time for os.time(). lua.pushScalaFunction(lua => { // Game time is in ticks, so that each day has 24000 ticks, meaning // one hour is game time divided by one thousand. Also, Minecraft // starts days at 6 o'clock, so we add those six hours. Thus: - // timestamp = (time + 6000) / 1000[h] * 60[m] * 60[s] * 1000[ms] - lua.pushNumber((worldTime + 6000) * 60 * 60) + // timestamp = (time + 6000) * 60[kh] * 60[km] / 1000[s] + lua.pushNumber((worldTime + 6000) * 60 * 60 / 1000) 1 }) lua.setField(-2, "time") diff --git a/li/cil/oc/server/component/RedstoneCard.scala b/li/cil/oc/server/component/RedstoneCard.scala index 6a2e463b3..c5c16f8a2 100644 --- a/li/cil/oc/server/component/RedstoneCard.scala +++ b/li/cil/oc/server/component/RedstoneCard.scala @@ -33,7 +33,7 @@ class RedstoneCard extends ManagedComponent { @LuaCallback("setOutput") def setOutput(context: Context, args: Arguments): Array[AnyRef] = { val side = checkSide(args, 0) - val value = args.checkInteger(1) max 0 min 15 + val value = args.checkInteger(1) max 0 min 255 node.network.node(context.address).host match { case redstone: Redstone => redstone.output(ForgeDirection.getOrientation(side), value.toShort) diff --git a/li/cil/oc/util/GameTimeFormatter.scala b/li/cil/oc/util/GameTimeFormatter.scala new file mode 100644 index 000000000..076e0c1f5 --- /dev/null +++ b/li/cil/oc/util/GameTimeFormatter.scala @@ -0,0 +1,100 @@ +package li.cil.oc.util + +import scala.collection.mutable + +object GameTimeFormatter { + // Locale? What locale? Seriously though, since this would depend on the + // server's locale I think it makes more sense to keep it English always. + private val weekDays = Array("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday") + private val shortWeekDays = Array("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat") + private val months = Array("January", "Febuary", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December") + private val shortMonths = Array("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec") + private val amPm = Array("AM", "PM") + + class DateTime(val year: Int, val month: Int, val day: Int, + val weekDay: Int, val yearDay: Int, + val hour: Int, val minute: Int, val second: Int) + + // See http://www.cplusplus.com/reference/ctime/strftime/ + private val specifiers: Map[Char, (DateTime) => String] = Map( + 'a' -> (t => shortWeekDays(t.weekDay)), + 'A' -> (t => weekDays(t.weekDay)), + 'b' -> (t => shortMonths(t.month)), + 'B' -> (t => months(t.month)), + 'c' -> (t => format("%a %b %d %H:%M:%S %Y", t)), + 'C' -> (t => "%02d".format(t.year / 100)), + 'd' -> (t => "%02d".format(t.day + 1)), + 'D' -> (t => format("%m/%d/%y", t)), + 'e' -> (t => "% 2d".format(t.day + 1)), + 'F' -> (t => format("%Y-%m-%d", t)), + //'g' -> (t => ""), + //'G' -> (t => ""), + 'h' -> (t => format("%b", t)), + 'H' -> (t => "%02d".format(t.hour)), + 'I' -> (t => "%02d".format(t.hour % 12 + 1)), + 'j' -> (t => "%03d".format(t.yearDay)), + 'm' -> (t => "%02d".format(t.month + 1)), + 'M' -> (t => "%02d".format(t.minute)), + 'n' -> (t => "\n"), + 'p' -> (t => amPm(if (t.hour < 12) 0 else 1)), + 'r' -> (t => format("%I:%M:%S %p", t)), + 'R' -> (t => format("%H:%M", t)), + 'S' -> (t => "%02d".format(t.second)), + 't' -> (t => "\t"), + 'T' -> (t => format("%H:%M:%S", t)), + 'u' -> (t => ""), + //'U' -> (t => ""), + //'V' -> (t => ""), + 'w' -> (t => "%d".format(t.weekDay)), + //'W' -> (t => ""), + 'x' -> (t => format("%D", t)), + 'X' -> (t => format("%T", t)), + 'y' -> (t => "%02d".format(t.year % 100)), + 'Y' -> (t => "%04d".format(t.year)), + //'z' -> (t => ""), + //'Z' -> (t => ""), + '%' -> (t => "%") + ) + + def parse(time: Double) = { + var day = (time / 24000).toLong + val weekDay = ((4 + day) % 7).toInt + val year = 1970 + (day / 365.2425).toInt + val yearDay = (day % 365.2425).toInt + day = yearDay + val monthLengths = + if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) + Array(31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) + else + Array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) + var month = 0 + while (day > monthLengths(month)) { + day = day - monthLengths(month) + month = month + 1 + } + + var seconds = ((time % 24000) * 60 * 60 / 1000).toInt + var minutes = seconds / 60 + seconds = seconds % 60 + val hours = (minutes / 60) % 24 + minutes = minutes % 60 + + new DateTime(year, month, day.toInt, weekDay, yearDay, hours, minutes, seconds) + } + + def format(format: String, time: DateTime) = { + val result = new mutable.StringBuilder() + val iterator = format.iterator + while (iterator.hasNext) { + iterator.next() match { + case '%' if iterator.hasNext => + specifiers.get(iterator.next()) match { + case Some(specifier) => result.append(specifier(time)) + case _ => + } + case c => result.append(c) + } + } + result.toString() + } +} diff --git a/li/cil/oc/util/LuaStateFactory.scala b/li/cil/oc/util/LuaStateFactory.scala index ecc99a7d0..12740100b 100644 --- a/li/cil/oc/util/LuaStateFactory.scala +++ b/li/cil/oc/util/LuaStateFactory.scala @@ -5,7 +5,6 @@ import com.naef.jnlua.{LuaState, NativeSupport} import java.io.File import java.io.FileOutputStream import java.nio.channels.Channels -import java.util.{Locale, Calendar} import li.cil.oc.server.component.Computer import li.cil.oc.util.ExtendedLuaState._ import li.cil.oc.{OpenComputers, Config} @@ -154,15 +153,6 @@ object LuaStateFactory { }) state.setField(-2, "realTime") - // Date-time formatting using Java's formatting capabilities. - state.pushScalaFunction(lua => { - val calendar = Calendar.getInstance(Locale.ENGLISH) - calendar.setTimeInMillis(lua.checkInteger(1)) - // TODO - 1 - }) - state.setField(-2, "date") - // Pop the os table. state.pop(1)