diff --git a/assets/blocks.psd b/assets/blocks.psd
index 950f8f813..12c4eac52 100644
Binary files a/assets/blocks.psd and b/assets/blocks.psd differ
diff --git a/build.gradle b/build.gradle
index f7ea8a62d..5a42b5f34 100644
--- a/build.gradle
+++ b/build.gradle
@@ -45,12 +45,13 @@ def getGitRef() {
if (System.getenv("BUILD_NUMBER") != null)
version += ".${System.getenv("BUILD_NUMBER")}"
-else
- version += "-" + getGitRef()
if (config.oc.subversion != null && config.oc.subversion != "")
version += "-${config.oc.subversion}"
+if (System.getenv("BUILD_NUMBER") == null)
+ version += "+" + getGitRef()
+
ext.simpleVersion = version
version = "MC${config.minecraft.version}-${project.version}"
@@ -238,6 +239,7 @@ idea.module.scopes.PROVIDED.plus += [configurations.provided]
sourceSets {
main {
scala {
+ exclude 'li/cil/oc/integration/agricraft/**'
exclude 'li/cil/oc/integration/appeng/**'
exclude 'li/cil/oc/integration/bloodmagic/**'
exclude 'li/cil/oc/integration/bluepower/**'
diff --git a/build.properties b/build.properties
index cefd032a6..cf41e0f4c 100644
--- a/build.properties
+++ b/build.properties
@@ -1,7 +1,7 @@
minecraft.version=1.8
forge.version=11.14.3.1450
-oc.version=1.5.15
+oc.version=1.5.16
oc.subversion=
ae2.version=rv2-beta-26
diff --git a/src/main/java/li/cil/oc/api/API.java b/src/main/java/li/cil/oc/api/API.java
index 051bcb5d8..471ef36e7 100644
--- a/src/main/java/li/cil/oc/api/API.java
+++ b/src/main/java/li/cil/oc/api/API.java
@@ -12,7 +12,7 @@ import li.cil.oc.api.detail.*;
*/
public class API {
public static final String ID_OWNER = "OpenComputers|Core";
- public static final String VERSION = "5.5.3";
+ public static final String VERSION = "5.5.4";
public static DriverAPI driver = null;
public static FileSystemAPI fileSystem = null;
diff --git a/src/main/java/li/cil/oc/api/driver/item/MutableProcessor.java b/src/main/java/li/cil/oc/api/driver/item/MutableProcessor.java
new file mode 100644
index 000000000..04cc07634
--- /dev/null
+++ b/src/main/java/li/cil/oc/api/driver/item/MutableProcessor.java
@@ -0,0 +1,27 @@
+package li.cil.oc.api.driver.item;
+
+import li.cil.oc.api.machine.Architecture;
+import net.minecraft.item.ItemStack;
+
+/**
+ * May be implemented in processor drivers of processors that can be reconfigured.
+ *
+ * This is the case for OC's built-in CPUs, for example, which can be reconfigured
+ * to any registered architecture. It a CPU has such a driver, it may also be
+ * reconfigured by the machine it is running in (e.g. in the Lua case via
+ * computer.setArchitecture).
+ */
+public interface MutableProcessor extends Processor {
+ /**
+ * Get a list of all architectures supported by this processor.
+ */
+ java.util.Collection> allArchitectures();
+
+ /**
+ * Set the architecture to use for the specified processor.
+ *
+ * @param stack the processor to set the architecture for.
+ * @param architecture the architecture to use on the processor.
+ */
+ void setArchitecture(ItemStack stack, Class extends Architecture> architecture);
+}
diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf
index 863dbad11..09b6b656d 100644
--- a/src/main/resources/application.conf
+++ b/src/main/resources/application.conf
@@ -126,10 +126,10 @@ opencomputers {
# happen.
startupDelay: 0.25
- # The maximum size of the byte array that can be stored on EEPROMs.
+ # The maximum size of the byte array that can be stored on EEPROMs as executable data..
eepromSize: 4096
- # The maximum size of the byte array that can be stored on EEPROMs.
+ # The maximum size of the byte array that can be stored on EEPROMs as configuration data.
eepromDataSize: 256
# The number of components the different CPU tiers support. This list
@@ -767,6 +767,10 @@ opencomputers {
# Per-byte cost for ECDSA operation is controlled by `complex` value,
# because data is hashed with SHA256 before signing/verifying
dataCardAsymmetric: 10.0
+
+ # Energy required for one transposer operation (regardless of the number
+ # of items / fluid volume moved).
+ transposer: 1
}
# The rate at which different blocks accept external power. All of these
@@ -1191,6 +1195,11 @@ opencomputers {
# properties, i.e. througput, frequency and buffer size.
# Valid values are: 0 = none, 1 = tier 1, 2 = tier 2, 3 = tier 3.
serverRackSwitchTier: 1
+
+ # Enforced delay when changing a redstone emitting component's output,
+ # such as the redstone card and redstone I/O block. Lowering this can
+ # have very negative impact on server TPS, so beware.
+ redstoneDelay: 0.1
}
# Settings for mod integration (the mod previously known as OpenComponents).
diff --git a/src/main/resources/assets/opencomputers/blockstates/relay.json b/src/main/resources/assets/opencomputers/blockstates/relay.json
new file mode 100644
index 000000000..bb0444909
--- /dev/null
+++ b/src/main/resources/assets/opencomputers/blockstates/relay.json
@@ -0,0 +1,5 @@
+{
+ "variants": {
+ "normal": { "model": "opencomputers:relay" }
+ }
+}
diff --git a/src/main/resources/assets/opencomputers/blockstates/transposer.json b/src/main/resources/assets/opencomputers/blockstates/transposer.json
new file mode 100644
index 000000000..32bd3dad6
--- /dev/null
+++ b/src/main/resources/assets/opencomputers/blockstates/transposer.json
@@ -0,0 +1,5 @@
+{
+ "variants": {
+ "normal": { "model": "opencomputers:transposer" }
+ }
+}
diff --git a/src/main/resources/assets/opencomputers/doc/en_US/block/accessPoint.md b/src/main/resources/assets/opencomputers/doc/en_US/block/accessPoint.md
index 5dde0da28..78bacc26c 100644
--- a/src/main/resources/assets/opencomputers/doc/en_US/block/accessPoint.md
+++ b/src/main/resources/assets/opencomputers/doc/en_US/block/accessPoint.md
@@ -2,6 +2,8 @@

+*This block is deprecated and will be removed in a future version.* Craft it into a [relay](relay.md) to avoid losing it.
+
The access point is the wireless version of the [switch](switch.md). It can be used to separate subnetworks so that machines in them will not see [components](../general/computer.md) in other networks, while still allowing to send network messages to the machines in other networks.
In addition to that, this block can act as a repeater: it can re-send wired messages as wired messages to other devices; or wireless messages as wired or wireless messages.
diff --git a/src/main/resources/assets/opencomputers/doc/en_US/block/index.md b/src/main/resources/assets/opencomputers/doc/en_US/block/index.md
index a33f40557..26a47e734 100644
--- a/src/main/resources/assets/opencomputers/doc/en_US/block/index.md
+++ b/src/main/resources/assets/opencomputers/doc/en_US/block/index.md
@@ -36,10 +36,9 @@ Keep in mind that some of these may not be available, depending on the recipe se
* [Disassembler](disassembler.md)
## Networking
-* [Access Point](accessPoint.md)
* [Cable](cable.md)
* [Net Splitter](netSplitter.md)
-* [Switch](switch.md)
+* [Relay](relay.md)
## Power management
* [Capacitor](capacitor.md)
diff --git a/src/main/resources/assets/opencomputers/doc/en_US/block/netSplitter.md b/src/main/resources/assets/opencomputers/doc/en_US/block/netSplitter.md
index 230e4484f..f6c02a752 100644
--- a/src/main/resources/assets/opencomputers/doc/en_US/block/netSplitter.md
+++ b/src/main/resources/assets/opencomputers/doc/en_US/block/netSplitter.md
@@ -2,6 +2,6 @@

-The net splitter is a device that allows controlling connectivity between subnetworks. Unlike the [switch](switch.md) or [power converter](powerConverter.md) it directly connects adjacent subnetworks, i.e. components can be accessed. Each side's connectivity can be toggled using a wrench (e.g. the [scrench](../item/wrench.md)). When a redstone signal is applied to the net splitter, all sides' connectivity is inverted.
+The net splitter is a device that allows controlling connectivity between subnetworks. Unlike the [relay](relay.md) or [power converter](powerConverter.md) it directly connects adjacent subnetworks, i.e. components can be accessed. Each side's connectivity can be toggled using a wrench (e.g. the [scrench](../item/wrench.md)). When a redstone signal is applied to the net splitter, all sides' connectivity is inverted.
This block can therefore be used to toggle connectivity to certain parts of a component network. Use a [redstone I/O block](redstone.md) or [redstone cards](../item/redstoneCard1.md) to automate the net splitter.
diff --git a/src/main/resources/assets/opencomputers/doc/en_US/block/relay.md b/src/main/resources/assets/opencomputers/doc/en_US/block/relay.md
new file mode 100644
index 000000000..212369838
--- /dev/null
+++ b/src/main/resources/assets/opencomputers/doc/en_US/block/relay.md
@@ -0,0 +1,13 @@
+# Relay
+
+
+
+The relay can be used to allow different subnetworks to send network messages to each other, without exposing components to [computers](../general/computer.md) in other networks. Keeping components local is usually a good idea, to avoid [computers](../general/computer.md) using the wrong [screen](screen1.md) or to avoid component overflows to happen (causing [computers](../general/computer.md) to crash and refuse to boot up).
+
+The relay can be upgraded by inserting a [wireless network card](../item/wlanCard.md) to also relay messages wirelessly. Wireless messages can be received and relayed by other relays with a wireless network card, or by [computers](../general/computer.md) with a wireless network card.
+
+Alternatively the relay can be upgraded using [linked cards](../item/linkedCard.md). In this case it will forward messages through the tunnel provided by the linked card, too; at the usual cost, so make sure the relay is sufficiently powered.
+
+Relays do *not* keep track of which packets they forwarded recently, so avoid cycles in your network or you may receive the same packet multiple times. Due to the limited buffer size of relays, sending messages too frequently will result in packet loss. You can upgrade your relays to increase the speed with which they relay messages, as well as their internal message queue size.
+
+Packets are only re-sent a certain number of times, so chaining an arbitrary number of relays is not possible. By default, a packet will be re-sent up to five times.
diff --git a/src/main/resources/assets/opencomputers/doc/en_US/block/serverRack.md b/src/main/resources/assets/opencomputers/doc/en_US/block/serverRack.md
index 49f1df835..5630023b4 100644
--- a/src/main/resources/assets/opencomputers/doc/en_US/block/serverRack.md
+++ b/src/main/resources/assets/opencomputers/doc/en_US/block/serverRack.md
@@ -6,4 +6,4 @@ A server rack houses up to four [servers](../item/server1.md). A [server](../ite
Each [server](../item/server1.md) in a server rack can only communicate with one "face" of the server rack at a time - or none at all. Which side each [server](../item/server1.md) is connected to can be configured in the server rack's GUI. Beware that the sides are from the point of view of the server rack, i.e. if you are looking at the front of the server rack, `sides.right` will be to your left and vice versa.
-Server racks act as [switch](switch.md) and [power distributor](powerDistributor.md) in one. The switch mode of the server rack can be configured in its GUI, with the two options being internal and external. In external mode the server rack will behave like a normal [switch](switch.md). In internal mode, messages are only passed to the [servers](../item/server1.md) in the rack, and will not be automatically relayed to the other faces of the rack. [Servers](../item/server1.md) will still be able to send messages to each other. This allows using server racks as advanced [switches](switch.md) that can perform filter and mapping operations, for example.
+Server racks act as [relay](relay.md) and [power distributor](powerDistributor.md) in one. The switch mode of the server rack can be configured in its GUI, with the two options being internal and external. In external mode the server rack will behave like a normal [relay](relay.md). In internal mode, messages are only passed to the [servers](../item/server1.md) in the rack, and will not be automatically relayed to the other faces of the rack. [Servers](../item/server1.md) will still be able to send messages to each other. This allows using server racks as advanced [relays](relay.md) that can perform filter and mapping operations, for example.
diff --git a/src/main/resources/assets/opencomputers/doc/en_US/block/switch.md b/src/main/resources/assets/opencomputers/doc/en_US/block/switch.md
index a607e0cfd..35ad59fd4 100644
--- a/src/main/resources/assets/opencomputers/doc/en_US/block/switch.md
+++ b/src/main/resources/assets/opencomputers/doc/en_US/block/switch.md
@@ -2,6 +2,8 @@

+*This block is deprecated and will be removed in a future version.* Craft it into a [relay](relay.md) to avoid losing it.
+
The switch can be used to allow different subnetworks to send network messages to each other, without exposing components to [computers](../general/computer.md) in other networks. Keeping components local is usually a good idea, to avoid [computers](../general/computer.md) using the wrong [screen](screen1.md) or to avoid component overflows to happen (causing [computers](../general/computer.md) to crash and refuse to boot up).
There is also a wireless variation of this block, called the [access point](accessPoint.md), which will also relay messages wirelessly. Wireless messages can be received and relayed by other [access points](accessPoint.md), or by [computers](../general/computer.md) with a [wireless network card](../item/wlanCard.md).
diff --git a/src/main/resources/assets/opencomputers/doc/en_US/block/transposer.md b/src/main/resources/assets/opencomputers/doc/en_US/block/transposer.md
new file mode 100644
index 000000000..ccc3e07ff
--- /dev/null
+++ b/src/main/resources/assets/opencomputers/doc/en_US/block/transposer.md
@@ -0,0 +1,9 @@
+# Transposer
+
+
+
+The transposer bridges the gap between redstone controlled hoppers and [robots](robot.md), allowing [computer](../general/computer.md)-controlled transferral of items and fluids between adjacent blocks.
+
+*Note that this block has no internal inventory.*
+
+Besides moving things around, it can also be used to inspect the contents of the adjacent inventories, like an [adapter](adapter.md) with an [inventory controller upgrade](../item/inventoryControllerUpgrade.md) could, and the contents of adjacent tanks, like and adapter with a [tank controller upgrade](../item/tankControllerUpgrade.md) could.
diff --git a/src/main/resources/assets/opencomputers/doc/en_US/item/lanCard.md b/src/main/resources/assets/opencomputers/doc/en_US/item/lanCard.md
index 3fbd8a6c4..fc730e035 100644
--- a/src/main/resources/assets/opencomputers/doc/en_US/item/lanCard.md
+++ b/src/main/resources/assets/opencomputers/doc/en_US/item/lanCard.md
@@ -2,4 +2,4 @@

-The network card allows [computers](../general/computer.md) to send and receive network messages. Messages (or packets) can be broadcasted to all receiving nodes in a subnetwork, or sent to a specific node with a specified address. [Switches](../block/switch.md) and [access points](../block/accessPoint.md) can be used to bridge multiple subnetworks by relaying messages between the subnetworks they are connected to. It is also possible to send a targeted message if the receiver is in another subnetwork, if the networks are connected via one or more [switches](../block/switch.md).
+The network card allows [computers](../general/computer.md) to send and receive network messages. Messages (or packets) can be broadcasted to all receiving nodes in a subnetwork, or sent to a specific node with a specified address. [Relays](../block/relay.md) can be used to bridge multiple subnetworks by relaying messages between the subnetworks they are connected to. It is also possible to send a targeted message if the receiver is in another subnetwork, if the networks are connected via one or more [relays](../block/relay.md).
diff --git a/src/main/resources/assets/opencomputers/doc/ru_RU/block/accessPoint.md b/src/main/resources/assets/opencomputers/doc/ru_RU/block/accessPoint.md
index 744abec69..faa4a62bf 100644
--- a/src/main/resources/assets/opencomputers/doc/ru_RU/block/accessPoint.md
+++ b/src/main/resources/assets/opencomputers/doc/ru_RU/block/accessPoint.md
@@ -2,6 +2,8 @@

+*Этот блок устарел и будет удален в следующих версиях.* Замените его на [ретранслятор](relay.md).
+
Точки доступа, это беспроводные версии [коммутаторов](switch.md). Они могут быть использованы, когда требуется разделить подсети, чтобы устройства не видели [компоненты](../general/computer.md) в других сетях, однако сохраняя при этом возможность передачи сообщений между подсетями.
В дополнение к этому, точки доступа могут использоваться как повторители: они могут перенаправлять сообщения из проводной линии другим устройствам; или беспроводные сообщения как проводные, так и беспроводные.
diff --git a/src/main/resources/assets/opencomputers/doc/ru_RU/block/index.md b/src/main/resources/assets/opencomputers/doc/ru_RU/block/index.md
index a1e604531..ce0ebf1d9 100644
--- a/src/main/resources/assets/opencomputers/doc/ru_RU/block/index.md
+++ b/src/main/resources/assets/opencomputers/doc/ru_RU/block/index.md
@@ -39,6 +39,7 @@
* [Точка доступа](accessPoint.md)
* [Кабель](cable.md)
* [Сетевой переключатель](netSplitter.md)
+* [Ретранслятор](relay.md)
* [Коммутатор](switch.md)
## Управление питанием
diff --git a/src/main/resources/assets/opencomputers/doc/ru_RU/block/netSplitter.md b/src/main/resources/assets/opencomputers/doc/ru_RU/block/netSplitter.md
index 37e4bc296..e3725dcb5 100644
--- a/src/main/resources/assets/opencomputers/doc/ru_RU/block/netSplitter.md
+++ b/src/main/resources/assets/opencomputers/doc/ru_RU/block/netSplitter.md
@@ -2,6 +2,6 @@

-Сетевой переключатель позволяет контролировать соединение между подсетями. В отличие от [коммутатора](switch.md) или [конвертера энергии](powerConverter.md) позволяет непосредственно соединить подсети, делая при этом доступными все компоненты. Соединение каждой стороны переключается [ключем](../item/wrench.md). При подаче редстоун-сигнала все соединения инвертируются.
+Сетевой переключатель позволяет контролировать соединение между подсетями. В отличие от [ретранслятора](relay.md) или [конвертера энергии](powerConverter.md) позволяет непосредственно соединить подсети, делая при этом доступными все компоненты. Соединение каждой стороны переключается [ключем](../item/wrench.md). При подаче редстоун-сигнала все соединения инвертируются.
Таким образом, этот блок может быть использован для переключения соединения определенных компонентов сети. Используйте [редстоун-I/O](redstone.md) или [редстоун карты](../item/redstoneCard1.md) для автоматизации сетевого переключателя.
\ No newline at end of file
diff --git a/src/main/resources/assets/opencomputers/doc/ru_RU/block/relay.md b/src/main/resources/assets/opencomputers/doc/ru_RU/block/relay.md
new file mode 100644
index 000000000..64caecb9f
--- /dev/null
+++ b/src/main/resources/assets/opencomputers/doc/ru_RU/block/relay.md
@@ -0,0 +1,13 @@
+# Ретранслятор
+
+
+
+Ретранслятор используется для передачи сообщений между несколькими подсетями, изолируя компоненты [компьютеров](../general/computer.md) в других сетях. Сохранение компонентов, как правило, хорошая идея, что не позволяет [компьютерам](../general/computer.md) использовать неверный [монитор](screen1.md) или не позволяет перенагружать их (в результате чего [компьютеры](../general/computer.md) выключатся и отказываются загружаться).
+
+Может быть улучшен добавлением [беспроводной сетевой карты](../item/wlanCard.md), что позволит ретранслировать беспроводные сообщения. Сообщения, переданные по беспроводной линии могут быть получены или перенаправлены другими ретрансляторами или [компьютерами](../general/computer.md) с [беспроводной сетевой картой](../item/wlanCard.md).
+
+Также ретранслятор может быть улучшен с помощью [соединенной карты](../item/linkedCard.md). Это позволит передавать сообщения внутри туннеля, предоставленного картой, также; с обычными затратами энергии, поэтому убедитесь, что на ретранслятор поступает нужное количество энергии.
+
+Ретрансляторы *не* отслеживают, какие пакеты и куда они передали, поэтому для в сети могут образовываться петли или вы можете получать одно сообщение несколько раз. Из-за ограниченного буфера сообщений коммутатора, частое отправление сообщений приводит к их потере. Вы можете улучшить ретранслятор для увеличения скорости обработки сообщений, а также увеличения размера сообщений.
+
+Сообщения, могут перенаправлены всего несколько раз, поэтому цепочки с произвольным количеством ретрансляторов невозможны. По умолчанию, сообщение может быть перенаправлено пять раз.
\ No newline at end of file
diff --git a/src/main/resources/assets/opencomputers/doc/ru_RU/block/serverRack.md b/src/main/resources/assets/opencomputers/doc/ru_RU/block/serverRack.md
index 4a5af4ef2..850d739ed 100644
--- a/src/main/resources/assets/opencomputers/doc/ru_RU/block/serverRack.md
+++ b/src/main/resources/assets/opencomputers/doc/ru_RU/block/serverRack.md
@@ -6,4 +6,4 @@
Каждый [сервер](../item/server1.md) в серверной стойке может взаимодействовать только с одной "стороной" стойки или ни с какой. К какой стороне, какой [сервер](../item/server1.md) подключен, настраивается в интерфейсе стойки. Будьте внимательны, стороны считаются относительно самой стойки, например, если вы смотрите на стойку спереди, то `правая сторона` стойки для вас будет слева.
-Серверные стойки взаимодействуют с [коммутаторами](switch.md) и [распределителями энергии](powerDistributor.md). Переключатель режимов работы стойки, может быть настроен в интерфейсе самой стойки, он имеет 2 режима: внешний и внутренний. Во внешнем режиме сервер будет работать как обычный [коммутатор](switch.md). Во внутреннем режиме, сообщения будут передаваться только к [серверам](../item/server1.md) в стойке и не будут автоматически связаны со сторонами стойки. [Серверы](../item/server1.md) все также будут иметь возможность передачи сообщений друг другу. Это позволяет использовать серверные стойки как продвинутые [коммутаторы](switch.md) для операций фильтрации и направления данных, например.
+Серверные стойки взаимодействуют с [ретрансляторами](relay.md) и [распределителями энергии](powerDistributor.md). Переключатель режимов работы стойки, может быть настроен в интерфейсе самой стойки, он имеет 2 режима: внешний и внутренний. Во внешнем режиме сервер будет работать как обычный [ретранслятор](relay.md). Во внутреннем режиме, сообщения будут передаваться только к [серверам](../item/server1.md) в стойке и не будут автоматически связаны со сторонами стойки. [Серверы](../item/server1.md) все также будут иметь возможность передачи сообщений друг другу. Это позволяет использовать серверные стойки как продвинутые [ретрансляторы](relay.md) для операций фильтрации и направления данных, например.
diff --git a/src/main/resources/assets/opencomputers/doc/ru_RU/block/switch.md b/src/main/resources/assets/opencomputers/doc/ru_RU/block/switch.md
index 56445e17f..27b172988 100644
--- a/src/main/resources/assets/opencomputers/doc/ru_RU/block/switch.md
+++ b/src/main/resources/assets/opencomputers/doc/ru_RU/block/switch.md
@@ -2,6 +2,8 @@

+*Этот блок устарел и будет удален в следующих версиях.* Замените его на [ретранслятор](relay.md).
+
Коммутатор используется для передачи сообщений между несколькими подсетями, не используя компоненты [компьютеров](../general/computer.md) в других сетях. Сохранение компонентов, как правило, хорошая идея, что не позволяет [компьютерам](../general/computer.md) использовать неверный [монитор](screen1.md) или не позволяет перенагружать их (в результате чего [компьютеры](../general/computer.md) выключатся и отказываются загружаться).
Также есть беспроводная версия коммутатора, называемая [точка доступа](accessPoint.md), с ее помощью сообщения передаются по беспроводной линии. Сообщения, переданные по беспроводной линии могут быть получены или перенаправлены другими [точками доступа](accessPoint.md) или [компьютерами](../general/computer.md) с [беспроводной сетевой картой](../item/wlanCard.md).
diff --git a/src/main/resources/assets/opencomputers/doc/ru_RU/block/transposer.md b/src/main/resources/assets/opencomputers/doc/ru_RU/block/transposer.md
new file mode 100644
index 000000000..ea9c615e0
--- /dev/null
+++ b/src/main/resources/assets/opencomputers/doc/ru_RU/block/transposer.md
@@ -0,0 +1,9 @@
+# Транспозер
+
+
+
+Транспозер заполняет пробел между воронками, контролируемыми редстоун-сигналом и [роботами](robot.md), позволяя [компьютерам](../general/computer.md) контролировать перемещение предметов и жидкостей между соседними блоками.
+
+*Данный блок не имеет внутреннего инвентаря.*
+
+Кроме перемещения блоков, он также может быть использован для проверки содержимого соседних инвентарей, подобно [адаптеру](adapter.md) с [контроллером инвентаря](../item/inventoryControllerUpgrade.md), и содержимого соседних хранилищ жидкости, подобно адаптеру с [контроллером жидкости](../item/tankControllerUpgrade.md).
\ No newline at end of file
diff --git a/src/main/resources/assets/opencomputers/doc/ru_RU/item/lanCard.md b/src/main/resources/assets/opencomputers/doc/ru_RU/item/lanCard.md
index 7094ef65b..6a25e7b07 100644
--- a/src/main/resources/assets/opencomputers/doc/ru_RU/item/lanCard.md
+++ b/src/main/resources/assets/opencomputers/doc/ru_RU/item/lanCard.md
@@ -2,4 +2,4 @@

-Сетевая карта позволяет [компьютерам](../general/computer.md) отправлять и получать сетевые сообщения. Сообщения (или пакеты) будут отправлены всем принимающим устройствам в подсети или конкретной сетевой карте (после указания ее адреса). [Коммутаторы](../block/switch.md) и [точки доступа](../block/accessPoint.md) могут быть использованы для связи нескольких подсетей друг с другом, для передачи сообщений в них. Также возможно отправить письмо получателю, даже если он находится в другой подсети, если сеть подключена к одному или нескольким [коммутаторам](../block/switch.md).
+Сетевая карта позволяет [компьютерам](../general/computer.md) отправлять и получать сетевые сообщения. Сообщения (или пакеты) будут отправлены всем принимающим устройствам в подсети или конкретной сетевой карте (при указании её адреса). [Ретрансляторы](../block/relay.md) могут быть использованы для связи нескольких подсетей друг с другом и передачи сообщений. Также возможно отправить письмо получателю, даже если он находится в другой подсети, если сеть подключена к одному или нескольким [ретрасляторами](../block/relay.md).
diff --git a/src/main/resources/assets/opencomputers/lang/en_US.lang b/src/main/resources/assets/opencomputers/lang/en_US.lang
index 7091603c1..c53d25713 100644
--- a/src/main/resources/assets/opencomputers/lang/en_US.lang
+++ b/src/main/resources/assets/opencomputers/lang/en_US.lang
@@ -3,7 +3,7 @@
# Use [nl] to for a line break.
# Blocks
-tile.oc.accessPoint.name=Access Point
+tile.oc.accessPoint.name=§cAccess Point§7
tile.oc.adapter.name=Adapter
tile.oc.assembler.name=Electronics Assembler
tile.oc.cable.name=Cable
@@ -23,20 +23,22 @@ tile.oc.hologram2.name=Hologram Projector (Tier 2)
tile.oc.keyboard.name=Keyboard
tile.oc.microcontroller.name=Microcontroller
tile.oc.motionSensor.name=Motion Sensor
+tile.oc.netSplitter.name=Net Splitter
tile.oc.powerConverter.name=Power Converter
tile.oc.powerDistributor.name=Power Distributor
tile.oc.print.name=3D Print
tile.oc.printer.name=3D Printer
tile.oc.raid.name=Raid
tile.oc.redstone.name=Redstone I/O
+tile.oc.relay.name=Relay
tile.oc.robot.name=Robot
tile.oc.robotAfterimage.name=Robot
tile.oc.screen1.name=Screen (Tier 1)
tile.oc.screen2.name=Screen (Tier 2)
tile.oc.screen3.name=Screen (Tier 3)
tile.oc.serverRack.name=Server Rack
-tile.oc.switch.name=Switch
-tile.oc.netSplitter.name=Net Splitter
+tile.oc.switch.name=§cSwitch§7
+tile.oc.transposer.name=Transposer
tile.oc.waypoint.name=Waypoint
# Items
@@ -235,6 +237,7 @@ oc:container.Disassembler=Disassembler
oc:container.DiskDrive=Disk Drive
oc:container.Printer=Printer
oc:container.Raid=Raid
+oc:container.Relay=Relay
oc:container.Server=Server
oc:container.ServerRack=Server Rack
oc:container.Switch=Switch
@@ -328,6 +331,7 @@ oc:tooltip.RedstoneCard.RedNet=§fRedNet§7 is §asupported§7.
oc:tooltip.RedstoneCard.WirelessCBE=§fWireless Redstone (ChickenBones)§7 is §asupported§7.
oc:tooltip.RedstoneCard.WirelessSV=§fWireless Redstone (SlimeVoid)§7 is §asupported§7.
oc:tooltip.RedstoneCard=Allows reading and emitting redstone signals around the computer or robot.
+oc:tooltip.Relay=Allows connecting different networks to each other. Only network messages will be passed along, components will not be visible through this. Use this to separate networks while still allowing communication using Network Cards, for example.
oc:tooltip.Robot=Unlike computers, robots can move around and interact with the world much like a player can.[nl] §cCan not connect to external components.§7
# The underscore makes sure this isn't hidden with the rest of the tooltip.
oc:tooltip.Robot_Level=§fLevel§7: §a%s§7
@@ -345,6 +349,7 @@ oc:tooltip.Tier=§8Tier %s
oc:tooltip.NetSplitter=Acts as a dynamic connector. Connectivity of each side can be toggled by hitting it with a wrench. Connectivity of all sides can be inverted by applying a redstone signal.
oc:tooltip.TooLong=Hold [§f%s§7] for a detailed tooltip.
oc:tooltip.Transistor=A basic element in most other computer parts. It's a bit twisted, but it does the job.
+oc:tooltip.Transposer=Allows automated transferral of items and fluids between adjacent inventories and fluid containers.
oc:tooltip.UpgradeAngel=Allows robots to place blocks in thin air, even if there is no point of reference.
oc:tooltip.UpgradeBattery=Increase the amount of energy a device can store, allowing it work longer without having to be recharged. [nl] Capacity: §f%s§7
oc:tooltip.UpgradeChunkloader=If a robot moves in a forest and no one is around to see it, does it really move? This upgrades makes sure it does. It keeps the chunk a device is in loaded, but continually consumes energy while active.
diff --git a/src/main/resources/assets/opencomputers/lang/ru_RU.lang b/src/main/resources/assets/opencomputers/lang/ru_RU.lang
index 6062721fd..37e306090 100644
--- a/src/main/resources/assets/opencomputers/lang/ru_RU.lang
+++ b/src/main/resources/assets/opencomputers/lang/ru_RU.lang
@@ -3,7 +3,7 @@
# Use [nl] to for a line break.
# Blocks
-tile.oc.accessPoint.name=Точка доступа
+tile.oc.accessPoint.name=§cТочка доступа§7
tile.oc.adapter.name=Адаптер
tile.oc.assembler.name=Сборщик роботов
tile.oc.cable.name=Кабель
@@ -23,6 +23,7 @@ tile.oc.hologram2.name=Голографический проектор (2-ой
tile.oc.keyboard.name=Клавиатура
tile.oc.microcontroller.name=Микроконтроллер
tile.oc.motionSensor.name=Датчик движения
+tile.oc.netSplitter.name=Сетевой переключатель
tile.oc.powerConverter.name=Преобразователь энергии
tile.oc.powerDistributor.name=Распределитель энергии
tile.oc.print.name=3D модель
@@ -35,8 +36,8 @@ tile.oc.screen1.name=Монитор (1-ый уровень)
tile.oc.screen2.name=Монитор (2-ой уровень)
tile.oc.screen3.name=Монитор (3-ий уровень)
tile.oc.serverRack.name=Серверная стойка
-tile.oc.switch.name=Коммутатор
-tile.oc.netSplitter.name=Сетевой переключатель
+tile.oc.switch.name=§cКоммутатор§7
+tile.oc.transposer.name=Транспозер
tile.oc.waypoint.name=Путевая точка
# Items
@@ -232,6 +233,7 @@ oc:container.Disassembler=Разборщик
oc:container.DiskDrive=Дисковод
oc:container.Printer=Принтер
oc:container.Raid=RAID
+oc:container.Relay=Ретранслятор
oc:container.Server=Сервер
oc:container.ServerRack=Серверная стойка
oc:container.Switch=Коммутатор
@@ -325,6 +327,7 @@ oc:tooltip.RedstoneCard.RedNet=Мод §fRedNet§7 §aподдерживаетс
oc:tooltip.RedstoneCard.WirelessCBE=Мод §fWireless Redstone (ChickenBones)§7 §aподдерживается§7.
oc:tooltip.RedstoneCard.WirelessSV=Мод §fWireless Redstone (SlimeVoid)§7 §aподдерживается§7.
oc:tooltip.RedstoneCard=Позволяет принимать и излучать сигналы красного камня вокруг компьютера или робота.
+oc:tooltip.Relay=Позволяет соединять различные сети между собой. Передаются только сообщения, компоненты между сетями недоступны. Используйте его для разделения сетей с возможностью пересылать сообщения между ними.
oc:tooltip.Robot=В отличие от компьютеров, роботы могут передвигаться и взаимодействовать с миром, как игрок.[nl] §cНе могут взаимодействовать с внешними компонентами.§7
# The underscore makes sure this isn't hidden with the rest of the tooltip.
oc:tooltip.Robot_Level=§fУровень§7: §a%s§7.
@@ -342,6 +345,7 @@ oc:tooltip.Tier=§8Уровень %s
oc:tooltip.NetSplitter=Работает как переключатель. Соединение каждой стороны переключается ключем. При подаче сигнала красного камня все соединения инвертируются.
oc:tooltip.TooLong=Удерживайте [§f%s§7], чтобы отобразить описание.
oc:tooltip.Transistor=Базовый элемент для других частей компьютера. Он немного деформирован, но отлично выполняет свою работу.
+oc:tooltip.Transposer=Позволяет автоматизировать перемещение блоков и жидкостей между соседними инвентарями и хранилищами жидкости.
oc:tooltip.UpgradeAngel=Позволяет роботам размещать блоки в воздухе, даже если отсутствует точка опоры.
oc:tooltip.UpgradeBattery=Увеличивает количество энергии, которую робот может хранить, это позволяет ему работать дольше без перезарядки.
oc:tooltip.UpgradeChunkloader=Если робот движется в лесу и никто его не видит, действительно ли он движется? Это улучшение гарантирует, что движется. Оно держит чанк, на котором находится робот, загруженным, но постоянно потребляет энергию.
diff --git a/src/main/resources/assets/opencomputers/loot/OpenOS/lib/term.lua b/src/main/resources/assets/opencomputers/loot/OpenOS/lib/term.lua
index 1131c3f7c..e89b2acd2 100644
--- a/src/main/resources/assets/opencomputers/loot/OpenOS/lib/term.lua
+++ b/src/main/resources/assets/opencomputers/loot/OpenOS/lib/term.lua
@@ -31,6 +31,16 @@ function term.clear()
cursorX, cursorY = 1, 1
end
+function term.reset()
+ if term.isAvailable() then
+ local maxw, maxh = component.gpu.maxResolution()
+ component.gpu.setResolution(maxw, maxh)
+ component.gpu.setBackground(0x000000)
+ component.gpu.setForeground(0xFFFFFF)
+ term.clear()
+ end
+end
+
function term.clearLine()
if term.isAvailable() then
local w = component.gpu.getResolution()
diff --git a/src/main/resources/assets/opencomputers/loot/OpenOS/usr/misc/greetings.txt b/src/main/resources/assets/opencomputers/loot/OpenOS/usr/misc/greetings.txt
index 041959f85..68d49726b 100644
--- a/src/main/resources/assets/opencomputers/loot/OpenOS/usr/misc/greetings.txt
+++ b/src/main/resources/assets/opencomputers/loot/OpenOS/usr/misc/greetings.txt
@@ -12,7 +12,7 @@ Most programs can be interrupted by pressing Ctrl+Alt+C.
Paste the contents of the clipboard using the middle mouse button or a configurable key (default: insert).
Computers will consume less power while idling - i.e. when os.sleep(n > 0.05) is called.
Screens will consume more power the more lit characters they display.
-Most blocks act as 'cables' - use switches and power distributers to create separate networks.
+Most blocks act as 'cables' - use relays and power distributors to create separate networks.
Welcome to the dark side - here, have some cookies.
Screens can display Unicode - paste the special chars or use unicode.char.
Run `help` or `man programname` for ingame help on programs shipped with OpenOS - start with `man man`.
@@ -25,4 +25,4 @@ Have you tried turning it off and on again?
To disable this greeting, install OpenOS to a writeable medium and delete `/etc/motd`.
Did you know OpenComputers has a forum? No? Well, it's at http://oc.cil.li/.
Please report bugs on the Github issue tracker, thank you!
-Beware of cycles when building networks, or you may get duplicate messages!
\ No newline at end of file
+Beware of cycles when building networks, or you may get duplicate messages!
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/bin/cat.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/bin/cat.lua
index b292f2291..d1fd62ba5 100644
--- a/src/main/resources/assets/opencomputers/loot/Plan9k/bin/cat.lua
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/bin/cat.lua
@@ -8,7 +8,7 @@ if #args == 0 then
until not read
else
for i = 1, #args do
- local file, reason = io.open(args[i])
+ local file, reason = io.open(args[i],"rb")--TODO: make b an option
if not file then
io.stderr:write(reason .. "\n")
return
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/bin/dd.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/bin/dd.lua
index de4509434..afcf298c0 100644
--- a/src/main/resources/assets/opencomputers/loot/Plan9k/bin/dd.lua
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/bin/dd.lua
@@ -67,6 +67,8 @@ for n = 1, options.count do
end
if options.wait then
os.sleep(tonumber(options.wait))
+ else
+ os.sleep(0)
end
end
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/bin/df.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/bin/df.lua
index 9cbc74107..041514da9 100644
--- a/src/main/resources/assets/opencomputers/loot/Plan9k/bin/df.lua
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/bin/df.lua
@@ -5,7 +5,8 @@ local text = require("text")
local args, options = shell.parse(...)
local function formatSize(size)
- if not options.h then
+ size = tonumber(size) or size
+ if not options.h or type(size) ~= "number" then
return tostring(size)
end
local sizes = {"", "K", "M", "G"}
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/bin/edit.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/bin/edit.lua
index f178f7eec..c1943cd7c 100644
--- a/src/main/resources/assets/opencomputers/loot/Plan9k/bin/edit.lua
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/bin/edit.lua
@@ -73,6 +73,7 @@ local code = ""
codeHandler = function(char)
if char == "[" then code = code .. char
elseif char == "0" then code = code .. char
+ elseif char == "3" then code = code .. char
elseif code == "[" and char == "A" then
charHandler = baseHandler
if y - 1 < 1 then return end
@@ -139,6 +140,16 @@ codeHandler = function(char)
out:write(text)
out:close()
end
+ elseif code == "[3" and char == "~" then
+ charHandler = baseHandler
+ if x > unicode.len(lines[y]) then
+ lines[y] = lines[y] .. (lines[y + 1] or "")
+ table.remove(lines, y + 1)
+ render(y, atline + edith - y)
+ return
+ end
+ lines[y] = lines[y]:sub(1, x-1) .. lines[y]:sub(x+1)
+ render(y, 1)
else
charHandler = baseHandler
end
@@ -149,7 +160,7 @@ baseHandler = function(char)
code = ""
charHandler = codeHandler
elseif char == "\n" then
- line = lines[y]
+ local line = lines[y]
lines[y] = unicode.sub(line or "", 1, x - 1)
table.insert(lines, y + 1, unicode.sub(line or "", x))
x = 1
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/bin/getty.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/bin/getty.lua
index 2f2a21992..9cedb28e2 100644
--- a/src/main/resources/assets/opencomputers/loot/Plan9k/bin/getty.lua
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/bin/getty.lua
@@ -199,6 +199,8 @@ local commandList = {}
-- \x1b9[Row];[Col];[Height];[Width]F -- fill
-- \x1b9[Row];[Col];[Height];[Width];[Dest Row];[Dest Col]c -- copy
+--Add fake gpu component for compat(?)
+
function charHandlers.control(char)
if char == "\x1b" then
commandList = {}
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/bin/ifconfig.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/bin/ifconfig.lua
index e7a9ba5cd..42a9f4862 100644
--- a/src/main/resources/assets/opencomputers/loot/Plan9k/bin/ifconfig.lua
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/bin/ifconfig.lua
@@ -2,6 +2,21 @@ local network = require "network"
local computer = require "computer"
local args = {...}
+local function formatSize(size)
+ size = tonumber(size) or size
+ if type(size) ~= "number" then
+ return tostring(size)
+ end
+ local sizes = {"", "K", "M", "G"}
+ local unit = 1
+ local power = 1024
+ while size > power and unit < #sizes do
+ unit = unit + 1
+ size = size / power
+ end
+ return math.floor(size * 10) / 10 .. sizes[unit]
+end
+
local function align(txt)return txt .. (" "):sub(#txt+1)end
if #args < 1 then
@@ -14,7 +29,7 @@ if #args < 1 then
local pktIn, pktOut, bytesIn, bytesOut = network.info.getInterfaceInfo(node)
print(" RX packets:"..tostring(pktIn))
print(" TX packets:"..tostring(pktOut))
- print(" RX bytes:"..tostring(bytesIn).." TX bytes:"..tostring(bytesOut))
+ print(" RX bytes: ".. tostring(bytesIn) .. " (" ..formatSize(bytesIn).. ") TX bytes: " ..tostring(bytesOut) .. " (".. formatSize(bytesOut) .. ")")
end
elseif args[1] == "bind" and args[2] then
print("Address attached")
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/bin/init.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/bin/init.lua
index 71146c458..02a8042b3 100644
--- a/src/main/resources/assets/opencomputers/loot/Plan9k/bin/init.lua
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/bin/init.lua
@@ -1,13 +1,28 @@
--Plan9k userspace init for pipes kernel
+
+--TODO: pcall all + emergency shell(or do it higher, in pipes)
+
local pipes = require("pipes")
-local component = require("component")
local filesystem = require("filesystem")
+local component = require("component")
os.setenv("LIBPATH", "/lib/?.lua;/usr/lib/?.lua;/home/lib/?.lua;./?.lua;/lib/?/init.lua;/usr/lib/?/init.lua;/home/lib/?/init.lua;./?/init.lua")
os.setenv("PATH", "/usr/local/bin:/usr/bin:/bin:.")
os.setenv("PWD", "/")
os.setenv("PS1", "\x1b[33m$PWD\x1b[31m#\x1b[39m ")
+pipes.log("INIT: Mounting filesystems")
+
+if filesystem.exists("/etc/fstab") then
+ for entry in io.lines("/etc/fstab") do
+ if entry:sub(1,1) ~= "#" then
+
+ end
+ end
+end
+
+pipes.log("INIT: Starting terminals")
+
if not filesystem.exists("/root") then
filesystem.makeDirectory("/root")
end
@@ -37,7 +52,6 @@ end
local sin, sout
-
local screens = component.list("screen")
for gpu in component.list("gpu") do
local screen = screens()
@@ -85,7 +99,11 @@ end
pcall(services)
local kout = io.popen(function()
- pipes.setThreadName("/bin/tee.lua")
+ if filesystem.exists("/kern.log") then
+ filesystem.remove("/kern.log.old")
+ filesystem.rename("/kern.log", "/kern.log.old")
+ end
+ pipes.setThreadName("[init]/logd")
io.output(sout)
loadfile("/bin/tee.lua", nil, _G)("/kern.log")
end, "w")
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/bin/lua.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/bin/lua.lua
index fe946e194..f00045057 100644
--- a/src/main/resources/assets/opencomputers/loot/Plan9k/bin/lua.lua
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/bin/lua.lua
@@ -1,5 +1,8 @@
local serialization = require("serialization")
local term = require("term")
+local fs = require("filesystem")
+
+local args = {...}
local env = setmetatable({}, {__index = _ENV})
@@ -10,6 +13,13 @@ local function optrequire(...)
end
end
+if args[1] and fs.exists(args[1]) then --non standard, require -i !!!
+ local f = io.open(args[1])
+ local code = load(f:read("*all"), "="..args[1], "t", env)
+ f:close()
+ xpcall(code, debug.traceback)
+end
+
local hist = {}
while true do
io.write(tostring(env._PROMPT or "lua> "))
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/bin/more.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/bin/more.lua
new file mode 100644
index 000000000..648ef31c5
--- /dev/null
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/bin/more.lua
@@ -0,0 +1,44 @@
+local term = require("term")
+
+function usage()
+ print(
+[[Usage:
+ more [options] ...
+
+A file perusal filter for CRT viewing.]])
+end
+
+local args = {...}
+local file = args[1]
+if not file then usage() return end
+
+file = io.open(file)
+
+if not file then
+ print("File not found")
+ return
+end
+
+term.clear()
+local _, h = term.getResolution()
+io.write("\x1b[1;1H")
+print("...",h)
+for i = 1, h - 2 do
+ local line = file:read("*l")
+ if not line then print("input end")return end
+ print(line)
+end
+
+io.write("\x1b47m\x1b30m--More--\x1b39m\x1b49m")
+
+while true do
+ local c = io.read(1)
+ if c == "\n" then
+ local line = file:read("*l")
+ if not line then return end
+ print("\r\x1b[K" .. line)
+ io.write("\x1b47m\x1b30m--More--\x1b39m\x1b49m")
+ elseif c == "q" then
+ return
+ end
+end
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/bin/sh.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/bin/sh.lua
index 5fb8c378c..db4c3b3ef 100644
--- a/src/main/resources/assets/opencomputers/loot/Plan9k/bin/sh.lua
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/bin/sh.lua
@@ -203,6 +203,7 @@ while run do
if term.getCursor() > 1 then
io.write("\n")
end
+ io.write("\x1b49m\x1b39m")
io.write(expand(os.getenv("PS1")))
local cmd = term.read(history)--io.read("*l")
--print("--IN: ", cmd)
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/bin/tee.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/bin/tee.lua
index 828335ac3..41e324564 100644
--- a/src/main/resources/assets/opencomputers/loot/Plan9k/bin/tee.lua
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/bin/tee.lua
@@ -3,7 +3,7 @@ local args = {...}
local f = io.open(args[1], "a")
while true do
- local data = io.read(1)
+ local data = io.read("*L")
if not data then
f:close()
return
@@ -11,7 +11,7 @@ while true do
if io.input().remaining() > 0 then
data = data .. io.read(io.input().remaining())
end
+ io.write(data)
f:write(data)
f:flush()
- io.write(data)
end
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/10_devfs.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/10_devfs.lua
index 806a414c6..61b03bd22 100644
--- a/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/10_devfs.lua
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/10_devfs.lua
@@ -9,7 +9,6 @@ proxy.isReadOnly = function() return true end
proxy.rename = function() error("Permission Denied") end
proxy.remove = function() error("Permission Denied") end
proxy.setLabel = function() error("Permission Denied") end
-proxy.seek = function() error("Not supported") end
proxy.size = function(path)
local seg = kernel.modules.vfs.segments(path)
local file = data
@@ -22,7 +21,17 @@ proxy.getLabel = function() return "devfs" end
local allocator, handles = kernel.modules.util.getAllocator()
-proxy.exists = function()end
+proxy.exists = function(path)
+ local seg = kernel.modules.vfs.segments(path)
+ local file = data
+ for _, d in pairs(seg) do
+ if not file[d] then
+ return false
+ end
+ file = file[d]
+ end
+ return file and true or false
+end
proxy.open = function(path)
local seg = kernel.modules.vfs.segments(path)
local file = data
@@ -48,6 +57,9 @@ end
proxy.write = function(h, ...)
return handles[h].file.write(handles[h], ...)
end
+proxy.seek = function(h, ...)
+ return handles[h].file.seek(handles[h], ...)
+end
proxy.isDirectory = function(path)
local seg = kernel.modules.vfs.segments(path)
local dir = data
@@ -69,8 +81,8 @@ proxy.list = function(path)
error("File is not a directory")
end
local list = {}
- for f in pairs(dir) do
- list[#list + 1] = f
+ for f, node in pairs(dir) do
+ list[#list + 1] = f .. (node.__type and "" or "/")
end
return list
end
@@ -80,6 +92,12 @@ data.null = {
__type = "f",
write = function()end
}
+data.kmsg = {
+ __type = "f",
+ write = function(h, data)
+ kernel.io.println(data)
+ end
+}
data.zero = {
__type = "f",
read = function(h, c)
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/10_procfs.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/10_procfs.lua
index 7c60579b2..91a41d1a1 100644
--- a/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/10_procfs.lua
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/10_procfs.lua
@@ -15,6 +15,7 @@ proxy.getLabel = function() return "procfs" end
local allocator, handles = kernel.modules.util.getAllocator()
proxy.exists = function()end
+
proxy.open = function(path)
local seg = kernel.modules.vfs.segments(path)
local file = data
@@ -32,16 +33,21 @@ proxy.open = function(path)
end
return hnd.id
end
+
proxy.read = function(h, n)
return handles[h]:read(n)
end
+
proxy.close = function(h)
allocator:unset(handles[h])
end
+
proxy.write = function() error("Permission Denied") end
+
proxy.isDirectory = function()
end
+
proxy.list = function(path)
local seg = kernel.modules.vfs.segments(path)
local dir = data
@@ -51,11 +57,11 @@ proxy.list = function(path)
local list = {}
for pid, thr in pairs(kernel.modules.threading.threads) do
if thr.coro and dir == data then
- list[#list + 1] = tostring(pid)
+ list[#list + 1] = tostring(pid) .. "/"
end
end
- for f in pairs(dir) do
- list[#list + 1] = f
+ for f, node in pairs(dir) do
+ list[#list + 1] = f .. (type(node) == "table" and "/" or "")
end
return list
end
@@ -72,13 +78,36 @@ data.cpuinfo = function()
"cpu family : 1\n" ..
"model : 1\n" ..
"model name : OpenComputers Lua CPU @ unkown Tier\n" ..
- "microcode : 0x52\n" ..
+ "microcode : " .. (string.pack and "0x53" or "0x52") .. "\n" ..
"physical id : 0\n"
end
+data.uptime = function()
+ return tostring(computer.uptime())
+end
+
setmetatable(data, {__index = function(_, k)
if tonumber(k) and kernel.modules.threading.threads[tonumber(k)] and kernel.modules.threading.threads[tonumber(k)].coro then
- return {comm = function()return kernel.modules.threading.threads[tonumber(k)].name end}
+ return {
+ comm = function()return kernel.modules.threading.threads[tonumber(k)].name end,
+ limits = function()
+ local limits = "Limit Units Soft Limit\n"
+ limits = limits .. "Max pending signals signals " .. kernel.modules.threading.threads[tonumber(k)].maxPendingSignals .. "\n"
+ limits = limits .. "Max open files files " .. kernel.modules.threading.threads[tonumber(k)].maxOpenFiles .. "\n"
+ return limits
+ end,
+ status = function()
+ local status = ""
+ status = status .. "Name: " .. kernel.modules.threading.threads[tonumber(k)].name .. "\n"
+ status = status .. "State: " .. coroutine.status(kernel.modules.threading.threads[tonumber(k)].coro) .. "\n"
+ status = status .. "Pid: " .. kernel.modules.threading.threads[tonumber(k)].pid .. "\n"
+ status = status .. "Uid: " .. kernel.modules.threading.threads[tonumber(k)].uid .. "\n"
+
+ --TODO: count actual signals
+ status = status .. "SigQ: " .. #kernel.modules.threading.threads[tonumber(k)].eventQueue .. "/" .. kernel.modules.threading.threads[tonumber(k)].maxPendingSignals .. "\n"
+ return status
+ end
+ }
end
end})
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/15_userspace.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/15_userspace.lua
index 5efd66a64..4be9c6c50 100644
--- a/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/15_userspace.lua
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/15_userspace.lua
@@ -51,6 +51,7 @@ function kernel.userspace.os.spawn(prog, ...)
local thread = kernel.modules.threading.spawn(prog, 0, name, isThread, _, ...)
thread.io_output = kernel.modules.threading.currentThread.io_output
thread.io_input = kernel.modules.threading.currentThread.io_input
+ thread.io_error = kernel.modules.threading.currentThread.io_error
return thread.pid
end
@@ -78,6 +79,7 @@ end
function kernel.userspace.os.exit()
kernel.modules.threading.kill(kernel.modules.threading.currentThread.pid)
+ coroutine.yield("yield", 0)
end
function kernel.userspace.os.sleep(time)
@@ -142,7 +144,12 @@ end
kernel.userspace.coroutine = {}
-kernel.userspace.coroutine.yield = function(...)
- return coroutine.yield(...)--TODO: FIX; move to debug
-end
-
+--lua 5.3 <-> 5.2 compat
+kernel.userspace.bit32 = bit32 or load([[return {
+ band = function(a, b) return a & b end,
+ bor = function(a, b) return a | b end,
+ bxor = function(a, b) return a ~ b end,
+ bnot = function(a) return ~a end,
+ rshift = function(a, n) return a >> n end,
+ lshift = function(a, n) return a << n end,
+}]])()
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/16_require.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/16_require.lua
index bb72e1a5a..e25ba7113 100644
--- a/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/16_require.lua
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/16_require.lua
@@ -39,18 +39,17 @@ end
local function pathSearcher(module)
local filepath, reason = kernel.userspace.package.searchpath(module, kernel.userspace.os.getenv("LIBPATH"))
if filepath then
- local loader, reason = kernel.userspace.loadfile(filepath, "bt", setmetatable({},{__index = kernel.userspace}))
+ local loader
+ loader, reason = kernel.userspace.loadfile(filepath, "bt", setmetatable({},{__index = kernel.userspace}))
if loader then
- local state, mod = pcall(loader)
+ local state
+ state, reason = pcall(loader)
if state then
- return mod
- else
- kernel.io.println("Module '" .. tostring(module) .. "' loading failed: " .. tostring(mod))
+ return reason
end
end
- else
- return nil, reason
end
+ return nil, reason
end
kernel.userspace.package.searchers[#kernel.userspace.package.searchers + 1] = preloadSearcher
@@ -65,19 +64,23 @@ kernel.userspace.require = function(module)
if kernel.userspace.package.loading[module] then
error("Already loading "..tostring(module))
else
+ local reason
+
kernel.userspace.package.loading[module] = true
for _, searcher in ipairs(kernel.userspace.package.searchers) do
- local res, mod, reason = pcall(searcher, module)
- if res and mod then
+ local success, mod, res = pcall(searcher, module)
+ if success and mod then
kernel.userspace.package.loading[module] = nil
kernel.userspace.package.loaded[module] = mod
return mod
- elseif not mod and reason then
- kernel.io.println("Searcher for '" .. tostring(module) .. "' loading failed: " .. tostring(reason))
+ elseif (not success) and mod then
+ reason = mod
+ elseif res then
+ reason = res
end
end
kernel.userspace.package.loading[module] = nil
- error("Could not load module " .. tostring(module))
+ error(string.format("Could not load module '%s': %s", module, reason or "module returned nil"))
end
end
end
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/17_data.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/17_data.lua
new file mode 100644
index 000000000..4676746f4
--- /dev/null
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/17_data.lua
@@ -0,0 +1,47 @@
+cards = {}
+
+
+local function buildDevfs()
+ for file in pairs(kernel.modules.devfs.data) do
+ if file == "urandom" then
+ kernel.modules.devfs.data[file] = nil
+ end
+ end
+
+ kernel.modules.devfs.data["urandom"] = {
+ __type = "f",
+ read = function(h, len)
+ return component.invoke(cards[1], "random", len)
+ end
+ }
+end
+
+local function onComponentAdded(_, address, componentType)
+ if componentType == "data" then
+ cards[#cards + 1] = address
+ buildDevfs()
+ end
+end
+
+local function onComponentRemoved(_, address, componentType)
+ if componentType == "data" then
+ local t
+ for i, card in ipairs(cards) do
+ if card == address then
+ t = i
+ break
+ end
+ end
+ table.remove(cards, t)
+ buildDevfs()
+ end
+end
+
+--function start()
+ --for card, t in component.list("data") do
+ -- onComponentAdded(_, card, t)
+ --end
+--end
+
+kernel.modules.keventd.listen("component_added", onComponentAdded)
+kernel.modules.keventd.listen("component_removed", onComponentRemoved)
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/17_drive.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/17_drive.lua
new file mode 100644
index 000000000..a080514d7
--- /dev/null
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/17_drive.lua
@@ -0,0 +1,117 @@
+drives = {}
+
+function writeSectors(drive, data, at)
+ local sectorSize = component.invoke(drive, "getSectorSize")
+ repeat
+ local atSector = math.floor((at - 1) / sectorSize) + 1
+
+ local inSectorStart = (at - 1) % sectorSize + 1
+ local writable = math.min(#data, sectorSize - inSectorStart + 1)
+
+ local old = component.invoke(drive, "readSector", atSector)
+
+ local before = old:sub(0, inSectorStart - 1)
+ local after = old:sub(inSectorStart + writable)
+
+ local toWrite = before .. data:sub(1, writable) .. after
+ data = data:sub(writable + 1)
+
+ kernel.io.println("Wd: " .. atSector .. "/" .. #toWrite .. ": "..inSectorStart.." [ " .. writable .. " ] "..(inSectorStart + writable) .. " #old="..#old)
+ component.invoke(drive, "writeSector", atSector, toWrite)
+
+ at = at + writable
+ until #data < 1
+end
+
+function readSectors(drive, at, len)
+ local data = ""
+ local sectorSize = component.invoke(drive, "getSectorSize")
+ repeat
+ local atSector = math.floor(at / sectorSize) + 1
+
+ sector = component.invoke(drive, "readSector", atSector)
+ kernel.io.println("Rsect " .. atSector .. ": " .. tostring((at - 1) % sectorSize + 1) .. " -> " .. tostring(math.min((at - 1) % sectorSize + len - #data, sectorSize)))
+ local read = sector:sub((at - 1) % sectorSize + 1, math.min((at - 1) % sectorSize + len - #data, sectorSize))
+
+ data = data .. read
+ at = at + #read
+ until #data >= len
+ return data
+end
+
+local function buildDevfs()
+ for file in pairs(kernel.modules.devfs.data) do
+ if file:match("^sd") then
+ kernel.modules.devfs.data[file] = nil
+ end
+ end
+ for k, drive in ipairs(drives) do
+ kernel.modules.devfs.data["sd" .. string.char(k + 96)] = {
+ __type = "f",
+ open = function(hnd)
+ --component.invoke(drive, "seek", -math.huge)
+ hnd.drive = drive
+ hnd.pos = 1
+ --kernel.io.println("Od: " .. hnd.pos .. "/" .. component.invoke(drive, "getCapacity"))
+ end,
+ size = function()
+ return component.invoke(drive, "getCapacity")
+ end,
+ write = function(h, data)
+
+ writeSectors(drive, data, h.pos)
+ --kernel.io.println("Wd: " .. h.pos .. "(+" .. #data .. ")/" .. component.invoke(drive, "getCapacity"))
+ h.pos = h.pos + #data
+ return not (h.pos >= component.invoke(drive, "getCapacity"))
+ --TODO: do this correctly
+ end,
+ read = function(h, len)
+ len = math.ceil(len)
+ kernel.io.println("Rd " .. tostring(len) .. ": " .. h.pos .. "/" .. component.invoke(drive, "getCapacity"))
+ if h.pos >= component.invoke(drive, "getCapacity") then
+ return
+ end
+ local data = readSectors(drive, h.pos, len)
+ h.pos = h.pos + len
+ return data
+ end,
+ seek = function(h, whence, offset)
+ offset = offset or 0
+ if whence == "end" then
+ h.pos = math.min(component.invoke(drive, "getCapacity"), math.max(1, component.invoke(drive, "getCapacity") - offset))
+ return h.pos - 1
+ elseif whence == "set" then
+ h.pos = math.min(component.invoke(drive, "getCapacity"), math.max(1, 1 + offset))
+ return h.pos - 1
+ else
+ h.pos = math.min(component.invoke(drive, "getCapacity"), math.max(1, h.pos + offset))
+ return h.pos - 1
+ end
+ return math.floor(h.pos)
+ end
+ }
+ end
+end
+
+local function onComponentAdded(_, address, componentType)
+ if componentType == "drive" then
+ drives[#drives + 1] = address
+ buildDevfs()
+ end
+end
+
+local function onComponentRemoved(_, address, componentType)
+ if componentType == "drive" then
+ local t
+ for i, drive in ipairs(drives) do
+ if drive == address then
+ t = i
+ break
+ end
+ end
+ table.remove(drives, t)
+ buildDevfs()
+ end
+end
+kernel.modules.keventd.listen("component_added", onComponentAdded)
+kernel.modules.keventd.listen("component_removed", onComponentRemoved)
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/17_network.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/17_network.lua
index 33ad4d5ff..84ba412ed 100644
--- a/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/17_network.lua
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/17_network.lua
@@ -321,6 +321,7 @@ startNetwork = function()
end
sent = nil
else
+
--we've already requested this addr so if we get the route
--we'll respond
routeRequests[dest][#routeRequests[dest]+1] = {type = "R", host = origin}
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/17_tape.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/17_tape.lua
index faaf7efe9..359163dde 100644
--- a/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/17_tape.lua
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/17_tape.lua
@@ -15,20 +15,33 @@ local function buildDevfs()
end
component.invoke(tape, "seek", -math.huge)
hnd.tape = tape
+ hnd.pos = 0
end,
size = function()
return component.invoke(tape, "getSize")
end,
write = function(h, data)
component.invoke(tape, "write", data)
- return not component.invoke(tape, "isEnd", data)
+ h.pos = h.pos + #data
+ return not (h.pos >= component.invoke(tape, "getSize"))
--TODO: do this correctly
end,
read = function(h, len)
- if component.invoke(tape, "isEnd", data) then
+ if h.pos >= component.invoke(tape, "getSize") then
return
end
+ h.pos = h.pos + len
return component.invoke(tape, "read", len)
+ end,
+ seek = function(h, whence, offset)
+ if whence == "end" then
+ h.pos = h.pos + component.invoke(tape, "seek", component.invoke(tape, "getSize") - h.pos - (offset or 0))
+ elseif whence == "set" then
+ h.pos = h.pos + component.invoke(tape, "seek", (offset or 0) - h.pos)
+ else
+ h.pos = h.pos + component.invoke(tape, "seek", offset or 0)
+ end
+ return math.floor(h.pos)
end
}
end
@@ -55,11 +68,11 @@ local function onComponentRemoved(_, address, componentType)
end
end
-function start()
- for tape, t in component.list("tape_drive") do
- onComponentAdded(_, tape, t)
- end
-end
+--function start()
+-- for tape, t in component.list("tape_drive") do
+-- onComponentAdded(_, tape, t)
+-- end
+--end
kernel.modules.keventd.listen("component_added", onComponentAdded)
kernel.modules.keventd.listen("component_removed", onComponentRemoved)
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/20_threading.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/20_threading.lua
index 8dd4b8f2a..7d7015ed3 100644
--- a/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/20_threading.lua
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/20_threading.lua
@@ -71,6 +71,8 @@ function spawn(exec, child, name, isthread, _, ...)
currentHandlerArg = nil,
eventQueue = {{"arg", ...}},
name = name or "unnamed",
+ maxPendingSignals = 32,
+ maxOpenFiles = 8,
uid = nextUid
}
@@ -132,7 +134,7 @@ local function processSignals()
for _, thread in ipairs(threads) do
if thread.coro then
local nsig, oldest = countThreadSignals(thread, "signal")
- if nsig > 32 then --TODO: make it a bit more intelligent
+ if nsig > thread.maxPendingSignals then --TODO: make it a bit more intelligent
table.remove(thread.eventQueue, oldest)
end
if thread.currentHandler == "yield" then
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/21_threadUtil.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/21_threadUtil.lua
index d755e06ad..8c473878c 100644
--- a/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/21_threadUtil.lua
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/lib/modules/base/21_threadUtil.lua
@@ -1,4 +1,5 @@
function joinThread(pid)
+ --coroutine.yield("yield", 0)
while true do
local dead = coroutine.yield("kill")
if pid == dead then
@@ -30,7 +31,7 @@ function userKill(pid, signal, ...)
local args = {...}
local thread = kernel.modules.threading.threads[pid]
kernel.modules.manageg.protect(thread.sandbox)
- --TODO: probably ser threading.currentThread here
+ --TODO: probably set threading.currentThread here
local res, reason = pcall(function()
thread.kill[signal](table.unpack(args))
end)
@@ -41,7 +42,7 @@ function userKill(pid, signal, ...)
return true
end
-function setKillHandler(signal, handler)
+function setKillHandler(signal, handler) --WAT
if not kernel.modules.threading.threads[pid]
or not kernel.modules.threading.threads[pid].coro then
return nil, "Thread does not exists"
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/lib/term.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/lib/term.lua
index 21fda1bf1..5942d8322 100644
--- a/src/main/resources/assets/opencomputers/loot/Plan9k/lib/term.lua
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/lib/term.lua
@@ -1,6 +1,8 @@
local term = {}
-local function read(from, to)
+term.escape = "\x1b"
+local write = function(text) io.write(term.escape..text) end
+local read = function(from, to)
local started, data
while true do
local char = io.read(1)
@@ -19,6 +21,279 @@ local function read(from, to)
end
end
end
+
+term.color={}
+term.color.black=30
+term.color.red=31
+term.color.green=32
+term.color.yellow=33
+term.color.blue=34
+term.color.magenta=35
+term.color.cyan=36
+term.color.white=37
+
+term.attr={}
+term.attr.resetAllAttr=0
+term.attr.bright=1
+term.attr.dim=2
+term.attr.blink=5
+term.attr.reverse=7
+term.attr.hidden=8
+
+------------------------------------------
+--Set Display Attributes
+------------------------------------------
+
+--Set Attribute Mode *ESC*[{attr1};...;{attrn}m
+function term.setAttr(attr)
+ write(attr.."m")
+end
+
+function term.setForeground(color)
+ write(color.."m")
+end
+
+function term.setBackground(color)
+ write((color+10).."m")
+end
+
+------------------------------------------
+--Erasing Text
+------------------------------------------
+
+--Erase End of Line *ESC*[K
+--Erases from the current cursor position to the end of the current line.
+function term.eraseEndOfLine()
+ write("[K")
+end
+
+--Erase Start of Line *ESC*[1K
+--Erases from the current cursor position to the start of the current line.
+function term.eraseStartOfLine()
+ write("[1K")
+end
+
+--Erase Line *ESC*[2K
+--Erases the entire current line.
+function term.eraseLine()
+ write("[2K")
+end
+term.clearLine = term.eraseLine
+
+--Erase Down *ESC*[J
+--Erases the screen from the current line down to the bottom of the screen.
+function term.eraseDown()
+ write("[J")
+end
+
+--Erase Up *ESC*[1J
+--Erases the screen from the current line up to the top of the screen.
+function term.eraseUp()
+ write("[1J")
+end
+
+--Erase Screen *ESC*[2J
+--Erases the screen with the background colour and moves the cursor to home.
+function term.clear()
+ write("[2J")
+end
+
+
+-------------------------------------------
+--Tab Control
+------------------------------------------
+
+--Set Tab *ESC*H
+--Sets a tab at the current position.
+function term.tab()
+ write("[H")
+end
+
+--Clear Tab *ESC*[g
+--Clears tab at the current position.
+function term.clearTab()
+ write("[g")
+end
+
+--Clear All Tabs *ESC*[3g
+--Clears all tabs.
+function term.clearTabs()
+ write("[3g")
+end
+
+
+------------------------------------------
+--Scrolling
+------------------------------------------
+
+--Scroll Screen *ESC*[r
+--Enable scrolling for entire display.
+function term.enableScroll()
+ write("[r")
+end
+
+--Scroll Screen *ESC*[{start};{end}r
+--Enable scrolling from row {start} to row {end}.
+function term.scrollScreen(from,to)
+ write(string.format("[%d;%dr",from,to))
+end
+
+--Scroll Down *ESC*D
+--Scroll display down one line.
+function term.scrollScreenDown()
+ write("D")
+end
+
+--Scroll Up *ESC*M
+--Scroll display up one line.
+function term.scrollScreenUp()
+ write("M")
+end
+
+
+------------------------------------------
+--Cursor Control
+------------------------------------------
+
+--Cursor Home *ESC*[{ROW};{COLUMN}H
+--Sets the cursor position where subsequent text will begin. If no row/column parameters are provided (ie. *ESC*[H), the cursor will move to the home position, at the upper left of the screen.
+function term.setCursorPosition(row,col)
+ write(string.format("[%d;%dH", row, col))
+end
+
+function term.resetCursor()
+ write("[H")
+end
+
+--Cursor Up *ESC*[{COUNT}A
+--Moves the cursor up by COUNT rows; the default count is 1.
+function term.cursorUp(count)
+ write(string.format("[%dA",(count or 1)))
+end
+
+--Cursor Down *ESC*[{COUNT}B
+--Moves the cursor down by COUNT rows; the default count is 1.
+function term.cursorDown(count)
+ write(string.format("[%dB",(count or 1)))
+end
+
+--Cursor Forward *ESC*[{COUNT}C
+--Moves the cursor forward by COUNT columns; the default count is 1.
+function term.cursorForward(count)
+ write(string.format("[%dC",(count or 1)))
+end
+
+--Cursor Backward *ESC*[{COUNT}D
+--Moves the cursor backward by COUNT columns; the default count is 1.
+function term.cursorBackward(count)
+ write(string.format("[%dD",(count or 1)))
+end
+
+--Force Cursor Position *ESC*[{ROW};{COLUMN}f
+--Identical to Cursor Home.
+function term.forceCursorPosition(row, col)
+ write(string.format("[%d;%df", row, col))
+end
+
+--Save Cursor *ESC*[s
+--Save current cursor position.
+function term.saveCursor()
+ write("[s")
+end
+
+--Unsave Cursor *ESC*[u
+--Restores cursor position after a Save Cursor.
+function term.restoreCursor()
+ write("[u")
+end
+
+--Save Cursor & Attrs *ESC*7
+--Save current cursor position.
+function term.saveCursorAndAttr()
+ write("7")
+end
+
+--Restore Cursor & Attrs *ESC*8
+--Restores cursor position after a Save Cursor.
+function term.restoreCursorAndAttr()
+ write("8")
+end
+
+
+------------------------------------------
+--Terminal Setup
+------------------------------------------
+
+--Reset Device *ESC*c
+--Reset all terminal settings to default.
+function term.reset()
+ write("c")
+end
+
+--Enable Line Wrap *ESC*[7h
+--Text wraps to next line if longer than the length of the display area.
+function term.enableLineWrap()
+ write("[7h")
+end
+
+--Disable Line Wrap *ESC*[7l
+--Disables line wrapping.
+function term.disableLineWrap()
+ write("[7l")
+end
+
+------------------------------------------
+--Plan9k codes
+------------------------------------------
+
+-- \x1b9[H];[W]R - set resolution
+function term.setResolution(height,width)
+ write(string.format("9%d;%dR", height, width))
+end
+
+-- \x1b9[Row];[Col];[Height];[Width]F -- fill
+function term.fill(row, col, height, width)
+ write(string.format("9%d;%d;%d;%dF", row, col, height, width))
+end
+
+-- \x1b9[Row];[Col];[Height];[Width];[Dest Row];[Dest Col]c -- copy
+function term.copy(row, col, height, width, destRow, destCol)
+ write(string.format("9%d;%d;%d;%d;%d;%dc", row, col, height, width, destRow, destCol ))
+end
+
+--get resolution
+function term.getResolution()
+ local y, x = term.getCursorPosition()
+ term.setCursorPosition(999,999)
+ local h, w = term.getCursorPosition()
+ term.setCursorPosition(y,x)
+ return tonumber(h), tonumber(w)
+end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
function term.clear()
io.write("\x1b[2J")
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/usr/bin/base64.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/usr/bin/base64.lua
new file mode 100644
index 000000000..2a5d9d58f
--- /dev/null
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/usr/bin/base64.lua
@@ -0,0 +1,42 @@
+local shell = require("shell")
+local data = require("data")
+
+
+local args, parms = shell.parse(...)
+if parms.h or parms.help then
+ io.stderr:write("See: man base64" .. "\n")
+ os.exit(true)
+end
+local encodingfun = nil
+local encode
+if parms.d or parms.decode then
+ encodingfun = data.decode64
+ encode = false
+else
+ encodingfun = data.encode64
+ encode = true
+end
+
+if #args == 0 then
+ repeat
+ local read = io.read(encode and 3 or 4)
+ if read then
+ io.write(encodingfun(read))
+ end
+ until not read
+else
+ for i = 1, #args do
+ local file, reason = io.open(shell.resolve(args[i]))
+ if not file then
+ io.stderr:write(tostring(reason) .. "\n")
+ os.exit(false)
+ end
+ repeat
+ local line = file:read(encode and 3 or 4)
+ if line then
+ io.write(encodingfun(line))
+ end
+ until not line
+ file:close()
+ end
+end
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/usr/bin/deflate.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/usr/bin/deflate.lua
new file mode 100644
index 000000000..cd8660ac2
--- /dev/null
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/usr/bin/deflate.lua
@@ -0,0 +1,25 @@
+local shell = require("shell")
+local data = require("data")
+
+local args = shell.parse(...)
+if #args == 0 then
+ local read = ""
+ repeat
+ local current = io.read("*a")
+ read = read .. current
+ until current ~= ""
+ io.write(data.deflate(read))
+else
+ local read = ""
+ local file, reason = io.open(shell.resolve(args[1]))
+ if not file then
+ io.stderr:write(tostring(reason) .. "\n")
+ os.exit(false)
+ end
+ repeat
+ local current = file:read("*a")
+ read = read .. current
+ until current ~= ""
+ file:close()
+ io.write(data.deflate(read))
+end
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/usr/bin/gpg.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/usr/bin/gpg.lua
new file mode 100644
index 000000000..5b2f5ad41
--- /dev/null
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/usr/bin/gpg.lua
@@ -0,0 +1,244 @@
+--[[
+-- A program that allows user to perform all crypto operations provided by Tier II / Tier III data cards
+-- Author: makkarpov
+--]]
+
+local shell = require("shell")
+local data = require("data")
+local term = require("term")
+local filesystem = require("filesystem")
+local serialization = require("serialization")
+
+local args, options = shell.parse(...)
+
+local function writeFile(path, data)
+ if filesystem.exists(path) then
+ io.stderr:write("gpg: failed to write file: " .. path .. "\n")
+ io.stderr:write("gpg: error was: file already exists\n")
+ return false
+ end
+
+ if type(data) == "table" then
+ data = serialization.serialize(data)
+ end
+
+ local h, err = io.open(path, "wb")
+
+ if not h then
+ io.stderr:write("gpg: failed to write file: " .. path .. "\n")
+ io.stderr:write("gpg: error was: " .. err .. "\n")
+ return false
+ end
+
+ h:write(data)
+ h:close()
+ return true
+end
+
+local function readFile(path, deserialize)
+ local h = io.open(path, "rb")
+ local r = h:read("*a")
+ h:close()
+
+ if deserialize then
+ r = serialization.unserialize(r)
+ end
+
+ return r
+end
+
+local function parseKey(path, isPublic)
+ local d = readFile(path, true)
+ local k, err = data.deserializeKey(d.d, d.t)
+
+ if not k then
+ io.stderr:write("gpg: failed to parse key: " .. err .. "\n")
+ return nil
+ end
+
+ if k.isPublic() ~= isPublic then
+ io.stderr:write("gpg: wrong key type\n")
+ return nil
+ end
+
+ return k
+end
+
+local function deriveName(base, encrypt)
+ if encrypt then
+ return base .. ".gpg"
+ else
+ local d = base:gsub(".gpg", "")
+ if d == base then
+ d = d .. ".dec"
+ io.write("gpg: decrypting to " .. d .. "\n")
+ end
+ return d
+ end
+end
+
+local function ensureMethods(...)
+ if not require("component").isAvailable("data") then
+ io.stderr:write("gpg: you must have data card in order to run this program\n")
+ error("data card is absent")
+ end
+
+ local names = table.pack(...)
+ for i = 1, names.n do
+ if names[i] and not data[names[i]] then
+ io.stderr:write("gpg: method " .. names[i] .. " required on data card to run this program\n")
+ error("data card tier insufficient")
+ end
+ end
+end
+
+if options['g'] and (#args == 2) then
+ ensureMethods("generateKeyPair")
+ local pub, priv = data.generateKeyPair(384)
+
+ priv = { t = priv.keyType(), d = priv.serialize() }
+ pub = { t = pub.keyType(), d = pub.serialize() }
+
+ if not writeFile(args[1], priv) then
+ io.stderr:write("gpg: failed to write private key, aborting\n")
+ return false
+ end
+
+ if not writeFile(args[2], pub) then
+ io.stderr:write("gpg: failed to write public key, aborting\n")
+ return false
+ end
+
+ return true
+end
+
+if options['c'] and (options['e'] or options['d']) and (#args == 1) then
+ ensureMethods("md5", "sha256", "encrypt", "decrypt", "random")
+ if options['d'] and options['e'] then
+ io.stderr:write("gpg: please specify either -d or -e\n")
+ return false
+ end
+
+ io.write("gpg: enter password: ")
+ local aesKey = data.md5(term.read(nil, nil, nil, "*"))
+ local checkValue = data.sha256(aesKey)
+
+ if options['e'] then
+ local iv = data.random(16)
+ local d = data.encrypt(readFile(args[1]), aesKey, iv)
+
+ return writeFile(deriveName(args[1], true), {
+ t = "pwd",
+ kdf = "md5",
+ iv = iv,
+ cv = checkValue,
+ d = d
+ })
+ else
+ local d = readFile(args[1], true)
+
+ if d.t ~= "pwd" then
+ io.stderr:write("gpg: file is not encrypted with a password\n")
+ return false
+ end
+
+ if checkValue ~= d.cv then
+ io.stderr:write("gpg: password incorrect\n")
+ return false
+ end
+
+ return writeFile(deriveName(args[1], false), data.decrypt(d.d, aesKey, d.iv))
+ end
+end
+
+if (options['d'] or options['e']) and (#args == 2) then
+ ensureMethods("md5", "sha256", "encrypt", "decrypt", "random", "generateKeyPair", "deserializeKey", "ecdh")
+ if options['d'] and options['e'] then
+ io.stderr:write("gpg: please specify either -d or -e\n")
+ return false
+ end
+
+ if options['e'] then
+ local userPub = parseKey(args[1], true)
+ local tmpPub, tmpPriv = data.generateKeyPair(384)
+ local aesKey = data.md5(data.ecdh(tmpPriv, userPub))
+ local checkValue = data.sha256(aesKey)
+ local iv = data.random(16)
+
+ local d = data.encrypt(readFile(args[2]), aesKey, iv)
+ return writeFile(deriveName(args[2], true), {
+ t = "ecdh",
+ kdf = "md5",
+ iv = iv,
+ cv = checkValue,
+ k = {
+ t = tmpPub.keyType(),
+ d = tmpPub.serialize()
+ },
+ d = d
+ })
+ else
+ local userPriv = parseKey(args[1], false)
+ local d = readFile(args[2], true)
+
+ if d.t ~= "ecdh" then
+ io.stderr:write("gpg: file is not encrypted with a key\n")
+ return false
+ end
+
+ local tmpPub = data.deserializeKey(d.k.d, d.k.t)
+ local aesKey = data.md5(data.ecdh(userPriv, tmpPub))
+
+ if d.cv ~= data.sha256(aesKey) then
+ io.stderr:write("gpg: invalid key\n")
+ return false
+ end
+
+ return writeFile(deriveName(args[2], false), data.decrypt(d.d, aesKey, d.iv))
+ end
+end
+
+if (options['s'] or options['v']) and (#args == 2) then
+ ensureMethods("deserializeKey", "ecdsa")
+ if options['s'] and options['v'] then
+ io.stderr:write("gpg: please specify either -s or -v\n")
+ return false
+ end
+
+ if options['s'] then
+ local userPriv = parseKey(args[1], false)
+ local sign = data.ecdsa(readFile(args[2]), userPriv)
+
+ return writeFile(args[2] .. ".sig", {
+ t = "ecdsa",
+ s = sign
+ })
+ else
+ local userPub = parseKey(args[1], true)
+ local sign = readFile(args[2] .. ".sig", true)
+
+ if sign.t ~= "ecdsa" then
+ io.stderr:write("gpg: unsupported signature type\n")
+ return false
+ end
+
+ if not data.ecdsa(readFile(args[2]), userPub, sign.s) then
+ io.stderr:write("gpg: signature verification failed\n")
+ return false
+ end
+
+ io.write("gpg: signature is valid\n")
+
+ return true
+ end
+end
+
+io.write("Usages:\n")
+io.write("gpg -ce -- encrypt file with password\n")
+io.write("gpg -cd -- decrypt file with password\n")
+io.write("gpg -e -- encrypt file\n")
+io.write("gpg -d -- decrypt file\n")
+io.write("gpg -g -- generate keypair\n")
+io.write("gpg -s -- sign file\n")
+io.write("gpg -v -- verify file\n")
+return false
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/usr/bin/inflate.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/usr/bin/inflate.lua
new file mode 100644
index 000000000..349c5f18b
--- /dev/null
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/usr/bin/inflate.lua
@@ -0,0 +1,25 @@
+local shell = require("shell")
+local data = require("data")
+
+local args = shell.parse(...)
+if #args == 0 then
+ local read = ""
+ repeat
+ local current = io.read("*a")
+ read = read .. current
+ until current ~= ""
+ io.write(data.inflate(read))
+else
+ local read = ""
+ local file, reason = io.open(shell.resolve(args[1]))
+ if not file then
+ io.stderr:write(tostring(reason) .. "\n")
+ os.exit(false)
+ end
+ repeat
+ local current = file:read("*a")
+ read = read .. current
+ until current ~= ""
+ file:close()
+ io.write(data.inflate(read))
+end
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/usr/bin/md5sum.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/usr/bin/md5sum.lua
new file mode 100644
index 000000000..37cba695f
--- /dev/null
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/usr/bin/md5sum.lua
@@ -0,0 +1,27 @@
+local shell = require("shell")
+local data = require("data")
+
+local args = shell.parse(...)
+if #args == 0 then
+ local read = ""
+ repeat
+ local current = io.read("*a")
+ read = read .. current
+ until current ~= ""
+ io.write(data.toHex(data.md5(read)))
+else
+ for i = 1, #args do
+ local read = ""
+ local file, reason = io.open(args[i])
+ if not file then
+ io.stderr:write(tostring(reason) .. "\n")
+ os.exit(false)
+ end
+ repeat
+ local current = file:read("*a")
+ read = read .. current
+ until current ~= ""
+ file:close()
+ io.write(data.toHex(data.md5(read)) .. "\t".. args[i] .. "\n")
+ end
+end
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/usr/bin/mpt.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/usr/bin/mpt.lua
index 5f9ef04de..49cdb030e 100644
--- a/src/main/resources/assets/opencomputers/loot/Plan9k/usr/bin/mpt.lua
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/usr/bin/mpt.lua
@@ -81,17 +81,25 @@ ocBackend = {
print(" Upgrade all packages that are out-of-date on the")
print(" local system. Only package versions are used to find outdated packages;")
print(" replacements are not checked here. This is a ?remove-then-add? process.")
+ print(" -f, --force")
+ print(" Force operation, in case of upgrade it redownloads all packages")
print(" --root='/some/dir'")
print(" Set alternative root directory")
print(" -v")
print(" More output")
print(" -y")
print(" Don't ask any questions, answer automatically")
+ print(" -r, --reboot")
+ print(" reboot after operation")
return
end
if options.v then loglevel = 0 end
+ if options.f or options.force then
+ core.data.force = true
+ end
+
if options.S or options.sync then
for _, pack in ipairs(args) do
core.install(pack)
@@ -114,6 +122,8 @@ ocBackend = {
if options.y then
ocBackend.prompt = function()return true end
end
+
+ core.reboot = optionsr or options.reboot
core.doWork()
end,
@@ -208,7 +218,8 @@ ocBackend = {
if p:sub(1,1):upper() ~= "Y" then
error("User stopped")
end
- end
+ end,
+ reboot = function()computer.shutdown(true) end
}
local mptFrontend
@@ -249,7 +260,7 @@ mptFrontend = {
local toCheck = {}
for pack, data in pairs(base.installed) do
if data.frontend == mptFrontend.name then
- toCheck[pack] = base.installed[pack].data.checksum
+ toCheck[pack] = base.installed[pack].data.checksum .. (core.data.force and "WAT" or "")
end
end
local updateResp = backend.getText(config.frontend.mpt.api.."update", toCheck)
@@ -368,6 +379,8 @@ core = {
upgrade = false,
remove = false,
+ force = false,
+
--User requested packages
userInstall = {},
@@ -603,7 +616,8 @@ end
core.log(1, "Main", "> Saving settings")
core.finalize()
-
-
+if core.reboot then
+ backend.reboot()
+end
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/usr/bin/sha256sum.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/usr/bin/sha256sum.lua
new file mode 100644
index 000000000..33d5b25c7
--- /dev/null
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/usr/bin/sha256sum.lua
@@ -0,0 +1,27 @@
+local shell = require("shell")
+local data = require("data")
+
+local args = shell.parse(...)
+if #args == 0 then
+ local read = ""
+ repeat
+ local current = io.read("*a")
+ read = read .. current
+ until current ~= ""
+ io.write(data.toHex(data.sha256(read)))
+else
+ for i = 1, #args do
+ local read = ""
+ local file, reason = io.open(shell.resolve(args[i]))
+ if not file then
+ io.stderr:write(tostring(reason) .. "\n")
+ os.exit(false)
+ end
+ repeat
+ local current = file:read("*a")
+ read = read .. current
+ until current ~= ""
+ file:close()
+ io.write(data.toHex(data.sha256(read)) .. "\t".. args[i] .. "\n")
+ end
+end
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/usr/lib/data.lua b/src/main/resources/assets/opencomputers/loot/Plan9k/usr/lib/data.lua
new file mode 100644
index 000000000..04fa9ff9c
--- /dev/null
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/usr/lib/data.lua
@@ -0,0 +1,26 @@
+local component = require("component")
+
+local data = {}
+
+-------------------------------------------------------------------------------
+
+-- Converts binary data into hexadecimal string.
+function data.toHex(data)
+ return (data:gsub('.', function (c)
+ return string.format('%02X', string.byte(c))
+ end))
+end
+
+-- Converts hexadecimal string into binary data.
+function data.fromHex(hex)
+ return (hex:gsub('..', function (cc)
+ return string.char(tonumber(cc, 16))
+ end))
+end
+
+-- Forward everything else to the primary data card.
+setmetatable(data, { __index = function(_, key) return component.data[key] end })
+
+-------------------------------------------------------------------------------
+
+return data
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/var/lib/mpt/config.db b/src/main/resources/assets/opencomputers/loot/Plan9k/var/lib/mpt/config.db
index 92ac3946c..3ecc57b30 100644
--- a/src/main/resources/assets/opencomputers/loot/Plan9k/var/lib/mpt/config.db
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/var/lib/mpt/config.db
@@ -1 +1 @@
-{cacheDir="/var/lib/mpt/cache/",database="/var/lib/mpt/base.db",frontend={mpt={api="http://mpt.magik6k.net/api/"}}}
\ No newline at end of file
+{database="/var/lib/mpt/base.db",cacheDir="/var/lib/mpt/cache/",frontend={mpt={api="http://mpt.magik6k.net/api/"}}}
\ No newline at end of file
diff --git a/src/main/resources/assets/opencomputers/loot/Plan9k/var/lib/mpt/mpt.db b/src/main/resources/assets/opencomputers/loot/Plan9k/var/lib/mpt/mpt.db
index 4c7665a18..d64373528 100644
--- a/src/main/resources/assets/opencomputers/loot/Plan9k/var/lib/mpt/mpt.db
+++ b/src/main/resources/assets/opencomputers/loot/Plan9k/var/lib/mpt/mpt.db
@@ -1 +1 @@
-{installed={["plan9k-drivers"]={frontend="MPT",data={name="plan9k-drivers",repo="plan9k",checksum="-2327b14292d5c08f547d24e9b5122349",files={"/lib/modules/base/17_tape.lua","/lib/modules/base/17_eeprom.lua","/lib/modules/base/17_nfc.lua","/lib/modules/base/17_chatbox.lua"},dependencies={}},deps={}},["plan9k-shell"]={frontend="MPT",data={name="plan9k-shell",repo="plan9k",checksum="-4ebb18f6dedde575d4d61460494b3bbb",files={"/bin/sh.lua"},dependencies={}},deps={}},["plan9k-corelibs"]={frontend="MPT",data={name="plan9k-corelibs",repo="plan9k",checksum="1d01d437b10e8561a39cb9f108cc3d47",files={"/lib/serialization.lua","/lib/term.lua","/lib/text.lua","/lib/shell.lua","/lib/event.lua"},dependencies={}},deps={}},mpt={frontend="MPT",data={name="mpt",repo="mpt",checksum="f9d7744571e5c46c658f405043c656",files={"/usr/bin/mpt.lua"},dependencies={}},deps={}},["plan9k-fsutil"]={frontend="MPT",data={name="plan9k-fsutil",repo="plan9k",checksum="aded7c8083efcdcfe43908183370687",files={"/bin/cat.lua","/bin/ln.lua","/bin/ls.lua","/bin/mv.lua","/bin/rm.lua","/bin/tee.lua","/bin/df.lua","/bin/dd.lua","/bin/cp.lua","/bin/touch.lua","/bin/mount.lua","/bin/mount.cow.lua","/bin/mkdir.lua","/bin/pwd.lua"},dependencies={"plan9k-corelibs"}},deps={"plan9k-corelibs"}},["plan9k-network"]={frontend="MPT",data={name="plan9k-network",repo="plan9k",checksum="-29d10c88b7ed4116d5b4eacd019d5ad9",files={"/lib/internet.lua","/bin/pastebin.lua","/bin/wget.lua","/lib/modules/base/17_network.lua","/lib/modules/base/19_libnetwork.lua","/bin/arp.lua","/bin/ifconfig.lua","/bin/ping.lua","/bin/route.lua","/lib/modules/network/loopback.lua","/lib/modules/network/modem.lua","/usr/bin/nc.lua","/lib/modules/network/tunnel.lua"},dependencies={}},deps={}},pipes={frontend="MPT",data={name="pipes",repo="plan9k",checksum="6d088970fd4fb29b78279eef4d417646",files={"/boot/kernel/pipes","/lib/modules/base/05_vfs.lua","/lib/modules/base/20_threading.lua","/lib/modules/base/19_manageg.lua","/lib/modules/base/25_init.lua","/lib/modules/base/15_userspace.lua","/usr/man/pipes","/lib/modules/base/16_buffer.lua","/lib/modules/base/17_io.lua","/lib/modules/base/16_require.lua","/lib/modules/base/18_syscall.lua","/lib/modules/base/21_threadUtil.lua","/lib/modules/base/21_timer.lua","/lib/modules/base/16_component.lua","/lib/modules/base/15_keventd.lua","/lib/modules/base/10_procfs.lua","/lib/modules/base/01_util.lua","/lib/modules/base/10_devfs.lua","/lib/modules/base/18_pty.lua","/lib/modules/base/17_keyboard.lua","/lib/modules/base/06_cowfs.lua","/lib/modules/base/09_rootfs.lua","/lib/modules/base/01_gc.lua"},dependencies={"openloader-init"}},deps={"openloader-init"}},["plan9k-edit"]={frontend="MPT",data={name="plan9k-edit",repo="plan9k",checksum="fed5f4ee1212297b07247afa1cfe3a2",files={"/bin/edit.lua"},dependencies={}},deps={}},["plan9k-core"]={frontend="MPT",data={name="plan9k-core",repo="plan9k",checksum="-4f5e4f44875482035444a2b61e96243d",files={"/bin/init.lua","/bin/getty.lua","/bin/readkey.lua","/lib/rc.lua","/bin/rc.lua"},dependencies={"pipes","plan9k-coreutil","plan9k-shell"}},deps={"pipes","plan9k-coreutil","plan9k-shell"}},plan9k={frontend="MPT",data={name="plan9k",repo="plan9k",checksum="-c16ccdf21a1ff13be8f6258d1a17d89",files={},dependencies={"plan9k-core","plan9k-network","plan9k-drivers","plan9k-edit"}},deps={"plan9k-core","plan9k-network","plan9k-drivers","plan9k-edit"}},["plan9k-installer"]={frontend="MPT",data={name="plan9k-installer",repo="plan9k",checksum="52c8f82357c966ce3e19c97bf3942012",files={"/bin/install.lua"},dependencies={"plan9k","mpt"}},deps={"plan9k","mpt"}},["openloader-init"]={frontend="MPT",data={name="openloader-init",repo="disks",checksum="-45e6d7b1e41468c1d335952ee3b89e13",files={"/init.lua"},dependencies={}},deps={}},["plan9k-coreutil"]={frontend="MPT",data={name="plan9k-coreutil",repo="plan9k",checksum="-50d3cee571b07b6cb05f6fb01989997c",files={"/bin/echo.lua","/bin/wc.lua","/bin/ps.lua","/bin/lua.lua","/bin/kill.lua","/bin/reboot.lua","/bin/sleep.lua","/bin/clear.lua","/bin/components.lua","/bin/hostname.lua","/bin/dmesg.lua","/bin/shutdown.lua","/bin/label.lua","/bin/uptime.lua","/bin/resolution.lua"},dependencies={"plan9k-corelibs","plan9k-fsutil"}},deps={"plan9k-corelibs","plan9k-fsutil"}}}}
\ No newline at end of file
+{installed={["plan9k-shell"]={deps={},frontend="MPT",data={checksum="517b9e0693e734a4ab516f12a6798f14",files={"/bin/sh.lua"},name="plan9k-shell",repo="plan9k",dependencies={}}},plan9k={deps={"plan9k-core","plan9k-network","plan9k-drivers","plan9k-edit","plan9k-data"},frontend="MPT",data={checksum="-2d8f4b84ea60b0c9d5846f57e9f1691c",files={},name="plan9k",repo="plan9k",dependencies={"plan9k-core","plan9k-network","plan9k-drivers","plan9k-edit","plan9k-data"}}},["plan9k-fsutil"]={deps={"plan9k-corelibs"},frontend="MPT",data={checksum="6a974a71f62315e6e2294eae132d1751",files={"/bin/cat.lua","/bin/ln.lua","/bin/ls.lua","/bin/mv.lua","/bin/rm.lua","/bin/tee.lua","/bin/df.lua","/bin/dd.lua","/bin/cp.lua","/bin/touch.lua","/bin/mount.lua","/bin/mount.cow.lua","/bin/mkdir.lua","/bin/pwd.lua","/bin/more.lua"},name="plan9k-fsutil",repo="plan9k",dependencies={"plan9k-corelibs"}}},["plan9k-edit"]={deps={},frontend="MPT",data={checksum="34b1046ac9b7a87a1cdd74f8c03f27ea",files={"/bin/edit.lua"},name="plan9k-edit",repo="plan9k",dependencies={}}},["plan9k-core"]={deps={"pipes","plan9k-coreutil","plan9k-shell"},frontend="MPT",data={checksum="-6f77a020200f96eefd1559dcd8af14a5",files={"/bin/init.lua","/bin/getty.lua","/bin/readkey.lua","/lib/rc.lua","/bin/rc.lua"},name="plan9k-core",repo="plan9k",dependencies={"pipes","plan9k-coreutil","plan9k-shell"}}},["plan9k-installer"]={deps={"plan9k","mpt"},frontend="MPT",data={checksum="52c8f82357c966ce3e19c97bf3942012",files={"/bin/install.lua"},name="plan9k-installer",repo="plan9k",dependencies={"plan9k","mpt"}}},["plan9k-drivers"]={deps={},frontend="MPT",data={checksum="-35f098652460458c13f83499b4633e2d",files={"/lib/modules/base/17_tape.lua","/lib/modules/base/17_eeprom.lua","/lib/modules/base/17_nfc.lua","/lib/modules/base/17_chatbox.lua","/lib/modules/base/17_data.lua","/lib/modules/base/17_drive.lua"},name="plan9k-drivers",repo="plan9k",dependencies={}}},["plan9k-coreutil"]={deps={"plan9k-corelibs","plan9k-fsutil"},frontend="MPT",data={checksum="5fa7e70e3aba17fef97ed65489efb0ca",files={"/bin/echo.lua","/bin/wc.lua","/bin/ps.lua","/bin/lua.lua","/bin/kill.lua","/bin/reboot.lua","/bin/sleep.lua","/bin/clear.lua","/bin/components.lua","/bin/hostname.lua","/bin/dmesg.lua","/bin/shutdown.lua","/bin/label.lua","/bin/uptime.lua","/bin/resolution.lua"},name="plan9k-coreutil",repo="plan9k",dependencies={"plan9k-corelibs","plan9k-fsutil"}}},["openloader-init"]={deps={},frontend="MPT",data={checksum="-45e6d7b1e41468c1d335952ee3b89e13",files={"/init.lua"},name="openloader-init",repo="disks",dependencies={}}},mpt={deps={},frontend="MPT",data={checksum="-3435ded79cf9de20e7185f403eee4a28",files={"/usr/bin/mpt.lua"},name="mpt",repo="mpt",dependencies={}}},pipes={deps={"openloader-init"},frontend="MPT",data={checksum="123b56992859cf05cd03af825437d734",files={"/boot/kernel/pipes","/lib/modules/base/05_vfs.lua","/lib/modules/base/20_threading.lua","/lib/modules/base/19_manageg.lua","/lib/modules/base/25_init.lua","/lib/modules/base/15_userspace.lua","/usr/man/pipes","/lib/modules/base/16_buffer.lua","/lib/modules/base/17_io.lua","/lib/modules/base/16_require.lua","/lib/modules/base/18_syscall.lua","/lib/modules/base/21_threadUtil.lua","/lib/modules/base/21_timer.lua","/lib/modules/base/16_component.lua","/lib/modules/base/15_keventd.lua","/lib/modules/base/10_procfs.lua","/lib/modules/base/01_util.lua","/lib/modules/base/10_devfs.lua","/lib/modules/base/18_pty.lua","/lib/modules/base/17_keyboard.lua","/lib/modules/base/06_cowfs.lua","/lib/modules/base/09_rootfs.lua","/lib/modules/base/01_gc.lua"},name="pipes",repo="plan9k",dependencies={"openloader-init"}}},["plan9k-corelibs"]={deps={},frontend="MPT",data={checksum="1cb7fa12e1a1a35fc168409b60cfe552",files={"/lib/serialization.lua","/lib/term.lua","/lib/text.lua","/lib/shell.lua","/lib/event.lua"},name="plan9k-corelibs",repo="plan9k",dependencies={}}},["plan9k-data"]={deps={},frontend="MPT",data={checksum="480a898a741b2bf424f9e0e86e5072ba",files={"/usr/bin/base64.lua","/usr/lib/data.lua","/usr/bin/deflate.lua","/usr/bin/inflate.lua","/usr/bin/md5sum.lua","/usr/bin/sha256sum.lua","/usr/bin/gpg.lua"},name="plan9k-data",repo="plan9k",dependencies={}}},["plan9k-network"]={deps={},frontend="MPT",data={checksum="6e06e6cf5028583fe54047ba9e19abc6",files={"/lib/internet.lua","/bin/pastebin.lua","/bin/wget.lua","/lib/modules/base/17_network.lua","/lib/modules/base/19_libnetwork.lua","/bin/arp.lua","/bin/ifconfig.lua","/bin/ping.lua","/bin/route.lua","/lib/modules/network/loopback.lua","/lib/modules/network/modem.lua","/usr/bin/nc.lua","/lib/modules/network/tunnel.lua"},name="plan9k-network",repo="plan9k",dependencies={}}}}}
\ No newline at end of file
diff --git a/src/main/resources/assets/opencomputers/lua/machine.lua b/src/main/resources/assets/opencomputers/lua/machine.lua
index 56e79e3c0..238fd4f51 100644
--- a/src/main/resources/assets/opencomputers/lua/machine.lua
+++ b/src/main/resources/assets/opencomputers/lua/machine.lua
@@ -1320,6 +1320,23 @@ local libcomputer = {
beep = function(...)
libcomponent.invoke(computer.address(), "beep", ...)
+ end,
+
+ getArchitectures = function(...)
+ return spcall(computer.getArchitectures, ...)
+ end,
+ getArchitecture = function(...)
+ return spcall(computer.getArchitecture, ...)
+ end,
+ setArchitecture = function(...)
+ local result, reason = spcall(computer.setArchitecture, ...)
+ if not result then
+ if reason then
+ return result, reason
+ end
+ else
+ coroutine.yield(true) -- reboot
+ end
end
}
sandbox.computer = libcomputer
diff --git a/src/main/resources/assets/opencomputers/models/block/relay.json b/src/main/resources/assets/opencomputers/models/block/relay.json
new file mode 100644
index 000000000..0af961954
--- /dev/null
+++ b/src/main/resources/assets/opencomputers/models/block/relay.json
@@ -0,0 +1,8 @@
+{
+ "parent": "block/cube_bottom_top",
+ "textures": {
+ "top": "opencomputers:blocks/switch_top",
+ "bottom": "opencomputers:blocks/generic_top",
+ "side": "opencomputers:blocks/switch_side"
+ }
+}
diff --git a/src/main/resources/assets/opencomputers/models/block/transposer.json b/src/main/resources/assets/opencomputers/models/block/transposer.json
new file mode 100644
index 000000000..5e20eb017
--- /dev/null
+++ b/src/main/resources/assets/opencomputers/models/block/transposer.json
@@ -0,0 +1,221 @@
+{
+ "textures": {
+ "bottom": "opencomputers:blocks/transposer_top",
+ "top": "opencomputers:blocks/transposer_top",
+ "side": "opencomputers:blocks/transposer_side",
+ "particle": "opencomputers:blocks/generic_top"
+ },
+ "elements": [
+ { "from": [ 0, 0, 0 ],
+ "to": [ 7, 7, 7 ],
+ "faces": {
+ "up": { "uv": [ 0, 0, 7, 7 ], "texture": "#top" },
+ "down": { "uv": [ 0, 9, 7, 16 ], "texture": "#bottom" },
+ "north": { "uv": [ 9, 9, 16, 16 ], "texture": "#side" },
+ "south": { "uv": [ 0, 9, 7, 16 ], "texture": "#side" },
+ "west": { "uv": [ 0, 9, 7, 16 ], "texture": "#side" },
+ "east": { "uv": [ 9, 9, 16, 16 ], "texture": "#side" }
+ }
+ },
+ { "from": [ 0, 0, 9 ],
+ "to": [ 7, 7, 16 ],
+ "faces": {
+ "up": { "uv": [ 0, 9, 7, 16 ], "texture": "#top" },
+ "down": { "uv": [ 0, 0, 7, 7 ], "texture": "#bottom" },
+ "north": { "uv": [ 9, 9, 16, 16 ], "texture": "#side" },
+ "south": { "uv": [ 0, 9, 7, 16 ], "texture": "#side" },
+ "west": { "uv": [ 9, 9, 16, 16 ], "texture": "#side" },
+ "east": { "uv": [ 0, 9, 7, 16 ], "texture": "#side" }
+ }
+ },
+ { "from": [ 0, 9, 0 ],
+ "to": [ 7, 16, 7 ],
+ "faces": {
+ "up": { "uv": [ 0, 0, 7, 7 ], "texture": "#top" },
+ "down": { "uv": [ 0, 9, 7, 16 ], "texture": "#bottom" },
+ "north": { "uv": [ 9, 0, 16, 7 ], "texture": "#side" },
+ "south": { "uv": [ 0, 0, 7, 7 ], "texture": "#side" },
+ "west": { "uv": [ 0, 0, 7, 7 ], "texture": "#side" },
+ "east": { "uv": [ 9, 0, 16, 7 ], "texture": "#side" }
+ }
+ },
+ { "from": [ 0, 9, 9 ],
+ "to": [ 7, 16, 16 ],
+ "faces": {
+ "up": { "uv": [ 0, 9, 7, 16 ], "texture": "#top" },
+ "down": { "uv": [ 0, 0, 7, 7 ], "texture": "#bottom" },
+ "north": { "uv": [ 9, 0, 16, 7 ], "texture": "#side" },
+ "south": { "uv": [ 0, 0, 7, 7 ], "texture": "#side" },
+ "west": { "uv": [ 9, 0, 16, 7 ], "texture": "#side" },
+ "east": { "uv": [ 0, 0, 7, 7 ], "texture": "#side" }
+ }
+ },
+ { "from": [ 9, 0, 0 ],
+ "to": [ 16, 7, 7 ],
+ "faces": {
+ "up": { "uv": [ 9, 0, 16, 7 ], "texture": "#top" },
+ "down": { "uv": [ 9, 9, 16, 16 ], "texture": "#bottom" },
+ "north": { "uv": [ 0, 9, 7, 16 ], "texture": "#side" },
+ "south": { "uv": [ 9, 9, 16, 16 ], "texture": "#side" },
+ "west": { "uv": [ 0, 9, 7, 16 ], "texture": "#side" },
+ "east": { "uv": [ 9, 9, 16, 16 ], "texture": "#side" }
+ }
+ },
+ { "from": [ 9, 0, 9 ],
+ "to": [ 16, 7, 16 ],
+ "faces": {
+ "up": { "uv": [ 9, 9, 16, 16 ], "texture": "#top" },
+ "down": { "uv": [ 9, 0, 16, 7 ], "texture": "#bottom" },
+ "north": { "uv": [ 0, 9, 7, 16 ], "texture": "#side" },
+ "south": { "uv": [ 9, 9, 16, 16 ], "texture": "#side" },
+ "west": { "uv": [ 9, 9, 16, 16 ], "texture": "#side" },
+ "east": { "uv": [ 0, 9, 7, 16 ], "texture": "#side" }
+ }
+ },
+ { "from": [ 9, 9, 0 ],
+ "to": [ 16, 16, 7 ],
+ "faces": {
+ "up": { "uv": [ 9, 0, 16, 7 ], "texture": "#top" },
+ "down": { "uv": [ 9, 9, 16, 16 ], "texture": "#bottom" },
+ "north": { "uv": [ 0, 0, 7, 7 ], "texture": "#side" },
+ "south": { "uv": [ 9, 0, 16, 7 ], "texture": "#side" },
+ "west": { "uv": [ 0, 0, 7, 7 ], "texture": "#side" },
+ "east": { "uv": [ 9, 0, 16, 7 ], "texture": "#side" }
+ }
+ },
+ { "from": [ 9, 9, 9 ],
+ "to": [ 16, 16, 16 ],
+ "faces": {
+ "up": { "uv": [ 9, 9, 16, 16 ], "texture": "#top" },
+ "down": { "uv": [ 9, 0, 16, 7 ], "texture": "#bottom" },
+ "north": { "uv": [ 0, 0, 7, 7 ], "texture": "#side" },
+ "south": { "uv": [ 9, 0, 16, 7 ], "texture": "#side" },
+ "west": { "uv": [ 9, 0, 16, 7 ], "texture": "#side" },
+ "east": { "uv": [ 0, 0, 7, 7 ], "texture": "#side" }
+ }
+ },
+
+ { "from": [ 0, 0, 7 ],
+ "to": [ 5, 5, 9 ],
+ "faces": {
+ "up": { "uv": [ 0, 7, 5, 9 ], "texture": "#top" },
+ "down": { "uv": [ 0, 7, 5, 9 ], "texture": "#bottom" },
+ "west": { "uv": [ 7, 11, 9, 16 ], "texture": "#side" },
+ "east": { "uv": [ 7, 11, 9, 16 ], "texture": "#side" }
+ }
+ },
+ { "from": [ 0, 11, 7 ],
+ "to": [ 5, 16, 9 ],
+ "faces": {
+ "up": { "uv": [ 0, 7, 5, 9 ], "texture": "#top" },
+ "down": { "uv": [ 0, 7, 5, 9 ], "texture": "#bottom" },
+ "west": { "uv": [ 7, 0, 9, 5 ], "texture": "#side" },
+ "east": { "uv": [ 7, 0, 9, 5 ], "texture": "#side" }
+ }
+ },
+ { "from": [ 11, 0, 7 ],
+ "to": [ 16, 5, 9 ],
+ "faces": {
+ "up": { "uv": [ 11, 7, 16, 9 ], "texture": "#top" },
+ "down": { "uv": [ 11, 7, 16, 9 ], "texture": "#bottom" },
+ "west": { "uv": [ 7, 11, 9, 16 ], "texture": "#side" },
+ "east": { "uv": [ 7, 11, 9, 16 ], "texture": "#side" }
+ }
+ },
+ { "from": [ 11, 11, 7 ],
+ "to": [ 16, 16, 9 ],
+ "faces": {
+ "up": { "uv": [ 11, 7, 16, 9 ], "texture": "#top" },
+ "down": { "uv": [ 11, 7, 16, 9 ], "texture": "#bottom" },
+ "west": { "uv": [ 7, 0, 9, 5 ], "texture": "#side" },
+ "east": { "uv": [ 7, 0, 9, 5 ], "texture": "#side" }
+ }
+ },
+
+ { "from": [ 0, 7, 0 ],
+ "to": [ 5, 9, 5 ],
+ "faces": {
+ "north": { "uv": [ 11, 7, 16, 9 ], "texture": "#side" },
+ "south": { "uv": [ 0, 7, 5, 9 ], "texture": "#side" },
+ "west": { "uv": [ 0, 7, 5, 9 ], "texture": "#side" },
+ "east": { "uv": [ 11, 7, 16, 9 ], "texture": "#side" }
+ }
+ },
+ { "from": [ 0, 7, 11 ],
+ "to": [ 5, 9, 16 ],
+ "faces": {
+ "north": { "uv": [ 11, 7, 16, 9 ], "texture": "#side" },
+ "south": { "uv": [ 0, 7, 5, 9 ], "texture": "#side" },
+ "west": { "uv": [ 11, 7, 16, 9 ], "texture": "#side" },
+ "east": { "uv": [ 0, 7, 5, 9 ], "texture": "#side" }
+ }
+ },
+ { "from": [ 11, 7, 0 ],
+ "to": [ 16, 9, 5 ],
+ "faces": {
+ "north": { "uv": [ 0, 7, 5, 9 ], "texture": "#side" },
+ "south": { "uv": [ 11, 7, 16, 9 ], "texture": "#side" },
+ "west": { "uv": [ 0, 7, 5, 9 ], "texture": "#side" },
+ "east": { "uv": [ 11, 7, 16, 9 ], "texture": "#side" }
+ }
+ },
+ { "from": [ 11, 7, 11 ],
+ "to": [ 16, 9, 16 ],
+ "faces": {
+ "north": { "uv": [ 0, 7, 5, 9 ], "texture": "#side" },
+ "south": { "uv": [ 11, 7, 16, 9 ], "texture": "#side" },
+ "west": { "uv": [ 11, 7, 16, 9 ], "texture": "#side" },
+ "east": { "uv": [ 0, 7, 5, 9 ], "texture": "#side" }
+ }
+ },
+
+ { "from": [ 7, 0, 0 ],
+ "to": [ 9, 5, 5 ],
+ "faces": {
+ "up": { "uv": [ 7, 0, 9, 5 ], "texture": "#top" },
+ "down": { "uv": [ 7, 11, 9, 16 ], "texture": "#bottom" },
+ "north": { "uv": [ 7, 11, 9, 16 ], "texture": "#side" },
+ "south": { "uv": [ 7, 11, 9, 16 ], "texture": "#side" }
+ }
+ },
+ { "from": [ 7, 0, 11 ],
+ "to": [ 9, 5, 16 ],
+ "faces": {
+ "up": { "uv": [ 7, 11, 9, 16 ], "texture": "#top" },
+ "down": { "uv": [ 7, 0, 9, 5 ], "texture": "#bottom" },
+ "north": { "uv": [ 7, 11, 9, 16 ], "texture": "#side" },
+ "south": { "uv": [ 7, 11, 9, 16 ], "texture": "#side" }
+ }
+ },
+ { "from": [ 7, 11, 0 ],
+ "to": [ 9, 16, 5 ],
+ "faces": {
+ "up": { "uv": [ 7, 0, 9, 5 ], "texture": "#top" },
+ "down": { "uv": [ 7, 11, 9, 16 ], "texture": "#bottom" },
+ "north": { "uv": [ 7, 0, 9, 5 ], "texture": "#side" },
+ "south": { "uv": [ 7, 0, 9, 5 ], "texture": "#side" }
+ }
+ },
+ { "from": [ 7, 11, 11 ],
+ "to": [ 9, 16, 16 ],
+ "faces": {
+ "up": { "uv": [ 7, 11, 9, 16 ], "texture": "#top" },
+ "down": { "uv": [ 7, 0, 9, 5 ], "texture": "#bottom" },
+ "north": { "uv": [ 7, 0, 9, 5 ], "texture": "#side" },
+ "south": { "uv": [ 7, 0, 9, 5 ], "texture": "#side" }
+ }
+ },
+
+ { "from": [ 1, 1, 1 ],
+ "to": [ 15, 15, 15 ],
+ "faces": {
+ "up": { "uv": [ 1, 1, 15, 15 ], "texture": "#top" },
+ "down": { "uv": [ 1, 1, 15, 15 ], "texture": "#bottom" },
+ "north": { "uv": [ 1, 1, 15, 15 ], "texture": "#side" },
+ "south": { "uv": [ 1, 1, 15, 15 ], "texture": "#side" },
+ "west": { "uv": [ 1, 1, 15, 15 ], "texture": "#side" },
+ "east": { "uv": [ 1, 1, 15, 15 ], "texture": "#side" }
+ }
+ }
+ ]
+}
diff --git a/src/main/resources/assets/opencomputers/models/item/relay.json b/src/main/resources/assets/opencomputers/models/item/relay.json
new file mode 100644
index 000000000..9283695ae
--- /dev/null
+++ b/src/main/resources/assets/opencomputers/models/item/relay.json
@@ -0,0 +1,10 @@
+{
+ "parent": "opencomputers:block/relay",
+ "display": {
+ "thirdperson": {
+ "rotation": [ 10, -45, 170 ],
+ "translation": [ 0, 1.5, -2.75 ],
+ "scale": [ 0.375, 0.375, 0.375 ]
+ }
+ }
+}
diff --git a/src/main/resources/assets/opencomputers/models/item/transposer.json b/src/main/resources/assets/opencomputers/models/item/transposer.json
new file mode 100644
index 000000000..843975aad
--- /dev/null
+++ b/src/main/resources/assets/opencomputers/models/item/transposer.json
@@ -0,0 +1,10 @@
+{
+ "parent": "opencomputers:block/transposer",
+ "display": {
+ "thirdperson": {
+ "rotation": [ 10, -45, 170 ],
+ "translation": [ 0, 1.5, -2.75 ],
+ "scale": [ 0.375, 0.375, 0.375 ]
+ }
+ }
+}
diff --git a/src/main/resources/assets/opencomputers/recipes/default.recipes b/src/main/resources/assets/opencomputers/recipes/default.recipes
index 456f87c33..9bb0d1f2d 100644
--- a/src/main/resources/assets/opencomputers/recipes/default.recipes
+++ b/src/main/resources/assets/opencomputers/recipes/default.recipes
@@ -504,11 +504,6 @@ interweb {
[string, string, string]]
}
-accessPoint {
- input: [[ingotIron, "oc:wlanCard", ingotIron]
- ["oc:cable", "oc:lanCard", "oc:cable"]
- [ingotIron, "oc:materialCircuitBoardPrinted", ingotIron]]
-}
adapter {
input: [[ingotIron, "oc:cable", ingotIron]
["oc:cable", "oc:circuitChip1", "oc:cable"]
@@ -607,7 +602,7 @@ powerDistributor {
serverRack {
input: [["oc:circuitChip2", "oc:wlanCard", "oc:circuitChip2"]
[fenceIron, chest, fenceIron]
- ["oc:switch", "oc:materialCircuitBoardPrinted", "oc:powerDistributor"]]
+ ["oc:relay", "oc:materialCircuitBoardPrinted", "oc:powerDistributor"]]
}
raid {
input: [[nuggetIron, "oc:cpu3", nuggetIron]
@@ -619,7 +614,7 @@ redstone {
[blockRedstone, "oc:redstoneCard1", blockRedstone]
[ingotIron, "oc:materialCircuitBoardPrinted", ingotIron]]
}
-switch {
+relay {
input: [[ingotIron, "oc:cable", ingotIron]
["oc:cable", "oc:lanCard", "oc:cable"]
[ingotIron, "oc:materialCircuitBoardPrinted", ingotIron]]
@@ -639,6 +634,12 @@ screen3 {
[yellowDust, "oc:circuitChip3", glass]
[obsidian, yellowDust, obsidian]]
}
+transposer {
+ input: [[ingotIron, "oc:inventoryControllerUpgrade", ingotIron]
+ [hopper, bucket, hopper]
+ [ingotIron, "oc:tankControllerUpgrade", ingotIron]]
+ output: 4
+}
waypoint {
input: [[ingotIron, "oc:circuitChip1", ingotIron]
["oc:materialTransistor", "oc:materialInterweb", "oc:materialTransistor"],
diff --git a/src/main/resources/assets/opencomputers/recipes/gregtech.recipes b/src/main/resources/assets/opencomputers/recipes/gregtech.recipes
index 4c95c8eb8..f2b3768c7 100644
--- a/src/main/resources/assets/opencomputers/recipes/gregtech.recipes
+++ b/src/main/resources/assets/opencomputers/recipes/gregtech.recipes
@@ -318,7 +318,7 @@ powerDistributor {
serverRack {
input: [[craftingToolScrewdriver, "oc:wlanCard", craftingToolWrench]
["ic2.reactorVentDiamond", chest, "ic2.reactorVentDiamond"]
- ["oc:switch", "oc:materialCircuitBoardPrinted","oc:powerDistributor"]]
+ ["oc:relay", "oc:materialCircuitBoardPrinted","oc:powerDistributor"]]
}
redstone {
# 32731 = Activity Detector
@@ -326,7 +326,7 @@ redstone {
[{item="gt.metaitem.01", subID=32731}, {block="gt.blockcasings", subID=2}, "oc:redstoneCard1"]
["oc:circuitChip2", "oc:materialCircuitBoardPrinted", "oc:circuitChip2"]]
}
-switch {
+relay {
input: [["", "oc:lanCard", ""]
["oc:cable", {block="gt.blockcasings", subID=2}, "oc:cable"]
["oc:materialCircuitBoardPrinted", craftingToolWrench, "oc:materialCircuitBoardPrinted"]]
diff --git a/src/main/resources/assets/opencomputers/recipes/hardmode.recipes b/src/main/resources/assets/opencomputers/recipes/hardmode.recipes
index ed930d4f3..fe0614746 100644
--- a/src/main/resources/assets/opencomputers/recipes/hardmode.recipes
+++ b/src/main/resources/assets/opencomputers/recipes/hardmode.recipes
@@ -363,14 +363,14 @@ powerDistributor {
serverRack {
input: [["oc:circuitChip3", "oc:wlanCard", "oc:circuitChip3"]
[fenceIron, chest, fenceIron]
- ["oc:switch", "oc:materialCircuitBoardPrinted","oc:powerDistributor"]]
+ ["oc:relay", "oc:materialCircuitBoardPrinted","oc:powerDistributor"]]
}
redstone {
input: [[ingotIron, "oc:circuitChip3", ingotIron]
[blockRedstone, "oc:redstoneCard1", blockRedstone]
[ingotIron, "oc:materialCircuitBoardPrinted", ingotIron]]
}
-switch {
+relay {
input: [[ingotIron, "oc:cable", ingotIron]
["oc:cable", "oc:lanCard", "oc:cable"]
[ingotIron, "oc:materialCircuitBoardPrinted", ingotIron]]
diff --git a/src/main/resources/assets/opencomputers/textures/blocks/Transposer_top.png b/src/main/resources/assets/opencomputers/textures/blocks/Transposer_top.png
new file mode 100644
index 000000000..63f2b8fd0
Binary files /dev/null and b/src/main/resources/assets/opencomputers/textures/blocks/Transposer_top.png differ
diff --git a/src/main/resources/assets/opencomputers/textures/blocks/overlay/transposer_on.png b/src/main/resources/assets/opencomputers/textures/blocks/overlay/transposer_on.png
new file mode 100644
index 000000000..0f2a818a7
Binary files /dev/null and b/src/main/resources/assets/opencomputers/textures/blocks/overlay/transposer_on.png differ
diff --git a/src/main/resources/assets/opencomputers/textures/blocks/transposer_side.png b/src/main/resources/assets/opencomputers/textures/blocks/transposer_side.png
new file mode 100644
index 000000000..8d10906de
Binary files /dev/null and b/src/main/resources/assets/opencomputers/textures/blocks/transposer_side.png differ
diff --git a/src/main/resources/assets/opencomputers/textures/gui/upgrade_tab.png b/src/main/resources/assets/opencomputers/textures/gui/upgrade_tab.png
new file mode 100644
index 000000000..2ca3b14c7
Binary files /dev/null and b/src/main/resources/assets/opencomputers/textures/gui/upgrade_tab.png differ
diff --git a/src/main/scala/li/cil/oc/Constants.scala b/src/main/scala/li/cil/oc/Constants.scala
index addb367c6..42cbe9eec 100644
--- a/src/main/scala/li/cil/oc/Constants.scala
+++ b/src/main/scala/li/cil/oc/Constants.scala
@@ -25,12 +25,14 @@ object Constants {
final val Keyboard = "keyboard"
final val Microcontroller = "microcontroller"
final val MotionSensor = "motionSensor"
+ final val NetSplitter = "netSplitter"
final val PowerConverter = "powerConverter"
final val PowerDistributor = "powerDistributor"
final val Print = "print"
final val Printer = "printer"
final val Raid = "raid"
final val Redstone = "redstone"
+ final val Relay = "relay"
final val Robot = "robot"
final val RobotAfterimage = "robotAfterimage"
final val ScreenTier1 = "screen1"
@@ -38,7 +40,7 @@ object Constants {
final val ScreenTier3 = "screen3"
final val ServerRack = "serverRack"
final val Switch = "switch"
- final val NetSplitter = "netSplitter"
+ final val Transposer = "transposer"
final val Waypoint = "waypoint"
def Case(tier: Int) = ItemUtils.caseNameWithTierSuffix("case", tier)
diff --git a/src/main/scala/li/cil/oc/Settings.scala b/src/main/scala/li/cil/oc/Settings.scala
index e4db25e40..aa8138f1c 100644
--- a/src/main/scala/li/cil/oc/Settings.scala
+++ b/src/main/scala/li/cil/oc/Settings.scala
@@ -201,6 +201,7 @@ class Settings(val config: Config) {
val dataCardComplex = config.getDouble("power.cost.dataCardComplex") max 0
val dataCardComplexByte = config.getDouble("power.cost.dataCardComplexByte") max 0
val dataCardAsymmetric = config.getDouble("power.cost.dataCardAsymmetric") max 0
+ val transposerCost = config.getDouble("power.cost.transposer") max 0
// power.rate
val accessPointRate = config.getDouble("power.rate.accessPoint") max 0
@@ -336,6 +337,7 @@ class Settings(val config: Config) {
val dataCardHardLimit = config.getInt("misc.dataCardHardLimit") max 0
val dataCardTimeout = config.getDouble("misc.dataCardTimeout") max 0
val serverRackSwitchTier = (config.getInt("misc.serverRackSwitchTier") - 1) max Tier.None min Tier.Three
+ val redstoneDelay = config.getDouble("misc.redstoneDelay") max 0
// ----------------------------------------------------------------------- //
// printer
diff --git a/src/main/scala/li/cil/oc/client/GuiHandler.scala b/src/main/scala/li/cil/oc/client/GuiHandler.scala
index 48d5ea80a..a886e372e 100644
--- a/src/main/scala/li/cil/oc/client/GuiHandler.scala
+++ b/src/main/scala/li/cil/oc/client/GuiHandler.scala
@@ -36,12 +36,14 @@ object GuiHandler extends CommonGuiHandler {
new gui.DiskDrive(player.inventory, t)
case t: tileentity.Printer if id == GuiType.Printer.id =>
new gui.Printer(player.inventory, t)
- case t: tileentity.Raid if id == GuiType.Raid.id =>
- new gui.Raid(player.inventory, t)
- case t: tileentity.RobotProxy if id == GuiType.Robot.id =>
- new gui.Robot(player.inventory, t.robot)
case t: tileentity.ServerRack if id == GuiType.Rack.id =>
new gui.ServerRack(player.inventory, t)
+ case t: tileentity.Raid if id == GuiType.Raid.id =>
+ new gui.Raid(player.inventory, t)
+ case t: tileentity.Relay if id == GuiType.Relay.id =>
+ new gui.Relay(player.inventory, t)
+ case t: tileentity.RobotProxy if id == GuiType.Robot.id =>
+ new gui.Robot(player.inventory, t.robot)
case t: tileentity.Screen if id == GuiType.Screen.id =>
new gui.Screen(t.origin.buffer, t.tier > 0, () => t.origin.hasKeyboard, () => t.origin.buffer.isRenderingEnabled)
case t: tileentity.Switch if id == GuiType.Switch.id =>
diff --git a/src/main/scala/li/cil/oc/client/PacketHandler.scala b/src/main/scala/li/cil/oc/client/PacketHandler.scala
index bb4cb64d5..9a2ea387d 100644
--- a/src/main/scala/li/cil/oc/client/PacketHandler.scala
+++ b/src/main/scala/li/cil/oc/client/PacketHandler.scala
@@ -81,6 +81,7 @@ object PacketHandler extends CommonPacketHandler {
case PacketType.ServerPresence => onServerPresence(p)
case PacketType.Sound => onSound(p)
case PacketType.SoundPattern => onSoundPattern(p)
+ case PacketType.TransposerActivity => onTransposerActivity(p)
case PacketType.WaypointLabel => onWaypointLabel(p)
case _ => // Invalid packet.
}
@@ -260,6 +261,15 @@ object PacketHandler extends CommonPacketHandler {
}
}
+ def onNetSplitterState(p: PacketParser) =
+ p.readTileEntity[NetSplitter]() match {
+ case Some(t) =>
+ t.isInverted = p.readBoolean()
+ t.openSides = t.uncompressSides(p.readByte())
+ t.world.markBlockForUpdate(t.getPos)
+ case _ => // Invalid packet.
+ }
+
def onParticleEffect(p: PacketParser) = {
val dimension = p.readInt()
world(p.player, dimension) match {
@@ -420,7 +430,7 @@ object PacketHandler extends CommonPacketHandler {
}
def onSwitchActivity(p: PacketParser) =
- p.readTileEntity[Switch]() match {
+ p.readTileEntity[traits.SwitchLike]() match {
case Some(t) => t.lastMessage = System.currentTimeMillis()
case _ => // Invalid packet.
}
@@ -589,15 +599,6 @@ object PacketHandler extends CommonPacketHandler {
buffer.rawSetForeground(col, row, color)
}
- def onNetSplitterState(p: PacketParser) =
- p.readTileEntity[NetSplitter]() match {
- case Some(t) =>
- t.isInverted = p.readBoolean()
- t.openSides = t.uncompressSides(p.readByte())
- t.world.markBlockForUpdate(t.getPos)
- case _ => // Invalid packet.
- }
-
def onScreenTouchMode(p: PacketParser) =
p.readTileEntity[Screen]() match {
case Some(t) => t.invertTouchMode = p.readBoolean()
@@ -638,6 +639,12 @@ object PacketHandler extends CommonPacketHandler {
}
}
+ def onTransposerActivity(p: PacketParser) =
+ p.readTileEntity[Transposer]() match {
+ case Some(transposer) => transposer.lastOperation = System.currentTimeMillis()
+ case _ => // Invalid packet.
+ }
+
def onWaypointLabel(p: PacketParser) =
p.readTileEntity[Waypoint]() match {
case Some(waypoint) => waypoint.label = p.readUTF()
diff --git a/src/main/scala/li/cil/oc/client/Proxy.scala b/src/main/scala/li/cil/oc/client/Proxy.scala
index 6f33385aa..e799b35bc 100644
--- a/src/main/scala/li/cil/oc/client/Proxy.scala
+++ b/src/main/scala/li/cil/oc/client/Proxy.scala
@@ -63,15 +63,17 @@ private[oc] class Proxy extends CommonProxy {
else
ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.Hologram], HologramRendererFallback)
ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.Microcontroller], MicrocontrollerRenderer)
+ ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.NetSplitter], NetSplitterRenderer)
ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.PowerDistributor], PowerDistributorRenderer)
ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.Printer], PrinterRenderer)
ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.Raid], RaidRenderer)
ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.ServerRack], ServerRackRenderer)
ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.Switch], SwitchRenderer)
ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.AccessPoint], SwitchRenderer)
+ ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.Relay], SwitchRenderer)
ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.RobotProxy], RobotRenderer)
ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.Screen], ScreenRenderer)
- ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.NetSplitter], NetSplitterRenderer)
+ ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.Transposer], TransposerRenderer)
ClientRegistry.registerKeyBinding(KeyBindings.materialCosts)
ClientRegistry.registerKeyBinding(KeyBindings.clipboardPaste)
diff --git a/src/main/scala/li/cil/oc/client/Textures.scala b/src/main/scala/li/cil/oc/client/Textures.scala
index 39505d661..e01630ba0 100644
--- a/src/main/scala/li/cil/oc/client/Textures.scala
+++ b/src/main/scala/li/cil/oc/client/Textures.scala
@@ -59,6 +59,7 @@ object Textures {
val RobotSelection = L("robot_selection")
val Server = L("server")
val Slot = L("slot")
+ val UpgradeTab = L("upgrade_tab")
val Waypoint = L("waypoint")
override protected def basePath = "textures/gui/%s.png"
@@ -142,6 +143,7 @@ object Textures {
val RaidFrontError = L("overlay/raid_front_error")
val ScreenUpIndicator = L("overlay/screen_up_indicator")
val SwitchSideOn = L("overlay/switch_side_on")
+ val TransposerOn = L("overlay/transposer_on")
val Cable = L("cable")
val CableCap = L("cableCap")
diff --git a/src/main/scala/li/cil/oc/client/gui/Relay.scala b/src/main/scala/li/cil/oc/client/gui/Relay.scala
new file mode 100644
index 000000000..c8cb3432b
--- /dev/null
+++ b/src/main/scala/li/cil/oc/client/gui/Relay.scala
@@ -0,0 +1,123 @@
+package li.cil.oc.client.gui
+
+import java.lang.Iterable
+import java.text.DecimalFormat
+import java.util
+
+import codechicken.nei.VisiblityData
+import codechicken.nei.api.INEIGuiHandler
+import codechicken.nei.api.TaggedInventoryArea
+import li.cil.oc.Localization
+import li.cil.oc.client.Textures
+import li.cil.oc.common.container
+import li.cil.oc.common.tileentity
+import li.cil.oc.integration.Mods
+import net.minecraft.client.Minecraft
+import net.minecraft.client.gui.inventory.GuiContainer
+import net.minecraft.client.renderer.Tessellator
+import net.minecraft.entity.player.InventoryPlayer
+import net.minecraft.item.ItemStack
+import net.minecraftforge.fml.common.Optional
+import org.lwjgl.opengl.GL11
+import org.lwjgl.util.Rectangle
+
+@Optional.Interface(iface = "codechicken.nei.api.INEIGuiHandler", modid = Mods.IDs.NotEnoughItems)
+class Relay(playerInventory: InventoryPlayer, val relay: tileentity.Relay) extends DynamicGuiContainer(new container.Relay(playerInventory, relay)) with INEIGuiHandler {
+ private val format = new DecimalFormat("#.##hz")
+
+ private val tabPosition = new Rectangle(xSize, 10, 23, 26)
+
+ override protected def drawSecondaryBackgroundLayer(): Unit = {
+ super.drawSecondaryBackgroundLayer()
+
+ // Tab background.
+ GL11.glColor4f(1, 1, 1, 1)
+ Minecraft.getMinecraft.getTextureManager.bindTexture(Textures.GUI.UpgradeTab)
+ val x = windowX + tabPosition.getX
+ val y = windowY + tabPosition.getY
+ val w = tabPosition.getWidth
+ val h = tabPosition.getHeight
+ val t = Tessellator.getInstance
+ val r = t.getWorldRenderer
+ r.startDrawingQuads()
+ r.addVertexWithUV(x, y + h, zLevel, 0, 1)
+ r.addVertexWithUV(x + w, y + h, zLevel, 1, 1)
+ r.addVertexWithUV(x + w, y, zLevel, 1, 0)
+ r.addVertexWithUV(x, y, zLevel, 0, 0)
+ t.draw()
+ }
+
+ override def mouseClicked(mouseX: Int, mouseY: Int, button: Int): Unit = {
+ // So MC doesn't throw away the item in the upgrade slot when we're trying to pick it up...
+ val originalWidth = xSize
+ try {
+ xSize += tabPosition.getWidth
+ super.mouseClicked(mouseX, mouseY, button)
+ }
+ finally {
+ xSize = originalWidth
+ }
+ }
+
+ override def mouseReleased(mouseX: Int, mouseY: Int, button: Int): Unit = {
+ // So MC doesn't throw away the item in the upgrade slot when we're trying to pick it up...
+ val originalWidth = xSize
+ try {
+ xSize += tabPosition.getWidth
+ super.mouseReleased(mouseX, mouseY, button)
+ }
+ finally {
+ xSize = originalWidth
+ }
+ }
+
+ override def drawSecondaryForegroundLayer(mouseX: Int, mouseY: Int) = {
+ super.drawSecondaryForegroundLayer(mouseX, mouseY)
+ fontRendererObj.drawString(
+ Localization.localizeImmediately(relay.getName),
+ 8, 6, 0x404040)
+
+ fontRendererObj.drawString(
+ Localization.Switch.TransferRate,
+ 14, 20, 0x404040)
+ fontRendererObj.drawString(
+ Localization.Switch.PacketsPerCycle,
+ 14, 39, 0x404040)
+ fontRendererObj.drawString(
+ Localization.Switch.QueueSize,
+ 14, 58, 0x404040)
+
+ fontRendererObj.drawString(
+ format.format(20f / inventoryContainer.relayDelay),
+ 108, 20, 0x404040)
+ fontRendererObj.drawString(
+ inventoryContainer.packetsPerCycleAvg + " / " + inventoryContainer.relayAmount,
+ 108, 39, thresholdBasedColor(inventoryContainer.packetsPerCycleAvg, math.ceil(inventoryContainer.relayAmount / 2f).toInt, inventoryContainer.relayAmount))
+ fontRendererObj.drawString(
+ inventoryContainer.queueSize + " / " + inventoryContainer.maxQueueSize,
+ 108, 58, thresholdBasedColor(inventoryContainer.queueSize, inventoryContainer.maxQueueSize / 2, inventoryContainer.maxQueueSize))
+ }
+
+ private def thresholdBasedColor(value: Int, yellow: Int, red: Int) = {
+ if (value < yellow) 0x009900
+ else if (value < red) 0x999900
+ else 0x990000
+ }
+
+ @Optional.Method(modid = Mods.IDs.NotEnoughItems)
+ override def modifyVisiblity(gui: GuiContainer, currentVisibility: VisiblityData): VisiblityData = null
+
+ @Optional.Method(modid = Mods.IDs.NotEnoughItems)
+ override def getItemSpawnSlots(gui: GuiContainer, stack: ItemStack): Iterable[Integer] = null
+
+ @Optional.Method(modid = Mods.IDs.NotEnoughItems)
+ override def getInventoryAreas(gui: GuiContainer): util.List[TaggedInventoryArea] = null
+
+ @Optional.Method(modid = Mods.IDs.NotEnoughItems)
+ override def handleDragNDrop(gui: GuiContainer, mouseX: Int, mouseY: Int, stack: ItemStack, button: Int): Boolean = false
+
+ @Optional.Method(modid = Mods.IDs.NotEnoughItems)
+ override def hideItemPanelSlot(gui: GuiContainer, x: Int, y: Int, w: Int, h: Int): Boolean = {
+ new Rectangle(x - windowX, y - windowY, w, h).intersects(tabPosition)
+ }
+}
diff --git a/src/main/scala/li/cil/oc/client/gui/Switch.scala b/src/main/scala/li/cil/oc/client/gui/Switch.scala
index aefe609ed..22096def3 100644
--- a/src/main/scala/li/cil/oc/client/gui/Switch.scala
+++ b/src/main/scala/li/cil/oc/client/gui/Switch.scala
@@ -7,6 +7,7 @@ import li.cil.oc.common.container
import li.cil.oc.common.tileentity
import net.minecraft.entity.player.InventoryPlayer
+// TODO Remove in 1.7
class Switch(playerInventory: InventoryPlayer, val switch: tileentity.Switch) extends DynamicGuiContainer(new container.Switch(playerInventory, switch)) {
private val format = new DecimalFormat("#.##hz")
diff --git a/src/main/scala/li/cil/oc/client/renderer/tileentity/SwitchRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/tileentity/SwitchRenderer.scala
index 7041bcf2c..2849a780a 100644
--- a/src/main/scala/li/cil/oc/client/renderer/tileentity/SwitchRenderer.scala
+++ b/src/main/scala/li/cil/oc/client/renderer/tileentity/SwitchRenderer.scala
@@ -12,7 +12,7 @@ object SwitchRenderer extends TileEntitySpecialRenderer {
override def renderTileEntityAt(tileEntity: TileEntity, x: Double, y: Double, z: Double, f: Float, damage: Int) {
RenderState.checkError(getClass.getName + ".renderTileEntityAt: entering (aka: wasntme)")
- val switch = tileEntity.asInstanceOf[tileentity.Switch]
+ val switch = tileEntity.asInstanceOf[tileentity.traits.SwitchLike]
val activity = math.max(0, 1 - (System.currentTimeMillis() - switch.lastMessage) / 1000.0)
if (activity > 0) {
RenderState.pushAttrib()
diff --git a/src/main/scala/li/cil/oc/client/renderer/tileentity/TransposerRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/tileentity/TransposerRenderer.scala
new file mode 100644
index 000000000..f922d64c1
--- /dev/null
+++ b/src/main/scala/li/cil/oc/client/renderer/tileentity/TransposerRenderer.scala
@@ -0,0 +1,78 @@
+package li.cil.oc.client.renderer.tileentity
+
+import li.cil.oc.client.Textures
+import li.cil.oc.common.tileentity
+import li.cil.oc.util.RenderState
+import net.minecraft.client.renderer.Tessellator
+import net.minecraft.client.renderer.texture.TextureMap
+import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer
+import net.minecraft.tileentity.TileEntity
+import org.lwjgl.opengl.GL11
+
+object TransposerRenderer extends TileEntitySpecialRenderer {
+ override def renderTileEntityAt(tileEntity: TileEntity, x: Double, y: Double, z: Double, f: Float, damage: Int) {
+ RenderState.checkError(getClass.getName + ".renderTileEntityAt: entering (aka: wasntme)")
+
+ val transposer = tileEntity.asInstanceOf[tileentity.Transposer]
+ val activity = math.max(0, 1 - (System.currentTimeMillis() - transposer.lastOperation) / 1000.0)
+ if (activity > 0) {
+ RenderState.pushAttrib()
+
+ RenderState.disableEntityLighting()
+ RenderState.makeItBlend()
+ RenderState.setBlendAlpha(activity.toFloat)
+
+ RenderState.pushMatrix()
+
+ GL11.glTranslated(x + 0.5, y + 0.5, z + 0.5)
+ GL11.glScaled(1.0025, -1.0025, 1.0025)
+ GL11.glTranslatef(-0.5f, -0.5f, -0.5f)
+
+ val t = Tessellator.getInstance
+ val r = t.getWorldRenderer
+
+ Textures.Block.bind()
+ r.startDrawingQuads()
+
+ val icon = Textures.getSprite(Textures.Block.TransposerOn)
+ r.addVertexWithUV(0, 1, 0, icon.getMaxU, icon.getMinV)
+ r.addVertexWithUV(1, 1, 0, icon.getMinU, icon.getMinV)
+ r.addVertexWithUV(1, 1, 1, icon.getMinU, icon.getMaxV)
+ r.addVertexWithUV(0, 1, 1, icon.getMaxU, icon.getMaxV)
+
+ r.addVertexWithUV(0, 0, 0, icon.getMaxU, icon.getMaxV)
+ r.addVertexWithUV(0, 0, 1, icon.getMaxU, icon.getMinV)
+ r.addVertexWithUV(1, 0, 1, icon.getMinU, icon.getMinV)
+ r.addVertexWithUV(1, 0, 0, icon.getMinU, icon.getMaxV)
+
+ r.addVertexWithUV(1, 1, 0, icon.getMinU, icon.getMaxV)
+ r.addVertexWithUV(0, 1, 0, icon.getMaxU, icon.getMaxV)
+ r.addVertexWithUV(0, 0, 0, icon.getMaxU, icon.getMinV)
+ r.addVertexWithUV(1, 0, 0, icon.getMinU, icon.getMinV)
+
+ r.addVertexWithUV(0, 1, 1, icon.getMinU, icon.getMaxV)
+ r.addVertexWithUV(1, 1, 1, icon.getMaxU, icon.getMaxV)
+ r.addVertexWithUV(1, 0, 1, icon.getMaxU, icon.getMinV)
+ r.addVertexWithUV(0, 0, 1, icon.getMinU, icon.getMinV)
+
+ r.addVertexWithUV(0, 1, 0, icon.getMinU, icon.getMaxV)
+ r.addVertexWithUV(0, 1, 1, icon.getMaxU, icon.getMaxV)
+ r.addVertexWithUV(0, 0, 1, icon.getMaxU, icon.getMinV)
+ r.addVertexWithUV(0, 0, 0, icon.getMinU, icon.getMinV)
+
+ r.addVertexWithUV(1, 1, 1, icon.getMinU, icon.getMaxV)
+ r.addVertexWithUV(1, 1, 0, icon.getMaxU, icon.getMaxV)
+ r.addVertexWithUV(1, 0, 0, icon.getMaxU, icon.getMinV)
+ r.addVertexWithUV(1, 0, 1, icon.getMinU, icon.getMinV)
+
+ t.draw()
+
+ RenderState.enableEntityLighting()
+
+ RenderState.popMatrix()
+ RenderState.popAttrib()
+ }
+
+ RenderState.checkError(getClass.getName + ".renderTileEntityAt: leaving")
+ }
+}
diff --git a/src/main/scala/li/cil/oc/common/GuiHandler.scala b/src/main/scala/li/cil/oc/common/GuiHandler.scala
index a0a7aaa5d..7c80029f9 100644
--- a/src/main/scala/li/cil/oc/common/GuiHandler.scala
+++ b/src/main/scala/li/cil/oc/common/GuiHandler.scala
@@ -30,6 +30,8 @@ abstract class GuiHandler extends IGuiHandler {
new container.Printer(player.inventory, t)
case t: tileentity.Raid if id == GuiType.Raid.id =>
new container.Raid(player.inventory, t)
+ case t: tileentity.Relay if id == GuiType.Relay.id =>
+ new container.Relay(player.inventory, t)
case t: tileentity.RobotProxy if id == GuiType.Robot.id =>
new container.Robot(player.inventory, t.robot)
case t: tileentity.ServerRack if id == GuiType.Rack.id =>
diff --git a/src/main/scala/li/cil/oc/common/GuiType.scala b/src/main/scala/li/cil/oc/common/GuiType.scala
index 11a6dbb5b..bf02db237 100644
--- a/src/main/scala/li/cil/oc/common/GuiType.scala
+++ b/src/main/scala/li/cil/oc/common/GuiType.scala
@@ -28,6 +28,7 @@ object GuiType extends ScalaEnum {
val Printer = new EnumVal { def name = "Printer"; def subType = GuiType.Category.Block }
val Rack = new EnumVal { def name = "Rack"; def subType = GuiType.Category.Block }
val Raid = new EnumVal { def name = "Raid"; def subType = GuiType.Category.Block }
+ val Relay = new EnumVal { def name = "Relay"; def subType = GuiType.Category.Block }
val Robot = new EnumVal { def name = "Robot"; def subType = GuiType.Category.Block }
val Screen = new EnumVal { def name = "Screen"; def subType = GuiType.Category.Block }
val Server = new EnumVal { def name = "Server"; def subType = GuiType.Category.Item }
diff --git a/src/main/scala/li/cil/oc/common/InventorySlots.scala b/src/main/scala/li/cil/oc/common/InventorySlots.scala
index 6961b5bd4..1ac7ef962 100644
--- a/src/main/scala/li/cil/oc/common/InventorySlots.scala
+++ b/src/main/scala/li/cil/oc/common/InventorySlots.scala
@@ -120,6 +120,13 @@ object InventorySlots {
)
)
+ val relay = Array(
+ InventorySlot(Slot.CPU, Tier.Three),
+ InventorySlot(Slot.Memory, Tier.Three),
+ InventorySlot(Slot.HDD, Tier.Three),
+ InventorySlot(Slot.Card, Tier.Three)
+ )
+
val switch = Array(
InventorySlot(Slot.CPU, Tier.Three),
InventorySlot(Slot.Memory, Tier.Three),
diff --git a/src/main/scala/li/cil/oc/common/PacketType.scala b/src/main/scala/li/cil/oc/common/PacketType.scala
index 0b3712d29..325612a99 100644
--- a/src/main/scala/li/cil/oc/common/PacketType.scala
+++ b/src/main/scala/li/cil/oc/common/PacketType.scala
@@ -20,6 +20,7 @@ object PacketType extends Enumeration {
HologramTranslation,
HologramValues,
LootDisk,
+ NetSplitterState,
ParticleEffect,
PetVisibility, // Goes both ways.
PowerState,
@@ -49,11 +50,11 @@ object PacketType extends Enumeration {
TextBufferMultiRawSetBackground,
TextBufferMultiRawSetForeground,
TextBufferPowerChange,
- NetSplitterState,
ScreenTouchMode,
ServerPresence,
Sound,
SoundPattern,
+ TransposerActivity,
WaypointLabel, // Goes both ways.
// Client -> Server
diff --git a/src/main/scala/li/cil/oc/common/block/AccessPoint.scala b/src/main/scala/li/cil/oc/common/block/AccessPoint.scala
index be8320dff..463d21341 100644
--- a/src/main/scala/li/cil/oc/common/block/AccessPoint.scala
+++ b/src/main/scala/li/cil/oc/common/block/AccessPoint.scala
@@ -4,6 +4,7 @@ import li.cil.oc.Settings
import li.cil.oc.common.tileentity
import net.minecraft.world.World
+// TODO Remove in 1.7
class AccessPoint extends Switch with traits.PowerAcceptor {
override def energyThroughput = Settings.get.accessPointRate
diff --git a/src/main/scala/li/cil/oc/common/block/Relay.scala b/src/main/scala/li/cil/oc/common/block/Relay.scala
new file mode 100644
index 000000000..8f1356566
--- /dev/null
+++ b/src/main/scala/li/cil/oc/common/block/Relay.scala
@@ -0,0 +1,17 @@
+package li.cil.oc.common.block
+
+import li.cil.oc.Settings
+import li.cil.oc.common.GuiType
+import li.cil.oc.common.tileentity
+import net.minecraft.block.state.IBlockState
+import net.minecraft.world.World
+
+class Relay extends SimpleBlock with traits.GUI with traits.PowerAcceptor {
+ override def guiType = GuiType.Relay
+
+ override def energyThroughput = Settings.get.accessPointRate
+
+ override def hasTileEntity(state: IBlockState) = true
+
+ override def createNewTileEntity(world: World, metadata: Int) = new tileentity.Relay()
+}
diff --git a/src/main/scala/li/cil/oc/common/block/Switch.scala b/src/main/scala/li/cil/oc/common/block/Switch.scala
index ab8c82e1d..f61016f14 100644
--- a/src/main/scala/li/cil/oc/common/block/Switch.scala
+++ b/src/main/scala/li/cil/oc/common/block/Switch.scala
@@ -5,6 +5,7 @@ import li.cil.oc.common.tileentity
import net.minecraft.block.state.IBlockState
import net.minecraft.world.World
+// TODO Remove in 1.7
class Switch extends SimpleBlock with traits.GUI {
override def guiType = GuiType.Switch
diff --git a/src/main/scala/li/cil/oc/common/block/Transposer.scala b/src/main/scala/li/cil/oc/common/block/Transposer.scala
new file mode 100644
index 000000000..ecd43afc2
--- /dev/null
+++ b/src/main/scala/li/cil/oc/common/block/Transposer.scala
@@ -0,0 +1,18 @@
+package li.cil.oc.common.block
+
+import li.cil.oc.common.tileentity
+import net.minecraft.block.state.IBlockState
+import net.minecraft.util.BlockPos
+import net.minecraft.util.EnumFacing
+import net.minecraft.world.IBlockAccess
+import net.minecraft.world.World
+
+class Transposer extends SimpleBlock {
+ override def isSideSolid(world: IBlockAccess, pos: BlockPos, side: EnumFacing): Boolean = false
+
+ // ----------------------------------------------------------------------- //
+
+ override def hasTileEntity(state: IBlockState): Boolean = true
+
+ override def createNewTileEntity(world: World, meta: Int) = new tileentity.Transposer()
+}
diff --git a/src/main/scala/li/cil/oc/common/container/Relay.scala b/src/main/scala/li/cil/oc/common/container/Relay.scala
new file mode 100644
index 000000000..d9ee4a54f
--- /dev/null
+++ b/src/main/scala/li/cil/oc/common/container/Relay.scala
@@ -0,0 +1,33 @@
+package li.cil.oc.common.container
+
+import li.cil.oc.common.Slot
+import li.cil.oc.common.tileentity
+import net.minecraft.entity.player.InventoryPlayer
+import net.minecraft.nbt.NBTTagCompound
+
+class Relay(playerInventory: InventoryPlayer, relay: tileentity.Relay) extends Player(playerInventory, relay) {
+ addSlotToContainer(151, 15, Slot.CPU)
+ addSlotToContainer(151, 34, Slot.Memory)
+ addSlotToContainer(151, 53, Slot.HDD)
+ addSlotToContainer(178, 15, Slot.Card)
+ addPlayerInventorySlots(8, 84)
+
+ def relayDelay = synchronizedData.getInteger("relayDelay")
+
+ def relayAmount = synchronizedData.getInteger("relayAmount")
+
+ def maxQueueSize = synchronizedData.getInteger("maxQueueSize")
+
+ def packetsPerCycleAvg = synchronizedData.getInteger("packetsPerCycleAvg")
+
+ def queueSize = synchronizedData.getInteger("queueSize")
+
+ override protected def detectCustomDataChanges(nbt: NBTTagCompound): Unit = {
+ synchronizedData.setInteger("relayDelay", relay.relayDelay)
+ synchronizedData.setInteger("relayAmount", relay.relayAmount)
+ synchronizedData.setInteger("maxQueueSize", relay.maxQueueSize)
+ synchronizedData.setInteger("packetsPerCycleAvg", relay.packetsPerCycleAvg())
+ synchronizedData.setInteger("queueSize", relay.queue.size)
+ super.detectCustomDataChanges(nbt)
+ }
+}
diff --git a/src/main/scala/li/cil/oc/common/container/Switch.scala b/src/main/scala/li/cil/oc/common/container/Switch.scala
index ae6c16254..78d884368 100644
--- a/src/main/scala/li/cil/oc/common/container/Switch.scala
+++ b/src/main/scala/li/cil/oc/common/container/Switch.scala
@@ -5,6 +5,7 @@ import li.cil.oc.common.tileentity
import net.minecraft.entity.player.InventoryPlayer
import net.minecraft.nbt.NBTTagCompound
+// TODO Remove in 1.7
class Switch(playerInventory: InventoryPlayer, switch: tileentity.Switch) extends Player(playerInventory, switch) {
addSlotToContainer(151, 15, Slot.CPU)
addSlotToContainer(151, 34, Slot.Memory)
diff --git a/src/main/scala/li/cil/oc/common/init/Blocks.scala b/src/main/scala/li/cil/oc/common/init/Blocks.scala
index 1d3a6b21d..a3146cc5c 100644
--- a/src/main/scala/li/cil/oc/common/init/Blocks.scala
+++ b/src/main/scala/li/cil/oc/common/init/Blocks.scala
@@ -24,20 +24,22 @@ object Blocks {
GameRegistry.registerTileEntity(classOf[tileentity.Geolyzer], Settings.namespace + "geolyzer")
GameRegistry.registerTileEntity(classOf[tileentity.Microcontroller], Settings.namespace + "microcontroller")
GameRegistry.registerTileEntity(classOf[tileentity.MotionSensor], Settings.namespace + "motionSensor")
+ GameRegistry.registerTileEntity(classOf[tileentity.NetSplitter], Settings.namespace + "netSplitter")
GameRegistry.registerTileEntity(classOf[tileentity.PowerConverter], Settings.namespace + "powerConverter")
GameRegistry.registerTileEntity(classOf[tileentity.PowerDistributor], Settings.namespace + "powerDistributor")
GameRegistry.registerTileEntity(classOf[tileentity.Print], Settings.namespace + "print")
GameRegistry.registerTileEntity(classOf[tileentity.Printer], Settings.namespace + "printer")
GameRegistry.registerTileEntity(classOf[tileentity.Raid], Settings.namespace + "raid")
GameRegistry.registerTileEntity(classOf[tileentity.Redstone], Settings.namespace + "redstone")
+ GameRegistry.registerTileEntity(classOf[tileentity.Relay], Settings.namespace + "relay")
GameRegistry.registerTileEntity(classOf[tileentity.RobotProxy], Settings.namespace + "robot")
GameRegistry.registerTileEntity(classOf[tileentity.Switch], Settings.namespace + "switch")
GameRegistry.registerTileEntity(classOf[tileentity.Screen], Settings.namespace + "screen")
GameRegistry.registerTileEntity(classOf[tileentity.ServerRack], Settings.namespace + "serverRack")
- GameRegistry.registerTileEntity(classOf[tileentity.NetSplitter], Settings.namespace + "netSplitter")
+ GameRegistry.registerTileEntity(classOf[tileentity.Transposer], Settings.namespace + "transposer")
GameRegistry.registerTileEntity(classOf[tileentity.Waypoint], Settings.namespace + "waypoint")
- Recipes.addBlock(new AccessPoint(), Constants.BlockName.AccessPoint, "oc:accessPoint")
+ Items.registerBlock(new AccessPoint(), Constants.BlockName.AccessPoint)
Recipes.addBlock(new Adapter(), Constants.BlockName.Adapter, "oc:adapter")
Recipes.addBlock(new Assembler(), Constants.BlockName.Assembler, "oc:assembler")
Recipes.addBlock(new Cable(), Constants.BlockName.Cable, "oc:cable")
@@ -59,11 +61,12 @@ object Blocks {
Recipes.addBlock(new Printer(), Constants.BlockName.Printer, "oc:printer")
Recipes.addBlock(new Raid(), Constants.BlockName.Raid, "oc:raid")
Recipes.addBlock(new Redstone(), Constants.BlockName.Redstone, "oc:redstone")
+ Recipes.addBlock(new Relay(), Constants.BlockName.Relay, "oc:relay")
Recipes.addBlock(new Screen(Tier.One), Constants.BlockName.ScreenTier1, "oc:screen1")
Recipes.addBlock(new Screen(Tier.Three), Constants.BlockName.ScreenTier3, "oc:screen3")
Recipes.addBlock(new Screen(Tier.Two), Constants.BlockName.ScreenTier2, "oc:screen2")
Recipes.addBlock(new ServerRack(), Constants.BlockName.ServerRack, "oc:serverRack")
- Recipes.addBlock(new Switch(), Constants.BlockName.Switch, "oc:switch")
+ Items.registerBlock(new Switch(), Constants.BlockName.Switch)
Recipes.addBlock(new Waypoint(), Constants.BlockName.Waypoint, "oc:waypoint")
Items.registerBlock(new Case(Tier.Four), Constants.BlockName.CaseCreative)
@@ -77,5 +80,8 @@ object Blocks {
// v1.5.14
Recipes.addBlock(new NetSplitter(), Constants.BlockName.NetSplitter, "oc:netSplitter")
+
+ // v1.5.16
+ Recipes.addBlock(new Transposer(), Constants.BlockName.Transposer, "oc:transposer")
}
}
diff --git a/src/main/scala/li/cil/oc/common/item/Tablet.scala b/src/main/scala/li/cil/oc/common/item/Tablet.scala
index edc3070f6..14bd58b8c 100644
--- a/src/main/scala/li/cil/oc/common/item/Tablet.scala
+++ b/src/main/scala/li/cil/oc/common/item/Tablet.scala
@@ -241,9 +241,11 @@ class TabletWrapper(var stack: ItemStack, var player: EntityPlayer) extends Comp
override def facing = RotationHelper.fromYaw(player.rotationYaw)
- override def toLocal(value: EnumFacing) = value // -T-O-D-O- do we care? no we don't
+ override def toLocal(value: EnumFacing) =
+ RotationHelper.toLocal(EnumFacing.NORTH, facing, value)
- override def toGlobal(value: EnumFacing) = value // -T-O-D-O- do we care? no we don't
+ override def toGlobal(value: EnumFacing) =
+ RotationHelper.toGlobal(EnumFacing.NORTH, facing, value)
def readFromNBT() {
if (stack.hasTagCompound) {
diff --git a/src/main/scala/li/cil/oc/common/item/traits/CPULike.scala b/src/main/scala/li/cil/oc/common/item/traits/CPULike.scala
index 31d20a765..4b26ca417 100644
--- a/src/main/scala/li/cil/oc/common/item/traits/CPULike.scala
+++ b/src/main/scala/li/cil/oc/common/item/traits/CPULike.scala
@@ -3,12 +3,10 @@ package li.cil.oc.common.item.traits
import java.util
import li.cil.oc.Settings
-import li.cil.oc.api
-import li.cil.oc.api.machine.Architecture
+import li.cil.oc.integration.opencomputers.DriverCPU
import li.cil.oc.util.Tooltip
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.item.ItemStack
-import net.minecraft.nbt.NBTTagCompound
import net.minecraft.util.ChatComponentTranslation
import net.minecraft.world.World
@@ -21,37 +19,19 @@ trait CPULike extends Delegate {
override protected def tooltipData: Seq[Any] = Seq(Settings.get.cpuComponentSupport(cpuTier))
override protected def tooltipExtended(stack: ItemStack, tooltip: util.List[String]) {
- (if (stack.hasTagCompound) {
- Option(stack.getTagCompound.getString(Settings.namespace + "archName"))
- }
- else {
- val architectures = allArchitectures
- architectures.headOption.map(_._2)
- }) match {
- case Some(archName) if !archName.isEmpty => tooltip.addAll(Tooltip.get("CPU.Architecture", archName))
- case _ => allArchitectures.headOption.collect {
- case ((_, name)) => tooltip.addAll(Tooltip.get("CPU.Architecture", name))
- }
- }
+ tooltip.addAll(Tooltip.get("CPU.Architecture", DriverCPU.getArchitectureName(DriverCPU.architecture(stack))))
}
override def onItemRightClick(stack: ItemStack, world: World, player: EntityPlayer) = {
if (player.isSneaking) {
if (!world.isRemote) {
- val architectures = allArchitectures
- if (architectures.length > 0) {
- val currentIndex = if (stack.hasTagCompound) {
- val currentArch = stack.getTagCompound.getString(Settings.namespace + "archClass")
- architectures.indexWhere(_._1.getName == currentArch)
- }
- else {
- stack.setTagCompound(new NBTTagCompound())
- -1
- }
- val index = (currentIndex + 1) % architectures.length
- val (archClass, archName) = architectures(index)
- stack.getTagCompound.setString(Settings.namespace + "archClass", archClass.getName)
- stack.getTagCompound.setString(Settings.namespace + "archName", archName)
+ val architectures = DriverCPU.allArchitectures.toList
+ if (architectures.nonEmpty) {
+ val currentIndex = architectures.indexOf(DriverCPU.architecture(stack))
+ val newIndex = (currentIndex + 1) % architectures.length
+ val archClass = architectures(newIndex)
+ val archName = DriverCPU.getArchitectureName(archClass)
+ DriverCPU.setArchitecture(stack, archClass)
player.addChatMessage(new ChatComponentTranslation(Settings.namespace + "tooltip.CPU.Architecture", archName))
}
player.swingItem()
@@ -59,11 +39,4 @@ trait CPULike extends Delegate {
}
stack
}
-
- private def allArchitectures = api.Machine.architectures.map { arch =>
- arch.getAnnotation(classOf[Architecture.Name]) match {
- case annotation: Architecture.Name => (arch, annotation.value)
- case _ => (arch, arch.getSimpleName)
- }
- }.toArray
}
diff --git a/src/main/scala/li/cil/oc/common/recipe/Recipes.scala b/src/main/scala/li/cil/oc/common/recipe/Recipes.scala
index 34c19bd6b..5e4125310 100644
--- a/src/main/scala/li/cil/oc/common/recipe/Recipes.scala
+++ b/src/main/scala/li/cil/oc/common/recipe/Recipes.scala
@@ -326,6 +326,12 @@ object Recipes {
GameRegistry.addRecipe(new ExtendedShapelessOreRecipe(
lightPrint,
print.createItemStack(1), new ItemStack(net.minecraft.init.Blocks.glowstone)))
+
+ // Switch/AccessPoint -> Relay conversion
+ GameRegistry.addShapelessRecipe(api.Items.get(Constants.BlockName.Relay).createItemStack(1),
+ api.Items.get(Constants.BlockName.AccessPoint).createItemStack(1))
+ GameRegistry.addShapelessRecipe(api.Items.get(Constants.BlockName.Relay).createItemStack(1),
+ api.Items.get(Constants.BlockName.Switch).createItemStack(1))
}
catch {
case e: Throwable => OpenComputers.log.error("Error parsing recipes, you may not be able to craft any items from this mod!", e)
diff --git a/src/main/scala/li/cil/oc/common/tileentity/AccessPoint.scala b/src/main/scala/li/cil/oc/common/tileentity/AccessPoint.scala
index 35b2746bc..63a195f23 100644
--- a/src/main/scala/li/cil/oc/common/tileentity/AccessPoint.scala
+++ b/src/main/scala/li/cil/oc/common/tileentity/AccessPoint.scala
@@ -15,6 +15,7 @@ import net.minecraftforge.common.util.Constants.NBT
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
+// TODO Remove in 1.7
class AccessPoint extends Switch with WirelessEndpoint with traits.PowerAcceptor {
var strength = Settings.get.maxWirelessRange
@@ -24,6 +25,8 @@ class AccessPoint extends Switch with WirelessEndpoint with traits.PowerAcceptor
withComponent("access_point").
create())
+ override def isWirelessEnabled = true
+
// ----------------------------------------------------------------------- //
@SideOnly(Side.CLIENT)
@@ -71,7 +74,7 @@ class AccessPoint extends Switch with WirelessEndpoint with traits.PowerAcceptor
override protected def relayPacket(sourceSide: Option[EnumFacing], packet: Packet) {
super.relayPacket(sourceSide, packet)
- if (strength > 0 && (sourceSide != None || isRepeater)) {
+ if (strength > 0 && (sourceSide.isDefined || isRepeater)) {
val cost = Settings.get.wirelessCostPerRange
val tryChangeBuffer = sourceSide match {
case Some(side) =>
diff --git a/src/main/scala/li/cil/oc/common/tileentity/Relay.scala b/src/main/scala/li/cil/oc/common/tileentity/Relay.scala
new file mode 100644
index 000000000..702d67638
--- /dev/null
+++ b/src/main/scala/li/cil/oc/common/tileentity/Relay.scala
@@ -0,0 +1,282 @@
+package li.cil.oc.common.tileentity
+
+import li.cil.oc.Constants
+import li.cil.oc.Localization
+import li.cil.oc.Settings
+import li.cil.oc.api
+import li.cil.oc.api.Driver
+import li.cil.oc.api.machine.Arguments
+import li.cil.oc.api.machine.Callback
+import li.cil.oc.api.machine.Context
+import li.cil.oc.api.network.Analyzable
+import li.cil.oc.api.network.Connector
+import li.cil.oc.api.network.Node
+import li.cil.oc.api.network.Packet
+import li.cil.oc.api.network.Visibility
+import li.cil.oc.api.network.WirelessEndpoint
+import li.cil.oc.common.InventorySlots
+import li.cil.oc.common.Slot
+import li.cil.oc.common.item
+import li.cil.oc.common.item.Delegator
+import li.cil.oc.integration.Mods
+import li.cil.oc.integration.opencomputers.DriverLinkedCard
+import li.cil.oc.server.network.QuantumNetwork
+import li.cil.oc.util.ExtendedNBT._
+import net.minecraft.entity.player.EntityPlayer
+import net.minecraft.item.ItemStack
+import net.minecraft.nbt.NBTTagCompound
+import net.minecraft.util.EnumFacing
+import net.minecraftforge.common.util.Constants.NBT
+import net.minecraftforge.fml.relauncher.Side
+import net.minecraftforge.fml.relauncher.SideOnly
+
+class Relay extends traits.SwitchLike with traits.ComponentInventory with traits.PowerAcceptor with Analyzable with WirelessEndpoint with QuantumNetwork.QuantumNode {
+ lazy final val WirelessNetworkCard = api.Items.get(Constants.ItemName.WirelessNetworkCard)
+ lazy final val LinkedCard = api.Items.get(Constants.ItemName.LinkedCard)
+
+ var strength = Settings.get.maxWirelessRange
+
+ var isRepeater = true
+
+ var isWirelessEnabled = false
+
+ var isLinkedEnabled = false
+
+ var tunnel = "creative"
+
+ val componentNodes = Array.fill(6)(api.Network.newNode(this, Visibility.Network).
+ withComponent("relay").
+ create())
+
+ override def canUpdate = isServer
+
+ // ----------------------------------------------------------------------- //
+
+ @SideOnly(Side.CLIENT)
+ override protected def hasConnector(side: EnumFacing) = true
+
+ override protected def connector(side: EnumFacing) = sidedNode(side) match {
+ case connector: Connector => Option(connector)
+ case _ => None
+ }
+
+ override def energyThroughput = Settings.get.accessPointRate
+
+ // ----------------------------------------------------------------------- //
+
+ override def onAnalyze(player: EntityPlayer, side: EnumFacing, hitX: Float, hitY: Float, hitZ: Float): Array[Node] = {
+ if (isWirelessEnabled) {
+ player.addChatMessage(Localization.Analyzer.WirelessStrength(strength))
+ Array(componentNodes(side.getIndex))
+ }
+ else null
+ }
+
+ // ----------------------------------------------------------------------- //
+
+ @Callback(direct = true, doc = """function():number -- Get the signal strength (range) used when relaying messages.""")
+ def getStrength(context: Context, args: Arguments): Array[AnyRef] = synchronized(result(strength))
+
+ @Callback(doc = """function(strength:number):number -- Set the signal strength (range) used when relaying messages.""")
+ def setStrength(context: Context, args: Arguments): Array[AnyRef] = synchronized {
+ strength = math.max(args.checkDouble(0), math.min(0, Settings.get.maxWirelessRange))
+ result(strength)
+ }
+
+ @Callback(direct = true, doc = """function():boolean -- Get whether the access point currently acts as a repeater (resend received wireless packets wirelessly).""")
+ def isRepeater(context: Context, args: Arguments): Array[AnyRef] = synchronized(result(isRepeater))
+
+ @Callback(doc = """function(enabled:boolean):boolean -- Set whether the access point should act as a repeater.""")
+ def setRepeater(context: Context, args: Arguments): Array[AnyRef] = synchronized {
+ isRepeater = args.checkBoolean(0)
+ result(isRepeater)
+ }
+
+ // ----------------------------------------------------------------------- //
+
+ protected def queueMessage(source: String, destination: String, port: Int, answerPort: Int, args: Array[AnyRef]) {
+ /* TODO ComputerCraft
+ for (computer <- computers.map(_.asInstanceOf[IComputerAccess])) {
+ val address = s"cc${computer.getID}_${computer.getAttachmentName}"
+ if (source != address && Option(destination).forall(_ == address) && openPorts(computer).contains(port))
+ computer.queueEvent("modem_message", Array(Seq(computer.getAttachmentName, Int.box(port), Int.box(answerPort)) ++ args.map {
+ case x: Array[Byte] => new String(x, Charsets.UTF_8)
+ case x => x
+ }: _*))
+ }
+ */
+ }
+
+ // ----------------------------------------------------------------------- //
+
+ override def receivePacket(packet: Packet, source: WirelessEndpoint): Unit = {
+ if (isWirelessEnabled) {
+ tryEnqueuePacket(None, packet)
+ }
+ }
+
+ override def receivePacket(packet: Packet): Unit = {
+ if (isLinkedEnabled) {
+ tryEnqueuePacket(None, packet)
+ }
+ }
+
+ override def tryEnqueuePacket(sourceSide: Option[EnumFacing], packet: Packet): Boolean = {
+ if (Mods.ComputerCraft.isAvailable) {
+ packet.data.headOption match {
+ case Some(answerPort: java.lang.Double) => queueMessage(packet.source, packet.destination, packet.port, answerPort.toInt, packet.data.drop(1))
+ case _ => queueMessage(packet.source, packet.destination, packet.port, -1, packet.data)
+ }
+ }
+ super.tryEnqueuePacket(sourceSide, packet)
+ }
+
+ override protected def relayPacket(sourceSide: Option[EnumFacing], packet: Packet): Unit = {
+ super.relayPacket(sourceSide, packet)
+
+ val tryChangeBuffer = sourceSide match {
+ case Some(side) =>
+ (amount: Double) => plugs(side.ordinal).node.asInstanceOf[Connector].tryChangeBuffer(amount)
+ case _ =>
+ (amount: Double) => plugs.exists(_.node.asInstanceOf[Connector].tryChangeBuffer(amount))
+ }
+
+ if (isWirelessEnabled && strength > 0 && (sourceSide.isDefined || isRepeater)) {
+ val cost = Settings.get.wirelessCostPerRange
+ if (tryChangeBuffer(-strength * cost)) {
+ api.Network.sendWirelessPacket(this, strength, packet)
+ }
+ }
+
+ if (isLinkedEnabled && sourceSide.isDefined) {
+ val cost = packet.size / 32.0 + Settings.get.wirelessCostPerRange * Settings.get.maxWirelessRange * 5
+ if (tryChangeBuffer(-cost)) {
+ val endpoints = QuantumNetwork.getEndpoints(tunnel).filter(_ != this)
+ for (endpoint <- endpoints) {
+ endpoint.receivePacket(packet)
+ }
+ }
+ }
+
+ onSwitchActivity()
+ }
+
+ // ----------------------------------------------------------------------- //
+
+ override protected def createNode(plug: Plug) = api.Network.newNode(plug, Visibility.Network).
+ withConnector(math.round(Settings.get.bufferAccessPoint)).
+ create()
+
+ override protected def onPlugConnect(plug: Plug, node: Node) {
+ super.onPlugConnect(plug, node)
+ if (node == plug.node) {
+ api.Network.joinWirelessNetwork(this)
+ }
+ if (plug.isPrimary)
+ plug.node.connect(componentNodes(plug.side.ordinal()))
+ else
+ componentNodes(plug.side.ordinal).remove()
+ }
+
+ override protected def onPlugDisconnect(plug: Plug, node: Node) {
+ super.onPlugDisconnect(plug, node)
+ if (node == plug.node) {
+ api.Network.leaveWirelessNetwork(this)
+ }
+ if (plug.isPrimary && node != plug.node)
+ plug.node.connect(componentNodes(plug.side.ordinal()))
+ else
+ componentNodes(plug.side.ordinal).remove()
+ }
+
+ // ----------------------------------------------------------------------- //
+
+ override protected def onItemAdded(slot: Int, stack: ItemStack) {
+ super.onItemAdded(slot, stack)
+ updateLimits(slot, stack)
+ }
+
+ private def updateLimits(slot: Int, stack: ItemStack) {
+ Option(Driver.driverFor(stack, getClass)) match {
+ case Some(driver) if driver.slot(stack) == Slot.CPU =>
+ relayDelay = math.max(1, relayBaseDelay - ((driver.tier(stack) + 1) * relayDelayPerUpgrade))
+ case Some(driver) if driver.slot(stack) == Slot.Memory =>
+ relayAmount = math.max(1, relayBaseAmount + (Delegator.subItem(stack) match {
+ case Some(ram: item.Memory) => (ram.tier + 1) * relayAmountPerUpgrade
+ case _ => (driver.tier(stack) + 1) * (relayAmountPerUpgrade * 2)
+ }))
+ case Some(driver) if driver.slot(stack) == Slot.HDD =>
+ maxQueueSize = math.max(1, queueBaseSize + (driver.tier(stack) + 1) * queueSizePerUpgrade)
+ case Some(driver) if driver.slot(stack) == Slot.Card =>
+ val descriptor = api.Items.get(stack)
+ if (descriptor == WirelessNetworkCard) {
+ isWirelessEnabled = true
+ }
+ if (descriptor == LinkedCard) {
+ val data = DriverLinkedCard.dataTag(stack)
+ if (data.hasKey(Settings.namespace + "tunnel")) {
+ tunnel = data.getString(Settings.namespace + "tunnel")
+ isLinkedEnabled = true
+ QuantumNetwork.add(this)
+ }
+ }
+ case _ => // Dafuq u doin.
+ }
+ }
+
+ override protected def onItemRemoved(slot: Int, stack: ItemStack) {
+ super.onItemRemoved(slot, stack)
+ Driver.driverFor(stack, getClass) match {
+ case driver if driver.slot(stack) == Slot.CPU => relayDelay = relayBaseDelay
+ case driver if driver.slot(stack) == Slot.Memory => relayAmount = relayBaseAmount
+ case driver if driver.slot(stack) == Slot.HDD => maxQueueSize = queueBaseSize
+ case driver if driver.slot(stack) == Slot.Card =>
+ isWirelessEnabled = false
+ isLinkedEnabled = false
+ QuantumNetwork.remove(this)
+ }
+ }
+
+ override def getSizeInventory = InventorySlots.relay.length
+
+ override def isItemValidForSlot(slot: Int, stack: ItemStack) =
+ Option(Driver.driverFor(stack, getClass)).fold(false)(driver => {
+ val provided = InventorySlots.relay(slot)
+ val tierSatisfied = driver.slot(stack) == provided.slot && driver.tier(stack) <= provided.tier
+ val cardTypeSatisfied = if (provided.slot == Slot.Card) api.Items.get(stack) == WirelessNetworkCard || api.Items.get(stack) == LinkedCard else true
+ tierSatisfied && cardTypeSatisfied
+ })
+
+ // ----------------------------------------------------------------------- //
+
+ override def readFromNBTForServer(nbt: NBTTagCompound) {
+ super.readFromNBTForServer(nbt)
+ for (slot <- items.indices) items(slot) collect {
+ case stack => updateLimits(slot, stack)
+ }
+
+ if (nbt.hasKey(Settings.namespace + "strength")) {
+ strength = nbt.getDouble(Settings.namespace + "strength") max 0 min Settings.get.maxWirelessRange
+ }
+ if (nbt.hasKey(Settings.namespace + "isRepeater")) {
+ isRepeater = nbt.getBoolean(Settings.namespace + "isRepeater")
+ }
+ nbt.getTagList(Settings.namespace + "componentNodes", NBT.TAG_COMPOUND).toArray[NBTTagCompound].
+ zipWithIndex.foreach {
+ case (tag, index) => componentNodes(index).load(tag)
+ }
+ }
+
+ override def writeToNBTForServer(nbt: NBTTagCompound) = {
+ super.writeToNBTForServer(nbt)
+ nbt.setDouble(Settings.namespace + "strength", strength)
+ nbt.setBoolean(Settings.namespace + "isRepeater", isRepeater)
+ nbt.setNewTagList(Settings.namespace + "componentNodes", componentNodes.map {
+ case node: Node =>
+ val tag = new NBTTagCompound()
+ node.save(tag)
+ tag
+ case _ => new NBTTagCompound()
+ })
+ }
+}
diff --git a/src/main/scala/li/cil/oc/common/tileentity/Switch.scala b/src/main/scala/li/cil/oc/common/tileentity/Switch.scala
index 1fae1a4b7..1db46a684 100644
--- a/src/main/scala/li/cil/oc/common/tileentity/Switch.scala
+++ b/src/main/scala/li/cil/oc/common/tileentity/Switch.scala
@@ -6,25 +6,49 @@ import li.cil.oc.common.InventorySlots
import li.cil.oc.common.Slot
import li.cil.oc.common.item
import li.cil.oc.common.item.Delegator
-import li.cil.oc.server.PacketSender
+import li.cil.oc.integration.Mods
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NBTTagCompound
import net.minecraft.util.EnumFacing
-class Switch extends traits.Hub with traits.NotAnalyzable with traits.ComponentInventory {
- var lastMessage = 0L
+// TODO Remove in 1.7
+class Switch extends traits.SwitchLike with traits.NotAnalyzable with traits.ComponentInventory {
+ override def isWirelessEnabled = false
+
+ override def isLinkedEnabled = false
override def canUpdate = isServer
// ----------------------------------------------------------------------- //
+ protected def queueMessage(source: String, destination: String, port: Int, answerPort: Int, args: Array[AnyRef]) {
+ /* TODO ComputerCraft
+ for (computer <- computers.map(_.asInstanceOf[IComputerAccess])) {
+ val address = s"cc${computer.getID}_${computer.getAttachmentName}"
+ if (source != address && Option(destination).forall(_ == address) && openPorts(computer).contains(port))
+ computer.queueEvent("modem_message", Array(Seq(computer.getAttachmentName, Int.box(port), Int.box(answerPort)) ++ args.map {
+ case x: Array[Byte] => new String(x, Charsets.UTF_8)
+ case x => x
+ }: _*))
+ }
+ */
+ }
+
+ // ----------------------------------------------------------------------- //
+
+ override def tryEnqueuePacket(sourceSide: Option[EnumFacing], packet: Packet): Boolean = {
+ if (Mods.ComputerCraft.isAvailable) {
+ packet.data.headOption match {
+ case Some(answerPort: java.lang.Double) => queueMessage(packet.source, packet.destination, packet.port, answerPort.toInt, packet.data.drop(1))
+ case _ => queueMessage(packet.source, packet.destination, packet.port, -1, packet.data)
+ }
+ }
+ super.tryEnqueuePacket(sourceSide, packet)
+ }
+
override protected def relayPacket(sourceSide: Option[EnumFacing], packet: Packet) {
super.relayPacket(sourceSide, packet)
- val now = System.currentTimeMillis()
- if (now - lastMessage >= (relayDelay - 1) * 50) {
- lastMessage = now
- PacketSender.sendSwitchActivity(this)
- }
+ onSwitchActivity()
}
// ----------------------------------------------------------------------- //
diff --git a/src/main/scala/li/cil/oc/common/tileentity/Transposer.scala b/src/main/scala/li/cil/oc/common/tileentity/Transposer.scala
new file mode 100644
index 000000000..b3e43b9f4
--- /dev/null
+++ b/src/main/scala/li/cil/oc/common/tileentity/Transposer.scala
@@ -0,0 +1,25 @@
+package li.cil.oc.common.tileentity
+
+import li.cil.oc.server.component
+import net.minecraft.nbt.NBTTagCompound
+
+class Transposer extends traits.Environment {
+ val transposer = new component.Transposer(this)
+
+ def node = transposer.node
+
+ // Used on client side to check whether to render activity indicators.
+ var lastOperation = 0L
+
+ override def canUpdate = false
+
+ override def readFromNBTForServer(nbt: NBTTagCompound) {
+ super.readFromNBTForServer(nbt)
+ transposer.load(nbt)
+ }
+
+ override def writeToNBTForServer(nbt: NBTTagCompound) {
+ super.writeToNBTForServer(nbt)
+ transposer.save(nbt)
+ }
+}
diff --git a/src/main/scala/li/cil/oc/common/tileentity/traits/Rotatable.scala b/src/main/scala/li/cil/oc/common/tileentity/traits/Rotatable.scala
index 9ccdc88cd..d7cb725a3 100644
--- a/src/main/scala/li/cil/oc/common/tileentity/traits/Rotatable.scala
+++ b/src/main/scala/li/cil/oc/common/tileentity/traits/Rotatable.scala
@@ -5,6 +5,7 @@ import li.cil.oc.common.block
import li.cil.oc.server.{PacketSender => ServerPacketSender}
import li.cil.oc.util.ExtendedEnumFacing._
import li.cil.oc.util.ExtendedWorld._
+import li.cil.oc.util.RotationHelper
import net.minecraft.block.state.IBlockState
import net.minecraft.entity.Entity
import net.minecraft.util.EnumFacing
@@ -15,69 +16,9 @@ trait Rotatable extends RotationAware with internal.Rotatable {
// Lookup tables
// ----------------------------------------------------------------------- //
- /**
- * Translates forge directions based on the block's pitch and yaw. The base
- * forward direction is facing south with no pitch. The outer array is for
- * the three different pitch states, the inner for the four different yaw
- * states.
- */
- private val translations = Array(
- // Pitch = Down
- Array(
- // Yaw = North
- Array(D.south, D.north, D.up, D.down, D.east, D.west),
- // Yaw = South
- Array(D.south, D.north, D.down, D.up, D.west, D.east),
- // Yaw = West
- Array(D.south, D.north, D.west, D.east, D.up, D.down),
- // Yaw = East
- Array(D.south, D.north, D.east, D.west, D.down, D.up)),
- // Pitch = Up
- Array(
- // Yaw = North
- Array(D.north, D.south, D.down, D.up, D.east, D.west),
- // Yaw = South
- Array(D.north, D.south, D.up, D.down, D.west, D.east),
- // Yaw = West
- Array(D.north, D.south, D.west, D.east, D.down, D.up),
- // Yaw = East
- Array(D.north, D.south, D.east, D.west, D.up, D.down)),
- // Pitch = Forward (North|East|South|West)
- Array(
- // Yaw = North
- Array(D.down, D.up, D.south, D.north, D.east, D.west),
- // Yaw = South
- Array(D.down, D.up, D.north, D.south, D.west, D.east),
- // Yaw = West
- Array(D.down, D.up, D.west, D.east, D.south, D.north),
- // Yaw = East
- Array(D.down, D.up, D.east, D.west, D.north, D.south)))
+ private val pitch2Direction = Array(EnumFacing.UP, EnumFacing.NORTH, EnumFacing.DOWN)
- private val pitch2Direction = Array(D.up, D.north, D.down)
-
- private val yaw2Direction = Array(D.south, D.west, D.north, D.east)
-
- /** Shortcuts for forge directions to make the above more readable. */
- private object D {
- val down = EnumFacing.DOWN
- val up = EnumFacing.UP
- val north = EnumFacing.NORTH
- val south = EnumFacing.SOUTH
- val west = EnumFacing.WEST
- val east = EnumFacing.EAST
- }
-
- // ----------------------------------------------------------------------- //
- // State
- // ----------------------------------------------------------------------- //
-
- /** Translation for facings based on current pitch and yaw. */
- private var cachedTranslation: Array[EnumFacing] = null
-
- /** Translation from local to global coordinates. */
- private var cachedInverseTranslation: Array[EnumFacing] = null
-
- protected var cacheDirty = true
+ private val yaw2Direction = Array(EnumFacing.SOUTH, EnumFacing.WEST, EnumFacing.NORTH, EnumFacing.EAST)
// ----------------------------------------------------------------------- //
// Accessors
@@ -148,17 +89,9 @@ trait Rotatable extends RotationAware with internal.Rotatable {
else false
}
- override def toLocal(value: EnumFacing) = if (value != null) this.synchronized {
- updateTranslation()
- cachedTranslation(value.ordinal)
- }
- else null
+ override def toLocal(value: EnumFacing) = if (value == null) null else RotationHelper.toLocal(pitch, yaw, value)
- override def toGlobal(value: EnumFacing) = if (value != null) this.synchronized {
- updateTranslation()
- cachedInverseTranslation(value.ordinal)
- }
- else null
+ override def toGlobal(value: EnumFacing) = if (value == null) null else RotationHelper.toGlobal(pitch, yaw, value)
def validFacings = Array(EnumFacing.NORTH, EnumFacing.SOUTH, EnumFacing.WEST, EnumFacing.EAST)
@@ -177,16 +110,9 @@ trait Rotatable extends RotationAware with internal.Rotatable {
// ----------------------------------------------------------------------- //
/** Updates cached translation array and sends notification to clients. */
- protected def updateTranslation(): Unit = if (cacheDirty) {
- cacheDirty = false
-
- val newTranslation = translations(pitch.ordinal)(yaw.ordinal - 2)
- if (cachedTranslation != newTranslation) {
- cachedTranslation = newTranslation
- cachedInverseTranslation = invert(cachedTranslation)
- if (world != null) {
- onRotationChanged()
- }
+ protected def updateTranslation(): Unit = {
+ if (world != null) {
+ onRotationChanged()
}
}
@@ -196,7 +122,6 @@ trait Rotatable extends RotationAware with internal.Rotatable {
def setState(newState: IBlockState): Boolean = {
if (oldState.hashCode() != newState.hashCode()) {
world.setBlockState(getPos, newState)
- cacheDirty = true
true
}
else false
@@ -209,7 +134,4 @@ trait Rotatable extends RotationAware with internal.Rotatable {
case _ => false
}
}
-
- private def invert(t: Array[EnumFacing]) =
- t.indices.map(i => EnumFacing.getFront(t.indexOf(EnumFacing.getFront(i)))).toArray
}
diff --git a/src/main/scala/li/cil/oc/common/tileentity/traits/RotatableTile.scala b/src/main/scala/li/cil/oc/common/tileentity/traits/RotatableTile.scala
index 68ccc1773..aae9b9da0 100644
--- a/src/main/scala/li/cil/oc/common/tileentity/traits/RotatableTile.scala
+++ b/src/main/scala/li/cil/oc/common/tileentity/traits/RotatableTile.scala
@@ -86,7 +86,6 @@ trait RotatableTile extends Rotatable {
_yaw = yaw
}
if (changed) {
- cacheDirty = true
updateTranslation()
}
changed
diff --git a/src/main/scala/li/cil/oc/common/tileentity/traits/SwitchLike.scala b/src/main/scala/li/cil/oc/common/tileentity/traits/SwitchLike.scala
new file mode 100644
index 000000000..9b14c128a
--- /dev/null
+++ b/src/main/scala/li/cil/oc/common/tileentity/traits/SwitchLike.scala
@@ -0,0 +1,27 @@
+package li.cil.oc.common.tileentity.traits
+
+import li.cil.oc.server.PacketSender
+
+import scala.collection.mutable
+
+trait SwitchLike extends Hub {
+ def relayDelay: Int
+
+ def isWirelessEnabled: Boolean
+
+ def isLinkedEnabled: Boolean
+
+ val computers = mutable.Buffer.empty[AnyRef]
+
+ val openPorts = mutable.Map.empty[AnyRef, mutable.Set[Int]]
+
+ var lastMessage = 0L
+
+ def onSwitchActivity(): Unit = {
+ val now = System.currentTimeMillis()
+ if (now - lastMessage >= (relayDelay - 1) * 50) {
+ lastMessage = now
+ PacketSender.sendSwitchActivity(this)
+ }
+ }
+}
diff --git a/src/main/scala/li/cil/oc/integration/Mods.scala b/src/main/scala/li/cil/oc/integration/Mods.scala
index b401e5492..292770682 100644
--- a/src/main/scala/li/cil/oc/integration/Mods.scala
+++ b/src/main/scala/li/cil/oc/integration/Mods.scala
@@ -19,8 +19,10 @@ object Mods {
def All = knownMods.clone()
+ val AgriCraft = new SimpleMod(IDs.AgriCraft, version = "@[1.4.0,)")
val AppliedEnergistics2 = new SimpleMod(IDs.AppliedEnergistics2, version = "@[rv1,)", providesPower = true)
val BattleGear2 = new SimpleMod(IDs.BattleGear2)
+ val BetterRecords = new SimpleMod(IDs.BetterRecords)
val BloodMagic = new SimpleMod(IDs.BloodMagic)
val BluePower = new SimpleMod(IDs.BluePower, version = "@[0.2.928,)")
val BuildCraft = new SimpleMod(IDs.BuildCraft)
@@ -39,7 +41,7 @@ object Mods {
val ElectricalAge = new SimpleMod(IDs.ElectricalAge, providesPower = true)
val EnderIO = new SimpleMod(IDs.EnderIO)
val EnderStorage = new SimpleMod(IDs.EnderStorage)
- val ExtraCells = new SimpleMod(IDs.ExtraCells, version = "@[2.2.73,)")
+ val ExtraCells = new SimpleMod(IDs.ExtraCells, version = "@[2.2.73,)")
val Factorization = new SimpleMod(IDs.Factorization, providesPower = true)
val Forestry = new SimpleMod(IDs.Forestry)
val ForgeMultipart = new SimpleMod(IDs.ForgeMultipart)
@@ -84,7 +86,9 @@ object Mods {
// ----------------------------------------------------------------------- //
val Proxies = Array(
+ // integration.agricraft.ModAgriCraft,
// integration.appeng.ModAppEng,
+ // integration.betterrecords.ModBetterRecords,
// integration.bloodmagic.ModBloodMagic,
// integration.bluepower.ModBluePower,
// integration.buildcraft.library.ModBuildCraftAPILibrary,
@@ -155,8 +159,10 @@ object Mods {
// ----------------------------------------------------------------------- //
object IDs {
+ final val AgriCraft = "AgriCraft"
final val AppliedEnergistics2 = "appliedenergistics2"
final val BattleGear2 = "battlegear2"
+ final val BetterRecords = "betterrecords"
final val BloodMagic = "AWWayofTime"
final val BluePower = "bluepowerAPI"
final val BuildCraft = "BuildCraft|Core"
diff --git a/src/main/scala/li/cil/oc/integration/agricraft/ApiHandler.scala b/src/main/scala/li/cil/oc/integration/agricraft/ApiHandler.scala
new file mode 100644
index 000000000..03d5b3bc3
--- /dev/null
+++ b/src/main/scala/li/cil/oc/integration/agricraft/ApiHandler.scala
@@ -0,0 +1,17 @@
+package li.cil.oc.integration.agricraft
+
+import com.InfinityRaider.AgriCraft
+
+object ApiHandler {
+ lazy val Api = AgriCraft.api.API.getAPI(1) match {
+ case api: AgriCraft.api.v1.APIv1 if isApiUsable(api) => Option(api)
+ case _ => None
+ }
+
+ private def isApiUsable(api: AgriCraft.api.APIBase) = {
+ val status = api.getStatus
+ status == AgriCraft.api.APIStatus.OK ||
+ status == AgriCraft.api.APIStatus.BACKLEVEL_OK ||
+ status == AgriCraft.api.APIStatus.BACKLEVEL_LIMITED
+ }
+}
diff --git a/src/main/scala/li/cil/oc/integration/agricraft/ConverterSeeds.scala b/src/main/scala/li/cil/oc/integration/agricraft/ConverterSeeds.scala
new file mode 100644
index 000000000..e6b4c5a4d
--- /dev/null
+++ b/src/main/scala/li/cil/oc/integration/agricraft/ConverterSeeds.scala
@@ -0,0 +1,31 @@
+package li.cil.oc.integration.agricraft
+
+import java.util
+
+import com.InfinityRaider.AgriCraft.api.v1.ISeedStats
+import li.cil.oc.api.driver.Converter
+import net.minecraft.item.ItemStack
+
+import scala.collection.convert.WrapAsScala._
+
+object ConverterSeeds extends Converter {
+ override def convert(value: scala.Any, output: util.Map[AnyRef, AnyRef]): Unit = {
+ value match {
+ case stack: ItemStack => ApiHandler.Api.foreach(api => {
+ if (api.isHandledByAgricraft(stack) && stack.hasTagCompound && stack.getTagCompound.getBoolean("analyzed")) api.getSeedStats(stack) match {
+ case stats: ISeedStats =>
+ output += "agricraft" -> Map(
+ "gain" -> float2Float(stats.getGain),
+ "maxGain" -> float2Float(stats.getMaxGain),
+ "growth" -> float2Float(stats.getGrowth),
+ "maxGrowth" -> float2Float(stats.getMaxGrowth),
+ "strength" -> float2Float(stats.getStrength),
+ "maxStrength" -> float2Float(stats.getMaxStrength)
+ )
+ case _ =>
+ }
+ })
+ case _ =>
+ }
+ }
+}
diff --git a/src/main/scala/li/cil/oc/integration/agricraft/EventHandlerAgriCraft.scala b/src/main/scala/li/cil/oc/integration/agricraft/EventHandlerAgriCraft.scala
new file mode 100644
index 000000000..5dc941772
--- /dev/null
+++ b/src/main/scala/li/cil/oc/integration/agricraft/EventHandlerAgriCraft.scala
@@ -0,0 +1,31 @@
+package li.cil.oc.integration.agricraft
+
+import com.InfinityRaider.AgriCraft.api.v1.ISeedStats
+import cpw.mods.fml.common.eventhandler.SubscribeEvent
+import li.cil.oc.api.event.GeolyzerEvent
+
+import scala.collection.convert.WrapAsScala._
+
+object EventHandlerAgriCraft {
+ @SubscribeEvent
+ def onGeolyzerAnalyze(e: GeolyzerEvent.Analyze) {
+ val world = e.host.world
+
+ ApiHandler.Api.foreach(api => if (api.isCrops(world, e.x, e.y, e.z)) {
+ e.data += "growth" -> float2Float(if (api.isMature(world, e.x, e.y, e.z)) 1f else 0f)
+
+ if (api.isAnalyzed(world, e.x, e.y, e.z)) {
+ api.getStats(world, e.x, e.y, e.z) match {
+ case stats: ISeedStats =>
+ e.data += "gain" -> float2Float(stats.getGain)
+ e.data += "maxGain" -> float2Float(stats.getMaxGain)
+ e.data += "growth" -> float2Float(stats.getGrowth)
+ e.data += "maxGrowth" -> float2Float(stats.getMaxGrowth)
+ e.data += "strength" -> float2Float(stats.getStrength)
+ e.data += "maxStrength" -> float2Float(stats.getMaxStrength)
+ case _ => // Invalid crop.
+ }
+ }
+ })
+ }
+}
diff --git a/src/main/scala/li/cil/oc/integration/agricraft/ModAgriCraft.scala b/src/main/scala/li/cil/oc/integration/agricraft/ModAgriCraft.scala
new file mode 100644
index 000000000..f3325428f
--- /dev/null
+++ b/src/main/scala/li/cil/oc/integration/agricraft/ModAgriCraft.scala
@@ -0,0 +1,17 @@
+package li.cil.oc.integration.agricraft
+
+import li.cil.oc.api.Driver
+import li.cil.oc.integration.Mod
+import li.cil.oc.integration.ModProxy
+import li.cil.oc.integration.Mods
+import net.minecraftforge.common.MinecraftForge
+
+object ModAgriCraft extends ModProxy {
+ override def getMod: Mod = Mods.AgriCraft
+
+ override def initialize(): Unit = {
+ Driver.add(ConverterSeeds)
+
+ MinecraftForge.EVENT_BUS.register(EventHandlerAgriCraft)
+ }
+}
diff --git a/src/main/scala/li/cil/oc/integration/betterrecords/ConverterRecord.scala b/src/main/scala/li/cil/oc/integration/betterrecords/ConverterRecord.scala
new file mode 100644
index 000000000..ad145e330
--- /dev/null
+++ b/src/main/scala/li/cil/oc/integration/betterrecords/ConverterRecord.scala
@@ -0,0 +1,40 @@
+package li.cil.oc.integration.betterrecords
+
+import java.util
+
+import li.cil.oc.api.driver.Converter
+import li.cil.oc.util.ExtendedNBT._
+import net.minecraft.item.ItemStack
+import net.minecraft.nbt.NBTTagCompound
+import net.minecraftforge.common.util.Constants.NBT
+
+import scala.collection.convert.WrapAsScala._
+
+object ConverterRecord extends Converter {
+ final val UrlRecordClassName = "com.codingforcookies.betterrecords.src.items.ItemURLRecord"
+ final val UrlMultiRecordClassName = "com.codingforcookies.betterrecords.src.items.ItemURLMultiRecord"
+ final val FreqCrystalClassName = "com.codingforcookies.betterrecords.src.items.ItemFreqCrystal"
+
+ override def convert(value: scala.Any, output: util.Map[AnyRef, AnyRef]): Unit = value match {
+ case stack: ItemStack if stack.getItem != null && stack.getItem.getClass.getName == UrlRecordClassName && stack.hasTagCompound =>
+ convertRecord(stack.getTagCompound, output)
+ case stack: ItemStack if stack.getItem != null && stack.getItem.getClass.getName == UrlMultiRecordClassName && stack.hasTagCompound =>
+ output += "songs" -> stack.getTagCompound.getTagList("songs", NBT.TAG_COMPOUND).map((nbt: NBTTagCompound) => convertRecord(nbt, new util.HashMap[AnyRef, AnyRef]()))
+ case stack: ItemStack if stack.getItem != null && stack.getItem.getClass.getName == FreqCrystalClassName && stack.hasTagCompound =>
+ convertRecord(stack.getTagCompound, output)
+ case _ =>
+ }
+
+ private def convertRecord(nbt: NBTTagCompound, output: util.Map[AnyRef, AnyRef]) = {
+ if (nbt.hasKey("url", NBT.TAG_STRING))
+ output += "url" -> nbt.getString("url")
+ if (nbt.hasKey("name", NBT.TAG_STRING))
+ output += "filename" -> nbt.getString("name")
+ if (nbt.hasKey("author", NBT.TAG_STRING))
+ output += "author" -> nbt.getString("author")
+ if (nbt.hasKey("local", NBT.TAG_STRING))
+ output += "title" -> nbt.getString("local")
+
+ output
+ }
+}
diff --git a/src/main/scala/li/cil/oc/integration/betterrecords/ModBetterRecords.scala b/src/main/scala/li/cil/oc/integration/betterrecords/ModBetterRecords.scala
new file mode 100644
index 000000000..258b557af
--- /dev/null
+++ b/src/main/scala/li/cil/oc/integration/betterrecords/ModBetterRecords.scala
@@ -0,0 +1,13 @@
+package li.cil.oc.integration.betterrecords
+
+import li.cil.oc.api.Driver
+import li.cil.oc.integration.ModProxy
+import li.cil.oc.integration.Mods
+
+object ModBetterRecords extends ModProxy {
+ override def getMod = Mods.BetterRecords
+
+ override def initialize() {
+ Driver.add(ConverterRecord)
+ }
+}
diff --git a/src/main/scala/li/cil/oc/integration/computercraft/PeripheralProvider.scala b/src/main/scala/li/cil/oc/integration/computercraft/PeripheralProvider.scala
index b2e54e940..5f17cf9df 100644
--- a/src/main/scala/li/cil/oc/integration/computercraft/PeripheralProvider.scala
+++ b/src/main/scala/li/cil/oc/integration/computercraft/PeripheralProvider.scala
@@ -2,7 +2,7 @@ package li.cil.oc.integration.computercraft
import dan200.computercraft.api.ComputerCraftAPI
import dan200.computercraft.api.peripheral.IPeripheralProvider
-import li.cil.oc.common.tileentity.Switch
+import li.cil.oc.common.tileentity.traits.SwitchLike
import net.minecraft.world.World
object PeripheralProvider extends IPeripheralProvider {
@@ -11,7 +11,7 @@ object PeripheralProvider extends IPeripheralProvider {
}
override def getPeripheral(world: World, x: Int, y: Int, z: Int, side: Int) = world.getTileEntity(x, y, z) match {
- case switch: Switch => new SwitchPeripheral(switch)
+ case switch: SwitchLike => new SwitchPeripheral(switch)
case _ => null
}
}
diff --git a/src/main/scala/li/cil/oc/integration/computercraft/SwitchPeripheral.scala b/src/main/scala/li/cil/oc/integration/computercraft/SwitchPeripheral.scala
index 804b6bb47..234e93e4b 100644
--- a/src/main/scala/li/cil/oc/integration/computercraft/SwitchPeripheral.scala
+++ b/src/main/scala/li/cil/oc/integration/computercraft/SwitchPeripheral.scala
@@ -8,8 +8,7 @@ import li.cil.oc.Settings
import li.cil.oc.api
import li.cil.oc.api.machine.Context
import li.cil.oc.api.network.Component
-import li.cil.oc.common.tileentity.AccessPoint
-import li.cil.oc.common.tileentity.Switch
+import li.cil.oc.common.tileentity.traits.SwitchLike
import li.cil.oc.util.ResultWrapper._
import net.minecraftforge.common.util.ForgeDirection
@@ -17,7 +16,7 @@ import scala.collection.convert.WrapAsJava._
import scala.collection.convert.WrapAsScala._
import scala.collection.mutable
-class SwitchPeripheral(val switch: Switch) extends IPeripheral {
+class SwitchPeripheral(val switch: SwitchLike) extends IPeripheral {
private val methods = Map[String, (IComputerAccess, ILuaContext, Array[AnyRef]) => Array[AnyRef]](
// Generic modem methods.
"open" -> ((computer, context, arguments) => {
@@ -86,7 +85,10 @@ class SwitchPeripheral(val switch: Switch) extends IPeripheral {
// OC specific.
"isAccessPoint" -> ((computer, context, arguments) => {
- result(switch.isInstanceOf[AccessPoint])
+ result(switch.isWirelessEnabled)
+ }),
+ "isTunnel" -> ((computer, context, arguments) => {
+ result(switch.isLinkedEnabled)
}),
"maxPacketSize" -> ((computer, context, arguments) => {
result(Settings.get.maxNetworkPacketSize)
diff --git a/src/main/scala/li/cil/oc/integration/opencomputers/DriverCPU.scala b/src/main/scala/li/cil/oc/integration/opencomputers/DriverCPU.scala
index 14db12664..62e8557d3 100644
--- a/src/main/scala/li/cil/oc/integration/opencomputers/DriverCPU.scala
+++ b/src/main/scala/li/cil/oc/integration/opencomputers/DriverCPU.scala
@@ -5,7 +5,7 @@ import li.cil.oc.OpenComputers
import li.cil.oc.Settings
import li.cil.oc.api
import li.cil.oc.api.driver.EnvironmentHost
-import li.cil.oc.api.driver.item.Processor
+import li.cil.oc.api.driver.item.MutableProcessor
import li.cil.oc.api.machine.Architecture
import li.cil.oc.api.network.ManagedEnvironment
import li.cil.oc.common.Slot
@@ -14,12 +14,14 @@ import li.cil.oc.common.item
import li.cil.oc.common.item.Delegator
import li.cil.oc.server.machine.luac.NativeLuaArchitecture
import net.minecraft.item.ItemStack
+import net.minecraft.nbt.NBTTagCompound
+import scala.collection.convert.WrapAsJava._
import scala.collection.convert.WrapAsScala._
object DriverCPU extends DriverCPU
-abstract class DriverCPU extends Item with Processor {
+abstract class DriverCPU extends Item with MutableProcessor {
override def worksWith(stack: ItemStack) = isOneOf(stack,
api.Items.get(Constants.ItemName.CPUTier1),
api.Items.get(Constants.ItemName.CPUTier2),
@@ -39,6 +41,8 @@ abstract class DriverCPU extends Item with Processor {
override def supportedComponents(stack: ItemStack) = Settings.get.cpuComponentSupport(cpuTier(stack))
+ override def allArchitectures = api.Machine.architectures.toList
+
override def architecture(stack: ItemStack): Class[_ <: Architecture] = {
if (stack.hasTagCompound) {
val archClass = stack.getTagCompound.getString(Settings.namespace + "archClass") match {
@@ -57,4 +61,18 @@ abstract class DriverCPU extends Item with Processor {
}
api.Machine.architectures.headOption.orNull
}
+
+ override def setArchitecture(stack: ItemStack, architecture: Class[_ <: Architecture]): Unit = {
+ if (!worksWith(stack)) throw new IllegalArgumentException("Unsupported processor type.")
+ if (!stack.hasTagCompound) stack.setTagCompound(new NBTTagCompound())
+ stack.getTagCompound.setString(Settings.namespace + "archClass", architecture.getName)
+ stack.getTagCompound.setString(Settings.namespace + "archName", getArchitectureName(architecture))
+ }
+
+ // TODO Move to Machine API in 1.6
+ def getArchitectureName(architecture: Class[_ <: Architecture]) =
+ architecture.getAnnotation(classOf[Architecture.Name]) match {
+ case annotation: Architecture.Name => annotation.value
+ case _ => architecture.getSimpleName
+ }
}
diff --git a/src/main/scala/li/cil/oc/integration/opencomputers/DriverUpgradePiston.scala b/src/main/scala/li/cil/oc/integration/opencomputers/DriverUpgradePiston.scala
index 3ffb00733..b58201993 100644
--- a/src/main/scala/li/cil/oc/integration/opencomputers/DriverUpgradePiston.scala
+++ b/src/main/scala/li/cil/oc/integration/opencomputers/DriverUpgradePiston.scala
@@ -5,7 +5,7 @@ import li.cil.oc.api
import li.cil.oc.api.driver.EnvironmentAware
import li.cil.oc.api.driver.EnvironmentHost
import li.cil.oc.api.driver.item.HostAware
-import li.cil.oc.api.internal.Rotatable
+import li.cil.oc.api.internal
import li.cil.oc.common.Slot
import li.cil.oc.server.component
import net.minecraft.item.ItemStack
@@ -15,7 +15,9 @@ object DriverUpgradePiston extends Item with HostAware with EnvironmentAware {
api.Items.get(Constants.ItemName.PistonUpgrade))
override def createEnvironment(stack: ItemStack, host: EnvironmentHost) = host match {
- case rotatable: Rotatable with EnvironmentHost => new component.UpgradePiston(rotatable)
+ case host: internal.Drone => new component.UpgradePiston.Drone(host)
+ case host: internal.Tablet => new component.UpgradePiston.Tablet(host)
+ case host: internal.Rotatable with EnvironmentHost => new component.UpgradePiston.Rotatable(host)
case _ => null
}
diff --git a/src/main/scala/li/cil/oc/integration/vanilla/EventHandlerVanilla.scala b/src/main/scala/li/cil/oc/integration/vanilla/EventHandlerVanilla.scala
new file mode 100644
index 000000000..60ff823af
--- /dev/null
+++ b/src/main/scala/li/cil/oc/integration/vanilla/EventHandlerVanilla.scala
@@ -0,0 +1,29 @@
+package li.cil.oc.integration.vanilla
+
+import li.cil.oc.api.event.GeolyzerEvent
+import net.minecraft.block.BlockCrops
+import net.minecraft.init.Blocks
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+import scala.collection.convert.WrapAsScala._
+
+object EventHandlerVanilla {
+ @SubscribeEvent
+ def onGeolyzerAnalyze(e: GeolyzerEvent.Analyze) {
+ val world = e.host.world
+ val blockState = world.getBlockState(e.pos)
+ val block = blockState.getBlock
+ if (block.isInstanceOf[BlockCrops] || block == Blocks.melon_stem || block == Blocks.pumpkin_stem || block == Blocks.carrots || block == Blocks.potatoes) {
+ e.data += "growth" -> float2Float((block.getMetaFromState(blockState) / 7f) max 0 min 1)
+ }
+ if (block == Blocks.cocoa) {
+ e.data += "growth" -> float2Float(((block.getMetaFromState(blockState) >> 2) / 2f) max 0 min 1)
+ }
+ if (block == Blocks.nether_wart) {
+ e.data += "growth" -> float2Float((block.getMetaFromState(blockState) / 3f) max 0 min 1)
+ }
+ if (block == Blocks.melon_block || block == Blocks.pumpkin || block == Blocks.cactus || block == Blocks.reeds) {
+ e.data += "growth" -> float2Float(1f)
+ }
+ }
+}
diff --git a/src/main/scala/li/cil/oc/integration/vanilla/ModVanilla.scala b/src/main/scala/li/cil/oc/integration/vanilla/ModVanilla.scala
index 5b7b97b5f..c95903413 100644
--- a/src/main/scala/li/cil/oc/integration/vanilla/ModVanilla.scala
+++ b/src/main/scala/li/cil/oc/integration/vanilla/ModVanilla.scala
@@ -11,6 +11,7 @@ import li.cil.oc.util.ExtendedWorld._
import net.minecraft.block.BlockRedstoneWire
import net.minecraft.init.Blocks
import net.minecraft.util.EnumFacing
+import net.minecraftforge.common.MinecraftForge
object ModVanilla extends ModProxy with RedstoneProvider {
def getMod = Mods.Minecraft
@@ -45,6 +46,8 @@ object ModVanilla extends ModProxy with RedstoneProvider {
RecipeHandler.init()
BundledRedstone.addProvider(this)
+
+ MinecraftForge.EVENT_BUS.register(EventHandlerVanilla)
}
override def computeInput(pos: BlockPosition, side: EnumFacing): Int = {
diff --git a/src/main/scala/li/cil/oc/server/PacketSender.scala b/src/main/scala/li/cil/oc/server/PacketSender.scala
index d710bcd5f..9975e0deb 100644
--- a/src/main/scala/li/cil/oc/server/PacketSender.scala
+++ b/src/main/scala/li/cil/oc/server/PacketSender.scala
@@ -227,6 +227,16 @@ object PacketSender {
}
}
+ def sendNetSplitterState(t: tileentity.NetSplitter): Unit = {
+ val pb = new SimplePacketBuilder(PacketType.NetSplitterState)
+
+ pb.writeTileEntity(t)
+ pb.writeBoolean(t.isInverted)
+ pb.writeByte(t.compressSides)
+
+ pb.sendToPlayersNearTileEntity(t)
+ }
+
def sendParticleEffect(position: BlockPosition, particleType: EnumParticleTypes, count: Int, velocity: Double, direction: Option[EnumFacing] = None): Unit = if (count > 0) {
val pb = new SimplePacketBuilder(PacketType.ParticleEffect)
@@ -385,7 +395,7 @@ object PacketSender {
pb.sendToPlayersNearTileEntity(t)
}
- def sendSwitchActivity(t: tileentity.Switch) {
+ def sendSwitchActivity(t: tileentity.traits.SwitchLike) {
val pb = new SimplePacketBuilder(PacketType.SwitchActivity)
pb.writeTileEntity(t)
@@ -522,16 +532,6 @@ object PacketSender {
pb.sendToPlayersNearHost(host)
}
- def sendNetSplitterState(t: tileentity.NetSplitter): Unit = {
- val pb = new SimplePacketBuilder(PacketType.NetSplitterState)
-
- pb.writeTileEntity(t)
- pb.writeBoolean(t.isInverted)
- pb.writeByte(t.compressSides)
-
- pb.sendToPlayersNearTileEntity(t)
- }
-
def sendScreenTouchMode(t: tileentity.Screen, value: Boolean) {
val pb = new SimplePacketBuilder(PacketType.ScreenTouchMode)
@@ -612,6 +612,14 @@ object PacketSender {
pb.sendToNearbyPlayers(world, x, y, z, Option(32))
}
+ def sendTransposerActivity(t: tileentity.Transposer) {
+ val pb = new SimplePacketBuilder(PacketType.TransposerActivity)
+
+ pb.writeTileEntity(t)
+
+ pb.sendToPlayersNearTileEntity(t, Option(32))
+ }
+
def sendWaypointLabel(t: Waypoint): Unit = {
val pb = new SimplePacketBuilder(PacketType.WaypointLabel)
diff --git a/src/main/scala/li/cil/oc/server/component/GraphicsCard.scala b/src/main/scala/li/cil/oc/server/component/GraphicsCard.scala
index 4ad804a3b..e90de08bb 100644
--- a/src/main/scala/li/cil/oc/server/component/GraphicsCard.scala
+++ b/src/main/scala/li/cil/oc/server/component/GraphicsCard.scala
@@ -55,22 +55,26 @@ abstract class GraphicsCard extends prefab.ManagedEnvironment {
}
}
- @Callback(doc = """function(address:string):boolean -- Binds the GPU to the screen with the specified address.""")
+ @Callback(doc = """function(address:string[, reset:boolean=true]):boolean -- Binds the GPU to the screen with the specified address and resets screen settings if `reset` is true.""")
def bind(context: Context, args: Arguments): Array[AnyRef] = {
val address = args.checkString(0)
+ val reset = args.optBoolean(1, true)
node.network.node(address) match {
case null => result(Unit, "invalid address")
case node: Node if node.host.isInstanceOf[TextBuffer] =>
screenAddress = Option(address)
screenInstance = Some(node.host.asInstanceOf[TextBuffer])
screen(s => {
- val (gmw, gmh) = maxResolution
- val smw = s.getMaximumWidth
- val smh = s.getMaximumHeight
- s.setResolution(math.min(gmw, smw), math.min(gmh, smh))
- s.setColorDepth(ColorDepth.values.apply(math.min(maxDepth.ordinal, s.getMaximumColorDepth.ordinal)))
- s.setForegroundColor(0xFFFFFF)
- s.setBackgroundColor(0x000000)
+ if (reset) {
+ val (gmw, gmh) = maxResolution
+ val smw = s.getMaximumWidth
+ val smh = s.getMaximumHeight
+ s.setResolution(math.min(gmw, smw), math.min(gmh, smh))
+ s.setColorDepth(ColorDepth.values.apply(math.min(maxDepth.ordinal, s.getMaximumColorDepth.ordinal)))
+ s.setForegroundColor(0xFFFFFF)
+ s.setBackgroundColor(0x000000)
+ }
+ else context.pause(0.2) // To discourage outputting "in realtime" to multiple screens using one GPU.
result(true)
})
case _ => result(Unit, "not a screen")
diff --git a/src/main/scala/li/cil/oc/server/component/LinkedCard.scala b/src/main/scala/li/cil/oc/server/component/LinkedCard.scala
index 3d3c27ad5..980609515 100644
--- a/src/main/scala/li/cil/oc/server/component/LinkedCard.scala
+++ b/src/main/scala/li/cil/oc/server/component/LinkedCard.scala
@@ -12,7 +12,7 @@ import net.minecraft.nbt.NBTTagCompound
import scala.collection.convert.WrapAsScala._
-class LinkedCard extends prefab.ManagedEnvironment {
+class LinkedCard extends prefab.ManagedEnvironment with QuantumNetwork.QuantumNode {
override val node = Network.newNode(this, Visibility.Network).
withComponent("tunnel", Visibility.Neighbors).
withConnector().
@@ -28,8 +28,8 @@ class LinkedCard extends prefab.ManagedEnvironment {
// Cast to iterable to use Scala's toArray instead of the Arguments' one (which converts byte arrays to Strings).
val packet = Network.newPacket(node.address, null, 0, args.asInstanceOf[java.lang.Iterable[AnyRef]].toArray)
if (node.tryChangeBuffer(-(packet.size / 32.0 + Settings.get.wirelessCostPerRange * Settings.get.maxWirelessRange * 5))) {
- for (card <- endpoints) {
- card.receivePacket(packet)
+ for (endpoint <- endpoints) {
+ endpoint.receivePacket(packet)
}
result(true)
}
diff --git a/src/main/scala/li/cil/oc/server/component/NetworkCard.scala b/src/main/scala/li/cil/oc/server/component/NetworkCard.scala
index 111e76ff2..a62f92b10 100644
--- a/src/main/scala/li/cil/oc/server/component/NetworkCard.scala
+++ b/src/main/scala/li/cil/oc/server/component/NetworkCard.scala
@@ -24,6 +24,8 @@ class NetworkCard(val host: EnvironmentHost) extends prefab.ManagedEnvironment {
protected var wakeMessage: Option[String] = None
+ protected var wakeMessageFuzzy = false
+
// ----------------------------------------------------------------------- //
@Callback(doc = """function(port:number):boolean -- Opens the specified port. Returns true if the port was opened.""")
@@ -39,7 +41,7 @@ class NetworkCard(val host: EnvironmentHost) extends prefab.ManagedEnvironment {
@Callback(doc = """function([port:number]):boolean -- Closes the specified port (default: all ports). Returns true if ports were closed.""")
def close(context: Context, args: Arguments): Array[AnyRef] = {
if (args.count == 0) {
- val closed = openPorts.size > 0
+ val closed = openPorts.nonEmpty
openPorts.clear()
result(closed)
}
@@ -78,17 +80,21 @@ class NetworkCard(val host: EnvironmentHost) extends prefab.ManagedEnvironment {
@Callback(direct = true, doc = """function():number -- Gets the maximum packet size (config setting).""")
def maxPacketSize(context: Context, args: Arguments): Array[AnyRef] = result(Settings.get.maxNetworkPacketSize)
- @Callback(direct = true, doc = """function():string -- Get the current wake-up message.""")
- def getWakeMessage(context: Context, args: Arguments): Array[AnyRef] = result(wakeMessage.orNull)
+ @Callback(direct = true, doc = """function():string, boolean -- Get the current wake-up message.""")
+ def getWakeMessage(context: Context, args: Arguments): Array[AnyRef] = result(wakeMessage.orNull, wakeMessageFuzzy)
- @Callback(doc = """function(message:string):string -- Set the wake-up message.""")
+ @Callback(doc = """function(message:string[, fuzzy:boolean]):string -- Set the wake-up message and whether to ignore additional data/parameters.""")
def setWakeMessage(context: Context, args: Arguments): Array[AnyRef] = {
val oldMessage = wakeMessage
+ val oldFuzzy = wakeMessageFuzzy
+
if (args.optAny(0, null) == null)
wakeMessage = None
else
wakeMessage = Option(args.checkString(0))
- result(oldMessage.orNull)
+ wakeMessageFuzzy = args.optBoolean(1, wakeMessageFuzzy)
+
+ result(oldMessage.orNull, oldFuzzy)
}
protected def doSend(packet: Packet) {
@@ -136,6 +142,10 @@ class NetworkCard(val host: EnvironmentHost) extends prefab.ManagedEnvironment {
node.sendToNeighbors("computer.start")
case Array(message: String) if wakeMessage.contains(message) =>
node.sendToNeighbors("computer.start")
+ case Array(message: Array[Byte], _*) if wakeMessageFuzzy && wakeMessage.contains(new String(message, Charsets.UTF_8)) =>
+ node.sendToNeighbors("computer.start")
+ case Array(message: String, _*) if wakeMessageFuzzy && wakeMessage.contains(message) =>
+ node.sendToNeighbors("computer.start")
case _ =>
}
}
@@ -151,6 +161,7 @@ class NetworkCard(val host: EnvironmentHost) extends prefab.ManagedEnvironment {
if (nbt.hasKey("wakeMessage")) {
wakeMessage = Option(nbt.getString("wakeMessage"))
}
+ wakeMessageFuzzy = nbt.getBoolean("wakeMessageFuzzy")
}
override def save(nbt: NBTTagCompound) {
@@ -158,6 +169,7 @@ class NetworkCard(val host: EnvironmentHost) extends prefab.ManagedEnvironment {
nbt.setIntArray("openPorts", openPorts.toArray)
wakeMessage.foreach(nbt.setString("wakeMessage", _))
+ nbt.setBoolean("wakeMessageFuzzy", wakeMessageFuzzy)
}
// ----------------------------------------------------------------------- //
diff --git a/src/main/scala/li/cil/oc/server/component/RedstoneBundled.scala b/src/main/scala/li/cil/oc/server/component/RedstoneBundled.scala
index bfa7120cb..fd6f6ecac 100644
--- a/src/main/scala/li/cil/oc/server/component/RedstoneBundled.scala
+++ b/src/main/scala/li/cil/oc/server/component/RedstoneBundled.scala
@@ -1,5 +1,6 @@
package li.cil.oc.server.component
+import li.cil.oc.Settings
import li.cil.oc.api.driver.EnvironmentHost
import li.cil.oc.api.machine.Arguments
import li.cil.oc.api.machine.Callback
@@ -36,14 +37,16 @@ trait RedstoneBundled extends RedstoneVanilla {
case (color, number: Number) => redstone.bundledOutput(side, color, number.intValue())
case _ =>
}
- context.pause(0.1)
+ if (Settings.get.redstoneDelay > 0)
+ context.pause(Settings.get.redstoneDelay)
result(true)
}
else {
val color = checkColor(args, 1)
val value = args.checkInteger(2)
redstone.bundledOutput(side, color, value)
- context.pause(0.1)
+ if (Settings.get.redstoneDelay > 0)
+ context.pause(Settings.get.redstoneDelay)
result(redstone.bundledOutput(side, color))
}
}
diff --git a/src/main/scala/li/cil/oc/server/component/RedstoneVanilla.scala b/src/main/scala/li/cil/oc/server/component/RedstoneVanilla.scala
index f9de50dca..f5721494f 100644
--- a/src/main/scala/li/cil/oc/server/component/RedstoneVanilla.scala
+++ b/src/main/scala/li/cil/oc/server/component/RedstoneVanilla.scala
@@ -1,5 +1,6 @@
package li.cil.oc.server.component
+import li.cil.oc.Settings
import li.cil.oc.api.driver.EnvironmentHost
import li.cil.oc.api.machine.Arguments
import li.cil.oc.api.machine.Callback
@@ -30,7 +31,8 @@ trait RedstoneVanilla extends RedstoneSignaller {
val side = checkSide(args, 0)
val value = args.checkInteger(1)
redstone.output(side, value)
- context.pause(0.1)
+ if (Settings.get.redstoneDelay > 0)
+ context.pause(Settings.get.redstoneDelay)
result(redstone.output(side))
}
diff --git a/src/main/scala/li/cil/oc/server/component/RedstoneWireless.scala b/src/main/scala/li/cil/oc/server/component/RedstoneWireless.scala
index 04760308a..5337c6869 100644
--- a/src/main/scala/li/cil/oc/server/component/RedstoneWireless.scala
+++ b/src/main/scala/li/cil/oc/server/component/RedstoneWireless.scala
@@ -6,6 +6,7 @@ import codechicken.wirelessredstone.core.WirelessReceivingDevice
import codechicken.wirelessredstone.core.WirelessTransmittingDevice
*/
+import li.cil.oc.Settings
import li.cil.oc.api.driver.EnvironmentHost
import li.cil.oc.api.machine.Arguments
import li.cil.oc.api.machine.Callback
@@ -44,11 +45,17 @@ trait RedstoneWireless extends RedstoneSignaller /* with WirelessReceivingDevice
@Callback(doc = """function(value:boolean):boolean -- Set the wireless redstone output.""")
def setWirelessOutput(context: Context, args: Arguments): Array[AnyRef] = {
val oldValue = wirelessOutput
- wirelessOutput = args.checkBoolean(0)
+ val newValue = args.checkBoolean(0)
- util.WirelessRedstone.updateOutput(this)
+ if (oldValue != newValue) {
+ wirelessOutput = newValue
+
+ util.WirelessRedstone.updateOutput(this)
+
+ if (Settings.get.redstoneDelay > 0)
+ context.pause(Settings.get.redstoneDelay)
+ }
- context.pause(0.1)
result(oldValue)
}
@@ -60,16 +67,19 @@ trait RedstoneWireless extends RedstoneSignaller /* with WirelessReceivingDevice
val oldValue = wirelessFrequency
val newValue = args.checkInteger(0)
- util.WirelessRedstone.removeReceiver(this)
- util.WirelessRedstone.removeTransmitter(this)
+ if (oldValue != newValue) {
+ util.WirelessRedstone.removeReceiver(this)
+ util.WirelessRedstone.removeTransmitter(this)
- wirelessFrequency = newValue
- wirelessInput = false
- wirelessOutput = false
+ wirelessFrequency = newValue
+ wirelessInput = false
+ wirelessOutput = false
- util.WirelessRedstone.addReceiver(this)
+ util.WirelessRedstone.addReceiver(this)
+
+ context.pause(0.5)
+ }
- context.pause(0.5)
result(oldValue)
}
diff --git a/src/main/scala/li/cil/oc/server/component/Transposer.scala b/src/main/scala/li/cil/oc/server/component/Transposer.scala
new file mode 100644
index 000000000..c51faeaf2
--- /dev/null
+++ b/src/main/scala/li/cil/oc/server/component/Transposer.scala
@@ -0,0 +1,69 @@
+package li.cil.oc.server.component
+
+import li.cil.oc.Settings
+import li.cil.oc.api
+import li.cil.oc.api.machine.Arguments
+import li.cil.oc.api.machine.Callback
+import li.cil.oc.api.machine.Context
+import li.cil.oc.api.network.Visibility
+import li.cil.oc.api.prefab
+import li.cil.oc.common.tileentity
+import li.cil.oc.server.{PacketSender => ServerPacketSender}
+import li.cil.oc.util.BlockPosition
+import li.cil.oc.util.ExtendedArguments._
+import li.cil.oc.util.FluidUtils
+import li.cil.oc.util.InventoryUtils
+import net.minecraft.util.EnumFacing
+
+import scala.language.existentials
+
+class Transposer(val host: tileentity.Transposer) extends prefab.ManagedEnvironment with traits.WorldInventoryAnalytics with traits.WorldTankAnalytics {
+ override val node = api.Network.newNode(this, Visibility.Network).
+ withComponent("transposer").
+ withConnector().
+ create()
+
+ override def position = BlockPosition(host)
+
+ override protected def checkSideForAction(args: Arguments, n: Int) = args.checkSide(n, EnumFacing.values: _*)
+
+ @Callback(doc = """function(sourceSide:number, sinkSide:number[, count:number[, sourceSlot:number, sinkSlot:number]]):number -- Transfer some items between two inventories.""")
+ def transferItem(context: Context, args: Arguments): Array[AnyRef] = {
+ val sourceSide = checkSideForAction(args, 0)
+ val sourcePos = position.offset(sourceSide)
+ val sinkSide = checkSideForAction(args, 1)
+ val sinkPos = position.offset(sinkSide)
+ val count = args.optItemCount(2)
+
+ if (node.tryChangeBuffer(-Settings.get.transposerCost)) {
+ ServerPacketSender.sendTransposerActivity(host)
+
+ if (args.count > 3) {
+ val sourceSlot = args.checkSlot(InventoryUtils.inventoryAt(sourcePos).getOrElse(throw new IllegalArgumentException("no inventory")), 3)
+ val sinkSlot = args.checkSlot(InventoryUtils.inventoryAt(sinkPos).getOrElse(throw new IllegalArgumentException("no inventory")), 4)
+
+ result(InventoryUtils.transferBetweenInventoriesSlotsAt(sourcePos, sourceSide.getOpposite, sourceSlot, sinkPos, Option(sinkSide.getOpposite), sinkSlot, count))
+ }
+ else result(InventoryUtils.transferBetweenInventoriesAt(sourcePos, sourceSide.getOpposite, sinkPos, Option(sinkSide.getOpposite), count))
+ }
+ else result(Unit, "not enough energy")
+ }
+
+ @Callback(doc = """function(sourceSide:number, sinkSide:number[, count:number]):number -- Transfer some items between two inventories.""")
+ def transferFluid(context: Context, args: Arguments): Array[AnyRef] = {
+ val sourceSide = checkSideForAction(args, 0)
+ val sourcePos = position.offset(sourceSide)
+ val sinkSide = checkSideForAction(args, 1)
+ val sinkPos = position.offset(sinkSide)
+ val count = args.optFluidCount(2)
+
+ if (node.tryChangeBuffer(-Settings.get.transposerCost)) {
+ ServerPacketSender.sendTransposerActivity(host)
+
+ val moved = FluidUtils.transferBetweenFluidHandlersAt(sourcePos, sourceSide.getOpposite, sinkPos, sinkSide.getOpposite, count)
+ if (moved > 0) context.pause(moved / 1000 * 0.25) // Allow up to 4 buckets per second.
+ result(moved > 0, moved)
+ }
+ else result(Unit, "not enough energy")
+ }
+}
diff --git a/src/main/scala/li/cil/oc/server/component/UpgradePiston.scala b/src/main/scala/li/cil/oc/server/component/UpgradePiston.scala
index 1b0912c12..01adf59a1 100644
--- a/src/main/scala/li/cil/oc/server/component/UpgradePiston.scala
+++ b/src/main/scala/li/cil/oc/server/component/UpgradePiston.scala
@@ -3,27 +3,34 @@ package li.cil.oc.server.component
import li.cil.oc.Settings
import li.cil.oc.api.Network
import li.cil.oc.api.driver.EnvironmentHost
-import li.cil.oc.api.internal.Rotatable
+import li.cil.oc.api.internal
import li.cil.oc.api.machine.Arguments
import li.cil.oc.api.machine.Callback
import li.cil.oc.api.machine.Context
import li.cil.oc.api.network.Visibility
import li.cil.oc.api.prefab
import li.cil.oc.util.BlockPosition
+import li.cil.oc.util.ExtendedArguments._
import li.cil.oc.util.ExtendedWorld._
import net.minecraft.init.Blocks
+import net.minecraft.util.EnumFacing
-class UpgradePiston(val host: Rotatable with EnvironmentHost) extends prefab.ManagedEnvironment {
+abstract class UpgradePiston(val host: EnvironmentHost) extends prefab.ManagedEnvironment {
override val node = Network.newNode(this, Visibility.Network).
withComponent("piston").
withConnector().
create()
- @Callback(doc = """function():boolean -- Tries to push the block in front of the container of the upgrade.""")
+ def pushDirection(args: Arguments, index: Int): EnumFacing
+
+ def pushOrigin(side: EnumFacing) = BlockPosition(host)
+
+ @Callback(doc = """function([side:number]):boolean -- Tries to push the block on the specified side of the container of the upgrade. Defaults to front.""")
def push(context: Context, args: Arguments): Array[AnyRef] = {
- val hostPos = BlockPosition(host)
- val blockPos = hostPos.offset(host.facing)
- if (!host.world.isAirBlock(blockPos) && node.tryChangeBuffer(-Settings.get.pistonCost) && Blocks.piston.doMove(host.world, hostPos.toBlockPos, host.facing, true)) {
+ val side = pushDirection(args, 0)
+ val hostPos = pushOrigin(side)
+ val blockPos = hostPos.offset(side)
+ if (!host.world.isAirBlock(blockPos) && node.tryChangeBuffer(-Settings.get.pistonCost) && Blocks.piston.doMove(host.world, hostPos.toBlockPos, side, true)) {
host.world.setBlockToAir(blockPos)
host.world.playSoundEffect(host.xPosition, host.yPosition, host.zPosition, "tile.piston.out", 0.5f, host.world.rand.nextFloat() * 0.25f + 0.6f)
context.pause(0.5)
@@ -32,3 +39,21 @@ class UpgradePiston(val host: Rotatable with EnvironmentHost) extends prefab.Man
else result(false)
}
}
+
+object UpgradePiston {
+
+ class Drone(drone: internal.Drone) extends UpgradePiston(drone) {
+ override def pushDirection(args: Arguments, index: Int) = args.optSide(index, EnumFacing.SOUTH, EnumFacing.values: _*)
+ }
+
+ class Tablet(tablet: internal.Tablet) extends Rotatable(tablet) {
+ override def pushOrigin(side: EnumFacing) =
+ if (side == EnumFacing.DOWN && tablet.player.getEyeHeight > 1) super.pushOrigin(side).offset(EnumFacing.DOWN)
+ else super.pushOrigin(side)
+ }
+
+ class Rotatable(val rotatable: internal.Rotatable with EnvironmentHost) extends UpgradePiston(rotatable) {
+ override def pushDirection(args: Arguments, index: Int) = rotatable.toGlobal(args.optSideForAction(index, EnumFacing.SOUTH))
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/scala/li/cil/oc/server/component/traits/InventoryControl.scala b/src/main/scala/li/cil/oc/server/component/traits/InventoryControl.scala
index d8273c60e..b4001edec 100644
--- a/src/main/scala/li/cil/oc/server/component/traits/InventoryControl.scala
+++ b/src/main/scala/li/cil/oc/server/component/traits/InventoryControl.scala
@@ -50,7 +50,7 @@ trait InventoryControl extends InventoryAware {
@Callback(doc = "function(toSlot:number[, amount:number]):boolean -- Move up to the specified amount of items from the selected slot into the specified slot.")
def transferTo(context: Context, args: Arguments): Array[AnyRef] = {
val slot = args.checkSlot(inventory, 0)
- val count = args.optionalItemCount(1)
+ val count = args.optItemCount(1)
if (slot == selectedSlot || count == 0) {
result(true)
}
diff --git a/src/main/scala/li/cil/oc/server/component/traits/InventoryWorldControl.scala b/src/main/scala/li/cil/oc/server/component/traits/InventoryWorldControl.scala
index f01f866d1..d0f08bbf5 100644
--- a/src/main/scala/li/cil/oc/server/component/traits/InventoryWorldControl.scala
+++ b/src/main/scala/li/cil/oc/server/component/traits/InventoryWorldControl.scala
@@ -33,7 +33,7 @@ trait InventoryWorldControl extends InventoryAware with WorldAware with SideRest
@Callback(doc = "function(side:number[, count:number=64]):boolean -- Drops items from the selected slot towards the specified side.")
def drop(context: Context, args: Arguments): Array[AnyRef] = {
val facing = checkSideForAction(args, 0)
- val count = args.optionalItemCount(1)
+ val count = args.optItemCount(1)
val stack = inventory.getStackInSlot(selectedSlot)
if (stack != null && stack.stackSize > 0) {
val blockPos = position.offset(facing)
@@ -69,7 +69,7 @@ trait InventoryWorldControl extends InventoryAware with WorldAware with SideRest
@Callback(doc = "function(side:number[, count:number=64]):boolean -- Suck up items from the specified side.")
def suck(context: Context, args: Arguments): Array[AnyRef] = {
val facing = checkSideForAction(args, 0)
- val count = args.optionalItemCount(1)
+ val count = args.optItemCount(1)
val blockPos = position.offset(facing)
if (InventoryUtils.inventoryAt(blockPos).exists(inventory => {
diff --git a/src/main/scala/li/cil/oc/server/component/traits/InventoryWorldControlMk2.scala b/src/main/scala/li/cil/oc/server/component/traits/InventoryWorldControlMk2.scala
index 9822c2303..c4ac0b57f 100644
--- a/src/main/scala/li/cil/oc/server/component/traits/InventoryWorldControlMk2.scala
+++ b/src/main/scala/li/cil/oc/server/component/traits/InventoryWorldControlMk2.scala
@@ -14,7 +14,7 @@ trait InventoryWorldControlMk2 extends InventoryAware with WorldAware with SideR
@Callback(doc = """function(facing:number, slot:number[, count:number]):boolean -- Drops the selected item stack into the specified slot of an inventory.""")
def dropIntoSlot(context: Context, args: Arguments): Array[AnyRef] = {
val facing = checkSideForAction(args, 0)
- val count = args.optionalItemCount(2)
+ val count = args.optItemCount(2)
val stack = inventory.getStackInSlot(selectedSlot)
if (stack != null && stack.stackSize > 0) {
withInventory(facing, inventory => {
@@ -43,7 +43,7 @@ trait InventoryWorldControlMk2 extends InventoryAware with WorldAware with SideR
@Callback(doc = """function(facing:number, slot:number[, count:number]):boolean -- Sucks items from the specified slot of an inventory.""")
def suckFromSlot(context: Context, args: Arguments): Array[AnyRef] = {
val facing = checkSideForAction(args, 0)
- val count = args.optionalItemCount(2)
+ val count = args.optItemCount(2)
withInventory(facing, inventory => {
val slot = args.checkSlot(inventory, 1)
if (InventoryUtils.extractFromInventorySlot(InventoryUtils.insertIntoInventory(_, this.inventory, slots = Option(insertionSlots)), inventory, facing.getOpposite, slot, count)) {
diff --git a/src/main/scala/li/cil/oc/server/component/traits/TankControl.scala b/src/main/scala/li/cil/oc/server/component/traits/TankControl.scala
index a57293d32..33815f2e9 100644
--- a/src/main/scala/li/cil/oc/server/component/traits/TankControl.scala
+++ b/src/main/scala/li/cil/oc/server/component/traits/TankControl.scala
@@ -53,7 +53,7 @@ trait TankControl extends TankAware {
@Callback(doc = "function(index:number[, count:number=1000]):boolean -- Move the specified amount of fluid from the selected tank into the specified tank.")
def transferFluidTo(context: Context, args: Arguments): Array[AnyRef] = {
val index = args.checkTank(tank, 0)
- val count = args.optionalFluidCount(1)
+ val count = args.optFluidCount(1)
if (index == selectedTank || count == 0) {
result(true)
}
diff --git a/src/main/scala/li/cil/oc/server/component/traits/TankInventoryControl.scala b/src/main/scala/li/cil/oc/server/component/traits/TankInventoryControl.scala
index dc90c8578..b54ca7e91 100644
--- a/src/main/scala/li/cil/oc/server/component/traits/TankInventoryControl.scala
+++ b/src/main/scala/li/cil/oc/server/component/traits/TankInventoryControl.scala
@@ -35,7 +35,7 @@ trait TankInventoryControl extends WorldAware with InventoryAware with TankAware
@Callback(doc = """function([amount:number]):boolean -- Transfers fluid from a tank in the selected inventory slot to the selected tank.""")
def drain(context: Context, args: Arguments): Array[AnyRef] = {
- val amount = args.optionalFluidCount(0)
+ val amount = args.optFluidCount(0)
Option(tank.getFluidTank(selectedTank)) match {
case Some(into) => inventory.getStackInSlot(selectedSlot) match {
case stack: ItemStack =>
@@ -77,7 +77,7 @@ trait TankInventoryControl extends WorldAware with InventoryAware with TankAware
@Callback(doc = """function([amount:number]):boolean -- Transfers fluid from the selected tank to a tank in the selected inventory slot.""")
def fill(context: Context, args: Arguments): Array[AnyRef] = {
- val amount = args.optionalFluidCount(0)
+ val amount = args.optFluidCount(0)
Option(tank.getFluidTank(selectedTank)) match {
case Some(from) => inventory.getStackInSlot(selectedSlot) match {
case stack: ItemStack =>
diff --git a/src/main/scala/li/cil/oc/server/component/traits/TankWorldControl.scala b/src/main/scala/li/cil/oc/server/component/traits/TankWorldControl.scala
index 5024723bb..ee6254298 100644
--- a/src/main/scala/li/cil/oc/server/component/traits/TankWorldControl.scala
+++ b/src/main/scala/li/cil/oc/server/component/traits/TankWorldControl.scala
@@ -4,14 +4,9 @@ import li.cil.oc.api.machine.Arguments
import li.cil.oc.api.machine.Callback
import li.cil.oc.api.machine.Context
import li.cil.oc.util.ExtendedArguments._
-import li.cil.oc.util.ExtendedBlock._
-import li.cil.oc.util.ExtendedWorld._
+import li.cil.oc.util.FluidUtils
import li.cil.oc.util.ResultWrapper.result
-import net.minecraft.block.BlockLiquid
-import net.minecraftforge.fluids.FluidRegistry
import net.minecraftforge.fluids.FluidStack
-import net.minecraftforge.fluids.IFluidBlock
-import net.minecraftforge.fluids.IFluidHandler
trait TankWorldControl extends TankAware with WorldAware with SideRestricted {
@Callback(doc = "function(side:number):boolean -- Compare the fluid in the selected tank with the fluid on the specified side. Returns true if equal.")
@@ -19,16 +14,10 @@ trait TankWorldControl extends TankAware with WorldAware with SideRestricted {
val side = checkSideForAction(args, 0)
fluidInTank(selectedTank) match {
case Some(stack) =>
- val blockPos = position.offset(side)
- if (world.blockExists(blockPos)) world.getTileEntity(blockPos) match {
- case handler: IFluidHandler =>
- result(Option(handler.getTankInfo(side.getOpposite)).exists(_.exists(other => stack.isFluidEqual(other.fluid))))
- case _ =>
- val block = world.getBlock(blockPos)
- val fluid = FluidRegistry.lookupFluidForBlock(block)
- result(stack.getFluid == fluid)
+ FluidUtils.fluidHandlerAt(position.offset(side)) match {
+ case Some(handler) => result(Option(handler.getTankInfo(side.getOpposite)).exists(_.exists(other => stack.isFluidEqual(other.fluid))))
+ case _ => result(false)
}
- else result(false)
case _ => result(false)
}
}
@@ -36,18 +25,14 @@ trait TankWorldControl extends TankAware with WorldAware with SideRestricted {
@Callback(doc = "function(side:boolean[, amount:number=1000]):boolean, number or string -- Drains the specified amount of fluid from the specified side. Returns the amount drained, or an error message.")
def drain(context: Context, args: Arguments): Array[AnyRef] = {
val facing = checkSideForAction(args, 0)
- val count = args.optionalFluidCount(1)
+ val count = args.optFluidCount(1) max 0
getTank(selectedTank) match {
case Some(tank) =>
val space = tank.getCapacity - tank.getFluidAmount
val amount = math.min(count, space)
- if (count > 0 && amount == 0) {
- result(Unit, "tank is full")
- }
- else {
- val blockPos = position.offset(facing)
- if (world.blockExists(blockPos)) world.getTileEntity(blockPos) match {
- case handler: IFluidHandler =>
+ if (count < 1 || amount > 0) {
+ FluidUtils.fluidHandlerAt(position.offset(facing)) match {
+ case Some(handler) =>
tank.getFluid match {
case stack: FluidStack =>
val drained = handler.drain(facing.getOpposite, new FluidStack(stack, amount), true)
@@ -60,36 +45,10 @@ trait TankWorldControl extends TankAware with WorldAware with SideRestricted {
val transferred = tank.fill(handler.drain(facing.getOpposite, amount, true), true)
result(transferred > 0, transferred)
}
- case _ => world.getBlock(blockPos) match {
- case fluidBlock: IFluidBlock if fluidBlock.canDrain(world, blockPos.toBlockPos) =>
- val drained = fluidBlock.drain(world, blockPos.toBlockPos, false)
- if ((drained != null && drained.amount > 0) && (drained.amount <= amount || amount == 0)) {
- if (drained.amount <= amount) {
- val filled = tank.fill(fluidBlock.drain(world, blockPos.toBlockPos, true), true)
- result(true, filled)
- }
- else /* if (amount == 0) */ {
- result(true, 0)
- }
- }
- else result(Unit, "tank is full")
- case liquidBlock: BlockLiquid if world.getBlockState(blockPos.toBlockPos) == liquidBlock.getDefaultState =>
- val fluid = FluidRegistry.lookupFluidForBlock(liquidBlock)
- if (fluid == null) {
- result(Unit, "incompatible or no fluid")
- }
- else if (tank.fill(new FluidStack(fluid, 1000), false) == 1000) {
- tank.fill(new FluidStack(fluid, 1000), true)
- world.setBlockToAir(blockPos)
- result(true, 1000)
- }
- else result(Unit, "tank is full")
- case _ =>
- result(Unit, "incompatible or no fluid")
- }
+ case _ => result(Unit, "incompatible or no fluid")
}
- else result(Unit, "incompatible or no fluid")
}
+ else result(Unit, "tank is full")
case _ => result(Unit, "no tank selected")
}
}
@@ -97,49 +56,28 @@ trait TankWorldControl extends TankAware with WorldAware with SideRestricted {
@Callback(doc = "function(side:number[, amount:number=1000]):boolean, number of string -- Eject the specified amount of fluid to the specified side. Returns the amount ejected or an error message.")
def fill(context: Context, args: Arguments): Array[AnyRef] = {
val facing = checkSideForAction(args, 0)
- val count = args.optionalFluidCount(1)
+ val count = args.optFluidCount(1) max 0
getTank(selectedTank) match {
case Some(tank) =>
val amount = math.min(count, tank.getFluidAmount)
- if (count > 0 && amount == 0) {
- result(Unit, "tank is empty")
+ if (count < 1 || amount > 0) {
+ FluidUtils.fluidHandlerAt(position.offset(facing)) match {
+ case Some(handler) =>
+ tank.getFluid match {
+ case stack: FluidStack =>
+ val filled = handler.fill(facing.getOpposite, new FluidStack(stack, amount), true)
+ if (filled > 0 || amount == 0) {
+ tank.drain(filled, true)
+ result(true, filled)
+ }
+ else result(Unit, "incompatible or no fluid")
+ case _ =>
+ result(Unit, "tank is empty")
+ }
+ case _ => result(Unit, "no space")
+ }
}
- val blockPos = position.offset(facing)
- if (world.blockExists(blockPos)) world.getTileEntity(blockPos) match {
- case handler: IFluidHandler =>
- tank.getFluid match {
- case stack: FluidStack =>
- val filled = handler.fill(facing.getOpposite, new FluidStack(stack, amount), true)
- if (filled > 0 || amount == 0) {
- tank.drain(filled, true)
- result(true, filled)
- }
- else result(Unit, "incompatible or no fluid")
- case _ =>
- result(Unit, "tank is empty")
- }
- case _ =>
- val block = world.getBlock(blockPos)
- if (!block.isAir(blockPos) && !block.isReplaceable(blockPos)) {
- result(Unit, "no space")
- }
- else if (tank.getFluidAmount < 1000) {
- result(Unit, "tank is empty")
- }
- else if (!tank.getFluid.getFluid.canBePlacedInWorld) {
- result(Unit, "incompatible fluid")
- }
- else {
- val fluidBlock = tank.getFluid.getFluid.getBlock
- tank.drain(1000, true)
- world.breakBlock(blockPos)
- world.setBlock(blockPos, fluidBlock)
- // This fake neighbor update is required to get stills to start flowing.
- world.notifyBlockOfNeighborChange(blockPos, world.getBlock(position))
- result(true, 1000)
- }
- }
- else result(Unit, "no space")
+ else result(Unit, "tank is empty")
case _ => result(Unit, "no tank selected")
}
}
diff --git a/src/main/scala/li/cil/oc/server/component/traits/WorldTankAnalytics.scala b/src/main/scala/li/cil/oc/server/component/traits/WorldTankAnalytics.scala
index a0a863fd1..ec7950223 100644
--- a/src/main/scala/li/cil/oc/server/component/traits/WorldTankAnalytics.scala
+++ b/src/main/scala/li/cil/oc/server/component/traits/WorldTankAnalytics.scala
@@ -5,15 +5,15 @@ import li.cil.oc.api.machine.Arguments
import li.cil.oc.api.machine.Callback
import li.cil.oc.api.machine.Context
import li.cil.oc.server.component.result
-import li.cil.oc.util.ExtendedWorld._
-import net.minecraftforge.fluids.IFluidHandler
+import li.cil.oc.util.FluidUtils
trait WorldTankAnalytics extends WorldAware with SideRestricted {
@Callback(doc = """function(side:number):number -- Get the amount of fluid in the tank on the specified side.""")
def getTankLevel(context: Context, args: Arguments): Array[AnyRef] = {
val facing = checkSideForAction(args, 0)
- world.getTileEntity(position.offset(facing)) match {
- case handler: IFluidHandler =>
+
+ FluidUtils.fluidHandlerAt(position.offset(facing)) match {
+ case Some(handler) =>
result(handler.getTankInfo(facing.getOpposite).map(info => Option(info.fluid).fold(0)(_.amount)).sum)
case _ => result(Unit, "no tank")
}
@@ -22,8 +22,8 @@ trait WorldTankAnalytics extends WorldAware with SideRestricted {
@Callback(doc = """function(side:number):number -- Get the capacity of the tank on the specified side.""")
def getTankCapacity(context: Context, args: Arguments): Array[AnyRef] = {
val facing = checkSideForAction(args, 0)
- world.getTileEntity(position.offset(facing)) match {
- case handler: IFluidHandler =>
+ FluidUtils.fluidHandlerAt(position.offset(facing)) match {
+ case Some(handler) =>
result(handler.getTankInfo(facing.getOpposite).map(_.capacity).foldLeft(0)((max, capacity) => math.max(max, capacity)))
case _ => result(Unit, "no tank")
}
@@ -32,8 +32,8 @@ trait WorldTankAnalytics extends WorldAware with SideRestricted {
@Callback(doc = """function(side:number):table -- Get a description of the fluid in the the tank on the specified side.""")
def getFluidInTank(context: Context, args: Arguments): Array[AnyRef] = if (Settings.get.allowItemStackInspection) {
val facing = checkSideForAction(args, 0)
- world.getTileEntity(position.offset(facing)) match {
- case handler: IFluidHandler =>
+ FluidUtils.fluidHandlerAt(position.offset(facing)) match {
+ case Some(handler) =>
result(handler.getTankInfo(facing.getOpposite))
case _ => result(Unit, "no tank")
}
diff --git a/src/main/scala/li/cil/oc/server/machine/Machine.scala b/src/main/scala/li/cil/oc/server/machine/Machine.scala
index ddaed36e8..71bc99d22 100644
--- a/src/main/scala/li/cil/oc/server/machine/Machine.scala
+++ b/src/main/scala/li/cil/oc/server/machine/Machine.scala
@@ -478,7 +478,7 @@ class Machine(val host: MachineHost) extends prefab.ManagedEnvironment with mach
// Check if we should switch states. These are all the states in which we're
// guaranteed that the executor thread isn't running anymore.
- state.synchronized(state.top match {
+ state.synchronized(state.top) match {
// Booting up.
case Machine.State.Starting =>
verifyComponents()
@@ -539,10 +539,8 @@ class Machine(val host: MachineHost) extends prefab.ManagedEnvironment with mach
finally {
inSynchronizedCall = false
}
-
- assert(!isExecuting)
case _ => // Nothing special to do, just avoid match errors.
- })
+ }
// Finally check if we should stop the computer. We cannot lock the state
// because we may have to wait for the executor thread to finish, which
@@ -946,6 +944,8 @@ class Machine(val host: MachineHost) extends prefab.ManagedEnvironment with mach
case Machine.State.Stopping =>
state.clear()
state.push(Machine.State.Stopping)
+ case Machine.State.Restarting =>
+ // Nothing to do!
case _ => throw new AssertionError("Invalid state in executor post-processing.")
}
assert(!isExecuting)
diff --git a/src/main/scala/li/cil/oc/server/machine/luac/ComputerAPI.scala b/src/main/scala/li/cil/oc/server/machine/luac/ComputerAPI.scala
index cf3b18f37..c92ea091b 100644
--- a/src/main/scala/li/cil/oc/server/machine/luac/ComputerAPI.scala
+++ b/src/main/scala/li/cil/oc/server/machine/luac/ComputerAPI.scala
@@ -1,9 +1,15 @@
package li.cil.oc.server.machine.luac
import li.cil.oc.Settings
+import li.cil.oc.api
+import li.cil.oc.api.driver.item.MutableProcessor
+import li.cil.oc.api.driver.item.Processor
import li.cil.oc.api.network.Connector
+import li.cil.oc.integration.opencomputers.DriverCPU
import li.cil.oc.util.ExtendedLuaState.extendLuaState
+import scala.collection.convert.WrapAsScala._
+
class ComputerAPI(owner: NativeLuaArchitecture) extends NativeLuaAPI(owner) {
def initialize() {
// Computer API, stuff that kinda belongs to os, but we don't want to
@@ -105,6 +111,51 @@ class ComputerAPI(owner: NativeLuaArchitecture) extends NativeLuaAPI(owner) {
})
lua.setField(-2, "maxEnergy")
+ lua.pushScalaFunction(lua => {
+ machine.host.internalComponents.map(stack => (stack, api.Driver.driverFor(stack))).collectFirst {
+ case (stack, processor: MutableProcessor) => processor.allArchitectures.toSeq
+ case (stack, processor: Processor) => Seq(processor.architecture(stack))
+ } match {
+ case Some(architectures) =>
+ lua.pushValue(architectures.map(DriverCPU.getArchitectureName))
+ case _ =>
+ lua.newTable()
+ }
+ 1
+ })
+ lua.setField(-2, "getArchitectures")
+
+ lua.pushScalaFunction(lua => {
+ machine.host.internalComponents.map(stack => (stack, api.Driver.driverFor(stack))).collectFirst {
+ case (stack, processor: Processor) =>
+ lua.pushString(DriverCPU.getArchitectureName(processor.architecture(stack)))
+ 1
+ }.getOrElse(0)
+ })
+ lua.setField(-2, "getArchitecture")
+
+ lua.pushScalaFunction(lua => {
+ val archName = lua.checkString(1)
+ machine.host.internalComponents.map(stack => (stack, api.Driver.driverFor(stack))).collectFirst {
+ case (stack, processor: MutableProcessor) => processor.allArchitectures.find(arch => DriverCPU.getArchitectureName(arch) == archName) match {
+ case Some(archClass) =>
+ if (archClass != processor.architecture(stack)) {
+ processor.setArchitecture(stack, archClass)
+ lua.pushBoolean(true)
+ }
+ else {
+ lua.pushBoolean(false)
+ }
+ 1
+ case _ =>
+ lua.pushNil()
+ lua.pushString("unknown architecture")
+ 2
+ }
+ }.getOrElse(0)
+ })
+ lua.setField(-2, "setArchitecture")
+
// Set the computer table.
lua.setGlobal("computer")
}
diff --git a/src/main/scala/li/cil/oc/server/machine/luaj/ComputerAPI.scala b/src/main/scala/li/cil/oc/server/machine/luaj/ComputerAPI.scala
index d27b826cb..cb87c1180 100644
--- a/src/main/scala/li/cil/oc/server/machine/luaj/ComputerAPI.scala
+++ b/src/main/scala/li/cil/oc/server/machine/luaj/ComputerAPI.scala
@@ -1,11 +1,17 @@
package li.cil.oc.server.machine.luaj
import li.cil.oc.Settings
+import li.cil.oc.api
+import li.cil.oc.api.driver.item.MutableProcessor
+import li.cil.oc.api.driver.item.Processor
import li.cil.oc.api.network.Connector
+import li.cil.oc.integration.opencomputers.DriverCPU
import li.cil.oc.util.ScalaClosure._
import li.cil.repack.org.luaj.vm2.LuaValue
import li.cil.repack.org.luaj.vm2.Varargs
+import scala.collection.convert.WrapAsScala._
+
class ComputerAPI(owner: LuaJLuaArchitecture) extends LuaJAPI(owner) {
override def initialize() {
// Computer API, stuff that kinda belongs to os, but we don't want to
@@ -54,6 +60,40 @@ class ComputerAPI(owner: LuaJLuaArchitecture) extends LuaJAPI(owner) {
computer.set("maxEnergy", (_: Varargs) => LuaValue.valueOf(node.asInstanceOf[Connector].globalBufferSize))
+ computer.set("getArchitectures", (args: Varargs) => {
+ machine.host.internalComponents.map(stack => (stack, api.Driver.driverFor(stack))).collectFirst {
+ case (stack, processor: MutableProcessor) => processor.allArchitectures.toSeq
+ case (stack, processor: Processor) => Seq(processor.architecture(stack))
+ } match {
+ case Some(architectures) => LuaValue.listOf(architectures.map(DriverCPU.getArchitectureName).map(LuaValue.valueOf).toArray)
+ case _ => LuaValue.tableOf()
+ }
+ })
+
+ computer.set("getArchitecture", (args: Varargs) => {
+ machine.host.internalComponents.map(stack => (stack, api.Driver.driverFor(stack))).collectFirst {
+ case (stack, processor: Processor) => LuaValue.valueOf(DriverCPU.getArchitectureName(processor.architecture(stack)))
+ }.getOrElse(LuaValue.NONE)
+ })
+
+ computer.set("setArchitecture", (args: Varargs) => {
+ val archName = args.checkjstring(1)
+ machine.host.internalComponents.map(stack => (stack, api.Driver.driverFor(stack))).collectFirst {
+ case (stack, processor: MutableProcessor) => processor.allArchitectures.find(arch => DriverCPU.getArchitectureName(arch) == archName) match {
+ case Some(archClass) =>
+ if (archClass != processor.architecture(stack)) {
+ processor.setArchitecture(stack, archClass)
+ LuaValue.TRUE
+ }
+ else {
+ LuaValue.FALSE
+ }
+ case _ =>
+ LuaValue.varargsOf(LuaValue.NIL, LuaValue.valueOf("unknown architecture"))
+ }
+ }.getOrElse(LuaValue.NONE)
+ })
+
// Set the computer table.
lua.set("computer", computer)
}
diff --git a/src/main/scala/li/cil/oc/server/network/QuantumNetwork.scala b/src/main/scala/li/cil/oc/server/network/QuantumNetwork.scala
index ce56d5f01..ca6284836 100644
--- a/src/main/scala/li/cil/oc/server/network/QuantumNetwork.scala
+++ b/src/main/scala/li/cil/oc/server/network/QuantumNetwork.scala
@@ -1,20 +1,27 @@
package li.cil.oc.server.network
-import li.cil.oc.server.component.LinkedCard
+import li.cil.oc.api.network.Packet
import scala.collection.mutable
// Just because the name if so fancy!
object QuantumNetwork {
- val tunnels = mutable.Map.empty[String, mutable.WeakHashMap[LinkedCard, Unit]]
+ val tunnels = mutable.Map.empty[String, mutable.WeakHashMap[QuantumNode, Unit]]
- def add(card: LinkedCard) {
+ def add(card: QuantumNode) {
tunnels.getOrElseUpdate(card.tunnel, mutable.WeakHashMap.empty).put(card, Unit)
}
- def remove(card: LinkedCard) {
+ def remove(card: QuantumNode) {
tunnels.get(card.tunnel).foreach(_.remove(card))
}
- def getEndpoints(tunnel: String) = tunnels.get(tunnel).fold(Iterable.empty[LinkedCard])(_.keys)
+ def getEndpoints(tunnel: String) = tunnels.get(tunnel).fold(Iterable.empty[QuantumNode])(_.keys)
+
+ trait QuantumNode {
+ def tunnel: String
+
+ def receivePacket(packet: Packet): Unit
+ }
+
}
diff --git a/src/main/scala/li/cil/oc/util/ExtendedArguments.scala b/src/main/scala/li/cil/oc/util/ExtendedArguments.scala
index 1bfbe6b86..eb6812c5f 100644
--- a/src/main/scala/li/cil/oc/util/ExtendedArguments.scala
+++ b/src/main/scala/li/cil/oc/util/ExtendedArguments.scala
@@ -4,6 +4,7 @@ import li.cil.oc.api.internal.MultiTank
import li.cil.oc.api.machine.Arguments
import net.minecraft.inventory.IInventory
import net.minecraft.util.EnumFacing
+import net.minecraftforge.fluids.FluidContainerRegistry
import scala.language.implicitConversions
@@ -12,17 +13,13 @@ object ExtendedArguments {
implicit def extendedArguments(args: Arguments): ExtendedArguments = new ExtendedArguments(args)
class ExtendedArguments(val args: Arguments) {
- def optionalItemCount(n: Int) =
- if (args.count > n && args.checkAny(n) != null) {
- math.max(0, math.min(64, args.checkInteger(n)))
- }
- else 64
+ def optItemCount(index: Int, default: Int = 64) =
+ if (!isDefined(index) || !hasValue(index)) default
+ else math.max(0, math.min(64, args.checkInteger(index)))
- def optionalFluidCount(n: Int) =
- if (args.count > n && args.checkAny(n) != null) {
- math.max(0, args.checkInteger(n))
- }
- else 1000
+ def optFluidCount(index: Int, default: Int = FluidContainerRegistry.BUCKET_VOLUME) =
+ if (!isDefined(index) || !hasValue(index)) default
+ else math.max(0, args.checkInteger(index))
def checkSlot(inventory: IInventory, n: Int) = {
val slot = args.checkInteger(n) - 1
@@ -32,9 +29,9 @@ object ExtendedArguments {
slot
}
- def optSlot(inventory: IInventory, n: Int, default: Int) = {
- if (n >= 0 && n < args.count()) checkSlot(inventory, n)
- else default
+ def optSlot(inventory: IInventory, index: Int, default: Int) = {
+ if (!isDefined(index)) default
+ else checkSlot(inventory, index)
}
def checkTank(multi: MultiTank, n: Int) = {
@@ -45,14 +42,26 @@ object ExtendedArguments {
tank
}
- def checkSideForAction(n: Int) = checkSide(n, EnumFacing.SOUTH, EnumFacing.UP, EnumFacing.DOWN)
+ def checkSideForAction(index: Int) = checkSide(index, EnumFacing.SOUTH, EnumFacing.UP, EnumFacing.DOWN)
- def checkSideForMovement(n: Int) = checkSide(n, EnumFacing.SOUTH, EnumFacing.NORTH, EnumFacing.UP, EnumFacing.DOWN)
+ def optSideForAction(index: Int, default: EnumFacing) =
+ if (!isDefined(index)) default
+ else checkSideForAction(index)
- def checkSideForFace(n: Int, facing: EnumFacing) = checkSide(n, EnumFacing.values.filter(_ != facing.getOpposite): _*)
+ def checkSideForMovement(index: Int) = checkSide(index, EnumFacing.SOUTH, EnumFacing.NORTH, EnumFacing.UP, EnumFacing.DOWN)
- def checkSide(n: Int, allowed: EnumFacing*) = {
- val side = args.checkInteger(n)
+ def optSideForMovement(index: Int, default: EnumFacing) =
+ if (!isDefined(index)) default
+ else checkSideForMovement(index)
+
+ def checkSideForFace(index: Int, facing: EnumFacing) = checkSide(index, EnumFacing.values.filter(_ != facing.getOpposite): _*)
+
+ def optSideForFace(index: Int, default: EnumFacing) =
+ if (!isDefined(index)) default
+ else checkSideForAction(index)
+
+ def checkSide(index: Int, allowed: EnumFacing*) = {
+ val side = args.checkInteger(index)
if (side < 0 || side > 5) {
throw new IllegalArgumentException("invalid side")
}
@@ -60,6 +69,15 @@ object ExtendedArguments {
if (allowed.isEmpty || (allowed contains direction)) direction
else throw new IllegalArgumentException("unsupported side")
}
+
+ def optSide(index: Int, default: EnumFacing, allowed: EnumFacing*) = {
+ if (!isDefined(index)) default
+ else checkSide(index, allowed: _*)
+ }
+
+ private def isDefined(index: Int) = index >= 0 && index < args.count()
+
+ private def hasValue(index: Int) = args.checkAny(index) != null
}
}
diff --git a/src/main/scala/li/cil/oc/util/ExtendedBlock.scala b/src/main/scala/li/cil/oc/util/ExtendedBlock.scala
index 158a3e007..77a991fe7 100644
--- a/src/main/scala/li/cil/oc/util/ExtendedBlock.scala
+++ b/src/main/scala/li/cil/oc/util/ExtendedBlock.scala
@@ -1,6 +1,7 @@
package li.cil.oc.util
import net.minecraft.block.Block
+import net.minecraftforge.fluids.IFluidBlock
import scala.language.implicitConversions
@@ -22,4 +23,14 @@ object ExtendedBlock {
def getCollisionBoundingBoxFromPool(position: BlockPosition) = block.getCollisionBoundingBox(position.world.get, position.toBlockPos, position.world.get.getBlockState(position.toBlockPos))
}
+ implicit def extendedFluidBlock(block: IFluidBlock): ExtendedFluidBlock = new ExtendedFluidBlock(block)
+
+ class ExtendedFluidBlock(val block: IFluidBlock) {
+ def drain(position: BlockPosition, doDrain: Boolean) = block.drain(position.world.get, position.toBlockPos, doDrain)
+
+ def canDrain(position: BlockPosition) = block.canDrain(position.world.get, position.toBlockPos)
+
+ def getFilledPercentage(position: BlockPosition) = block.getFilledPercentage(position.world.get, position.toBlockPos)
+ }
+
}
diff --git a/src/main/scala/li/cil/oc/util/FluidUtils.scala b/src/main/scala/li/cil/oc/util/FluidUtils.scala
new file mode 100644
index 000000000..1a58af63b
--- /dev/null
+++ b/src/main/scala/li/cil/oc/util/FluidUtils.scala
@@ -0,0 +1,157 @@
+package li.cil.oc.util
+
+import li.cil.oc.util.ExtendedBlock._
+import li.cil.oc.util.ExtendedWorld._
+import net.minecraft.block.Block
+import net.minecraft.block.BlockLiquid
+import net.minecraft.util.EnumFacing
+import net.minecraftforge.fluids.Fluid
+import net.minecraftforge.fluids.FluidContainerRegistry
+import net.minecraftforge.fluids.FluidRegistry
+import net.minecraftforge.fluids.FluidStack
+import net.minecraftforge.fluids.FluidTank
+import net.minecraftforge.fluids.FluidTankInfo
+import net.minecraftforge.fluids.IFluidBlock
+import net.minecraftforge.fluids.IFluidHandler
+
+object FluidUtils {
+ /**
+ * Retrieves an actual fluid handler implementation for a specified world coordinate.
+ *
+ * This performs special handling for in-world liquids.
+ */
+ def fluidHandlerAt(position: BlockPosition): Option[IFluidHandler] = position.world match {
+ case Some(world) if world.blockExists(position) => world.getTileEntity(position) match {
+ case handler: IFluidHandler => Option(handler)
+ case _ => Option(new GenericBlockWrapper(position))
+ }
+ case _ => None
+ }
+
+ /**
+ * Transfers some fluid between two fluid handlers.
+ *
+ * This will try to extract up the specified amount of fluid from any handler,
+ * then insert it into the specified sink handler. If the insertion fails, the
+ * fluid will remain in the source handler.
+ *
+ * This returns true if some fluid was transferred.
+ */
+ def transferBetweenFluidHandlers(source: IFluidHandler, sourceSide: EnumFacing, sink: IFluidHandler, sinkSide: EnumFacing, limit: Int = FluidContainerRegistry.BUCKET_VOLUME) = {
+ val drained = source.drain(sourceSide, limit, false)
+ val filled = sink.fill(sinkSide, drained, false)
+ sink.fill(sinkSide, source.drain(sourceSide, filled, true), true)
+ }
+
+ /**
+ * Utility method for calling transferBetweenFluidHandlers on handlers
+ * in the world.
+ *
+ * This uses the fluidHandlerAt method, and therefore handles special
+ * cases such as fluid blocks.
+ */
+ def transferBetweenFluidHandlersAt(sourcePos: BlockPosition, sourceSide: EnumFacing, sinkPos: BlockPosition, sinkSide: EnumFacing, limit: Int = FluidContainerRegistry.BUCKET_VOLUME) =
+ fluidHandlerAt(sourcePos).fold(0)(source =>
+ fluidHandlerAt(sinkPos).fold(0)(sink =>
+ transferBetweenFluidHandlers(source, sourceSide, sink, sinkSide, limit)))
+
+ // ----------------------------------------------------------------------- //
+
+ private class GenericBlockWrapper(position: BlockPosition) extends IFluidHandler {
+ override def canDrain(from: EnumFacing, fluid: Fluid): Boolean = currentWrapper.fold(false)(_.canDrain(from, fluid))
+
+ override def drain(from: EnumFacing, resource: FluidStack, doDrain: Boolean): FluidStack = currentWrapper.fold(null: FluidStack)(_.drain(from, resource, doDrain))
+
+ override def drain(from: EnumFacing, maxDrain: Int, doDrain: Boolean): FluidStack = currentWrapper.fold(null: FluidStack)(_.drain(from, maxDrain, doDrain))
+
+ override def canFill(from: EnumFacing, fluid: Fluid): Boolean = currentWrapper.fold(false)(_.canFill(from, fluid))
+
+ override def fill(from: EnumFacing, resource: FluidStack, doFill: Boolean): Int = currentWrapper.fold(0)(_.fill(from, resource, doFill))
+
+ override def getTankInfo(from: EnumFacing): Array[FluidTankInfo] = currentWrapper.fold(Array.empty[FluidTankInfo])(_.getTankInfo(from))
+
+ def currentWrapper = if (position.world.get.blockExists(position)) position.world.get.getBlock(position) match {
+ case block: IFluidBlock => Option(new FluidBlockWrapper(position, block))
+ case block: BlockLiquid if FluidRegistry.lookupFluidForBlock(block) != null => Option(new LiquidBlockWrapper(position, block))
+ case block: Block if block.isAir(position) || block.isReplaceable(position) => Option(new AirBlockWrapper(position, block))
+ case _ => None
+ }
+ else None
+ }
+
+ private trait BlockWrapperBase extends IFluidHandler {
+ protected def uncheckedDrain(doDrain: Boolean): FluidStack
+
+ override def drain(from: EnumFacing, resource: FluidStack, doDrain: Boolean): FluidStack = {
+ val drained = uncheckedDrain(false)
+ if (drained != null && (resource == null || (drained.getFluid == resource.getFluid && drained.amount <= resource.amount))) {
+ uncheckedDrain(doDrain)
+ }
+ else null
+ }
+
+ override def drain(from: EnumFacing, maxDrain: Int, doDrain: Boolean): FluidStack = {
+ val drained = uncheckedDrain(false)
+ if (drained != null && drained.amount <= maxDrain) {
+ uncheckedDrain(doDrain)
+ }
+ else null
+ }
+
+ override def canFill(from: EnumFacing, fluid: Fluid): Boolean = false
+
+ override def fill(from: EnumFacing, resource: FluidStack, doFill: Boolean): Int = 0
+ }
+
+ private class FluidBlockWrapper(val position: BlockPosition, val block: IFluidBlock) extends BlockWrapperBase {
+ final val AssumedCapacity = FluidContainerRegistry.BUCKET_VOLUME
+
+ override def canDrain(from: EnumFacing, fluid: Fluid): Boolean = block.canDrain(position)
+
+ override def getTankInfo(from: EnumFacing): Array[FluidTankInfo] = Array(new FluidTankInfo(new FluidTank(block.getFluid, (block.getFilledPercentage(position) * AssumedCapacity).toInt, AssumedCapacity)))
+
+ override protected def uncheckedDrain(doDrain: Boolean): FluidStack = block.drain(position, doDrain)
+ }
+
+ private class LiquidBlockWrapper(val position: BlockPosition, val block: BlockLiquid) extends BlockWrapperBase {
+ val fluid = FluidRegistry.lookupFluidForBlock(block)
+
+ override def canDrain(from: EnumFacing, fluid: Fluid): Boolean = true
+
+ override def getTankInfo(from: EnumFacing): Array[FluidTankInfo] = Array(new FluidTankInfo(new FluidTank(fluid, FluidContainerRegistry.BUCKET_VOLUME, FluidContainerRegistry.BUCKET_VOLUME)))
+
+ override protected def uncheckedDrain(doDrain: Boolean): FluidStack = {
+ if (doDrain) {
+ position.world.get.setBlockToAir(position)
+ }
+ new FluidStack(fluid, FluidContainerRegistry.BUCKET_VOLUME)
+ }
+ }
+
+ private class AirBlockWrapper(val position: BlockPosition, val block: Block) extends IFluidHandler {
+ override def canDrain(from: EnumFacing, fluid: Fluid): Boolean = false
+
+ override def drain(from: EnumFacing, resource: FluidStack, doDrain: Boolean): FluidStack = null
+
+ override def drain(from: EnumFacing, maxDrain: Int, doDrain: Boolean): FluidStack = null
+
+ override def canFill(from: EnumFacing, fluid: Fluid): Boolean = fluid.canBePlacedInWorld
+
+ override def fill(from: EnumFacing, resource: FluidStack, doFill: Boolean): Int = {
+ if (resource != null && resource.getFluid.canBePlacedInWorld && resource.getFluid.getBlock != null) {
+ if (doFill) {
+ val world = position.world.get
+ world.breakBlock(position)
+ world.setBlock(position, resource.getFluid.getBlock)
+ // This fake neighbor update is required to get stills to start flowing.
+ world.notifyBlockOfNeighborChange(position, world.getBlock(position))
+ }
+ FluidContainerRegistry.BUCKET_VOLUME
+ }
+ else 0
+ }
+
+ override def getTankInfo(from: EnumFacing): Array[FluidTankInfo] = Array.empty
+ }
+
+}
diff --git a/src/main/scala/li/cil/oc/util/InventoryUtils.scala b/src/main/scala/li/cil/oc/util/InventoryUtils.scala
index b27e2154e..ff2936839 100644
--- a/src/main/scala/li/cil/oc/util/InventoryUtils.scala
+++ b/src/main/scala/li/cil/oc/util/InventoryUtils.scala
@@ -155,7 +155,10 @@ object InventoryUtils {
(stack != null && limit > 0) && {
var success = false
var remaining = limit
- val range = slots.getOrElse(0 until inventory.getSizeInventory)
+ val range = slots.getOrElse(inventory match {
+ case sided: ISidedInventory => sided.getSlotsForFace(side.orNull).toIterable
+ case _ => 0 until inventory.getSizeInventory
+ })
if (range.nonEmpty) {
// This is a special case for inserting with an explicit ordering,
@@ -204,8 +207,13 @@ object InventoryUtils {
*
* This returns true if at least one item was extracted.
*/
- def extractFromInventory(consumer: (ItemStack) => Unit, inventory: IInventory, side: EnumFacing, limit: Int = 64) =
- (0 until inventory.getSizeInventory).exists(slot => extractFromInventorySlot(consumer, inventory, side, slot, limit))
+ def extractFromInventory(consumer: (ItemStack) => Unit, inventory: IInventory, side: EnumFacing, limit: Int = 64) = {
+ val range = inventory match {
+ case sided: ISidedInventory => sided.getSlotsForFace(side).toIterable
+ case _ => 0 until inventory.getSizeInventory
+ }
+ range.exists(slot => extractFromInventorySlot(consumer, inventory, side, slot, limit))
+ }
/**
* Utility method for calling insertIntoInventory on an inventory
@@ -221,6 +229,48 @@ object InventoryUtils {
def extractFromInventoryAt(consumer: (ItemStack) => Unit, position: BlockPosition, side: EnumFacing, limit: Int = 64) =
inventoryAt(position).exists(extractFromInventory(consumer, _, side, limit))
+ /**
+ * Transfers some items between two inventories.
+ *
+ * This will try to extract up the specified number of items from any inventory,
+ * then insert it into the specified sink inventory. If the insertion fails, the
+ * items will remain in the source inventory.
+ *
+ * This uses the extractFromInventory and insertIntoInventory
+ * methods, and therefore handles special cases such as sided inventories and
+ * stack size limits.
+ *
+ * This returns true if at least one item was transferred.
+ */
+ def transferBetweenInventories(source: IInventory, sourceSide: EnumFacing, sink: IInventory, sinkSide: Option[EnumFacing], limit: Int = 64) =
+ extractFromInventory(
+ insertIntoInventory(_, sink, sinkSide, limit), source, sourceSide, limit)
+
+ /**
+ * Like transferBetweenInventories but moving between specific slots.
+ */
+ def transferBetweenInventoriesSlots(source: IInventory, sourceSide: EnumFacing, sourceSlot: Int, sink: IInventory, sinkSide: Option[EnumFacing], sinkSlot: Int, limit: Int = 64) =
+ extractFromInventorySlot(
+ insertIntoInventorySlot(_, sink, sinkSide, sinkSlot, limit), source, sourceSide, sourceSlot, limit)
+
+ /**
+ * Utility method for calling transferBetweenInventories on inventories
+ * in the world.
+ */
+ def transferBetweenInventoriesAt(source: BlockPosition, sourceSide: EnumFacing, sink: BlockPosition, sinkSide: Option[EnumFacing], limit: Int = 64) =
+ inventoryAt(source).exists(sourceInventory =>
+ inventoryAt(sink).exists(sinkInventory =>
+ transferBetweenInventories(sourceInventory, sourceSide, sinkInventory, sinkSide, limit)))
+
+ /**
+ * Utility method for calling transferBetweenInventoriesSlots on inventories
+ * in the world.
+ */
+ def transferBetweenInventoriesSlotsAt(sourcePos: BlockPosition, sourceSide: EnumFacing, sourceSlot: Int, sinkPos: BlockPosition, sinkSide: Option[EnumFacing], sinkSlot: Int, limit: Int = 64) =
+ inventoryAt(sourcePos).exists(sourceInventory =>
+ inventoryAt(sinkPos).exists(sinkInventory =>
+ transferBetweenInventoriesSlots(sourceInventory, sourceSide, sourceSlot, sinkInventory, sinkSide, sinkSlot, limit)))
+
/**
* Utility method for dropping contents from a single inventory slot into
* the world.
diff --git a/src/main/scala/li/cil/oc/util/RotationHelper.scala b/src/main/scala/li/cil/oc/util/RotationHelper.scala
index b90710e1e..8eb8ee6e3 100644
--- a/src/main/scala/li/cil/oc/util/RotationHelper.scala
+++ b/src/main/scala/li/cil/oc/util/RotationHelper.scala
@@ -2,6 +2,8 @@ package li.cil.oc.util
import net.minecraft.util.EnumFacing
+import scala.collection.mutable
+
object RotationHelper {
def fromYaw(yaw: Float) = {
(yaw / 360 * 4).round & 3 match {
@@ -11,4 +13,81 @@ object RotationHelper {
case 3 => EnumFacing.EAST
}
}
+
+ def toLocal(pitch: EnumFacing, yaw: EnumFacing, value: EnumFacing) =
+ translationFor(pitch, yaw)(value.ordinal)
+
+ def toGlobal(pitch: EnumFacing, yaw: EnumFacing, value: EnumFacing) =
+ inverseTranslationFor(pitch, yaw)(value.ordinal)
+
+ def translationFor(pitch: EnumFacing, yaw: EnumFacing) =
+ translationCache.synchronized(translationCache.
+ getOrElseUpdate(pitch, mutable.Map.empty).
+ getOrElseUpdate(yaw, translations(pitch.ordinal)(yaw.ordinal - 2)))
+
+ def inverseTranslationFor(pitch: EnumFacing, yaw: EnumFacing) =
+ inverseTranslationCache.synchronized(inverseTranslationCache.
+ getOrElseUpdate(pitch, mutable.Map.empty).
+ getOrElseUpdate(yaw, {
+ val t = translationFor(pitch, yaw)
+ t.indices.
+ map(EnumFacing.getFront).
+ map(t.indexOf).
+ map(EnumFacing.getFront).
+ toArray
+ }))
+
+ // ----------------------------------------------------------------------- //
+
+ private val translationCache = mutable.Map.empty[EnumFacing, mutable.Map[EnumFacing, Array[EnumFacing]]]
+ private val inverseTranslationCache = mutable.Map.empty[EnumFacing, mutable.Map[EnumFacing, Array[EnumFacing]]]
+
+ /**
+ * Translates forge directions based on the block's pitch and yaw. The base
+ * forward direction is facing south with no pitch. The outer array is for
+ * the three different pitch states, the inner for the four different yaw
+ * states.
+ */
+ private val translations = Array(
+ // Pitch = Down
+ Array(
+ // Yaw = North
+ Array(D.south, D.north, D.up, D.down, D.east, D.west),
+ // Yaw = South
+ Array(D.south, D.north, D.down, D.up, D.west, D.east),
+ // Yaw = West
+ Array(D.south, D.north, D.west, D.east, D.up, D.down),
+ // Yaw = East
+ Array(D.south, D.north, D.east, D.west, D.down, D.up)),
+ // Pitch = Up
+ Array(
+ // Yaw = North
+ Array(D.north, D.south, D.down, D.up, D.east, D.west),
+ // Yaw = South
+ Array(D.north, D.south, D.up, D.down, D.west, D.east),
+ // Yaw = West
+ Array(D.north, D.south, D.west, D.east, D.down, D.up),
+ // Yaw = East
+ Array(D.north, D.south, D.east, D.west, D.up, D.down)),
+ // Pitch = Forward (North|East|South|West)
+ Array(
+ // Yaw = North
+ Array(D.down, D.up, D.south, D.north, D.east, D.west),
+ // Yaw = South
+ Array(D.down, D.up, D.north, D.south, D.west, D.east),
+ // Yaw = West
+ Array(D.down, D.up, D.west, D.east, D.south, D.north),
+ // Yaw = East
+ Array(D.down, D.up, D.east, D.west, D.north, D.south)))
+
+ /** Shortcuts for forge directions to make the above more readable. */
+ private object D {
+ val down = EnumFacing.DOWN
+ val up = EnumFacing.UP
+ val north = EnumFacing.NORTH
+ val south = EnumFacing.SOUTH
+ val west = EnumFacing.WEST
+ val east = EnumFacing.EAST
+ }
+
}