From 27130ef44af5a8207a3b5d45bf5a377096254e0d Mon Sep 17 00:00:00 2001 From: chaoskagami Date: Mon, 7 Sep 2015 21:58:54 -0400 Subject: [PATCH 1/6] Update to the proper final repo for NeK. Or so I hope, at least. --- build.gradle | 4 ++-- build.properties | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index efb672f38..6e5763368 100644 --- a/build.gradle +++ b/build.gradle @@ -93,7 +93,7 @@ repositories { } maven { name = "dmodoomsirius" - url = "http://ci.dmodoomsirius.me/maven/" + url = "http://api.dmodoomsirius.me/" } maven { name = "ue" @@ -187,7 +187,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 c0e393604..aeddccac8 100644 --- a/build.properties +++ b/build.properties @@ -32,7 +32,7 @@ mekanism.version=7.1.2 mfr.cf=2229/626 mfr.version=[1.7.10]2.8.0RC8-86 nei.version=1.0.3.57 -nek.version=1.0.0b2dev +nek.version=2.0.0b4 projred.version=1.7.10-4.6.2.82 qmunitylib.version=0.1.105 rc.cf=2219/321 From d63c1cfbe186b64c346145a5490c3688dd6696c5 Mon Sep 17 00:00:00 2001 From: Vexatos Date: Mon, 14 Sep 2015 15:50:36 +0200 Subject: [PATCH 2/6] Added transposer to manual index. --- src/main/resources/assets/opencomputers/doc/en_US/block/index.md | 1 + 1 file changed, 1 insertion(+) 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 From b4a632896eab6cd4f01e5ba412ddb37d5569e0d7 Mon Sep 17 00:00:00 2001 From: cyber01 Date: Mon, 14 Sep 2015 20:43:51 +0400 Subject: [PATCH 3/6] Added transposer to manual index in RU --- src/main/resources/assets/opencomputers/doc/ru_RU/block/index.md | 1 + 1 file changed, 1 insertion(+) 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) ## Сборка / Печать From da2c5ed3051cb9ef07c5704e096e98e1f87b919a Mon Sep 17 00:00:00 2001 From: payonel Date: Sat, 12 Sep 2015 22:57:36 -0700 Subject: [PATCH 4/6] Fix and improve the tab complete in sh use path as given by user in sh shell in tab completion, and do not show resolved path don't add space to single directory completion in case user wants to add more directories escape magic chars in possible hint handler from user input on command line --- .../opencomputers/loot/OpenOS/bin/sh.lua | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) 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 From 085309587d71deae706cbc6c4f9ea3c80e6d46e8 Mon Sep 17 00:00:00 2001 From: Maxim Karpov Date: Wed, 16 Sep 2015 11:55:30 +0300 Subject: [PATCH 5/6] Correct links in readme.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 From 8f3a0a5b66921fcc97e0b24c32887d72c645b35a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Thu, 17 Sep 2015 21:23:32 +0200 Subject: [PATCH 6/6] Energy based, configurable (and potentially deadly) buff system: nanomachines! Also made Grog consumable, closing #410. --- assets/items.psd | Bin 652726 -> 607468 bytes src/main/java/li/cil/oc/api/API.java | 3 +- src/main/java/li/cil/oc/api/Nanomachines.java | 100 ++++++ .../li/cil/oc/api/detail/NanomachinesAPI.java | 66 ++++ .../li/cil/oc/api/nanomachines/Behavior.java | 50 +++ .../oc/api/nanomachines/BehaviorProvider.java | 66 ++++ .../cil/oc/api/nanomachines/Controller.java | 116 +++++++ src/main/resources/application.conf | 77 +++++ .../doc/en_US/general/example.md | 69 ++++ .../opencomputers/doc/en_US/item/acid.md | 4 +- .../opencomputers/doc/en_US/item/index.md | 1 + .../doc/en_US/item/nanomachines.md | 29 ++ .../assets/opencomputers/lang/de_DE.lang | 2 +- .../assets/opencomputers/lang/en_US.lang | 4 +- .../opencomputers/recipes/default.recipes | 10 +- .../opencomputers/recipes/hardmode.recipes | 4 - .../textures/gui/nanomachines_power.png | Bin 0 -> 213 bytes .../textures/gui/nanomachines_power_bar.png | Bin 0 -> 199 bytes .../textures/items/Nanomachines.png | Bin 0 -> 1244 bytes .../textures/items/Nanomachines.png.mcmeta | 5 + src/main/scala/li/cil/oc/Constants.scala | 1 + src/main/scala/li/cil/oc/Settings.scala | 25 +- .../li/cil/oc/client/PacketHandler.scala | 50 +++ src/main/scala/li/cil/oc/client/Proxy.scala | 2 + .../scala/li/cil/oc/client/Textures.scala | 3 + .../li/cil/oc/common/PacketBuilder.scala | 2 + .../scala/li/cil/oc/common/PacketType.scala | 3 + src/main/scala/li/cil/oc/common/Proxy.scala | 1 + .../oc/common/event/HoverBootsHandler.scala | 2 +- .../event/NanomachinesEventHandler.scala | 145 +++++++++ .../scala/li/cil/oc/common/init/Items.scala | 3 + .../scala/li/cil/oc/common/item/Acid.scala | 33 +- .../li/cil/oc/common/item/Nanomachines.scala | 30 ++ .../common/nanomachines/ControllerImpl.scala | 301 ++++++++++++++++++ .../oc/common/nanomachines/Nanomachines.scala | 50 +++ .../common/nanomachines/NeuralNetwork.scala | 161 ++++++++++ .../provider/DisintegrationProvider.scala | 133 ++++++++ .../provider/MagnetProvider.scala | 41 +++ .../provider/ParticleProvider.scala | 54 ++++ .../provider/PotionProvider.scala | 61 ++++ .../provider/SimpleBehavior.scala | 14 + .../provider/SimpleProvider.scala | 35 ++ .../li/cil/oc/common/tileentity/Charger.scala | 76 +++-- .../opencomputers/ModOpenComputers.scala | 10 + .../scala/li/cil/oc/server/PacketSender.scala | 48 +++ .../scala/li/cil/oc/server/agent/Player.scala | 4 +- .../cil/oc/server/component/NetworkCard.scala | 6 - .../component/WirelessNetworkCard.scala | 6 + .../scala/li/cil/oc/util/BlockPosition.scala | 17 + .../scala/li/cil/oc/util/ExtendedWorld.scala | 2 + .../scala/li/cil/oc/util/PlayerUtils.scala | 11 + 51 files changed, 1896 insertions(+), 40 deletions(-) create mode 100644 src/main/java/li/cil/oc/api/Nanomachines.java create mode 100644 src/main/java/li/cil/oc/api/detail/NanomachinesAPI.java create mode 100644 src/main/java/li/cil/oc/api/nanomachines/Behavior.java create mode 100644 src/main/java/li/cil/oc/api/nanomachines/BehaviorProvider.java create mode 100644 src/main/java/li/cil/oc/api/nanomachines/Controller.java create mode 100644 src/main/resources/assets/opencomputers/doc/en_US/general/example.md create mode 100644 src/main/resources/assets/opencomputers/doc/en_US/item/nanomachines.md create mode 100644 src/main/resources/assets/opencomputers/textures/gui/nanomachines_power.png create mode 100644 src/main/resources/assets/opencomputers/textures/gui/nanomachines_power_bar.png create mode 100644 src/main/resources/assets/opencomputers/textures/items/Nanomachines.png create mode 100644 src/main/resources/assets/opencomputers/textures/items/Nanomachines.png.mcmeta create mode 100644 src/main/scala/li/cil/oc/common/event/NanomachinesEventHandler.scala create mode 100644 src/main/scala/li/cil/oc/common/item/Nanomachines.scala create mode 100644 src/main/scala/li/cil/oc/common/nanomachines/ControllerImpl.scala create mode 100644 src/main/scala/li/cil/oc/common/nanomachines/Nanomachines.scala create mode 100644 src/main/scala/li/cil/oc/common/nanomachines/NeuralNetwork.scala create mode 100644 src/main/scala/li/cil/oc/common/nanomachines/provider/DisintegrationProvider.scala create mode 100644 src/main/scala/li/cil/oc/common/nanomachines/provider/MagnetProvider.scala create mode 100644 src/main/scala/li/cil/oc/common/nanomachines/provider/ParticleProvider.scala create mode 100644 src/main/scala/li/cil/oc/common/nanomachines/provider/PotionProvider.scala create mode 100644 src/main/scala/li/cil/oc/common/nanomachines/provider/SimpleBehavior.scala create mode 100644 src/main/scala/li/cil/oc/common/nanomachines/provider/SimpleProvider.scala diff --git a/assets/items.psd b/assets/items.psd index d62ef2babf274d1e8a501f1bf73b3fc6908a2a5e..2df42515fa5b5e501be578dc6391bb4d39d2767b 100644 GIT binary patch delta 20533 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 H#?OBPZ6b^{ delta 22139 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 T{TkD+cxLjieESC6uYvnNqPPU_ 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 792fd0b6f..005ced3a2 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 @@ -957,6 +973,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/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/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 d656c91a3..64a6b2164 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/recipes/default.recipes b/src/main/resources/assets/opencomputers/recipes/default.recipes index 79eedf014..227492680 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 0000000000000000000000000000000000000000..dd7eb1426b879dc42ba9b197101db7a6fe024188 GIT binary patch literal 213 zcmeAS@N?(olHy`uVBq!ia0vp^96-#&!3-ob9C+3PDajJoh?3y^w370~qErUQl>DSr z1<%~X^wgl##FWaylc_d9MbZI2A+8n{7J7PmW@ctQJUlu&I!a1P;^N{!$^ZZV&ndi_ z0%UQP1o;I6xnRH$>|Y@S6twnqaSW-r6>{bx7lQ(a^Tnl>-~Y$o(P-M`-_Gf@xy#3K zO19#m$?h#>NB%T)FLZf(*mV{Em!^OB^EeF{8rL$FPYti^2O7iR>FVdQ&MBb@07=S7 AC;$Ke literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c3dffb793a696e2b4bea99a3f6db1552ebac49ea GIT binary patch literal 199 zcmeAS@N?(olHy`uVBq!ia0vp^96-#&!3-ob9C+3PDajJoh?3y^w370~qErUQl>DSr z1<%~X^wgl##FWaylc_d9MWO*dA+D?c?>YGY+}Ho#FaN*u@c;AwV6fCCavx9udr6RA zFhqnw%A45@C}isC;uunK%jTq`5Q72-lY`*b|NeRs-Q3;Ba*~#9&e-E!o;G#CvO@0< mZ3p=!zB9KRIQopkfFXW5DSr z1<%~X^wgl##FWaylc_cg42)j`d_r8=1=z*J!~`S-SXfxJZA^uwg|)P_c_sb!=!b7u6)l_^)RmRt)AoHZ+S$Bxu19v-`Y{jaO@*0nX?YiXG)BU4@Narf{4 z?(X2_%cG}GjW~TefAP=%yLRQR($NtX7M?L9_~M`c+kgG9u)DlM*VNJ+(%D^&ptBmoMhg*sQrnM`{3#NHCyqkU(BZ(h0ngO zZoJ@moM2$KZ5dpPKo$d>+!9i;Oj)^H}MsJ7J9r-w|pBZ{n{?=Q*-=} zcDax4O5Xx3g8`8++wnq0(<1gFcs+r8NGH+cg{wy+o6B_ZZ zNcDHP!>440&)(^OCMkapEcrIo>2sR$k099(u4;cGW4wKo29eZkLZ-XF4* zegqePn_T#7M!@e{)xVLZuY)tc_ojdA`v3pG;lGu|z<6C=666;Q}%WQgQ3Z zwCaU~OV+V+>vxMx-jx{`aL7>he6*okux&iwX~)Hnw|<)Q?1E^bx$u_u5Ur`VI?|4K zyJuXuY|HDvbi0W{w%U`$CsOO@$#9rS7%jAn^kzOZ$5EHR{ZMoA^Na@;=QQ;BdfQH) zzuX|8*m2IYyLFz3=+LenDIswLZsfodxZIAaoQr_JyO9^M&B_ zI;Z}L<=hvNe|(F7vGLA7t}2r)Pp>&Y{E%eyzvbf(!5yr&Y%SL>D63s_?&o_c{DmWK z54-KY)=3wV^=@$4My&&>ElB?KTm13Go%dL6r?oJAnUQK+^QQjizps*CDr7!8_Rl@< j_^j4MZo=_-49pDQA9d;mFfK_0CPW5LS3j3^P6 + (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 @@ -305,7 +315,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 @@ -337,6 +348,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 eaf687dd2..169023208 100644 --- a/src/main/scala/li/cil/oc/client/PacketHandler.scala +++ b/src/main/scala/li/cil/oc/client/PacketHandler.scala @@ -6,12 +6,14 @@ import cpw.mods.fml.common.eventhandler.SubscribeEvent import cpw.mods.fml.common.network.FMLNetworkEvent.ClientCustomPacketEvent 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} @@ -58,6 +60,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) @@ -286,6 +291,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 c5a174afb..cfb750795 100644 --- a/src/main/scala/li/cil/oc/client/Proxy.scala +++ b/src/main/scala/li/cil/oc/client/Proxy.scala @@ -21,6 +21,7 @@ import li.cil.oc.client.renderer.item.ItemRenderer 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.tileentity import li.cil.oc.common.tileentity.ServerRack @@ -80,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 e229115ae..da3547953 100644 --- a/src/main/scala/li/cil/oc/client/Textures.scala +++ b/src/main/scala/li/cil/oc/client/Textures.scala @@ -63,6 +63,9 @@ object Textures { val upgradeGenerator = new ResourceLocation(Settings.resourceDomain, "textures/model/UpgradeGenerator.png") val upgradeInventory = new ResourceLocation(Settings.resourceDomain, "textures/model/UpgradeInventory.png") + val overlayNanomachines = new ResourceLocation(Settings.resourceDomain, "textures/gui/nanomachines_power.png") + val overlayNanomachinesBar = new ResourceLocation(Settings.resourceDomain, "textures/gui/nanomachines_power_bar.png") + object Cable { var iconCap: IIcon = _ } diff --git a/src/main/scala/li/cil/oc/common/PacketBuilder.scala b/src/main/scala/li/cil/oc/common/PacketBuilder.scala index 46cbad78c..2154b59bd 100644 --- a/src/main/scala/li/cil/oc/common/PacketBuilder.scala +++ b/src/main/scala/li/cil/oc/common/PacketBuilder.scala @@ -53,6 +53,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.worldObj, e.posX, e.posY, e.posZ, range) + def sendToPlayersNearTileEntity(t: TileEntity, range: Option[Double] = None): Unit = sendToNearbyPlayers(t.getWorldObj, t.xCoord + 0.5, t.yCoord + 0.5, t.zCoord + 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 c8885f1ce..bad9b12b4 100644 --- a/src/main/scala/li/cil/oc/common/PacketType.scala +++ b/src/main/scala/li/cil/oc/common/PacketType.scala @@ -23,6 +23,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 6b89d2c17..390f037bb 100644 --- a/src/main/scala/li/cil/oc/common/Proxy.scala +++ b/src/main/scala/li/cil/oc/common/Proxy.scala @@ -68,6 +68,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 94970d4b3..9022de254 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..c55f24225 --- /dev/null +++ b/src/main/scala/li/cil/oc/common/event/NanomachinesEventHandler.scala @@ -0,0 +1,145 @@ +package li.cil.oc.common.event + +import java.io.FileInputStream +import java.io.FileOutputStream + +import cpw.mods.fml.common.eventhandler.SubscribeEvent +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 + +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.overlayNanomachines) + drawRect(left.toInt, top.toInt, sizeX, sizeY, sizeX, sizeY) + Minecraft.getMinecraft.getTextureManager.bindTexture(Textures.overlayNanomachinesBar) + 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.instance + t.startDrawingQuads() + t.addVertexWithUV(x, y + h, 0, 0, h * sy) + t.addVertexWithUV(x + w, y + h, 0, w * sx, h * sy) + t.addVertexWithUV(x + w, y + h * (1 - fill), 0, w * sx, 1 - fill) + t.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/init/Items.scala b/src/main/scala/li/cil/oc/common/init/Items.scala index 512c42955..e5446ba13 100644 --- a/src/main/scala/li/cil/oc/common/init/Items.scala +++ b/src/main/scala/li/cil/oc/common/init/Items.scala @@ -547,6 +547,9 @@ object Items extends ItemAPI { Recipes.addSubItem(new item.DataCard(multi, Tier.Two), Constants.ItemName.DataCardTier2, "oc:dataCard2") Recipes.addSubItem(new item.DataCard(multi, Tier.Three), Constants.ItemName.DataCardTier3, "oc:dataCard3") + // 1.5.18 + Recipes.addSubItem(new item.Nanomachines(multi), Constants.ItemName.Nanomachines, "oc:nanomachines") + // Register aliases. for ((k, v) <- aliases) { descriptors.getOrElseUpdate(k, descriptors(v)) 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..6c5cb8ad9 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 onEaten(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.field_76443_y.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..afd8020d6 --- /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 onEaten(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..214014487 --- /dev/null +++ b/src/main/scala/li/cil/oc/common/nanomachines/ControllerImpl.scala @@ -0,0 +1,301 @@ +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.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, "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..c470b058d --- /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 cpw.mods.fml.common.eventhandler.Event +import li.cil.oc.api +import li.cil.oc.Settings +import li.cil.oc.util.BlockPosition +import li.cil.oc.util.ExtendedWorld._ +import net.minecraft.block.Block +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 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, pos.x, pos.y, pos.z, 0, world) + val allowed = !event.isCanceled && event.useBlock != Event.Result.DENY && event.useItem != Event.Result.DENY + val adventureOk = !world.getWorldInfo.getGameType.isAdventure || player.isCurrentToolAdventureModeExempt(pos.x, pos.y, pos.z) + if (allowed && adventureOk && !world.isAirBlock(pos)) { + val block = world.getBlock(pos) + val hardness = block.getPlayerRelativeBlockHardness(player, world, pos.x, pos.y, pos.z) + if (hardness > 0) { + val timeToBreak = (1 / hardness).toInt + if (timeToBreak < 20 * 30) { + val meta = world.getBlockMetadata(pos) + val info = new SlowBreakInfo(now, now + timeToBreak, pos, Option(player.getCurrentEquippedItem).map(_.copy()), block, meta) + 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 block: Block, val meta: Int) { + 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.getBlock(pos) == block && world.getBlockMetadata(pos) == meta + if (sameBlock) { + world.destroyBlockInWorldPartially(pos.hashCode(), pos, -1) + if (player.theItemInWorldManager.tryHarvestBlock(pos.x, pos.y, pos.z)) { + world.playAuxSFX(2001, pos, Block.getIdFromBlock(block) + (meta << 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..72adcd9c0 --- /dev/null +++ b/src/main/scala/li/cil/oc/common/nanomachines/provider/MagnetProvider.scala @@ -0,0 +1,41 @@ +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.boundingBox.expand(actualRange, actualRange, actualRange)) + items.collect { + case item: EntityItem if item.delayBeforeCanPickup < 1 && 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 = Vec3.createVectorHelper(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..75c4a2fd4 --- /dev/null +++ b/src/main/scala/li/cil/oc/common/nanomachines/provider/ParticleProvider.scala @@ -0,0 +1,54 @@ +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 + +object ParticleProvider extends SimpleProvider { + final val Id = "b48c4bbd-51bb-4915-9367-16cff3220e4b" + + final val ParticleNames = Array( + "fireworksSpark", + "townaura", + "smoke", + "witchMagic", + "note", + "enchantmenttable", + "flame", + "lava", + "splash", + "reddust", + "slime", + "heart", + "happyVillager" + ) + + override def doCreateBehaviors(player: EntityPlayer): Iterable[Behavior] = ParticleNames.map(new ParticleBehavior(_, player)) + + override def doWriteToNBT(behavior: Behavior, nbt: NBTTagCompound): Unit = { + behavior match { + case particles: ParticleBehavior => + nbt.setString("effectName", particles.effectName) + case _ => // Wat. + } + } + + override def doReadFromNBT(player: EntityPlayer, nbt: NBTTagCompound): Behavior = { + val effectName = nbt.getString("effectName") + new ParticleBehavior(effectName, player) + } + + class ParticleBehavior(var effectName: String, player: EntityPlayer) extends SimpleBehavior(player) { + override def getNameHint = "particles" + + override def update(): Unit = { + val world = player.getEntityWorld + if (world.isRemote) { + PlayerUtils.spawnParticleAround(player, effectName, 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 728ee3d4f..ad20157f7 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Charger.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Charger.scala @@ -8,6 +8,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 @@ -29,7 +30,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 @@ -80,9 +81,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)))) } } @@ -110,15 +109,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("happyVillager", 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("happyVillager", position.xCoord + dx, position.yCoord + dz, position.zCoord + dy, 0, 0, 0) + }) } } @@ -192,23 +191,60 @@ class Charger extends traits.Environment with traits.PowerAcceptor with traits.R } def updateConnectors() { - val robotConnectors = ForgeDirection.VALID_DIRECTIONS.map(side => { + val robots = ForgeDirection.VALID_DIRECTIONS.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 => (Vec3.createVectorHelper(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.notifyBlocksOfNeighborChange(x, y, z, block) } } + + 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 = Vec3.createVectorHelper(drone.posX, drone.posY, drone.posZ) + } + + class PlayerChargeable(val player: EntityPlayer) extends Chargeable { + override def pos: Vec3 = Vec3.createVectorHelper(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 9283c4a51..579c6cb52 100644 --- a/src/main/scala/li/cil/oc/integration/opencomputers/ModOpenComputers.scala +++ b/src/main/scala/li/cil/oc/integration/opencomputers/ModOpenComputers.scala @@ -28,6 +28,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 @@ -79,6 +83,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) @@ -245,6 +250,11 @@ object ModOpenComputers extends ModProxy { api.Manual.addTab(new TextureTabIconRenderer(Textures.guiManualHome), "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, x: Int, y: Int, z: Int, 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 9f23aaf74..06c18448b 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 @@ -258,6 +261,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 fd5a6a1a2..d268cb4dc 100644 --- a/src/main/scala/li/cil/oc/server/agent/Player.scala +++ b/src/main/scala/li/cil/oc/server/agent/Player.scala @@ -299,7 +299,8 @@ class Player(val agent: internal.Agent) extends FakePlayer(agent.world.asInstanc block.onBlockClicked(world, x, y, z, this) world.extinguishFire(this, x, y, z, side) - val isBlockUnbreakable = block.getBlockHardness(world, x, y, z) < 0 + val hardness = block.getBlockHardness(world, x, y, z) + val isBlockUnbreakable = hardness < 0 val canDestroyBlock = !isBlockUnbreakable && block.canEntityDestroy(world, x, y, z, this) if (!canDestroyBlock) { return 0 @@ -315,7 +316,6 @@ class Player(val agent: internal.Agent) extends FakePlayer(agent.world.asInstanc return 0 } - val hardness = block.getBlockHardness(world, x, y, z) val strength = getBreakSpeed(block, false, metadata, x, y, z) 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 e87d8f527..a6802d84b 100644 --- a/src/main/scala/li/cil/oc/server/component/WirelessNetworkCard.scala +++ b/src/main/scala/li/cil/oc/server/component/WirelessNetworkCard.scala @@ -33,6 +33,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 7f2e8c286..3664288a5 100644 --- a/src/main/scala/li/cil/oc/util/BlockPosition.scala +++ b/src/main/scala/li/cil/oc/util/BlockPosition.scala @@ -1,6 +1,7 @@ package li.cil.oc.util import appeng.api.util.DimensionalCoord +import com.google.common.hash.Hashing import cpw.mods.fml.common.Optional import li.cil.oc.api.driver.EnvironmentHost import li.cil.oc.integration.Mods @@ -40,6 +41,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 { @@ -51,6 +64,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 7c2006d76..15656fc77 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.func_147480_a(position.x, position.y, position.z, drops) + def destroyBlockInWorldPartially(entityId: Int, position: BlockPosition, progress: Int) = world.destroyBlockInWorldPartially(entityId, position.x, position.y, position.z, progress) + def extinguishFire(player: EntityPlayer, position: BlockPosition, side: ForgeDirection) = world.extinguishFire(player, position.x, position.y, position.z, side.ordinal) def getBlockHardness(position: BlockPosition) = getBlock(position).getBlockHardness(world, position.x, position.y, position.z) diff --git a/src/main/scala/li/cil/oc/util/PlayerUtils.scala b/src/main/scala/li/cil/oc/util/PlayerUtils.scala index 5a80710b1..a913b947d 100644 --- a/src/main/scala/li/cil/oc/util/PlayerUtils.scala +++ b/src/main/scala/li/cil/oc/util/PlayerUtils.scala @@ -11,4 +11,15 @@ object PlayerUtils { } nbt.getCompoundTag(EntityPlayer.PERSISTED_NBT_TAG) } + + def spawnParticleAround(player: EntityPlayer, effectName: String, chance: Double = 1.0): Unit = { + val rng = player.getEntityWorld.rand + if (chance >= 1 || rng.nextDouble() < chance) { + val bounds = player.boundingBox + 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(effectName, x, y, z, 0, 0, 0) + } + } }