diff --git a/README.md b/README.md index 9e1adcc80..861db20b5 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ This mod is [licensed under the **MIT license**](https://github.com/MightyPirate If you would like to contribute better textures for certain items or blocks, feel free to pull-request them. If you would like to contribute *alternative* textures, make it a resource pack, and post it on the forums, for example. 3. **Documentation** Help with keeping the [wiki][] up to date would be *really* appreciated. If you notice anything amiss and know better, fix it. If you don't ask someone who does, then fix it. If you had a question answered, consider adding that information somewhere in the wiki where you would have expected to find that information. - There are also the [files containing the ingame help][manpages], which could probably be much better than they are right now. Improvements to them, and new ones (e.g. for the libraries, such as `text` or `sides`) would help a lot. Thanks! + There are also the files containing the ingame help [for programs][manpages] and [for blocks and items][manual], which could probably be much better than they are right now. Improvements to them, and new ones (e.g. for the libraries, such as `text` or `sides`) would help a lot. Thanks! 4. **Robot Names** Robots get a random name when placed (unless set with an Anvil). The list the names are chose from [can be found here][robot names]. Feel free to pull request additional names! *However*: since the list has grown to a considerable length already, here are the two basic criteria for new names: it must either be a real or fictional robot, or an AI that at least *appears* to be self-aware. @@ -99,7 +99,8 @@ In the case you wish to use Eclipse rather than IntelliJ IDEA, the process is mo [jenkins]: http://ci.cil.li/ [localizations]: https://github.com/MightyPirates/OpenComputers/tree/master-MC1.7.10/src/main/resources/assets/opencomputers/lang [loot]: https://github.com/MightyPirates/OpenComputers/tree/master-MC1.7.10/src/main/resources/assets/opencomputers/loot -[manpages]: https://github.com/MightyPirates/OpenComputers/tree/master-MC1.7.10/src/main/resources/assets/opencomputers/lua/rom/usr/man +[manpages]: https://github.com/MightyPirates/OpenComputers/tree/master-MC1.7.10/src/main/resources/assets/opencomputers/loot/OpenOS/usr/man +[manual]: https://github.com/MightyPirates/OpenComputers/tree/master-MC1.7.10/src/main/resources/assets/opencomputers/doc [mcf]: http://www.minecraftforum.net/topic/2201440-opencomputers-v122/ [pack.mcmeta]: https://github.com/MightyPirates/OpenComputers/blob/master-MC1.7.10/src/main/resources/pack.mcmeta [releases]: https://github.com/MightyPirates/OpenComputers/releases diff --git a/assets/items.psd b/assets/items.psd index d62ef2bab..2df42515f 100644 Binary files a/assets/items.psd and b/assets/items.psd differ diff --git a/build.gradle b/build.gradle index 02f90fbb0..35df78c0d 100644 --- a/build.gradle +++ b/build.gradle @@ -106,7 +106,7 @@ repositories { } maven { name = "dmodoomsirius" - url = "http://ci.dmodoomsirius.me/maven/" + url = "http://api.dmodoomsirius.me/" } maven { name = "ue" @@ -204,7 +204,7 @@ dependencies { provided "mcp.mobius.waila:Waila:${config.waila.version}_${config.minecraft.version}:dev" provided "net.industrial-craft:industrialcraft-2:${config.ic2.version}:dev" provided "net.sengir.forestry:forestry_${config.minecraft.version}:${config.forestry.version}:dev" - provided "notenoughkeys:NeK:${config.minecraft.version}-${config.nek.version}:deobf-dev" + provided "dev.modwarriors.notenoughkeys:NotEnoughKeys:${config.minecraft.version}-${config.nek.version}:deobf-dev" provided "qmunity:QmunityLib:${config.qmunitylib.version}:deobf" provided "tmech:TMechworks:${config.minecraft.version}-${config.tmech.version}:deobf" provided ("mrtjp:ProjectRed:${config.projred.version}:dev") { diff --git a/build.properties b/build.properties index a8683e892..7e24c7137 100644 --- a/build.properties +++ b/build.properties @@ -33,7 +33,7 @@ mekanism.version=7.1.2 mfr.cf=2229/626 mfr.version=[1.7.10]2.8.0RC8-86 nei.version=1.0.5.82 -nek.version=1.0.0b2dev +nek.version=2.0.0b4 poweradvantage.version=1.2.0 projred.version=1.7.10-4.6.2.82 qmunitylib.version=0.1.105 diff --git a/src/main/java/li/cil/oc/api/API.java b/src/main/java/li/cil/oc/api/API.java index 471ef36e7..d25a42825 100644 --- a/src/main/java/li/cil/oc/api/API.java +++ b/src/main/java/li/cil/oc/api/API.java @@ -12,13 +12,14 @@ import li.cil.oc.api.detail.*; */ public class API { public static final String ID_OWNER = "OpenComputers|Core"; - public static final String VERSION = "5.5.4"; + public static final String VERSION = "5.5.5"; public static DriverAPI driver = null; public static FileSystemAPI fileSystem = null; public static ItemAPI items = null; public static MachineAPI machine = null; public static ManualAPI manual = null; + public static NanomachinesAPI nanomachines = null; public static NetworkAPI network = null; public static Config config = null; diff --git a/src/main/java/li/cil/oc/api/Nanomachines.java b/src/main/java/li/cil/oc/api/Nanomachines.java new file mode 100644 index 000000000..afa239b8d --- /dev/null +++ b/src/main/java/li/cil/oc/api/Nanomachines.java @@ -0,0 +1,100 @@ +package li.cil.oc.api; + +import li.cil.oc.api.nanomachines.BehaviorProvider; +import li.cil.oc.api.nanomachines.Controller; +import net.minecraft.entity.player.EntityPlayer; + +import java.util.Collections; + +/** + * This API allows interfacing with nanomachines. + *

+ * It allows registering custom behavior providers as well as querying for all + * presently registered providers and getting a controller for a player. + */ +public class Nanomachines { + /** + * Register a new behavior provider. + *

+ * When a controller is reconfigured it will draw behaviors from all + * registered providers and build a new random connection graph to + * those behaviors. + * + * @param provider the provider to add. + */ + public static void addProvider(BehaviorProvider provider) { + if (API.nanomachines != null) + API.nanomachines.addProvider(provider); + } + + /** + * Get a list of all currently registered providers. + * + * @return the list of all currently registered providers. + */ + public static Iterable getProviders() { + if (API.nanomachines != null) + return API.nanomachines.getProviders(); + return Collections.emptyList(); + } + + /** + * Check whether a player has a nanomachine controller installed. + * + * @param player the player to check for. + * @return true if the player has a controller, false otherwise. + */ + public static boolean hasController(EntityPlayer player) { + if (API.nanomachines != null) + return API.nanomachines.hasController(player); + return false; + } + + /** + * Get the nanomachine controller of the specified player. + *

+ * If the player has a controller installed, this will initialize the + * controller if it has not already been loaded. If the player has no + * controller, this will return null. + * + * @param player the player to get the controller for. + * @return the controller for the specified player. + */ + public static Controller getController(EntityPlayer player) { + if (API.nanomachines != null) + return API.nanomachines.getController(player); + return null; + } + + /** + * Install a controller for the specified player if it doesn't already + * have one. + *

+ * This will also initialize the controller if it has not already been + * initialized. + * + * @param player the player to install a nanomachine controller for. + */ + public static Controller installController(EntityPlayer player) { + if (API.nanomachines != null) + return API.nanomachines.installController(player); + return null; + } + + /** + * Uninstall a controller from the specified player if it has one. + *

+ * This will disable all active behaviors before disposing the controller. + * + * @param player the player to uninstall a nanomachine controller from. + */ + public static void uninstallController(EntityPlayer player) { + if (API.nanomachines != null) + API.nanomachines.uninstallController(player); + } + + // ----------------------------------------------------------------------- // + + private Nanomachines() { + } +} diff --git a/src/main/java/li/cil/oc/api/detail/NanomachinesAPI.java b/src/main/java/li/cil/oc/api/detail/NanomachinesAPI.java new file mode 100644 index 000000000..4a85f5fcf --- /dev/null +++ b/src/main/java/li/cil/oc/api/detail/NanomachinesAPI.java @@ -0,0 +1,66 @@ +package li.cil.oc.api.detail; + +import li.cil.oc.api.nanomachines.BehaviorProvider; +import li.cil.oc.api.nanomachines.Controller; +import net.minecraft.entity.player.EntityPlayer; + +public interface NanomachinesAPI { + /** + * Register a new behavior provider. + *

+ * When a controller is reconfigured it will draw behaviors from all + * registered providers and build a new random connection graph to + * those behaviors. + * + * @param provider the provider to add. + */ + void addProvider(BehaviorProvider provider); + + /** + * Get a list of all currently registered providers. + * + * @return the list of all currently registered providers. + */ + Iterable getProviders(); + + /** + * Check whether a player has a nanomachine controller installed. + * + * @param player the player to check for. + * @return true if the player has a controller, false otherwise. + */ + boolean hasController(EntityPlayer player); + + /** + * Get the nanomachine controller of the specified player. + *

+ * If the player has a controller installed, this will initialize the + * controller if it has not already been loaded. If the player has no + * controller, this will return null. + * + * @param player the player to get the controller for. + * @return the controller for the specified player. + */ + Controller getController(EntityPlayer player); + + /** + * Install a controller for the specified player if it doesn't already + * have one. + *

+ * This will also initialize the controller if it has not already been + * initialized. + * + * @param player the player to install a nanomachine controller for. + * @return the controller for the specified player. + */ + Controller installController(EntityPlayer player); + + /** + * Uninstall a controller from the specified player if it has one. + *

+ * This will disable all active behaviors before disposing the controller. + * + * @param player the player to uninstall a nanomachine controller from. + */ + void uninstallController(EntityPlayer player); +} diff --git a/src/main/java/li/cil/oc/api/nanomachines/Behavior.java b/src/main/java/li/cil/oc/api/nanomachines/Behavior.java new file mode 100644 index 000000000..8ed7ae29e --- /dev/null +++ b/src/main/java/li/cil/oc/api/nanomachines/Behavior.java @@ -0,0 +1,50 @@ +package li.cil.oc.api.nanomachines; + +/** + * Implemented by single behaviors. + *

+ * If you need a reference to the player this behavior applies to (which you'll + * probably usually want to have), pass it along from {@link BehaviorProvider#createBehavior}. + */ +public interface Behavior { + /** + * A short name / description of this behavior. + *

+ * You can not use commas (,) or double quotes (") + * in the returned string. If you do, they'll automatically be replaced with + * underscores. + *

+ * This is entirely optional and may even return null. It is made + * accessible via the controller's wireless protocol, to allow better + * automating reconfigurations / determining input mappings. In some cases + * you may not wish to make this possible, in those cases return null + * or a random string. + *

+ * Again, you can return whatever you like here, it's not used in mod internal + * logic, but only provided to ingame devices as a hint to make configuring + * nanomachines a little easier. + * + * @return the name to provide for this behavior, if any. + */ + String getNameHint(); + + /** + * Called when this behavior becomes active because all its required inputs + * are now satisfied. + *

+ * Use this to initialize permanent effects. + */ + void onEnable(); + + /** + * Called when this behavior becomes inactive. + *

+ * Use this to remove permanent effects. + */ + void onDisable(); + + /** + * Called each tick while this behavior is active. + */ + void update(); +} diff --git a/src/main/java/li/cil/oc/api/nanomachines/BehaviorProvider.java b/src/main/java/li/cil/oc/api/nanomachines/BehaviorProvider.java new file mode 100644 index 000000000..4e0ecf952 --- /dev/null +++ b/src/main/java/li/cil/oc/api/nanomachines/BehaviorProvider.java @@ -0,0 +1,66 @@ +package li.cil.oc.api.nanomachines; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.nbt.NBTTagCompound; + +/** + * Implemented by providers for behaviors. + *

+ * You may implement one provider for each of your behaviors, or one provider + * for all of your behaviors; it really doesn't matter. This just allows for + * some logical grouping of behaviors, where desired. + *

+ * Each behavior provider must be capable or serializing the behaviors it + * creates, and re-create the behavior from its serialized form. It will + * not be given any hints as to whether a provided tag was originally + * produced by it, so you should add a sufficiently unique marker to the + * output NBT to allow identification later on. I recommend generating a + * UUID once, and using that. This is necessary to both save and restore + * neural connection state between saves without breaking the state when + * new behaviors are added, as well as to send states to the client. + */ +public interface BehaviorProvider { + /** + * Create all behaviors valid for the specified player. + *

+ * Note that this is only called on the server side when reconfiguring + * nanomachines. If you have a behavior that actually acts client-only, + * you still need to return it here, as it will be synchronized to the + * client using {@link #writeToNBT} and {@link #readFromNBT}. + * + * @param player the player the behaviors should be created for. + * @return list of new behaviors, may be null. + */ + Iterable createBehaviors(EntityPlayer player); + + /** + * Write a behavior to NBT. + *

+ * This will only be called for behaviors originally created by this provider. + *

+ * This will only be called on the server. All behaviors not saved will be + * lost when loading again, they will not be regenerated using + * {@link #createBehaviors}, so make sure to save all your behaviors. + * + * @param behavior the behavior to serialize. + * @return the serialized representation of the specified behavior. + */ + NBTTagCompound writeToNBT(Behavior behavior); + + /** + * Restore a behavior from NBT. + *

+ * You are not guaranteed that his nbt belongs to a behavior + * created by this provider! If the NBT cannot be handled, return + * null. + *

+ * This is called both on the server and the client; on the server it + * is called when restoring a saved player, on the client when + * synchronizing a configuration. + * + * @param player the player the behaviors should be created for. + * @param nbt the tag to restore the behavior from. + * @return the restored behavior, or null if unhandled. + */ + Behavior readFromNBT(EntityPlayer player, NBTTagCompound nbt); +} diff --git a/src/main/java/li/cil/oc/api/nanomachines/Controller.java b/src/main/java/li/cil/oc/api/nanomachines/Controller.java new file mode 100644 index 000000000..6dd37f296 --- /dev/null +++ b/src/main/java/li/cil/oc/api/nanomachines/Controller.java @@ -0,0 +1,116 @@ +package li.cil.oc.api.nanomachines; + +/** + * The nanomachine controller is responsible for keeping track of the current + * layout of neural connections (i.e. how nanomachine "inputs" connect to + * behaviors, directly or indirectly). + *

+ * Each input can connect to one or more nodes. A node can either be a + * behavior, or an indirect connection, which in turn is connected to one + * or more behaviors (there is at maximum one layer of indirection). Each + * indirection may trigger one or more behaviors, but may also require one + * or more inputs to activate its outputs. + *

+ * Each node, input or indirection, will only connect to one or two other + * nodes, to keep randomization at a somewhat manageable level, but to still + * allow for some optimization by re-rolling the connections. + *

+ * This interface is not meant to be implemented externally. To get a reference + * to a controller, use {@link li.cil.oc.api.Nanomachines#getController}. + */ +public interface Controller { + /** + * Reconfigure the neural connections managed by this controller. This + * will lead to the system being unavailable for a short while, in which + * the neural connections are rebuilt in a new configuration. In addition, + * some debuffs will be applied to the player. + *

+ * This will reset all inputs to disabled and deactivate all previously + * active behaviors. + * + * @return the controller itself, for chaining / convenience. + */ + Controller reconfigure(); + + /** + * Get the number of inputs available. + *

+ * This number depends on the total number of behaviors available, to keep + * randomization at a manageable level. It is computed internally and + * based on a configuration value. + * + * @return the total number of available inputs. + */ + int getTotalInputCount(); + + /** + * Get the total number of inputs that may be active at the same time + * before negative effects are applied to the player. + *

+ * The number of active inputs may exceed this value, but this will + * have negative effects on the player. + * + * @return the number of inputs that may be active at a time. + */ + int getSafeInputCount(); + + /** + * Get whether the input with the specified index is active. + * + * @param index the input index. + * @return whether the input is active. + * @throws IndexOutOfBoundsException if index < 0 or index >= getInputCount. + */ + boolean getInput(int index); + + /** + * Set the state of the input with the specified index. + * + * @param index the input index. + * @param value whether the input should be active. + * @throws IndexOutOfBoundsException if index < 0 or index >= getInputCount. + */ + void setInput(int index, boolean value); + + /** + * Get the list of currently active behaviors, based on the current input states. + *

+ * Note that behaviors may behave differently depending on how many active + * inputs they have. Behaviors in the returned list will have at least one + * active input. + * + * @return the list of currently active behaviors. Never null. + */ + Iterable getActiveBehaviors(); + + /** + * Get the number of active inputs for the specified behavior. + * + * @param behavior the behavior to get the number of inputs for. + * @return the number of inputs active for the specified behavior. + */ + int getInputCount(Behavior behavior); + + // ----------------------------------------------------------------------- // + + /** + * The amount of energy stored by this nanomachine controller. + */ + double getLocalBuffer(); + + /** + * The maximum amount of energy stored by this nanomachine controller. + */ + double getLocalBufferSize(); + + /** + * Try to apply the specified delta to the controller's buffer. + *

+ * A negative value will drain energy from the buffer, a positive value + * will inject energy into the buffer. + * + * @param delta the amount of energy to consume or store. + * @return the remainder of the delta that could not be applied. + */ + double changeBuffer(double delta); +} diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index 09b6b656d..35d0cdef2 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -100,6 +100,12 @@ opencomputers { # The radius in which computer beeps can be heard. beepRadius: 16 + + # Position of the power indicator for nanomachines, by default left to the + # player's health, specified by negative values. Values in [0, 1) will be + # treated as relative positions, values in [1, inf) will be treated as + # absolute positions. + nanomachineHudPos: [-1, -1] } # Computer related settings, concerns server performance and security. @@ -566,6 +572,10 @@ opencomputers { # The internal buffer size of the hover boots. hoverBoots: 15000.0 + + # Amount of energy stored by nanomachines. Yeah, I also don't know + # where all that energy is stored. It's quite fascinating. + nanomachines: 100000 } # Default "costs", i.e. how much energy certain operations consume. @@ -771,6 +781,12 @@ opencomputers { # Energy required for one transposer operation (regardless of the number # of items / fluid volume moved). transposer: 1 + + # Energy consumed per tick per active input node by nanomachines. + nanomachineInput: 0.5 + + # Energy consumed when reconfiguring nanomachines. + nanomachinesReconfigure: 5000 } # The rate at which different blocks accept external power. All of these @@ -958,6 +974,67 @@ opencomputers { relayAmountUpgrade: 1 } + # Nanomachine related values. Note that most of these are relative, as + # they scale with the number of total effects controlled by nanomachines, + # which may very much vary depending on other mods used together with OC. + # To configure this, you'll need to know how this works a bit more in- + # depth, so here goes: there are three layers, the behavior layer, the + # connector layer, and the input layer. The behavior layer consists of + # one node for each behavior provided by registered providers (by default + # these will be potion effects and a few other things). The connector + # layer merely serves to mix things up a little. The input layer is made + # up from nodes that can be triggered by the nanomachines. Each connector + # node has behavior nodes it outputs to, and gets signals from input nodes. + # Behavior nodes get signals from both the connector and the input layers. + # Reconfiguring builds up random connections. Some behaviors change what + # they do based on the number of active inputs (e.g. potion effects will + # increase their amplification value). + nanomachines { + # The relative amount of triggers available based on the number of + # available behaviors (such as different potion effects). For example, + # if there are a total of 10 behaviors available, 0.5 means there will + # be 5 trigger inputs, triggers being the inputs that can be activated + # via nanomachines. + triggerQuota: 0.5 + + # The relative number of connectors based on the number of available + # behaviors (see triggerQuota). + connectorQuota: 0.2 + + # The maximum number of inputs for each node of the "neural network" + # nanomachines connect to. I.e. each behavior node and connector node + # may only have up to this many inputs. + maxInputs: 2 + + # The maximum number of outputs for each node (see maxInputs). + maxOutputs: 2 + + # How many input nodes may be active at the same time before negative + # effects are applied to the player. + safeInputCount: 2 + + # The time in seconds it takes to reconfigure the nanomachines. This is + # to avoid spamming reconfigurations. + reconfigureCooldown: 5 + + # Range of the item magnet behavior added for each active input. + magnetRange: 8 + + # Radius in blocks of the disintegration behavior for each active input. + disintegrationRange: 1 + + # Blacklisted potions, i.e. potions that won't be used for the potion + # behaviors nanomachines may trigger. This can contain strings or numbers. + # In the case of strings, it has to be the internal name of the potion, + # in case of a number it has to be the potion ID. + potionBlacklist: [ + "potion.heal", + "potion.regeneration", + "potion.invisibility", + "potion.saturation" + ] + } + # 3D printer related stuff. printer { # The maximum number of shape for a state of a 3D print allowed. This is 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 26a47e734..744a458d7 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 @@ -26,6 +26,7 @@ Keep in mind that some of these may not be available, depending on the recipe se * [Geolyzer](geolyzer.md) * [Motion Sensor](motionSensor.md) * [Redstone I/O](redstone.md) +* [Transposer](transposer.md) * [Waypoint](waypoint.md) ## Assembly / Printing diff --git a/src/main/resources/assets/opencomputers/doc/en_US/general/example.md b/src/main/resources/assets/opencomputers/doc/en_US/general/example.md new file mode 100644 index 000000000..3bdda70bc --- /dev/null +++ b/src/main/resources/assets/opencomputers/doc/en_US/general/example.md @@ -0,0 +1,69 @@ +# Headline with more lines [with link](redirect1.md) and *some* more + +This is some test text for the subset of Markdown supported by the planned ingame documentation system for OpenComputers. +![This is a tooltip...](opencomputers:textures/gui/printer_ink.png) +![This is a tooltip...](opencomputers:/textures/gui/printer_material.png) +*This* is *italic* text, ~~strikethrough~~ maybe abc-ter **some** text **in bold**. Is _this underlined_? Oh, no, _it's also italic!_ Well, this [a link](../index.md). +![This is rendered live.](oredict:oc:assembler) +## Smaller headline [also with *link* but this __one__ longer](../block/adapter.md) + +![This is another tooltip.](item:OpenComputers:item@23) + +some text directly above the item stack renderer to test spacing +![All the colors.](oredict:craftingPiston) +some text directly below the item stack renderer to test spacing + +This is *italic +over two* lines. But *this ... no *this is* **_bold italic_** *text*. + +### even smaller + +*not italic *because ** why would it be*eh + +`test for code` +`that's not code yet` +`function f(a)` +` testingIndent(a)` +` do` +` lalala()` +` end` +`end` +yeah, line spacing is a bit low, but otherwise too little text fits on one screen. +this is some `code` that's inline. then `some more CODE that` line wraps and so on. + +isn't*. + + # not a header + +* this is a list item and the text that will be wrapped will be indented appropriately +- this should also `work for code rendered text, if it doesn't i` will be a sad person + +asdasd ![oh my god, the recursion!](img/example.png) qweqwe + +And finally, [this is a link!](https://avatars1.githubusercontent.com/u/514903). + +![broken item image](item:this is broken) +![broken item image](block:this is broken) +![broken item image](oredict:this is broken) + +wrap testing +12345678901234567890.1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +`123456789012345678901234567890.12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890` + +* 12345678901234567890.1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +- `123456789012345678901234567890.12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890` + +this is a test for an![](oredict:oc:cpu1)an inline image kakakakalalsd 123 as + +this is a test for an![](oredict:oc:cpu1) +an image with a break after it + +this is a test for an +![](oredict:oc:cpu1) +an image between two lines + +this is a test for an + +![](oredict:oc:cpu1) + +an image between two blank lines diff --git a/src/main/resources/assets/opencomputers/doc/en_US/item/acid.md b/src/main/resources/assets/opencomputers/doc/en_US/item/acid.md index 333f93900..7ea744816 100644 --- a/src/main/resources/assets/opencomputers/doc/en_US/item/acid.md +++ b/src/main/resources/assets/opencomputers/doc/en_US/item/acid.md @@ -2,4 +2,6 @@ ![Reflux?](oredict:oc:materialAcid) -Encountered only when using hard-mode recipes, it is used to etch [circuit boards](circuitBoard.md) before crafting [printed circuit boards](printedCircuitBoard.md). +This tasty [citation needed] concoction can be consumed if you ever feel the need for some... fun. Or ruining your digestive tract. Or both. It can also serve as ingredient in other, more useful items. + +When using hard-mode recipes, it is used to etch [circuit boards](circuitBoard.md) before crafting [printed circuit boards](printedCircuitBoard.md). diff --git a/src/main/resources/assets/opencomputers/doc/en_US/item/index.md b/src/main/resources/assets/opencomputers/doc/en_US/item/index.md index ee645cf42..ba0ca1192 100644 --- a/src/main/resources/assets/opencomputers/doc/en_US/item/index.md +++ b/src/main/resources/assets/opencomputers/doc/en_US/item/index.md @@ -87,3 +87,4 @@ Keep in mind that some of these may not be available, depending on the recipe se ## Other * [Hover Boots](hoverBoots.md) +* [Nanomachines](nanomachines.md) diff --git a/src/main/resources/assets/opencomputers/doc/en_US/item/nanomachines.md b/src/main/resources/assets/opencomputers/doc/en_US/item/nanomachines.md new file mode 100644 index 000000000..226a5d427 --- /dev/null +++ b/src/main/resources/assets/opencomputers/doc/en_US/item/nanomachines.md @@ -0,0 +1,29 @@ +# Nanomachines + +![Nanomachines, son.](oredict:oc:nanomachines) + +These little guys interface with your nervous system to make you harder, better, faster, stronger, or kill you. Sometimes both at the same time! Put simply, nanomachines provide a power driven system for applying buffs (and debuffs) to the player they reside in. To "install" nanomachines, eat them! + +Once injected, a new power indicator in your HUD will indicate how much energy your nanomachines have left to work with. You can recharge them by standing near a [charger](../block/charger.md). The more use you make of the nanomachines, the more energy they'll consume. + +Nanomachines provide a certain number of "inputs" that can be triggered, causing many different effects on the player, ranging from visual effects such as particles spawning near the player, to select potion effects and some more rare and special behaviors! + +Which input triggers what effect depends on the current configuration of the nanomachines, the actual "connections" being random per configuration. This means you'll have to try enabling different inputs to see what they do. If you're unhappy with a configuration, you can always reconfigure your nanomachines. Beware that enabling too many inputs at a time has severe negative effects on you! + +By default, the nanomachines will be on standby. You'll need to control them using wireless messages, so carrying a [tablet](tablet.md) with a [wireless network card](wlanCard.md) is strongly recommended. Nanomachines will only react to wireless signals emitted by devices no further than two meters away, but they will react to messages on any port, and from any device! + +Nanomachines react to a simple, proprietary protocol: each packet must consist of multiple parts, the first of which is the "header" and must equal the string `nanomachines`. The second part must be the command name. Additional parts are parameters for the command. The following commands are available, formatted as `commandName(arg1, ...)`: + +- `setResponsePort(port:number)` - Set the port nanomachines should send response messages to, for commands that have a response. +- `dispose()` - Destroy all nanomachines currently in the player. +- `reconfigure()` - Cause the nanomachines to enter a new configuration. +- `getTotalInputCount()` - Request a message with the total number of available inputs. +- `getSafeInputCount()` - Request a message with the number of *safe* inputs. +- `getInput(index:number)` - Request a message with the current state of the input with the specified index. +- `setInput(index:number, value:boolean)` - Set the state of the input with the specified index to the specified value. +- `getActiveEffects()` - Request a list of active effects. Note that some effects may not show up in this list. +- `getPowerState()` - Request a message with the currently stored and maximum stored energy of the nanomachines. + +For example, in OpenOS: +- `component.modem.broadcast(1, "nanomachines", "setInput", 1, true)` will enable the first input. +- `component.modem.broadcast(1, "nanomachines", "reconfigure")` will reconfigure the nanomachines. 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 ce0ebf1d9..c8087e8f3 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 @@ -26,6 +26,7 @@ * [Геоанализатор](geolyzer.md) * [Датчик движения](motionSensor.md) * [Редстоун-I/O](redstone.md) +* [Транспозер](transposer.md) * [Путевая точка](waypoint.md) ## Сборка / Печать diff --git a/src/main/resources/assets/opencomputers/lang/de_DE.lang b/src/main/resources/assets/opencomputers/lang/de_DE.lang index 199bfec5f..311fa9f35 100644 --- a/src/main/resources/assets/opencomputers/lang/de_DE.lang +++ b/src/main/resources/assets/opencomputers/lang/de_DE.lang @@ -247,7 +247,7 @@ key.materialCosts=Materialkosten anzeigen # Item / Block Tooltips oc:tooltip.AccessPoint=Verhält sich wie ein Switch, aber empfängt zusätzlich Drahtlosnachrichten und leitet Pakete aus dem Festnetz drahtlos weiter. oc:tooltip.AbstractBusCard=Erlaubt es, LIP-Pakete des Abstrakten Busses von §fStargateTech 2§7 zu senden und zu empfangen. -oc:tooltip.Acid=Eine hochgiftige Möchtegernflüssigkeit, wird üblicherweise nur von gewissen Piraten konsumiert. Dank ihrer korrosiven Eigenschaften ideal zum Bedrucken von Leiterplatten geeignet. +oc:tooltip.Acid=Eine hochgiftige Möchtegernflüssigkeit, wird üblicherweise nur von gewissen Piraten konsumiert. Mag sich aber auch zu anderen Zwecken eignen. oc:tooltip.Adapter=Erlaubt es, Blöcke anzusteuern, die keine Komponentenblöcke sind, wie etwa reguläre Minecraft-Blöcke oder Blöcke anderer Mods. oc:tooltip.ALU=Zählt Zahlen zum Zeitvertreib. Klingt komisch, is aber so. oc:tooltip.Analyzer=Erlaubt es, Informationen über Blöcke anzuzeigen, wie zum Bleistift ihre §fAdresse§7 und ihren §fKomponentennamen§7.[nl] Erlaubt zudem, den Fehler anzuzeigen, der zu einem Computerabsturz geführt hat, falls der Computer nicht regulär heruntergefahren wurde. diff --git a/src/main/resources/assets/opencomputers/lang/en_US.lang b/src/main/resources/assets/opencomputers/lang/en_US.lang index c53d25713..5da4b99c1 100644 --- a/src/main/resources/assets/opencomputers/lang/en_US.lang +++ b/src/main/resources/assets/opencomputers/lang/en_US.lang @@ -100,6 +100,7 @@ item.oc.Microchip2.name=Microchip (Tier 3) item.oc.MicrocontrollerCase0.name=Microcontroller Case (Tier 1) item.oc.MicrocontrollerCase1.name=Microcontroller Case (Tier 2) item.oc.MicrocontrollerCase3.name=Microcontroller Case (Creative) +item.oc.Nanomachines.name=Nanomachines item.oc.NetworkCard.name=Network Card item.oc.NumPad.name=Numeric Keypad item.oc.Present.name=A little something... @@ -250,7 +251,7 @@ key.materialCosts=Show Material Costs # Item / Block Tooltips oc:tooltip.AccessPoint=Acts like a Switch, but additionally receives wireless packets and relays wired packets wirelessly. oc:tooltip.AbstractBusCard=Allows interacting with §fStargateTech 2§7's abstract bus by sending and receiving LIP packets. -oc:tooltip.Acid=A highly toxic pseudo-liquid, usually only consumed by certain pirates. Thanks to its corrosive nature it is perfectly suited for etching circuit boards. +oc:tooltip.Acid=A highly toxic pseudo-liquid, usually only consumed by certain pirates. May prove to be useful in other ways, too, however. oc:tooltip.Adapter=Used to control non-component blocks, such as vanilla blocks or blocks from other mods. oc:tooltip.ALU=Adds numbers so you don't have to. It might be better this way. oc:tooltip.Analyzer=Used to display information about blocks, such as their §faddress§7 and §fcomponent name§7.[nl] Also displays the error that caused a computer to crash if it did not shut down normally. @@ -306,6 +307,7 @@ oc:tooltip.Microchip=The chip formerly known as Integrated Circuit. I have no id oc:tooltip.Microcontroller=Microcontrollers are computers boiled down to the essentials. They are intended to take care of very specific tasks, running only a single program that is provided on the EEPROM built into them.[nl] §cCan not connect to external components.§7 oc:tooltip.MicrocontrollerCase=Base component for building microcontrollers. Place it into an assembler to add further components and assemble a microcontroller. oc:tooltip.MotionSensor=Can detect movement of nearby living beings. Requires clear line-of-sight. +oc:tooltip.Nanomachines=Control unit and a bunch of nanomachines for ingestion, if you dare. oc:tooltip.NetworkCard=Allows distant computers connected by other blocks (such as cable) to communicate by sending messages to each other. oc:tooltip.PowerAcceptor=Energy conversion speed: §f%s/t§7 oc:tooltip.PowerConverter.BuildCraft=§fBuildCraft MJ§7: §a%s:%s§7 diff --git a/src/main/resources/assets/opencomputers/loot/OpenOS/bin/sh.lua b/src/main/resources/assets/opencomputers/loot/OpenOS/bin/sh.lua index cfd9a5656..cb19a9592 100644 --- a/src/main/resources/assets/opencomputers/loot/OpenOS/bin/sh.lua +++ b/src/main/resources/assets/opencomputers/loot/OpenOS/bin/sh.lua @@ -384,6 +384,10 @@ end local args, options = shell.parse(...) local history = {} +local function escapeMagic(text) + return text:gsub('[%(%)%.%%%+%-%*%?%[%^%$]', '%%%1') +end + local function getMatchingPrograms(baseName) local result = {} -- TODO only matching files with .lua extension for now, might want to @@ -391,7 +395,7 @@ local function getMatchingPrograms(baseName) if not baseName or #baseName == 0 then baseName = "^(.*)%.lua$" else - baseName = "^(" .. baseName .. ".*)%.lua$" + baseName = "^(" .. escapeMagic(baseName) .. ".*)%.lua$" end for basePath in string.gmatch(os.getenv("PATH"), "[^:]+") do for file in fs.list(basePath) do @@ -404,7 +408,8 @@ local function getMatchingPrograms(baseName) return result end -local function getMatchingFiles(baseName) +local function getMatchingFiles(partialPrefix, name) + local baseName = shell.resolve(partialPrefix .. name) local result, basePath = {} -- note: we strip the trailing / to make it easier to navigate through -- directories using tab completion (since entering the / will then serve @@ -416,12 +421,12 @@ local function getMatchingFiles(baseName) baseName = "^(.-)/?$" else basePath = fs.path(baseName) or "/" - baseName = "^(" .. fs.name(baseName) .. ".-)/?$" + baseName = "^(" .. escapeMagic(fs.name(baseName)) .. ".-)/?$" end for file in fs.list(basePath) do local match = file:match(baseName) if match then - table.insert(result, fs.concat(basePath, match)) + table.insert(result, partialPrefix .. match) end end -- (cont.) but if there's only one match and it's a directory, *then* we @@ -444,10 +449,20 @@ local function hintHandler(line, cursor) -- first part and no path, look for programs in the $PATH result = getMatchingPrograms(line) else -- just look normal files - result = getMatchingFiles(shell.resolve(partial or line)) + local partialPrefix = (partial or line) + local name = fs.name(partialPrefix) + partialPrefix = partialPrefix:sub(1, -name:len() - 1) + result = getMatchingFiles(partialPrefix, name) end + local resultSuffix = "" + if searchInPath then + resultSuffix = " " + elseif #result == 1 and result[1]:sub(-1) ~= '/' then + resultSuffix = " " + end + prefix = prefix or "" for i = 1, #result do - result[i] = (prefix or "") .. result[i] .. (searchInPath and " " or "") + result[i] = prefix .. result[i] .. resultSuffix end table.sort(result) return result diff --git a/src/main/resources/assets/opencomputers/recipes/default.recipes b/src/main/resources/assets/opencomputers/recipes/default.recipes index 9bb0d1f2d..f37df08bf 100644 --- a/src/main/resources/assets/opencomputers/recipes/default.recipes +++ b/src/main/resources/assets/opencomputers/recipes/default.recipes @@ -15,6 +15,11 @@ manual { type: shapeless input: [book, "oc:circuitChip1"] } +nanomachines { + input: [["oc:chamelium", "oc:wlanCard", "oc:chamelium"] + ["oc:cpu2", "oc:materialAcid", "oc:ram1"] + ["oc:chamelium", "oc:capacitor", "oc:chamelium"]] +} texturePicker { input: [[dyeBlack, dyeRed, dyeGreen] [dyeBlue, "oc:analyzer", dyePurple] @@ -360,7 +365,10 @@ nuggetIron { output: 9 } cuttingWire = false -acid = false +acid { + type: shapeless + input: [bucketWater, sugar, slimeball, fermentedSpiderEye, bone] +} ingotIron { input: [[nuggetIron, nuggetIron, nuggetIron], [nuggetIron, nuggetIron, nuggetIron], diff --git a/src/main/resources/assets/opencomputers/recipes/hardmode.recipes b/src/main/resources/assets/opencomputers/recipes/hardmode.recipes index fe0614746..2618e9179 100644 --- a/src/main/resources/assets/opencomputers/recipes/hardmode.recipes +++ b/src/main/resources/assets/opencomputers/recipes/hardmode.recipes @@ -190,10 +190,6 @@ nuggetIron { cuttingWire { input: [[stickWood, nuggetIron, stickWood]] } -acid { - type: shapeless - input: [bucketWater, sugar, slimeball, fermentedSpiderEye, bone] -} disk { input: [["", nuggetIron, ""] [nuggetIron, "", nuggetIron] diff --git a/src/main/resources/assets/opencomputers/textures/gui/nanomachines_power.png b/src/main/resources/assets/opencomputers/textures/gui/nanomachines_power.png new file mode 100644 index 000000000..dd7eb1426 Binary files /dev/null and b/src/main/resources/assets/opencomputers/textures/gui/nanomachines_power.png differ diff --git a/src/main/resources/assets/opencomputers/textures/gui/nanomachines_power_bar.png b/src/main/resources/assets/opencomputers/textures/gui/nanomachines_power_bar.png new file mode 100644 index 000000000..c3dffb793 Binary files /dev/null and b/src/main/resources/assets/opencomputers/textures/gui/nanomachines_power_bar.png differ diff --git a/src/main/resources/assets/opencomputers/textures/items/Nanomachines.png b/src/main/resources/assets/opencomputers/textures/items/Nanomachines.png new file mode 100644 index 000000000..288cb4244 Binary files /dev/null and b/src/main/resources/assets/opencomputers/textures/items/Nanomachines.png differ diff --git a/src/main/resources/assets/opencomputers/textures/items/Nanomachines.png.mcmeta b/src/main/resources/assets/opencomputers/textures/items/Nanomachines.png.mcmeta new file mode 100644 index 000000000..f6730e30a --- /dev/null +++ b/src/main/resources/assets/opencomputers/textures/items/Nanomachines.png.mcmeta @@ -0,0 +1,5 @@ +{ + "animation": { + "frametime": 4 + } +} \ No newline at end of file diff --git a/src/main/scala/li/cil/oc/Constants.scala b/src/main/scala/li/cil/oc/Constants.scala index 42cbe9eec..29d363c05 100644 --- a/src/main/scala/li/cil/oc/Constants.scala +++ b/src/main/scala/li/cil/oc/Constants.scala @@ -119,6 +119,7 @@ object Constants { final val MicrocontrollerCaseCreative = "microcontrollerCaseCreative" final val MicrocontrollerCaseTier1 = "microcontrollerCase1" final val MicrocontrollerCaseTier2 = "microcontrollerCase2" + final val Nanomachines = "nanomachines" final val NavigationUpgrade = "navigationUpgrade" final val NetworkCard = "lanCard" final val NumPad = "numPad" diff --git a/src/main/scala/li/cil/oc/Settings.scala b/src/main/scala/li/cil/oc/Settings.scala index aa8138f1c..b8444fac2 100644 --- a/src/main/scala/li/cil/oc/Settings.scala +++ b/src/main/scala/li/cil/oc/Settings.scala @@ -39,6 +39,13 @@ class Settings(val config: Config) { val beepSampleRate = config.getInt("client.beepSampleRate") val beepAmplitude = config.getInt("client.beepVolume") max 0 min Byte.MaxValue val beepRadius = config.getDouble("client.beepRadius").toFloat max 1 min 32 + val nanomachineHudPos = Array(config.getDoubleList("client.nanomachineHudPos"): _*) match { + case Array(x, y) => + (x: Double, y: Double) + case _ => + OpenComputers.log.warn("Bad number of HUD coordiantes, ignoring.") + (-1.0, -1.0) + } // ----------------------------------------------------------------------- // // computer @@ -157,6 +164,7 @@ class Settings(val config: Config) { val bufferDrone = config.getDouble("power.buffer.drone") max 0 val bufferMicrocontroller = config.getDouble("power.buffer.mcu") max 0 val bufferHoverBoots = config.getDouble("power.buffer.hoverBoots") max 1 + val bufferNanomachines = config.getDouble("power.buffer.nanomachines") max 0 // power.cost val computerCost = config.getDouble("power.cost.computer") max 0 @@ -202,6 +210,8 @@ class Settings(val config: Config) { 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 + val nanomachineCost = config.getDouble("power.cost.nanomachineInput") max 0 + val nanomachineReconfigureCost = config.getDouble("power.cost.nanomachinesReconfigure") max 0 // power.rate val accessPointRate = config.getDouble("power.rate.accessPoint") max 0 @@ -307,7 +317,8 @@ class Settings(val config: Config) { val inputUsername = config.getBoolean("misc.inputUsername") val maxClipboard = config.getInt("misc.maxClipboard") max 0 val maxNetworkPacketSize = config.getInt("misc.maxNetworkPacketSize") max 0 - val maxNetworkPacketParts = config.getInt("misc.maxNetworkPacketParts") max 0 + // Need at least 4 for nanomachine protocol. Because I can! + val maxNetworkPacketParts = config.getInt("misc.maxNetworkPacketParts") max 4 val maxOpenPorts = config.getInt("misc.maxOpenPorts") max 0 val maxWirelessRange = config.getDouble("misc.maxWirelessRange") max 0 val rTreeMaxEntries = 10 @@ -339,6 +350,18 @@ class Settings(val config: Config) { val serverRackSwitchTier = (config.getInt("misc.serverRackSwitchTier") - 1) max Tier.None min Tier.Three val redstoneDelay = config.getDouble("misc.redstoneDelay") max 0 + // ----------------------------------------------------------------------- // + // nanomachines + val nanomachineTriggerQuota = config.getDouble("nanomachines.triggerQuota") max 0 + val nanomachineConnectorQuota = config.getDouble("nanomachines.connectorQuota") max 0 + val nanomachineMaxInputs = config.getInt("nanomachines.maxInputs") max 1 + val nanomachineMaxOutputs = config.getInt("nanomachines.maxOutputs") max 1 + val nanomachinesSafeInputCount = config.getDouble("nanomachines.safeInputCount") max 0 min 1 + val nanomachineReconfigureTimeout = config.getDouble("nanomachines.reconfigureCooldown") max 0 + val nanomachineMagnetRange = config.getDouble("nanomachines.magnetRange") max 0 + val nanomachineDisintegrationRange = config.getInt("nanomachines.disintegrationRange") max 0 + val nanomachinePotionBlacklist = config.getAnyRefList("nanomachines.potionBlacklist") + // ----------------------------------------------------------------------- // // printer val maxPrintComplexity = config.getInt("printer.maxShapes") diff --git a/src/main/scala/li/cil/oc/client/PacketHandler.scala b/src/main/scala/li/cil/oc/client/PacketHandler.scala index a240f4f01..8cf0dadc5 100644 --- a/src/main/scala/li/cil/oc/client/PacketHandler.scala +++ b/src/main/scala/li/cil/oc/client/PacketHandler.scala @@ -4,12 +4,14 @@ import java.io.EOFException import li.cil.oc.Localization import li.cil.oc.Settings +import li.cil.oc.api import li.cil.oc.api.component import li.cil.oc.api.event.FileSystemAccessEvent import li.cil.oc.client.renderer.PetRenderer import li.cil.oc.common.Loot import li.cil.oc.common.PacketType import li.cil.oc.common.container +import li.cil.oc.common.nanomachines.ControllerImpl import li.cil.oc.common.tileentity._ import li.cil.oc.common.tileentity.traits._ import li.cil.oc.common.{PacketHandler => CommonPacketHandler} @@ -60,6 +62,9 @@ object PacketHandler extends CommonPacketHandler { case PacketType.HologramTranslation => onHologramPositionOffsetY(p) case PacketType.HologramValues => onHologramValues(p) case PacketType.LootDisk => onLootDisk(p) + case PacketType.NanomachinesConfiguration => onNanomachinesConfiguration(p) + case PacketType.NanomachinesInputs => onNanomachinesInputs(p) + case PacketType.NanomachinesPower => onNanomachinesPower(p) case PacketType.NetSplitterState => onNetSplitterState(p) case PacketType.ParticleEffect => onParticleEffect(p) case PacketType.PetVisibility => onPetVisibility(p) @@ -283,6 +288,51 @@ object PacketHandler extends CommonPacketHandler { } } + def onNanomachinesConfiguration(p: PacketParser) = { + p.readEntity[EntityPlayer]() match { + case Some(player) => + val hasController = p.readBoolean() + if (hasController) { + api.Nanomachines.installController(player) match { + case controller: ControllerImpl => controller.load(p.readNBT()) + case _ => // Wat. + } + } + else { + api.Nanomachines.uninstallController(player) + } + case _ => // Invalid packet. + } + } + + def onNanomachinesInputs(p: PacketParser) = { + p.readEntity[EntityPlayer]() match { + case Some(player) => api.Nanomachines.getController(player) match { + case controller: ControllerImpl => + val inputs = new Array[Byte](p.readInt()) + p.read(inputs) + controller.configuration.synchronized { + for ((value, index) <- inputs.zipWithIndex if index < controller.configuration.triggers.length) { + controller.configuration.triggers(index).isActive = value == 1 + } + controller.activeBehaviorsDirty = true + } + case _ => // Wat. + } + case _ => // Invalid packet. + } + } + + def onNanomachinesPower(p: PacketParser) = { + p.readEntity[EntityPlayer]() match { + case Some(player) => api.Nanomachines.getController(player) match { + case controller: ControllerImpl => controller.storedEnergy = p.readDouble() + case _ => // Wat. + } + case _ => // Invalid packet. + } + } + def onNetSplitterState(p: PacketParser) = p.readTileEntity[NetSplitter]() match { case Some(t) => diff --git a/src/main/scala/li/cil/oc/client/Proxy.scala b/src/main/scala/li/cil/oc/client/Proxy.scala index e799b35bc..d967a4b9f 100644 --- a/src/main/scala/li/cil/oc/client/Proxy.scala +++ b/src/main/scala/li/cil/oc/client/Proxy.scala @@ -13,6 +13,8 @@ import li.cil.oc.client.renderer.entity.DroneRenderer import li.cil.oc.client.renderer.tileentity._ import li.cil.oc.common.component.TextBuffer import li.cil.oc.common.entity.Drone +import li.cil.oc.common.event.NanomachinesEventHandler +import li.cil.oc.common.init.Items import li.cil.oc.common.item.traits.Delegate import li.cil.oc.common.tileentity import li.cil.oc.common.tileentity.ServerRack @@ -79,6 +81,7 @@ private[oc] class Proxy extends CommonProxy { ClientRegistry.registerKeyBinding(KeyBindings.clipboardPaste) MinecraftForge.EVENT_BUS.register(HighlightRenderer) + MinecraftForge.EVENT_BUS.register(NanomachinesEventHandler.Client) MinecraftForge.EVENT_BUS.register(PetRenderer) MinecraftForge.EVENT_BUS.register(ServerRack) MinecraftForge.EVENT_BUS.register(Sound) diff --git a/src/main/scala/li/cil/oc/client/Textures.scala b/src/main/scala/li/cil/oc/client/Textures.scala index e01630ba0..1b1e3a45f 100644 --- a/src/main/scala/li/cil/oc/client/Textures.scala +++ b/src/main/scala/li/cil/oc/client/Textures.scala @@ -47,6 +47,8 @@ object Textures { val ManualHome = L("manual_home") val ManualMissingItem = L("manual_missing_item") val ManualTab = L("manual_tab") + val Nanomachines = L("nanomachines_power") + val NanomachinesBar = L("nanomachines_power_bar") val Printer = L("printer") val PrinterInk = L("printer_ink") val PrinterMaterial = L("printer_material") diff --git a/src/main/scala/li/cil/oc/common/PacketBuilder.scala b/src/main/scala/li/cil/oc/common/PacketBuilder.scala index 6332e932e..b0b5b0dd7 100644 --- a/src/main/scala/li/cil/oc/common/PacketBuilder.scala +++ b/src/main/scala/li/cil/oc/common/PacketBuilder.scala @@ -54,6 +54,8 @@ abstract class PacketBuilder(stream: OutputStream) extends DataOutputStream(stre def sendToAllPlayers() = OpenComputers.channel.sendToAll(packet) + def sendToPlayersNearEntity(e: Entity, range: Option[Double] = None): Unit = sendToNearbyPlayers(e.getEntityWorld, e.posX, e.posY, e.posZ, range) + def sendToPlayersNearTileEntity(t: TileEntity, range: Option[Double] = None): Unit = sendToNearbyPlayers(t.getWorld, t.getPos.getX + 0.5, t.getPos.getY + 0.5, t.getPos.getZ + 0.5, range) def sendToPlayersNearHost(host: EnvironmentHost, range: Option[Double] = None): Unit = sendToNearbyPlayers(host.world, host.xPosition, host.yPosition, host.zPosition, range) diff --git a/src/main/scala/li/cil/oc/common/PacketType.scala b/src/main/scala/li/cil/oc/common/PacketType.scala index 4ad59cad7..ec1a22d77 100644 --- a/src/main/scala/li/cil/oc/common/PacketType.scala +++ b/src/main/scala/li/cil/oc/common/PacketType.scala @@ -22,6 +22,9 @@ object PacketType extends Enumeration { HologramTranslation, HologramValues, LootDisk, + NanomachinesConfiguration, + NanomachinesInputs, + NanomachinesPower, NetSplitterState, ParticleEffect, PetVisibility, // Goes both ways. diff --git a/src/main/scala/li/cil/oc/common/Proxy.scala b/src/main/scala/li/cil/oc/common/Proxy.scala index 41fa708d0..7e8eae96a 100644 --- a/src/main/scala/li/cil/oc/common/Proxy.scala +++ b/src/main/scala/li/cil/oc/common/Proxy.scala @@ -73,6 +73,7 @@ class Proxy { api.API.fileSystem = fs.FileSystem api.API.items = Items api.API.machine = machine.Machine + api.API.nanomachines = nanomachines.Nanomachines api.API.network = network.Network api.API.config = Settings.get.config diff --git a/src/main/scala/li/cil/oc/common/event/HoverBootsHandler.scala b/src/main/scala/li/cil/oc/common/event/HoverBootsHandler.scala index 02976af87..e8dd72c6a 100644 --- a/src/main/scala/li/cil/oc/common/event/HoverBootsHandler.scala +++ b/src/main/scala/li/cil/oc/common/event/HoverBootsHandler.scala @@ -18,7 +18,7 @@ object HoverBootsHandler { val hasHoverBoots = !player.isSneaking && equippedArmor(player).exists(stack => stack.getItem match { case boots: HoverBoots => Settings.get.ignorePower || { - if (player.onGround && !player.capabilities.isCreativeMode && player.worldObj.getTotalWorldTime % 20 == 0) { + if (player.onGround && !player.capabilities.isCreativeMode && player.worldObj.getTotalWorldTime % Settings.get.tickFrequency == 0) { val velocity = player.motionX * player.motionX + player.motionY * player.motionY + player.motionZ * player.motionZ if (velocity > 0.015f) { boots.charge(stack, -Settings.get.hoverBootMove, simulate = false) diff --git a/src/main/scala/li/cil/oc/common/event/NanomachinesEventHandler.scala b/src/main/scala/li/cil/oc/common/event/NanomachinesEventHandler.scala new file mode 100644 index 000000000..d546af4e7 --- /dev/null +++ b/src/main/scala/li/cil/oc/common/event/NanomachinesEventHandler.scala @@ -0,0 +1,146 @@ +package li.cil.oc.common.event + +import java.io.FileInputStream +import java.io.FileOutputStream + +import li.cil.oc.OpenComputers +import li.cil.oc.Settings +import li.cil.oc.api +import li.cil.oc.api.nanomachines.Controller +import li.cil.oc.client.Textures +import li.cil.oc.common.nanomachines.ControllerImpl +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.ScaledResolution +import net.minecraft.client.renderer.Tessellator +import net.minecraft.entity.player.EntityPlayer +import net.minecraft.nbt.CompressedStreamTools +import net.minecraft.nbt.NBTTagCompound +import net.minecraftforge.client.event.RenderGameOverlayEvent +import net.minecraftforge.event.entity.living.LivingEvent +import net.minecraftforge.event.entity.player.PlayerEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +object NanomachinesEventHandler { + + object Client { + @SubscribeEvent + def onRenderGameOverlay(e: RenderGameOverlayEvent.Post): Unit = { + if (e.`type` == RenderGameOverlayEvent.ElementType.TEXT) { + val mc = Minecraft.getMinecraft + api.Nanomachines.getController(mc.thePlayer) match { + case controller: Controller => + val res = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight) + val sizeX = 8 + val sizeY = 12 + val width = res.getScaledWidth + val height = res.getScaledHeight + val (x, y) = Settings.get.nanomachineHudPos + val left = + math.min(width - sizeX, + if (x < 0) width / 2 - 91 - 12 + else if (x < 1) width * x + else x) + val top = + math.min(height - sizeY, + if (y < 0) height - 39 + else if (y < 1) y * height + else y) + val fill = controller.getLocalBuffer / controller.getLocalBufferSize + Minecraft.getMinecraft.getTextureManager.bindTexture(Textures.GUI.Nanomachines) + drawRect(left.toInt, top.toInt, sizeX, sizeY, sizeX, sizeY) + Minecraft.getMinecraft.getTextureManager.bindTexture(Textures.GUI.NanomachinesBar) + drawRect(left.toInt, top.toInt, sizeX, sizeY, sizeX, sizeY, fill) + case _ => // Nothing to show. + } + } + } + + private def drawRect(x: Int, y: Int, w: Int, h: Int, tw: Int, th: Int, fill: Double = 1) { + val sx = 1f / tw + val sy = 1f / th + val t = Tessellator.getInstance + val r = t.getWorldRenderer + r.startDrawingQuads() + r.addVertexWithUV(x, y + h, 0, 0, h * sy) + r.addVertexWithUV(x + w, y + h, 0, w * sx, h * sy) + r.addVertexWithUV(x + w, y + h * (1 - fill), 0, w * sx, 1 - fill) + r.addVertexWithUV(x, y + h * (1 - fill), 0, 0, 1 - fill) + t.draw() + } + } + + object Common { + @SubscribeEvent + def onLivingUpdate(e: LivingEvent.LivingUpdateEvent): Unit = { + e.entity match { + case player: EntityPlayer => api.Nanomachines.getController(player) match { + case controller: ControllerImpl => + if (controller.player eq player) { + controller.update() + } + else { + // Player entity instance changed (e.g. respawn), recreate the controller. + val nbt = new NBTTagCompound() + controller.save(nbt) + api.Nanomachines.uninstallController(controller.player) + api.Nanomachines.installController(player) match { + case newController: ControllerImpl => + newController.load(nbt) + newController.reset() + case _ => // Eh? + } + } + case _ => // Not a player with nanomachines. + } + case _ => // Not a player. + } + } + + @SubscribeEvent + def onPlayerSave(e: PlayerEvent.SaveToFile): Unit = { + val file = e.getPlayerFile("ocnm") + api.Nanomachines.getController(e.entityPlayer) match { + case controller: ControllerImpl => + try { + val nbt = new NBTTagCompound() + controller.save(nbt) + val fos = new FileOutputStream(file) + try CompressedStreamTools.writeCompressed(nbt, fos) catch { + case t: Throwable => + OpenComputers.log.warn("Error saving nanomachine state.", t) + } + fos.close() + } + catch { + case t: Throwable => + OpenComputers.log.warn("Error saving nanomachine state.", t) + } + case _ => // Not a player with nanomachines. + } + } + + @SubscribeEvent + def onPlayerLoad(e: PlayerEvent.LoadFromFile): Unit = { + val file = e.getPlayerFile("ocnm") + if (file.exists()) { + api.Nanomachines.getController(e.entityPlayer) match { + case controller: ControllerImpl => + try { + val fis = new FileInputStream(file) + try controller.load(CompressedStreamTools.readCompressed(fis)) catch { + case t: Throwable => + OpenComputers.log.warn("Error loading nanomachine state.", t) + } + fis.close() + } + catch { + case t: Throwable => + OpenComputers.log.warn("Error loading nanomachine state.", t) + } + case _ => // Not a player with nanomachines. + } + } + } + } + +} diff --git a/src/main/scala/li/cil/oc/common/item/Acid.scala b/src/main/scala/li/cil/oc/common/item/Acid.scala index 9f2d8480f..6c79e8df2 100644 --- a/src/main/scala/li/cil/oc/common/item/Acid.scala +++ b/src/main/scala/li/cil/oc/common/item/Acid.scala @@ -1,3 +1,34 @@ package li.cil.oc.common.item -class Acid(val parent: Delegator) extends traits.Delegate +import net.minecraft.entity.player.EntityPlayer +import net.minecraft.item.EnumAction +import net.minecraft.item.ItemStack +import net.minecraft.potion.Potion +import net.minecraft.potion.PotionEffect +import net.minecraft.world.World + +class Acid(val parent: Delegator) extends traits.Delegate { + override def onItemRightClick(stack: ItemStack, world: World, player: EntityPlayer): ItemStack = { + player.setItemInUse(stack, getMaxItemUseDuration(stack)) + stack + } + + override def getItemUseAction(stack: ItemStack): EnumAction = EnumAction.DRINK + + override def getMaxItemUseDuration(stack: ItemStack): Int = 32 + + override def onItemUseFinish(stack: ItemStack, world: World, player: EntityPlayer): ItemStack = { + if (!world.isRemote) { + player.addPotionEffect(new PotionEffect(Potion.blindness.id, 200)) + player.addPotionEffect(new PotionEffect(Potion.poison.id, 100)) + player.addPotionEffect(new PotionEffect(Potion.moveSlowdown.id, 600)) + player.addPotionEffect(new PotionEffect(Potion.confusion.id, 1200)) + player.addPotionEffect(new PotionEffect(Potion.fireResistance.id, 6000)) + player.addPotionEffect(new PotionEffect(Potion.saturation.id, 2000)) + + stack.stackSize -= 1 + } + if (stack.stackSize > 0) stack + else null + } +} diff --git a/src/main/scala/li/cil/oc/common/item/Nanomachines.scala b/src/main/scala/li/cil/oc/common/item/Nanomachines.scala new file mode 100644 index 000000000..4318f239b --- /dev/null +++ b/src/main/scala/li/cil/oc/common/item/Nanomachines.scala @@ -0,0 +1,30 @@ +package li.cil.oc.common.item + +import li.cil.oc.api +import net.minecraft.entity.player.EntityPlayer +import net.minecraft.item.EnumAction +import net.minecraft.item.ItemStack +import net.minecraft.world.World + +class Nanomachines(val parent: Delegator) extends traits.Delegate { + override def onItemRightClick(stack: ItemStack, world: World, player: EntityPlayer): ItemStack = { + if (!api.Nanomachines.hasController(player)) { + player.setItemInUse(stack, getMaxItemUseDuration(stack)) + } + stack + } + + override def getItemUseAction(stack: ItemStack): EnumAction = EnumAction.EAT + + override def getMaxItemUseDuration(stack: ItemStack): Int = 32 + + override def onItemUseFinish(stack: ItemStack, world: World, player: EntityPlayer): ItemStack = { + if (!world.isRemote && !api.Nanomachines.hasController(player)) { + api.Nanomachines.installController(player).reconfigure() + + stack.stackSize -= 1 + } + if (stack.stackSize > 0) stack + else null + } +} diff --git a/src/main/scala/li/cil/oc/common/nanomachines/ControllerImpl.scala b/src/main/scala/li/cil/oc/common/nanomachines/ControllerImpl.scala new file mode 100644 index 000000000..3b27983a7 --- /dev/null +++ b/src/main/scala/li/cil/oc/common/nanomachines/ControllerImpl.scala @@ -0,0 +1,302 @@ +package li.cil.oc.common.nanomachines + +import java.lang +import java.util.UUID + +import com.google.common.base.Charsets +import com.google.common.base.Strings +import li.cil.oc.Settings +import li.cil.oc.api +import li.cil.oc.api.nanomachines.Behavior +import li.cil.oc.api.nanomachines.Controller +import li.cil.oc.api.network.Packet +import li.cil.oc.api.network.WirelessEndpoint +import li.cil.oc.server.PacketSender +import li.cil.oc.util.BlockPosition +import li.cil.oc.util.ExtendedNBT._ +import li.cil.oc.util.PlayerUtils +import net.minecraft.entity.player.EntityPlayer +import net.minecraft.entity.player.EntityPlayerMP +import net.minecraft.nbt.NBTTagCompound +import net.minecraft.potion.Potion +import net.minecraft.potion.PotionEffect +import net.minecraft.util.EnumParticleTypes +import net.minecraft.world.World + +import scala.collection.convert.WrapAsJava._ +import scala.collection.convert.WrapAsScala._ +import scala.collection.mutable + +class ControllerImpl(val player: EntityPlayer) extends Controller with WirelessEndpoint { + if (isServer) api.Network.joinWirelessNetwork(this) + + final val MaxSenderDistance = 2f + final val FullSyncInterval = 20 * 60 + + var uuid = UUID.randomUUID.toString + var responsePort = 0 + var storedEnergy = Settings.get.bufferNanomachines * 0.25 + var hadPower = true + val configuration = new NeuralNetwork(this) + val activeBehaviors = mutable.Set.empty[Behavior] + var activeBehaviorsDirty = true + var configCooldown = 0 + var hasSentConfiguration = false + + override def world: World = player.getEntityWorld + + override def x: Int = BlockPosition(player).x + + override def y: Int = BlockPosition(player).y + + override def z: Int = BlockPosition(player).z + + override def receivePacket(packet: Packet, sender: WirelessEndpoint): Unit = { + if (getLocalBuffer > 0) { + val (dx, dy, dz) = ((sender.x + 0.5) - player.posX, (sender.y + 0.5) - player.posY, (sender.z + 0.5) - player.posZ) + val dSquared = dx * dx + dy * dy + dz * dz + if (dSquared < MaxSenderDistance * MaxSenderDistance) packet.data.headOption match { + case Some(header: Array[Byte]) if new String(header, Charsets.UTF_8) == "nanomachines" => + val command = packet.data.drop(1).map { + case value: Array[Byte] => new String(value, Charsets.UTF_8) + case value => value + } + command match { + case Array("setResponsePort", port: java.lang.Number) => + responsePort = port.intValue max 0 min 0xFFFF + respond(sender, "responsePort", responsePort) + case Array("dispose") => + api.Nanomachines.uninstallController(player) + respond(sender, "disposed") + case Array("reconfigure") => + reconfigure() + respond(sender, "reconfigured") + case Array("getTotalInputCount") => + respond(sender, "totalInputCount", getTotalInputCount) + case Array("getSafeInputCount") => + respond(sender, "safeInputCount", getSafeInputCount) + case Array("getInput", index: java.lang.Number) => + try { + val trigger = getInput(index.intValue - 1) + respond(sender, "input", index.intValue, trigger) + } + catch { + case _: Throwable => + respond(sender, "input", "error") + } + case Array("setInput", index: java.lang.Number, value: java.lang.Boolean) => + try { + setInput(index.intValue - 1, value.booleanValue) + respond(sender, "input", index.intValue, getInput(index.intValue - 1)) + } + catch { + case _: Throwable => + respond(sender, "input", "error") + } + case Array("getActiveEffects") => + configuration.synchronized { + val names = getActiveBehaviors.map(_.getNameHint).filterNot(Strings.isNullOrEmpty) + val joined = "{" + names.map(_.replace(',', '_').replace('"', '_')).mkString(",") + "}" + respond(sender, "active", joined) + } + case Array("getPowerState") => + respond(sender, "power", getLocalBuffer, getLocalBufferSize) + case _ => // Ignore. + } + case _ => // Not for us. + } + } + } + + def respond(endpoint: WirelessEndpoint, data: Any*): Unit = { + if (responsePort > 0) { + val cost = Settings.get.wirelessCostPerRange * 10 + val epsilon = 0.1 + if (changeBuffer(-cost) > -epsilon) { + val packet = api.Network.newPacket(uuid, null, responsePort, (Iterable("nanomachines") ++ data.map(_.asInstanceOf[AnyRef])).toArray) + api.Network.sendWirelessPacket(this, 10, packet) + } + } + } + + // ----------------------------------------------------------------------- // + + override def reconfigure() = { + if (isServer && configCooldown < 1) configuration.synchronized { + configuration.reconfigure() + activeBehaviorsDirty = true + configCooldown = (Settings.get.nanomachineReconfigureTimeout * 20).toInt + + player match { + case playerMP: EntityPlayerMP if playerMP.playerNetServerHandler != null => + player.addPotionEffect(new PotionEffect(Potion.blindness.id, 100)) + player.addPotionEffect(new PotionEffect(Potion.poison.id, 150)) + player.addPotionEffect(new PotionEffect(Potion.moveSlowdown.id, 200)) + changeBuffer(-Settings.get.nanomachineReconfigureCost) + + hasSentConfiguration = false + case _ => // We're still setting up / loading. + } + } + this + } + + override def getTotalInputCount: Int = configuration.synchronized(configuration.triggers.length) + + override def getSafeInputCount: Int = configuration.synchronized(configuration.triggers.length * Settings.get.nanomachinesSafeInputCount).toInt + + override def getInput(index: Int): Boolean = configuration.synchronized(configuration.triggers(index).isActive) + + override def setInput(index: Int, value: Boolean): Unit = { + if (isServer && configCooldown < 1) configuration.synchronized { + configuration.triggers(index).isActive = value + activeBehaviorsDirty = true + } + } + + override def getActiveBehaviors: lang.Iterable[Behavior] = configuration.synchronized { + cleanActiveBehaviors() + activeBehaviors + } + + override def getInputCount(behavior: Behavior): Int = configuration.synchronized(configuration.inputs(behavior)) + + // ----------------------------------------------------------------------- // + + override def getLocalBuffer: Double = storedEnergy + + override def getLocalBufferSize: Double = Settings.get.bufferNanomachines + + override def changeBuffer(delta: Double): Double = { + if (isClient) delta + else if (delta < 0 && (Settings.get.ignorePower || player.capabilities.isCreativeMode)) 0.0 + else { + val newValue = storedEnergy + delta + storedEnergy = math.min(math.max(newValue, 0), getLocalBufferSize) + newValue - storedEnergy + } + } + + // ----------------------------------------------------------------------- // + + def update(): Unit = { + if (isServer) { + api.Network.updateWirelessNetwork(this) + } + + if (configCooldown > 0) { + configCooldown -= 1 + return + } + + val hasPower = getLocalBuffer > 0 || Settings.get.ignorePower + lazy val active = getActiveBehaviors.toIterable // Wrap once. + lazy val activeInputs = configuration.triggers.count(_.isActive) + + if (hasPower != hadPower) { + if (!hasPower) active.foreach(_.onDisable()) + else active.foreach(_.onEnable()) + } + + if (hasPower) { + active.foreach(_.update()) + + if (isServer) { + if (player.getEntityWorld.getTotalWorldTime % Settings.get.tickFrequency == 0) { + changeBuffer(-Settings.get.nanomachineCost * Settings.get.tickFrequency * (activeInputs + 0.5)) + PacketSender.sendNanomachinePower(player) + } + + val overload = activeInputs - getSafeInputCount + if (!player.capabilities.isCreativeMode && overload > 0 && player.getEntityWorld.getTotalWorldTime % 20 == 0) { + player.setHealth(player.getHealth - overload) + player.performHurtAnimation() + } + } + + if (isClient) { + val energyRatio = getLocalBuffer / (getLocalBufferSize + 1) + val triggerRatio = activeInputs / (configuration.triggers.length + 1) + val intensity = (energyRatio + triggerRatio) * 0.25 + PlayerUtils.spawnParticleAround(player, EnumParticleTypes.PORTAL, intensity) + } + } + + if (isServer) { + // Send new power state, if it changed. + if (hadPower != hasPower) { + PacketSender.sendNanomachinePower(player) + } + + // Send a full sync every now and then, e.g. for other players coming + // closer that weren't there to get the initial info for an enabled + // input. + if (!hasSentConfiguration || player.getEntityWorld.getTotalWorldTime % FullSyncInterval == 0) { + hasSentConfiguration = true + PacketSender.sendNanomachineConfiguration(player) + } + } + + hadPower = hasPower + } + + def reset(): Unit = { + configuration.synchronized { + for (index <- 0 until getTotalInputCount) { + configuration.triggers(index).isActive = false + activeBehaviorsDirty = true + } + } + } + + def dispose(): Unit = { + reset() + if (isServer) { + api.Network.leaveWirelessNetwork(this) + PacketSender.sendNanomachineConfiguration(player) + } + } + + // ----------------------------------------------------------------------- // + + def save(nbt: NBTTagCompound): Unit = configuration.synchronized { + nbt.setString("uuid", uuid) + nbt.setInteger("port", responsePort) + nbt.setDouble("energy", storedEnergy) + nbt.setNewCompoundTag("configuration", configuration.save) + } + + def load(nbt: NBTTagCompound): Unit = configuration.synchronized { + uuid = nbt.getString("uuid") + responsePort = nbt.getInteger("port") + storedEnergy = nbt.getDouble("energy") + configuration.load(nbt.getCompoundTag("configuration")) + activeBehaviorsDirty = true + } + + // ----------------------------------------------------------------------- // + + private def isClient = world.isRemote + + private def isServer = !isClient + + private def cleanActiveBehaviors(): Unit = { + if (activeBehaviorsDirty) { + configuration.synchronized(if (activeBehaviorsDirty) { + activeBehaviors.clear() + val newBehaviors = configuration.behaviors.filter(_.isActive).map(_.behavior) + val addedBehaviors = newBehaviors -- activeBehaviors + val removedBehaviors = activeBehaviors -- newBehaviors + activeBehaviors.clear() + activeBehaviors ++= newBehaviors + activeBehaviorsDirty = false + addedBehaviors.foreach(_.onEnable()) + removedBehaviors.foreach(_.onDisable()) + + if (isServer) { + PacketSender.sendNanomachineInputs(player) + } + }) + } + } +} diff --git a/src/main/scala/li/cil/oc/common/nanomachines/Nanomachines.scala b/src/main/scala/li/cil/oc/common/nanomachines/Nanomachines.scala new file mode 100644 index 000000000..afaf3433a --- /dev/null +++ b/src/main/scala/li/cil/oc/common/nanomachines/Nanomachines.scala @@ -0,0 +1,50 @@ +package li.cil.oc.common.nanomachines + +import li.cil.oc.Settings +import li.cil.oc.api +import li.cil.oc.api.nanomachines.BehaviorProvider +import li.cil.oc.api.nanomachines.Controller +import li.cil.oc.util.PlayerUtils +import net.minecraft.entity.player.EntityPlayer + +import scala.collection.convert.WrapAsJava._ +import scala.collection.mutable + +object Nanomachines extends api.detail.NanomachinesAPI { + val providers = mutable.Set.empty[BehaviorProvider] + + val serverControllers = mutable.WeakHashMap.empty[EntityPlayer, ControllerImpl] + val clientControllers = mutable.WeakHashMap.empty[EntityPlayer, ControllerImpl] + + def controllers(player: EntityPlayer) = if (player.getEntityWorld.isRemote) clientControllers else serverControllers + + override def addProvider(provider: BehaviorProvider): Unit = providers += provider + + override def getProviders: java.lang.Iterable[BehaviorProvider] = providers + + def getController(player: EntityPlayer): Controller = { + if (hasController(player)) controllers(player).getOrElseUpdate(player, new ControllerImpl(player)) + else null + } + + def hasController(player: EntityPlayer) = { + PlayerUtils.persistedData(player).getBoolean(Settings.namespace + "hasNanomachines") + } + + def installController(player: EntityPlayer) = { + if (!hasController(player)) { + PlayerUtils.persistedData(player).setBoolean(Settings.namespace + "hasNanomachines", true) + } + getController(player) // Initialize controller instance. + } + + override def uninstallController(player: EntityPlayer): Unit = { + getController(player) match { + case controller: ControllerImpl => + PlayerUtils.persistedData(player).removeTag(Settings.namespace + "hasNanomachines") + controllers(player) -= player + controller.dispose() + case _ => // Doesn't have one anyway. + } + } +} diff --git a/src/main/scala/li/cil/oc/common/nanomachines/NeuralNetwork.scala b/src/main/scala/li/cil/oc/common/nanomachines/NeuralNetwork.scala new file mode 100644 index 000000000..6d1503e1c --- /dev/null +++ b/src/main/scala/li/cil/oc/common/nanomachines/NeuralNetwork.scala @@ -0,0 +1,161 @@ +package li.cil.oc.common.nanomachines + +import li.cil.oc.Settings +import li.cil.oc.api +import li.cil.oc.api.Persistable +import li.cil.oc.api.nanomachines.Behavior +import li.cil.oc.api.nanomachines.BehaviorProvider +import li.cil.oc.util.ExtendedNBT._ +import net.minecraft.nbt.NBTTagCompound +import net.minecraftforge.common.util.Constants.NBT + +import scala.collection.convert.WrapAsScala._ +import scala.collection.mutable +import scala.util.Random + +class NeuralNetwork(controller: ControllerImpl) extends Persistable { + val triggers = mutable.ArrayBuffer.empty[TriggerNeuron] + val connectors = mutable.ArrayBuffer.empty[ConnectorNeuron] + val behaviors = mutable.ArrayBuffer.empty[BehaviorNeuron] + + val behaviorMap = mutable.Map.empty[Behavior, BehaviorNeuron] + + def inputs(behavior: Behavior) = behaviorMap.get(behavior) match { + case Some(node) => node.inputs.count(_.isActive) + case _ => 0 + } + + def reconfigure(): Unit = { + // Rebuild list of valid behaviors. + behaviors.clear() + behaviors ++= api.Nanomachines.getProviders. + map(p => (p, Option(p.createBehaviors(controller.player)).map(_.filter(_ != null)).orNull)). // Remove null behaviors. + filter(_._2 != null). // Remove null lists.. + flatMap(pb => pb._2.map(b => new BehaviorNeuron(pb._1, b))) + + // Adjust length of trigger list and reset. + while (triggers.length > behaviors.length * Settings.get.nanomachineTriggerQuota) { + triggers.remove(triggers.length - 1) + } + triggers.foreach(_.isActive = false) + while (triggers.length < behaviors.length * Settings.get.nanomachineTriggerQuota) { + triggers += new TriggerNeuron() + } + + // Adjust length of connector list and reset. + while (connectors.length > behaviors.length * Settings.get.nanomachineConnectorQuota) { + connectors.remove(connectors.length - 1) + } + connectors.foreach(_.inputs.clear()) + while (connectors.length < behaviors.length * Settings.get.nanomachineConnectorQuota) { + connectors += new ConnectorNeuron() + } + + // Build connections. + val rng = new Random(controller.player.getEntityWorld.rand.nextInt()) + + def connect[Sink <: ConnectorNeuron, Source <: Neuron](sinks: Iterable[Sink], sources: mutable.ArrayBuffer[Source]): Unit = { + val sinkPool = sinks.toBuffer + rng.shuffle(sinkPool) + for (sink <- sinkPool if sources.nonEmpty) { + for (n <- 0 to rng.nextInt(Settings.get.nanomachineMaxInputs) if sources.nonEmpty) { + val sourceIndex = rng.nextInt(sources.length) + sink.inputs += sources.remove(sourceIndex) + } + } + } + + // Shuffle behavior and connector list to give each entry the same chance. + rng.shuffle(connectors) + rng.shuffle(behaviors) + + // Connect connectors to triggers, then behaviors to connectors and/or remaining triggers. + val sourcePool = mutable.ArrayBuffer.fill(Settings.get.nanomachineMaxOutputs)(triggers.map(_.asInstanceOf[Neuron])).flatten + connect(connectors, sourcePool) + sourcePool ++= mutable.ArrayBuffer.fill(Settings.get.nanomachineMaxOutputs)(connectors.map(_.asInstanceOf[Neuron])).flatten + connect(behaviors, sourcePool) + + // Clean up dead nodes. + val deadConnectors = connectors.filter(_.inputs.isEmpty) + connectors --= deadConnectors + behaviors.foreach(_.inputs --= deadConnectors) + + val deadBehaviors = behaviors.filter(_.inputs.isEmpty) + behaviors --= deadBehaviors + + behaviorMap.clear() + behaviorMap ++= behaviors.map(n => n.behavior -> n) + } + + override def save(nbt: NBTTagCompound): Unit = { + nbt.setNewTagList("triggers", triggers.map(t => { + val nbt = new NBTTagCompound() + nbt.setBoolean("isActive", t.isActive) + nbt + })) + + nbt.setNewTagList("connectors", connectors.map(c => { + val nbt = new NBTTagCompound() + nbt.setIntArray("triggerInputs", c.inputs.map(triggers.indexOf(_)).filter(_ >= 0).toArray) + nbt + })) + + nbt.setNewTagList("behaviors", behaviors.map(b => { + val nbt = new NBTTagCompound() + nbt.setIntArray("triggerInputs", b.inputs.map(triggers.indexOf(_)).filter(_ >= 0).toArray) + nbt.setIntArray("connectorInputs", b.inputs.map(connectors.indexOf(_)).filter(_ >= 0).toArray) + nbt.setTag("behavior", b.provider.writeToNBT(b.behavior)) + nbt + })) + } + + override def load(nbt: NBTTagCompound): Unit = { + triggers.clear() + nbt.getTagList("triggers", NBT.TAG_COMPOUND).foreach((t: NBTTagCompound) => { + val neuron = new TriggerNeuron() + neuron.isActive = t.getBoolean("isActive") + triggers += neuron + }) + + connectors.clear() + nbt.getTagList("connectors", NBT.TAG_COMPOUND).foreach((t: NBTTagCompound) => { + val neuron = new ConnectorNeuron() + neuron.inputs ++= t.getIntArray("triggerInputs").map(triggers.apply) + connectors += neuron + }) + + behaviors.clear() + nbt.getTagList("behaviors", NBT.TAG_COMPOUND).foreach((t: NBTTagCompound) => { + api.Nanomachines.getProviders.find(p => p.readFromNBT(controller.player, t.getCompoundTag("behavior")) match { + case b: Behavior => + val neuron = new BehaviorNeuron(p, b) + neuron.inputs ++= t.getIntArray("triggerInputs").map(triggers.apply) + neuron.inputs ++= t.getIntArray("connectorInputs").map(connectors.apply) + behaviors += neuron + true // Done. + case _ => + false // Keep looking. + }) + }) + + behaviorMap.clear() + behaviorMap ++= behaviors.map(n => n.behavior -> n) + } + + trait Neuron { + def isActive: Boolean + } + + class TriggerNeuron extends Neuron { + var isActive = false + } + + class ConnectorNeuron extends Neuron { + val inputs = mutable.ArrayBuffer.empty[Neuron] + + override def isActive = inputs.exists(_.isActive) + } + + class BehaviorNeuron(val provider: BehaviorProvider, val behavior: Behavior) extends ConnectorNeuron + +} diff --git a/src/main/scala/li/cil/oc/common/nanomachines/provider/DisintegrationProvider.scala b/src/main/scala/li/cil/oc/common/nanomachines/provider/DisintegrationProvider.scala new file mode 100644 index 000000000..63623f069 --- /dev/null +++ b/src/main/scala/li/cil/oc/common/nanomachines/provider/DisintegrationProvider.scala @@ -0,0 +1,133 @@ +package li.cil.oc.common.nanomachines.provider + +import li.cil.oc.Settings +import li.cil.oc.api +import li.cil.oc.util.BlockPosition +import li.cil.oc.util.ExtendedWorld._ +import net.minecraft.block.Block +import net.minecraft.block.state.IBlockState +import net.minecraft.entity.player.EntityPlayer +import net.minecraft.entity.player.EntityPlayerMP +import net.minecraft.item.ItemStack +import net.minecraft.nbt.NBTTagCompound +import net.minecraft.world.World +import net.minecraftforge.event.ForgeEventFactory +import net.minecraftforge.event.entity.player.PlayerInteractEvent.Action +import net.minecraftforge.fml.common.eventhandler.Event + +import scala.collection.mutable + +object DisintegrationProvider extends SimpleProvider { + final val Id = "c4e7e3c2-8069-4fbb-b08e-74b1bddcdfe7" + + override def doCreateBehaviors(player: EntityPlayer) = Iterable(new DisintegrationBehavior(player)) + + override def doReadFromNBT(player: EntityPlayer, nbt: NBTTagCompound) = new DisintegrationBehavior(player) + + class DisintegrationBehavior(player: EntityPlayer) extends SimpleBehavior(player) { + var breakingMap = mutable.Map.empty[BlockPosition, SlowBreakInfo] + var breakingMapNew = mutable.Map.empty[BlockPosition, SlowBreakInfo] + + // Note: intentionally not overriding getNameHint. Gotta find this one manually! + + override def onDisable(): Unit = { + val world = player.getEntityWorld + for (pos <- breakingMap.keys) { + world.destroyBlockInWorldPartially(pos.hashCode(), pos, -1) + } + breakingMap.clear() + } + + override def update(): Unit = { + val world = player.getEntityWorld + if (!world.isRemote) player match { + case playerMP: EntityPlayerMP => + val now = world.getTotalWorldTime + + // Check blocks in range. + val blockPos = BlockPosition(player) + val actualRange = Settings.get.nanomachineDisintegrationRange * api.Nanomachines.getController(player).getInputCount(this) + for (x <- -actualRange to actualRange; y <- 0 to actualRange * 2; z <- -actualRange to actualRange) { + val pos = BlockPosition(blockPos.offset(x, y, z)) + breakingMap.get(pos) match { + case Some(info) if info.checkTool(player) => + breakingMapNew += pos -> info + info.update(world, player, now) + case None => + val event = ForgeEventFactory.onPlayerInteract(player, Action.LEFT_CLICK_BLOCK, world, pos.toBlockPos, null) + val allowed = !event.isCanceled && event.useBlock != Event.Result.DENY && event.useItem != Event.Result.DENY + val adventureOk = !world.getWorldInfo.getGameType.isAdventure || player.canPlayerEdit(pos.toBlockPos, null, player.getCurrentEquippedItem) + if (allowed && adventureOk && !world.isAirBlock(pos)) { + val blockState = world.getBlockState(pos.toBlockPos) + val hardness = blockState.getBlock.getPlayerRelativeBlockHardness(player, world, pos.toBlockPos) + if (hardness > 0) { + val timeToBreak = (1 / hardness).toInt + if (timeToBreak < 20 * 30) { + val info = new SlowBreakInfo(now, now + timeToBreak, pos, Option(player.getCurrentEquippedItem).map(_.copy()), blockState) + world.destroyBlockInWorldPartially(pos.hashCode(), pos, 0) + breakingMapNew += pos -> info + } + } + } + case _ => // Tool changed, pretend block doesn't exist for this tick. + } + } + + // Handle completed breaks. + for ((pos, info) <- breakingMap) { + if (info.timeBroken < now) { + breakingMapNew -= pos + info.finish(world, playerMP) + } + } + + // Handle aborted / incomplete breaks. + for (pos <- breakingMap.keySet -- breakingMapNew.keySet) { + world.destroyBlockInWorldPartially(pos.hashCode(), pos, -1) + } + + val tmp = breakingMap + breakingMap.clear() + breakingMap = breakingMapNew + breakingMapNew = tmp + case _ => // Not available for fake players, sorry :P + } + } + } + + class SlowBreakInfo(val timeStarted: Long, val timeBroken: Long, val pos: BlockPosition, val originalTool: Option[ItemStack], val blockState: IBlockState) { + var lastDamageSent = 0 + + def checkTool(player: EntityPlayer): Boolean = { + val currentTool = Option(player.getCurrentEquippedItem).map(_.copy()) + (currentTool, originalTool) match { + case (Some(stackA), Some(stackB)) => stackA.getItem == stackB.getItem && (stackA.isItemStackDamageable || stackA.getItemDamage == stackB.getItemDamage) + case (None, None) => true + case _ => false + } + } + + def update(world: World, player: EntityPlayer, now: Long): Unit = { + val timeTotal = timeBroken - timeStarted + if (timeTotal > 0) { + val timeTaken = now - timeStarted + val damage = 10 * timeTaken / timeTotal + if (damage != lastDamageSent) { + lastDamageSent = damage.toInt + world.destroyBlockInWorldPartially(pos.hashCode(), pos, lastDamageSent) + } + } + } + + def finish(world: World, player: EntityPlayerMP): Unit = { + val sameBlock = world.getBlockState(pos.toBlockPos) == blockState + if (sameBlock) { + world.destroyBlockInWorldPartially(pos.hashCode(), pos, -1) + if (player.theItemInWorldManager.tryHarvestBlock(pos.toBlockPos)) { + world.playAuxSFX(2001, pos, Block.getIdFromBlock(blockState.getBlock) + (blockState.getBlock.getMetaFromState(blockState) << 12)) + } + } + } + } + +} diff --git a/src/main/scala/li/cil/oc/common/nanomachines/provider/MagnetProvider.scala b/src/main/scala/li/cil/oc/common/nanomachines/provider/MagnetProvider.scala new file mode 100644 index 000000000..3cf7fd4b2 --- /dev/null +++ b/src/main/scala/li/cil/oc/common/nanomachines/provider/MagnetProvider.scala @@ -0,0 +1,40 @@ +package li.cil.oc.common.nanomachines.provider + +import li.cil.oc.Settings +import li.cil.oc.api +import net.minecraft.entity.item.EntityItem +import net.minecraft.entity.player.EntityPlayer +import net.minecraft.nbt.NBTTagCompound +import net.minecraft.util.Vec3 + +import scala.collection.convert.WrapAsScala._ + +object MagnetProvider extends SimpleProvider { + // One-time generated UUID to identify our behaviors. + final val Id = "9324d5ec-71f1-41c2-b51c-406e527668fc" + + override def doCreateBehaviors(player: EntityPlayer) = Iterable(new MagnetBehavior(player)) + + override def doReadFromNBT(player: EntityPlayer, nbt: NBTTagCompound) = new MagnetBehavior(player) + + class MagnetBehavior(player: EntityPlayer) extends SimpleBehavior(player) { + override def getNameHint = "magnet" + + override def update(): Unit = { + val world = player.getEntityWorld + if (!world.isRemote) { + val actualRange = Settings.get.nanomachineMagnetRange * api.Nanomachines.getController(player).getInputCount(this) + val items = world.getEntitiesWithinAABB(classOf[EntityItem], player.getEntityBoundingBox.expand(actualRange, actualRange, actualRange)) + items.collect { + case item: EntityItem if !item.cannotPickup && item.getEntityItem != null && player.inventory.mainInventory.exists(stack => stack == null || stack.stackSize < stack.getMaxStackSize && stack.isItemEqual(item.getEntityItem)) => + val dx = player.posX - item.posX + val dy = player.posY - item.posY + val dz = player.posZ - item.posZ + val delta = new Vec3(dx, dy, dz).normalize() + item.addVelocity(delta.xCoord * 0.1, delta.yCoord * 0.1, delta.zCoord * 0.1) + } + } + } + } + +} diff --git a/src/main/scala/li/cil/oc/common/nanomachines/provider/ParticleProvider.scala b/src/main/scala/li/cil/oc/common/nanomachines/provider/ParticleProvider.scala new file mode 100644 index 000000000..dddf8bb10 --- /dev/null +++ b/src/main/scala/li/cil/oc/common/nanomachines/provider/ParticleProvider.scala @@ -0,0 +1,55 @@ +package li.cil.oc.common.nanomachines.provider + +import li.cil.oc.api +import li.cil.oc.api.nanomachines.Behavior +import li.cil.oc.util.PlayerUtils +import net.minecraft.entity.player.EntityPlayer +import net.minecraft.nbt.NBTTagCompound +import net.minecraft.util.EnumParticleTypes + +object ParticleProvider extends SimpleProvider { + final val Id = "b48c4bbd-51bb-4915-9367-16cff3220e4b" + + final val ParticleTypes = Array( + EnumParticleTypes.FIREWORKS_SPARK, + EnumParticleTypes.TOWN_AURA, + EnumParticleTypes.SMOKE_NORMAL, + EnumParticleTypes.SPELL_WITCH, + EnumParticleTypes.NOTE, + EnumParticleTypes.ENCHANTMENT_TABLE, + EnumParticleTypes.FLAME, + EnumParticleTypes.LAVA, + EnumParticleTypes.WATER_SPLASH, + EnumParticleTypes.REDSTONE, + EnumParticleTypes.SLIME, + EnumParticleTypes.HEART, + EnumParticleTypes.VILLAGER_HAPPY + ) + + override def doCreateBehaviors(player: EntityPlayer): Iterable[Behavior] = ParticleTypes.map(new ParticleBehavior(_, player)) + + override def doWriteToNBT(behavior: Behavior, nbt: NBTTagCompound): Unit = { + behavior match { + case particles: ParticleBehavior => + nbt.setInteger("effectName", particles.effectType.getParticleID) + case _ => // Wat. + } + } + + override def doReadFromNBT(player: EntityPlayer, nbt: NBTTagCompound): Behavior = { + val effectType = EnumParticleTypes.getParticleFromId(nbt.getInteger("effectName")) + new ParticleBehavior(effectType, player) + } + + class ParticleBehavior(var effectType: EnumParticleTypes, player: EntityPlayer) extends SimpleBehavior(player) { + override def getNameHint = "particles" + + override def update(): Unit = { + val world = player.getEntityWorld + if (world.isRemote) { + PlayerUtils.spawnParticleAround(player, effectType, api.Nanomachines.getController(player).getInputCount(this) * 0.25) + } + } + } + +} diff --git a/src/main/scala/li/cil/oc/common/nanomachines/provider/PotionProvider.scala b/src/main/scala/li/cil/oc/common/nanomachines/provider/PotionProvider.scala new file mode 100644 index 000000000..19c998b49 --- /dev/null +++ b/src/main/scala/li/cil/oc/common/nanomachines/provider/PotionProvider.scala @@ -0,0 +1,61 @@ +package li.cil.oc.common.nanomachines.provider + +import li.cil.oc.Settings +import li.cil.oc.api +import li.cil.oc.api.nanomachines.Behavior +import net.minecraft.entity.player.EntityPlayer +import net.minecraft.nbt.NBTTagCompound +import net.minecraft.potion.Potion +import net.minecraft.potion.PotionEffect + +import scala.collection.convert.WrapAsScala._ + +object PotionProvider extends SimpleProvider { + final val Id = "c29e4eec-5a46-479a-9b3d-ad0f06da784a" + + // Lazy to give other mods a chance to register their potions. + lazy val PotionBlacklist = Settings.get.nanomachinePotionBlacklist.map { + case name: String => Potion.potionTypes.find(p => p != null && p.getName == name) + case id: java.lang.Number if id.intValue() >= 0 && id.intValue() < Potion.potionTypes.length => Option(Potion.potionTypes(id.intValue())) + case _ => None + }.collect { + case Some(potion) => potion + }.toSet + + override def doCreateBehaviors(player: EntityPlayer) = { + Potion.potionTypes.filter(_ != null).filterNot(PotionBlacklist.contains).map(new PotionBehavior(_, player)) + } + + override def doWriteToNBT(behavior: Behavior, nbt: NBTTagCompound): Unit = { + behavior match { + case potionBehavior: PotionBehavior => + nbt.setInteger("potionId", potionBehavior.potion.id) + case _ => // Shouldn't happen, ever. + } + } + + override def doReadFromNBT(player: EntityPlayer, nbt: NBTTagCompound) = { + val potionId = nbt.getInteger("potionId") + new PotionBehavior(Potion.potionTypes(potionId), player) + } + + class PotionBehavior(val potion: Potion, player: EntityPlayer) extends SimpleBehavior(player) { + final val RefreshInterval = 40 + + def amplifier(player: EntityPlayer) = api.Nanomachines.getController(player).getInputCount(this) - 1 + + override def getNameHint: String = potion.getName.stripPrefix("potion.") + + override def onEnable(): Unit = {} + + override def onDisable(): Unit = {} + + override def update(): Unit = { + player.getActivePotionEffect(potion) match { + case effect: PotionEffect if effect.getDuration > RefreshInterval / 2 => // Effect still active. + case _ => player.addPotionEffect(new PotionEffect(potion.id, RefreshInterval, amplifier(player))) + } + } + } + +} diff --git a/src/main/scala/li/cil/oc/common/nanomachines/provider/SimpleBehavior.scala b/src/main/scala/li/cil/oc/common/nanomachines/provider/SimpleBehavior.scala new file mode 100644 index 000000000..25bc0d9a7 --- /dev/null +++ b/src/main/scala/li/cil/oc/common/nanomachines/provider/SimpleBehavior.scala @@ -0,0 +1,14 @@ +package li.cil.oc.common.nanomachines.provider + +import li.cil.oc.api.nanomachines.Behavior +import net.minecraft.entity.player.EntityPlayer + +class SimpleBehavior(val player: EntityPlayer) extends Behavior { + override def getNameHint: String = null + + override def onEnable(): Unit = {} + + override def onDisable(): Unit = {} + + override def update(): Unit = {} +} diff --git a/src/main/scala/li/cil/oc/common/nanomachines/provider/SimpleProvider.scala b/src/main/scala/li/cil/oc/common/nanomachines/provider/SimpleProvider.scala new file mode 100644 index 000000000..40f8ce6b3 --- /dev/null +++ b/src/main/scala/li/cil/oc/common/nanomachines/provider/SimpleProvider.scala @@ -0,0 +1,35 @@ +package li.cil.oc.common.nanomachines.provider + +import li.cil.oc.api.nanomachines.Behavior +import li.cil.oc.api.nanomachines.BehaviorProvider +import net.minecraft.entity.player.EntityPlayer +import net.minecraft.nbt.NBTTagCompound + +import scala.collection.convert.WrapAsJava._ + +abstract class SimpleProvider extends BehaviorProvider { + // One-time generated UUID to identify our behaviors. + def Id: String + + def doCreateBehaviors(player: EntityPlayer): Iterable[Behavior] + + def doWriteToNBT(behavior: Behavior, nbt: NBTTagCompound): Unit = {} + + def doReadFromNBT(player: EntityPlayer, nbt: NBTTagCompound): Behavior + + override def createBehaviors(player: EntityPlayer): java.lang.Iterable[Behavior] = asJavaIterable(doCreateBehaviors(player)) + + override def writeToNBT(behavior: Behavior): NBTTagCompound = { + val nbt = new NBTTagCompound() + nbt.setString("provider", Id) + doWriteToNBT(behavior: Behavior, nbt: NBTTagCompound) + nbt + } + + override def readFromNBT(player: EntityPlayer, nbt: NBTTagCompound): Behavior = { + if (nbt.getString("provider") == Id) { + doReadFromNBT(player, nbt) + } + else null + } +} diff --git a/src/main/scala/li/cil/oc/common/tileentity/Charger.scala b/src/main/scala/li/cil/oc/common/tileentity/Charger.scala index 294602978..9dcdd85dc 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Charger.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Charger.scala @@ -6,6 +6,7 @@ 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.nanomachines.Controller import li.cil.oc.api.network._ import li.cil.oc.common.Slot import li.cil.oc.common.entity.Drone @@ -30,7 +31,7 @@ class Charger extends traits.Environment with traits.PowerAcceptor with traits.R withConnector(Settings.get.bufferConverter). create() - val connectors = mutable.Set.empty[(Vec3, Connector)] + val connectors = mutable.Set.empty[Chargeable] var chargeSpeed = 0.0 @@ -81,9 +82,7 @@ class Charger extends traits.Environment with traits.PowerAcceptor with traits.R val charge = Settings.get.chargeRateExternal * chargeSpeed * Settings.get.tickFrequency canCharge ||= charge > 0 && node.globalBuffer >= charge * 0.5 if (canCharge) { - connectors.foreach { - case (_, connector) => node.changeBuffer(connector.changeBuffer(charge + node.changeBuffer(-charge))) - } + connectors.foreach(connector => node.changeBuffer(connector.changeBuffer(charge + node.changeBuffer(-charge)))) } } @@ -111,15 +110,15 @@ class Charger extends traits.Environment with traits.PowerAcceptor with traits.R } if (isClient && chargeSpeed > 0 && hasPower && world.getWorldInfo.getWorldTotalTime % 10 == 0) { - connectors.foreach { - case (position, _) => - val theta = world.rand.nextDouble * Math.PI - val phi = world.rand.nextDouble * Math.PI * 2 - val dx = 0.45 * Math.sin(theta) * Math.cos(phi) - val dy = 0.45 * Math.sin(theta) * Math.sin(phi) - val dz = 0.45 * Math.cos(theta) - world.spawnParticle(EnumParticleTypes.VILLAGER_HAPPY, position.xCoord + dx, position.yCoord + dz, position.zCoord + dy, 0, 0, 0) - } + connectors.foreach(connector => { + val position = connector.pos + val theta = world.rand.nextDouble * Math.PI + val phi = world.rand.nextDouble * Math.PI * 2 + val dx = 0.45 * Math.sin(theta) * Math.cos(phi) + val dy = 0.45 * Math.sin(theta) * Math.sin(phi) + val dz = 0.45 * Math.cos(theta) + world.spawnParticle(EnumParticleTypes.VILLAGER_HAPPY, position.xCoord + dx, position.yCoord + dz, position.zCoord + dy, 0, 0, 0) + }) } } @@ -193,23 +192,59 @@ class Charger extends traits.Environment with traits.PowerAcceptor with traits.R } def updateConnectors() { - val robotConnectors = EnumFacing.values.map(side => { + val robots = EnumFacing.values.map(side => { val blockPos = BlockPosition(this).offset(side) if (world.blockExists(blockPos)) Option(world.getTileEntity(blockPos)) else None }).collect { - case Some(t: RobotProxy) => (BlockPosition(t).toVec3, t.robot.node.asInstanceOf[Connector]) + case Some(t: RobotProxy) => new RobotChargeable(t.robot) } - val droneConnectors = world.getEntitiesWithinAABB(classOf[Drone], BlockPosition(this).bounds.expand(1, 1, 1)).collect { - case drone: Drone => (new Vec3(drone.posX, drone.posY, drone.posZ), drone.components.node.asInstanceOf[Connector]) + val bounds = BlockPosition(this).bounds.expand(1, 1, 1) + val drones = world.getEntitiesWithinAABB(classOf[Drone], bounds).collect { + case drone: Drone => new DroneChargeable(drone) + } + + val players = world.getEntitiesWithinAABB(classOf[EntityPlayer], bounds).collect { + case player: EntityPlayer => new PlayerChargeable(player) } // Only update list when we have to, keeps pointless block updates to a minimum. - if (connectors.size != robotConnectors.length + droneConnectors.size || (connectors.size > 0 && connectors.map(_._2).diff((robotConnectors ++ droneConnectors).map(_._2).toSet).size > 0)) { + + val newConnectors = robots ++ drones ++ players + if (connectors.size != newConnectors.length || (connectors.nonEmpty && (connectors -- newConnectors).nonEmpty)) { connectors.clear() - connectors ++= robotConnectors - connectors ++= droneConnectors + connectors ++= newConnectors world.notifyNeighborsOfStateChange(getPos, getBlockType) } } + + trait Chargeable { + def pos: Vec3 + + def changeBuffer(delta: Double): Double + } + + abstract class ConnectorChargeable(val connector: Connector) extends Chargeable { + override def changeBuffer(delta: Double): Double = connector.changeBuffer(delta) + } + + class RobotChargeable(val robot: Robot) extends ConnectorChargeable(robot.node.asInstanceOf[Connector]) { + override def pos: Vec3 = BlockPosition(robot).toVec3 + } + + class DroneChargeable(val drone: Drone) extends ConnectorChargeable(drone.components.node.asInstanceOf[Connector]) { + override def pos: Vec3 = new Vec3(drone.posX, drone.posY, drone.posZ) + } + + class PlayerChargeable(val player: EntityPlayer) extends Chargeable { + override def pos: Vec3 = new Vec3(player.posX, player.posY, player.posZ) + + override def changeBuffer(delta: Double): Double = { + api.Nanomachines.getController(player) match { + case controller: Controller => controller.changeBuffer(delta) + case _ => delta // Cannot charge. + } + } + } + } diff --git a/src/main/scala/li/cil/oc/integration/opencomputers/ModOpenComputers.scala b/src/main/scala/li/cil/oc/integration/opencomputers/ModOpenComputers.scala index 80edab37f..5e546142f 100644 --- a/src/main/scala/li/cil/oc/integration/opencomputers/ModOpenComputers.scala +++ b/src/main/scala/li/cil/oc/integration/opencomputers/ModOpenComputers.scala @@ -27,6 +27,10 @@ import li.cil.oc.common.item.Analyzer import li.cil.oc.common.item.Delegator import li.cil.oc.common.item.RedstoneCard import li.cil.oc.common.item.Tablet +import li.cil.oc.common.nanomachines.provider.DisintegrationProvider +import li.cil.oc.common.nanomachines.provider.MagnetProvider +import li.cil.oc.common.nanomachines.provider.ParticleProvider +import li.cil.oc.common.nanomachines.provider.PotionProvider import li.cil.oc.common.template._ import li.cil.oc.integration.ModProxy import li.cil.oc.integration.Mods @@ -80,6 +84,7 @@ object ModOpenComputers extends ModProxy { MinecraftForge.EVENT_BUS.register(GeolyzerHandler) MinecraftForge.EVENT_BUS.register(HoverBootsHandler) MinecraftForge.EVENT_BUS.register(Loot) + MinecraftForge.EVENT_BUS.register(NanomachinesEventHandler.Common) MinecraftForge.EVENT_BUS.register(RobotCommonHandler) MinecraftForge.EVENT_BUS.register(SaveHandler) MinecraftForge.EVENT_BUS.register(Tablet) @@ -246,6 +251,11 @@ object ModOpenComputers extends ModProxy { api.Manual.addTab(new TextureTabIconRenderer(Textures.GUI.ManualHome), "oc:gui.Manual.Home", "%LANGUAGE%/index.md") api.Manual.addTab(new ItemStackTabIconRenderer(api.Items.get("case1").createItemStack(1)), "oc:gui.Manual.Blocks", "%LANGUAGE%/block/index.md") api.Manual.addTab(new ItemStackTabIconRenderer(api.Items.get("cpu1").createItemStack(1)), "oc:gui.Manual.Items", "%LANGUAGE%/item/index.md") + + api.Nanomachines.addProvider(DisintegrationProvider) + api.Nanomachines.addProvider(ParticleProvider) + api.Nanomachines.addProvider(PotionProvider) + api.Nanomachines.addProvider(MagnetProvider) } def useWrench(player: EntityPlayer, pos: BlockPos, changeDurability: Boolean): Boolean = { diff --git a/src/main/scala/li/cil/oc/server/PacketSender.scala b/src/main/scala/li/cil/oc/server/PacketSender.scala index ea400d2b3..298b3645f 100644 --- a/src/main/scala/li/cil/oc/server/PacketSender.scala +++ b/src/main/scala/li/cil/oc/server/PacketSender.scala @@ -1,14 +1,17 @@ package li.cil.oc.server +import li.cil.oc.api import li.cil.oc.api.component.TextBuffer.ColorDepth import li.cil.oc.api.driver.EnvironmentHost import li.cil.oc.api.event.FileSystemAccessEvent import li.cil.oc.api.network.Node import li.cil.oc.common._ +import li.cil.oc.common.nanomachines.ControllerImpl import li.cil.oc.common.tileentity.Waypoint import li.cil.oc.common.tileentity.traits._ import li.cil.oc.util.BlockPosition import li.cil.oc.util.PackedColor +import net.minecraft.entity.player.EntityPlayer import net.minecraft.entity.player.EntityPlayerMP import net.minecraft.inventory.Container import net.minecraft.item.ItemStack @@ -251,6 +254,51 @@ object PacketSender { } } + def sendNanomachineConfiguration(player: EntityPlayer): Unit = { + val pb = new SimplePacketBuilder(PacketType.NanomachinesConfiguration) + + pb.writeEntity(player) + api.Nanomachines.getController(player) match { + case controller: ControllerImpl => + pb.writeBoolean(true) + val nbt = new NBTTagCompound() + controller.save(nbt) + pb.writeNBT(nbt) + case _ => + pb.writeBoolean(false) + } + + pb.sendToPlayersNearEntity(player) + } + + def sendNanomachineInputs(player: EntityPlayer): Unit = { + api.Nanomachines.getController(player) match { + case controller: ControllerImpl => + val pb = new SimplePacketBuilder(PacketType.NanomachinesInputs) + + pb.writeEntity(player) + val inputs = controller.configuration.triggers.map(i => if (i.isActive) 1.toByte else 0.toByte).toArray + pb.writeInt(inputs.length) + pb.write(inputs) + + pb.sendToPlayersNearEntity(player) + case _ => // Wat. + } + } + + def sendNanomachinePower(player: EntityPlayer): Unit = { + api.Nanomachines.getController(player) match { + case controller: ControllerImpl => + val pb = new SimplePacketBuilder(PacketType.NanomachinesPower) + + pb.writeEntity(player) + pb.writeDouble(controller.getLocalBuffer) + + pb.sendToPlayersNearEntity(player) + case _ => // Wat. + } + } + def sendNetSplitterState(t: tileentity.NetSplitter): Unit = { val pb = new SimplePacketBuilder(PacketType.NetSplitterState) diff --git a/src/main/scala/li/cil/oc/server/agent/Player.scala b/src/main/scala/li/cil/oc/server/agent/Player.scala index 6aa423645..2e9ddc5d6 100644 --- a/src/main/scala/li/cil/oc/server/agent/Player.scala +++ b/src/main/scala/li/cil/oc/server/agent/Player.scala @@ -304,7 +304,8 @@ class Player(val agent: internal.Agent) extends FakePlayer(agent.world.asInstanc block.onBlockClicked(world, pos, this) world.extinguishFire(this, pos, side) - val isBlockUnbreakable = block.getBlockHardness(world, pos) < 0 + val hardness = block.getBlockHardness(world, pos) + val isBlockUnbreakable = hardness < 0 val canDestroyBlock = !isBlockUnbreakable && block.canEntityDestroy(world, pos, this) if (!canDestroyBlock) { return 0 @@ -320,7 +321,6 @@ class Player(val agent: internal.Agent) extends FakePlayer(agent.world.asInstanc return 0 } - val hardness = block.getBlockHardness(world, pos) val strength = getBreakSpeed(state, pos) val breakTime = if (cobwebOverride) Settings.get.swingDelay 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 a62f92b10..7e58fa380 100644 --- a/src/main/scala/li/cil/oc/server/component/NetworkCard.scala +++ b/src/main/scala/li/cil/oc/server/component/NetworkCard.scala @@ -124,12 +124,6 @@ class NetworkCard(val host: EnvironmentHost) extends prefab.ManagedEnvironment { } } - def receivePacket(packet: Packet, source: WirelessEndpoint) { - val (dx, dy, dz) = ((source.x + 0.5) - host.xPosition, (source.y + 0.5) - host.yPosition, (source.z + 0.5) - host.zPosition) - val distance = Math.sqrt(dx * dx + dy * dy + dz * dz) - receivePacket(packet, distance) - } - protected def receivePacket(packet: Packet, distance: Double) { if (packet.source != node.address && Option(packet.destination).forall(_ == node.address)) { if (openPorts.contains(packet.port)) { diff --git a/src/main/scala/li/cil/oc/server/component/WirelessNetworkCard.scala b/src/main/scala/li/cil/oc/server/component/WirelessNetworkCard.scala index 97a31cdde..50128c4ab 100644 --- a/src/main/scala/li/cil/oc/server/component/WirelessNetworkCard.scala +++ b/src/main/scala/li/cil/oc/server/component/WirelessNetworkCard.scala @@ -36,6 +36,12 @@ class WirelessNetworkCard(host: EnvironmentHost) extends NetworkCard(host) with override def world = host.world + def receivePacket(packet: Packet, source: WirelessEndpoint) { + val (dx, dy, dz) = ((source.x + 0.5) - host.xPosition, (source.y + 0.5) - host.yPosition, (source.z + 0.5) - host.zPosition) + val distance = Math.sqrt(dx * dx + dy * dy + dz * dz) + receivePacket(packet, distance) + } + // ----------------------------------------------------------------------- // @Callback(direct = true, doc = """function():number -- Get the signal strength (range) used when sending messages.""") diff --git a/src/main/scala/li/cil/oc/util/BlockPosition.scala b/src/main/scala/li/cil/oc/util/BlockPosition.scala index 4ccb121d7..2fab8d0df 100644 --- a/src/main/scala/li/cil/oc/util/BlockPosition.scala +++ b/src/main/scala/li/cil/oc/util/BlockPosition.scala @@ -1,5 +1,6 @@ package li.cil.oc.util +import com.google.common.hash.Hashing import li.cil.oc.api.driver.EnvironmentHost import net.minecraft.entity.Entity import net.minecraft.util._ @@ -34,6 +35,18 @@ class BlockPosition(val x: Int, val y: Int, val z: Int, val world: Option[World] case position: BlockPosition => position.x == x && position.y == y && position.z == z && position.world == world case _ => super.equals(obj) } + + override def hashCode(): Int = { + Hashing. + goodFastHash(32). + newHasher(16). + putInt(x). + putInt(y). + putInt(z). + putInt(world.hashCode()). + hash(). + asInt() + } } object BlockPosition { @@ -45,6 +58,10 @@ object BlockPosition { def apply(x: Double, y: Double, z: Double) = new BlockPosition(x, y, z, None) + def apply(v: Vec3) = new BlockPosition(v.xCoord, v.yCoord, v.zCoord, None) + + def apply(v: Vec3, world: World) = new BlockPosition(v.xCoord, v.yCoord, v.zCoord, Option(world)) + def apply(host: EnvironmentHost): BlockPosition = BlockPosition(host.xPosition, host.yPosition, host.zPosition, host.world) def apply(entity: Entity): BlockPosition = BlockPosition(entity.posX, entity.posY, entity.posZ, entity.worldObj) diff --git a/src/main/scala/li/cil/oc/util/ExtendedWorld.scala b/src/main/scala/li/cil/oc/util/ExtendedWorld.scala index 4819cb57d..ae4a4b323 100644 --- a/src/main/scala/li/cil/oc/util/ExtendedWorld.scala +++ b/src/main/scala/li/cil/oc/util/ExtendedWorld.scala @@ -35,6 +35,8 @@ object ExtendedWorld { def breakBlock(position: BlockPosition, drops: Boolean = true) = world.destroyBlock(position.toBlockPos, drops) + def destroyBlockInWorldPartially(entityId: Int, position: BlockPosition, progress: Int) = world.sendBlockBreakProgress(entityId, position.toBlockPos, progress) + def extinguishFire(player: EntityPlayer, position: BlockPosition, side: EnumFacing) = world.extinguishFire(player, position.toBlockPos, side) def getBlockHardness(position: BlockPosition) = getBlock(position).getBlockHardness(world, position.toBlockPos) diff --git a/src/main/scala/li/cil/oc/util/PlayerUtils.scala b/src/main/scala/li/cil/oc/util/PlayerUtils.scala index 5a80710b1..6f24c64dc 100644 --- a/src/main/scala/li/cil/oc/util/PlayerUtils.scala +++ b/src/main/scala/li/cil/oc/util/PlayerUtils.scala @@ -2,6 +2,7 @@ package li.cil.oc.util import net.minecraft.entity.player.EntityPlayer import net.minecraft.nbt.NBTTagCompound +import net.minecraft.util.EnumParticleTypes object PlayerUtils { def persistedData(player: EntityPlayer): NBTTagCompound = { @@ -11,4 +12,15 @@ object PlayerUtils { } nbt.getCompoundTag(EntityPlayer.PERSISTED_NBT_TAG) } + + def spawnParticleAround(player: EntityPlayer, effectType: EnumParticleTypes, chance: Double = 1.0): Unit = { + val rng = player.getEntityWorld.rand + if (chance >= 1 || rng.nextDouble() < chance) { + val bounds = player.getEntityBoundingBox + val x = bounds.minX + (bounds.maxX - bounds.minX) * rng.nextDouble() * 1.5 + val y = bounds.minY + (bounds.maxY - bounds.minY) * rng.nextDouble() * 0.5 + val z = bounds.minZ + (bounds.maxZ - bounds.minZ) * rng.nextDouble() * 1.5 + player.getEntityWorld.spawnParticle(effectType, x, y, z, 0, 0, 0) + } + } }