Merge branch 'master-MC1.7.10' of github.com:MightyPirates/OpenComputers into master-MC1.8

Conflicts:
	build.properties
	src/main/scala/li/cil/oc/client/Proxy.scala
	src/main/scala/li/cil/oc/client/Textures.scala
	src/main/scala/li/cil/oc/common/PacketBuilder.scala
	src/main/scala/li/cil/oc/common/init/Items.scala
	src/main/scala/li/cil/oc/common/tileentity/Charger.scala
	src/main/scala/li/cil/oc/server/agent/Player.scala
	src/main/scala/li/cil/oc/util/BlockPosition.scala
	src/main/scala/li/cil/oc/util/ExtendedWorld.scala
This commit is contained in:
Florian Nücke 2015-09-17 22:15:33 +02:00
commit cb433d3b01
56 changed files with 1924 additions and 51 deletions

View File

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

Binary file not shown.

View File

@ -106,7 +106,7 @@ repositories {
}
maven {
name = "dmodoomsirius"
url = "http://ci.dmodoomsirius.me/maven/"
url = "http://api.dmodoomsirius.me/"
}
maven {
name = "ue"
@ -204,7 +204,7 @@ dependencies {
provided "mcp.mobius.waila:Waila:${config.waila.version}_${config.minecraft.version}:dev"
provided "net.industrial-craft:industrialcraft-2:${config.ic2.version}:dev"
provided "net.sengir.forestry:forestry_${config.minecraft.version}:${config.forestry.version}:dev"
provided "notenoughkeys:NeK:${config.minecraft.version}-${config.nek.version}:deobf-dev"
provided "dev.modwarriors.notenoughkeys:NotEnoughKeys:${config.minecraft.version}-${config.nek.version}:deobf-dev"
provided "qmunity:QmunityLib:${config.qmunitylib.version}:deobf"
provided "tmech:TMechworks:${config.minecraft.version}-${config.tmech.version}:deobf"
provided ("mrtjp:ProjectRed:${config.projred.version}:dev") {

View File

@ -33,7 +33,7 @@ mekanism.version=7.1.2
mfr.cf=2229/626
mfr.version=[1.7.10]2.8.0RC8-86
nei.version=1.0.5.82
nek.version=1.0.0b2dev
nek.version=2.0.0b4
poweradvantage.version=1.2.0
projred.version=1.7.10-4.6.2.82
qmunitylib.version=0.1.105

View File

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

View File

@ -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.
* <p/>
* 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.
* <p/>
* 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<BehaviorProvider> 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 <tt>true</tt> if the player has a controller, <tt>false</tt> 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.
* <p/>
* 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 <tt>null</tt>.
*
* @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.
* <p/>
* 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.
* <p/>
* 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() {
}
}

View File

@ -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.
* <p/>
* 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<BehaviorProvider> getProviders();
/**
* Check whether a player has a nanomachine controller installed.
*
* @param player the player to check for.
* @return <tt>true</tt> if the player has a controller, <tt>false</tt> otherwise.
*/
boolean hasController(EntityPlayer player);
/**
* Get the nanomachine controller of the specified player.
* <p/>
* 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 <tt>null</tt>.
*
* @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.
* <p/>
* 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.
* <p/>
* This will disable all active behaviors before disposing the controller.
*
* @param player the player to uninstall a nanomachine controller from.
*/
void uninstallController(EntityPlayer player);
}

View File

@ -0,0 +1,50 @@
package li.cil.oc.api.nanomachines;
/**
* Implemented by single behaviors.
* <p/>
* 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.
* <p/>
* You can <em>not</em> use commas (<tt>,</tt>) or double quotes (<tt>"</tt>)
* in the returned string. If you do, they'll automatically be replaced with
* underscores.
* <p/>
* This is entirely optional and may even return <tt>null</tt>. 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 <tt>null</tt>
* or a random string.
* <p/>
* 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.
* <p/>
* Use this to initialize permanent effects.
*/
void onEnable();
/**
* Called when this behavior becomes inactive.
* <p/>
* Use this to remove permanent effects.
*/
void onDisable();
/**
* Called each tick while this behavior is active.
*/
void update();
}

View File

@ -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.
* <p/>
* 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.
* <p/>
* 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.
* <p/>
* 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 <tt>null</tt>.
*/
Iterable<Behavior> createBehaviors(EntityPlayer player);
/**
* Write a behavior to NBT.
* <p/>
* This will only be called for behaviors originally created by this provider.
* <p/>
* This will only be called on the server. All behaviors not saved will be
* lost when loading again, they will <em>not</em> 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.
* <p/>
* You are <em>not</em> guaranteed that his nbt belongs to a behavior
* created by this provider! If the NBT cannot be handled, return
* <tt>null</tt>.
* <p/>
* 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 <tt>null</tt> if unhandled.
*/
Behavior readFromNBT(EntityPlayer player, NBTTagCompound nbt);
}

View File

@ -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).
* <p/>
* 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.
* <p/>
* 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.
* <p/>
* 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.
* <p/>
* 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.
* <p/>
* 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.
* <p/>
* 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 <code>index &lt; 0</code> or <code>index &gt;= getInputCount</code>.
*/
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 <code>index &lt; 0</code> or <code>index &gt;= getInputCount</code>.
*/
void setInput(int index, boolean value);
/**
* Get the list of currently active behaviors, based on the current input states.
* <p/>
* 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 <tt>null</tt>.
*/
Iterable<Behavior> 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.
* <p/>
* 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);
}

View File

@ -100,6 +100,12 @@ opencomputers {
# The radius in which computer beeps can be heard.
beepRadius: 16
# Position of the power indicator for nanomachines, by default left to the
# player's health, specified by negative values. Values in [0, 1) will be
# treated as relative positions, values in [1, inf) will be treated as
# absolute positions.
nanomachineHudPos: [-1, -1]
}
# Computer related settings, concerns server performance and security.
@ -566,6 +572,10 @@ opencomputers {
# The internal buffer size of the hover boots.
hoverBoots: 15000.0
# Amount of energy stored by nanomachines. Yeah, I also don't know
# where all that energy is stored. It's quite fascinating.
nanomachines: 100000
}
# Default "costs", i.e. how much energy certain operations consume.
@ -771,6 +781,12 @@ opencomputers {
# Energy required for one transposer operation (regardless of the number
# of items / fluid volume moved).
transposer: 1
# Energy consumed per tick per active input node by nanomachines.
nanomachineInput: 0.5
# Energy consumed when reconfiguring nanomachines.
nanomachinesReconfigure: 5000
}
# The rate at which different blocks accept external power. All of these
@ -958,6 +974,67 @@ opencomputers {
relayAmountUpgrade: 1
}
# Nanomachine related values. Note that most of these are relative, as
# they scale with the number of total effects controlled by nanomachines,
# which may very much vary depending on other mods used together with OC.
# To configure this, you'll need to know how this works a bit more in-
# depth, so here goes: there are three layers, the behavior layer, the
# connector layer, and the input layer. The behavior layer consists of
# one node for each behavior provided by registered providers (by default
# these will be potion effects and a few other things). The connector
# layer merely serves to mix things up a little. The input layer is made
# up from nodes that can be triggered by the nanomachines. Each connector
# node has behavior nodes it outputs to, and gets signals from input nodes.
# Behavior nodes get signals from both the connector and the input layers.
# Reconfiguring builds up random connections. Some behaviors change what
# they do based on the number of active inputs (e.g. potion effects will
# increase their amplification value).
nanomachines {
# The relative amount of triggers available based on the number of
# available behaviors (such as different potion effects). For example,
# if there are a total of 10 behaviors available, 0.5 means there will
# be 5 trigger inputs, triggers being the inputs that can be activated
# via nanomachines.
triggerQuota: 0.5
# The relative number of connectors based on the number of available
# behaviors (see triggerQuota).
connectorQuota: 0.2
# The maximum number of inputs for each node of the "neural network"
# nanomachines connect to. I.e. each behavior node and connector node
# may only have up to this many inputs.
maxInputs: 2
# The maximum number of outputs for each node (see maxInputs).
maxOutputs: 2
# How many input nodes may be active at the same time before negative
# effects are applied to the player.
safeInputCount: 2
# The time in seconds it takes to reconfigure the nanomachines. This is
# to avoid spamming reconfigurations.
reconfigureCooldown: 5
# Range of the item magnet behavior added for each active input.
magnetRange: 8
# Radius in blocks of the disintegration behavior for each active input.
disintegrationRange: 1
# Blacklisted potions, i.e. potions that won't be used for the potion
# behaviors nanomachines may trigger. This can contain strings or numbers.
# In the case of strings, it has to be the internal name of the potion,
# in case of a number it has to be the potion ID.
potionBlacklist: [
"potion.heal",
"potion.regeneration",
"potion.invisibility",
"potion.saturation"
]
}
# 3D printer related stuff.
printer {
# The maximum number of shape for a state of a 3D print allowed. This is

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -26,6 +26,7 @@
* [Геоанализатор](geolyzer.md)
* [Датчик движения](motionSensor.md)
* [Редстоун-I/O](redstone.md)
* [Транспозер](transposer.md)
* [Путевая точка](waypoint.md)
## Сборка / Печать

View File

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

View File

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

View File

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

View File

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

View File

@ -190,10 +190,6 @@ nuggetIron {
cuttingWire {
input: [[stickWood, nuggetIron, stickWood]]
}
acid {
type: shapeless
input: [bucketWater, sugar, slimeball, fermentedSpiderEye, bone]
}
disk {
input: [["", nuggetIron, ""]
[nuggetIron, "", nuggetIron]

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,5 @@
{
"animation": {
"frametime": 4
}
}

View File

@ -119,6 +119,7 @@ object Constants {
final val MicrocontrollerCaseCreative = "microcontrollerCaseCreative"
final val MicrocontrollerCaseTier1 = "microcontrollerCase1"
final val MicrocontrollerCaseTier2 = "microcontrollerCase2"
final val Nanomachines = "nanomachines"
final val NavigationUpgrade = "navigationUpgrade"
final val NetworkCard = "lanCard"
final val NumPad = "numPad"

View File

@ -39,6 +39,13 @@ class Settings(val config: Config) {
val beepSampleRate = config.getInt("client.beepSampleRate")
val beepAmplitude = config.getInt("client.beepVolume") max 0 min Byte.MaxValue
val beepRadius = config.getDouble("client.beepRadius").toFloat max 1 min 32
val nanomachineHudPos = Array(config.getDoubleList("client.nanomachineHudPos"): _*) match {
case Array(x, y) =>
(x: Double, y: Double)
case _ =>
OpenComputers.log.warn("Bad number of HUD coordiantes, ignoring.")
(-1.0, -1.0)
}
// ----------------------------------------------------------------------- //
// computer
@ -157,6 +164,7 @@ class Settings(val config: Config) {
val bufferDrone = config.getDouble("power.buffer.drone") max 0
val bufferMicrocontroller = config.getDouble("power.buffer.mcu") max 0
val bufferHoverBoots = config.getDouble("power.buffer.hoverBoots") max 1
val bufferNanomachines = config.getDouble("power.buffer.nanomachines") max 0
// power.cost
val computerCost = config.getDouble("power.cost.computer") max 0
@ -202,6 +210,8 @@ class Settings(val config: Config) {
val dataCardComplexByte = config.getDouble("power.cost.dataCardComplexByte") max 0
val dataCardAsymmetric = config.getDouble("power.cost.dataCardAsymmetric") max 0
val transposerCost = config.getDouble("power.cost.transposer") max 0
val nanomachineCost = config.getDouble("power.cost.nanomachineInput") max 0
val nanomachineReconfigureCost = config.getDouble("power.cost.nanomachinesReconfigure") max 0
// power.rate
val accessPointRate = config.getDouble("power.rate.accessPoint") max 0
@ -307,7 +317,8 @@ class Settings(val config: Config) {
val inputUsername = config.getBoolean("misc.inputUsername")
val maxClipboard = config.getInt("misc.maxClipboard") max 0
val maxNetworkPacketSize = config.getInt("misc.maxNetworkPacketSize") max 0
val maxNetworkPacketParts = config.getInt("misc.maxNetworkPacketParts") max 0
// Need at least 4 for nanomachine protocol. Because I can!
val maxNetworkPacketParts = config.getInt("misc.maxNetworkPacketParts") max 4
val maxOpenPorts = config.getInt("misc.maxOpenPorts") max 0
val maxWirelessRange = config.getDouble("misc.maxWirelessRange") max 0
val rTreeMaxEntries = 10
@ -339,6 +350,18 @@ class Settings(val config: Config) {
val serverRackSwitchTier = (config.getInt("misc.serverRackSwitchTier") - 1) max Tier.None min Tier.Three
val redstoneDelay = config.getDouble("misc.redstoneDelay") max 0
// ----------------------------------------------------------------------- //
// nanomachines
val nanomachineTriggerQuota = config.getDouble("nanomachines.triggerQuota") max 0
val nanomachineConnectorQuota = config.getDouble("nanomachines.connectorQuota") max 0
val nanomachineMaxInputs = config.getInt("nanomachines.maxInputs") max 1
val nanomachineMaxOutputs = config.getInt("nanomachines.maxOutputs") max 1
val nanomachinesSafeInputCount = config.getDouble("nanomachines.safeInputCount") max 0 min 1
val nanomachineReconfigureTimeout = config.getDouble("nanomachines.reconfigureCooldown") max 0
val nanomachineMagnetRange = config.getDouble("nanomachines.magnetRange") max 0
val nanomachineDisintegrationRange = config.getInt("nanomachines.disintegrationRange") max 0
val nanomachinePotionBlacklist = config.getAnyRefList("nanomachines.potionBlacklist")
// ----------------------------------------------------------------------- //
// printer
val maxPrintComplexity = config.getInt("printer.maxShapes")

View File

@ -4,12 +4,14 @@ import java.io.EOFException
import li.cil.oc.Localization
import li.cil.oc.Settings
import li.cil.oc.api
import li.cil.oc.api.component
import li.cil.oc.api.event.FileSystemAccessEvent
import li.cil.oc.client.renderer.PetRenderer
import li.cil.oc.common.Loot
import li.cil.oc.common.PacketType
import li.cil.oc.common.container
import li.cil.oc.common.nanomachines.ControllerImpl
import li.cil.oc.common.tileentity._
import li.cil.oc.common.tileentity.traits._
import li.cil.oc.common.{PacketHandler => CommonPacketHandler}
@ -60,6 +62,9 @@ object PacketHandler extends CommonPacketHandler {
case PacketType.HologramTranslation => onHologramPositionOffsetY(p)
case PacketType.HologramValues => onHologramValues(p)
case PacketType.LootDisk => onLootDisk(p)
case PacketType.NanomachinesConfiguration => onNanomachinesConfiguration(p)
case PacketType.NanomachinesInputs => onNanomachinesInputs(p)
case PacketType.NanomachinesPower => onNanomachinesPower(p)
case PacketType.NetSplitterState => onNetSplitterState(p)
case PacketType.ParticleEffect => onParticleEffect(p)
case PacketType.PetVisibility => onPetVisibility(p)
@ -283,6 +288,51 @@ object PacketHandler extends CommonPacketHandler {
}
}
def onNanomachinesConfiguration(p: PacketParser) = {
p.readEntity[EntityPlayer]() match {
case Some(player) =>
val hasController = p.readBoolean()
if (hasController) {
api.Nanomachines.installController(player) match {
case controller: ControllerImpl => controller.load(p.readNBT())
case _ => // Wat.
}
}
else {
api.Nanomachines.uninstallController(player)
}
case _ => // Invalid packet.
}
}
def onNanomachinesInputs(p: PacketParser) = {
p.readEntity[EntityPlayer]() match {
case Some(player) => api.Nanomachines.getController(player) match {
case controller: ControllerImpl =>
val inputs = new Array[Byte](p.readInt())
p.read(inputs)
controller.configuration.synchronized {
for ((value, index) <- inputs.zipWithIndex if index < controller.configuration.triggers.length) {
controller.configuration.triggers(index).isActive = value == 1
}
controller.activeBehaviorsDirty = true
}
case _ => // Wat.
}
case _ => // Invalid packet.
}
}
def onNanomachinesPower(p: PacketParser) = {
p.readEntity[EntityPlayer]() match {
case Some(player) => api.Nanomachines.getController(player) match {
case controller: ControllerImpl => controller.storedEnergy = p.readDouble()
case _ => // Wat.
}
case _ => // Invalid packet.
}
}
def onNetSplitterState(p: PacketParser) =
p.readTileEntity[NetSplitter]() match {
case Some(t) =>

View File

@ -13,6 +13,8 @@ import li.cil.oc.client.renderer.entity.DroneRenderer
import li.cil.oc.client.renderer.tileentity._
import li.cil.oc.common.component.TextBuffer
import li.cil.oc.common.entity.Drone
import li.cil.oc.common.event.NanomachinesEventHandler
import li.cil.oc.common.init.Items
import li.cil.oc.common.item.traits.Delegate
import li.cil.oc.common.tileentity
import li.cil.oc.common.tileentity.ServerRack
@ -79,6 +81,7 @@ private[oc] class Proxy extends CommonProxy {
ClientRegistry.registerKeyBinding(KeyBindings.clipboardPaste)
MinecraftForge.EVENT_BUS.register(HighlightRenderer)
MinecraftForge.EVENT_BUS.register(NanomachinesEventHandler.Client)
MinecraftForge.EVENT_BUS.register(PetRenderer)
MinecraftForge.EVENT_BUS.register(ServerRack)
MinecraftForge.EVENT_BUS.register(Sound)

View File

@ -47,6 +47,8 @@ object Textures {
val ManualHome = L("manual_home")
val ManualMissingItem = L("manual_missing_item")
val ManualTab = L("manual_tab")
val Nanomachines = L("nanomachines_power")
val NanomachinesBar = L("nanomachines_power_bar")
val Printer = L("printer")
val PrinterInk = L("printer_ink")
val PrinterMaterial = L("printer_material")

View File

@ -54,6 +54,8 @@ abstract class PacketBuilder(stream: OutputStream) extends DataOutputStream(stre
def sendToAllPlayers() = OpenComputers.channel.sendToAll(packet)
def sendToPlayersNearEntity(e: Entity, range: Option[Double] = None): Unit = sendToNearbyPlayers(e.getEntityWorld, e.posX, e.posY, e.posZ, range)
def sendToPlayersNearTileEntity(t: TileEntity, range: Option[Double] = None): Unit = sendToNearbyPlayers(t.getWorld, t.getPos.getX + 0.5, t.getPos.getY + 0.5, t.getPos.getZ + 0.5, range)
def sendToPlayersNearHost(host: EnvironmentHost, range: Option[Double] = None): Unit = sendToNearbyPlayers(host.world, host.xPosition, host.yPosition, host.zPosition, range)

View File

@ -22,6 +22,9 @@ object PacketType extends Enumeration {
HologramTranslation,
HologramValues,
LootDisk,
NanomachinesConfiguration,
NanomachinesInputs,
NanomachinesPower,
NetSplitterState,
ParticleEffect,
PetVisibility, // Goes both ways.

View File

@ -73,6 +73,7 @@ class Proxy {
api.API.fileSystem = fs.FileSystem
api.API.items = Items
api.API.machine = machine.Machine
api.API.nanomachines = nanomachines.Nanomachines
api.API.network = network.Network
api.API.config = Settings.get.config

View File

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

View File

@ -0,0 +1,146 @@
package li.cil.oc.common.event
import java.io.FileInputStream
import java.io.FileOutputStream
import li.cil.oc.OpenComputers
import li.cil.oc.Settings
import li.cil.oc.api
import li.cil.oc.api.nanomachines.Controller
import li.cil.oc.client.Textures
import li.cil.oc.common.nanomachines.ControllerImpl
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.ScaledResolution
import net.minecraft.client.renderer.Tessellator
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.nbt.CompressedStreamTools
import net.minecraft.nbt.NBTTagCompound
import net.minecraftforge.client.event.RenderGameOverlayEvent
import net.minecraftforge.event.entity.living.LivingEvent
import net.minecraftforge.event.entity.player.PlayerEvent
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
object NanomachinesEventHandler {
object Client {
@SubscribeEvent
def onRenderGameOverlay(e: RenderGameOverlayEvent.Post): Unit = {
if (e.`type` == RenderGameOverlayEvent.ElementType.TEXT) {
val mc = Minecraft.getMinecraft
api.Nanomachines.getController(mc.thePlayer) match {
case controller: Controller =>
val res = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight)
val sizeX = 8
val sizeY = 12
val width = res.getScaledWidth
val height = res.getScaledHeight
val (x, y) = Settings.get.nanomachineHudPos
val left =
math.min(width - sizeX,
if (x < 0) width / 2 - 91 - 12
else if (x < 1) width * x
else x)
val top =
math.min(height - sizeY,
if (y < 0) height - 39
else if (y < 1) y * height
else y)
val fill = controller.getLocalBuffer / controller.getLocalBufferSize
Minecraft.getMinecraft.getTextureManager.bindTexture(Textures.GUI.Nanomachines)
drawRect(left.toInt, top.toInt, sizeX, sizeY, sizeX, sizeY)
Minecraft.getMinecraft.getTextureManager.bindTexture(Textures.GUI.NanomachinesBar)
drawRect(left.toInt, top.toInt, sizeX, sizeY, sizeX, sizeY, fill)
case _ => // Nothing to show.
}
}
}
private def drawRect(x: Int, y: Int, w: Int, h: Int, tw: Int, th: Int, fill: Double = 1) {
val sx = 1f / tw
val sy = 1f / th
val t = Tessellator.getInstance
val r = t.getWorldRenderer
r.startDrawingQuads()
r.addVertexWithUV(x, y + h, 0, 0, h * sy)
r.addVertexWithUV(x + w, y + h, 0, w * sx, h * sy)
r.addVertexWithUV(x + w, y + h * (1 - fill), 0, w * sx, 1 - fill)
r.addVertexWithUV(x, y + h * (1 - fill), 0, 0, 1 - fill)
t.draw()
}
}
object Common {
@SubscribeEvent
def onLivingUpdate(e: LivingEvent.LivingUpdateEvent): Unit = {
e.entity match {
case player: EntityPlayer => api.Nanomachines.getController(player) match {
case controller: ControllerImpl =>
if (controller.player eq player) {
controller.update()
}
else {
// Player entity instance changed (e.g. respawn), recreate the controller.
val nbt = new NBTTagCompound()
controller.save(nbt)
api.Nanomachines.uninstallController(controller.player)
api.Nanomachines.installController(player) match {
case newController: ControllerImpl =>
newController.load(nbt)
newController.reset()
case _ => // Eh?
}
}
case _ => // Not a player with nanomachines.
}
case _ => // Not a player.
}
}
@SubscribeEvent
def onPlayerSave(e: PlayerEvent.SaveToFile): Unit = {
val file = e.getPlayerFile("ocnm")
api.Nanomachines.getController(e.entityPlayer) match {
case controller: ControllerImpl =>
try {
val nbt = new NBTTagCompound()
controller.save(nbt)
val fos = new FileOutputStream(file)
try CompressedStreamTools.writeCompressed(nbt, fos) catch {
case t: Throwable =>
OpenComputers.log.warn("Error saving nanomachine state.", t)
}
fos.close()
}
catch {
case t: Throwable =>
OpenComputers.log.warn("Error saving nanomachine state.", t)
}
case _ => // Not a player with nanomachines.
}
}
@SubscribeEvent
def onPlayerLoad(e: PlayerEvent.LoadFromFile): Unit = {
val file = e.getPlayerFile("ocnm")
if (file.exists()) {
api.Nanomachines.getController(e.entityPlayer) match {
case controller: ControllerImpl =>
try {
val fis = new FileInputStream(file)
try controller.load(CompressedStreamTools.readCompressed(fis)) catch {
case t: Throwable =>
OpenComputers.log.warn("Error loading nanomachine state.", t)
}
fis.close()
}
catch {
case t: Throwable =>
OpenComputers.log.warn("Error loading nanomachine state.", t)
}
case _ => // Not a player with nanomachines.
}
}
}
}
}

View File

@ -1,3 +1,34 @@
package li.cil.oc.common.item
class Acid(val parent: Delegator) extends traits.Delegate
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.item.EnumAction
import net.minecraft.item.ItemStack
import net.minecraft.potion.Potion
import net.minecraft.potion.PotionEffect
import net.minecraft.world.World
class Acid(val parent: Delegator) extends traits.Delegate {
override def onItemRightClick(stack: ItemStack, world: World, player: EntityPlayer): ItemStack = {
player.setItemInUse(stack, getMaxItemUseDuration(stack))
stack
}
override def getItemUseAction(stack: ItemStack): EnumAction = EnumAction.DRINK
override def getMaxItemUseDuration(stack: ItemStack): Int = 32
override def onItemUseFinish(stack: ItemStack, world: World, player: EntityPlayer): ItemStack = {
if (!world.isRemote) {
player.addPotionEffect(new PotionEffect(Potion.blindness.id, 200))
player.addPotionEffect(new PotionEffect(Potion.poison.id, 100))
player.addPotionEffect(new PotionEffect(Potion.moveSlowdown.id, 600))
player.addPotionEffect(new PotionEffect(Potion.confusion.id, 1200))
player.addPotionEffect(new PotionEffect(Potion.fireResistance.id, 6000))
player.addPotionEffect(new PotionEffect(Potion.saturation.id, 2000))
stack.stackSize -= 1
}
if (stack.stackSize > 0) stack
else null
}
}

View File

@ -0,0 +1,30 @@
package li.cil.oc.common.item
import li.cil.oc.api
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.item.EnumAction
import net.minecraft.item.ItemStack
import net.minecraft.world.World
class Nanomachines(val parent: Delegator) extends traits.Delegate {
override def onItemRightClick(stack: ItemStack, world: World, player: EntityPlayer): ItemStack = {
if (!api.Nanomachines.hasController(player)) {
player.setItemInUse(stack, getMaxItemUseDuration(stack))
}
stack
}
override def getItemUseAction(stack: ItemStack): EnumAction = EnumAction.EAT
override def getMaxItemUseDuration(stack: ItemStack): Int = 32
override def onItemUseFinish(stack: ItemStack, world: World, player: EntityPlayer): ItemStack = {
if (!world.isRemote && !api.Nanomachines.hasController(player)) {
api.Nanomachines.installController(player).reconfigure()
stack.stackSize -= 1
}
if (stack.stackSize > 0) stack
else null
}
}

View File

@ -0,0 +1,302 @@
package li.cil.oc.common.nanomachines
import java.lang
import java.util.UUID
import com.google.common.base.Charsets
import com.google.common.base.Strings
import li.cil.oc.Settings
import li.cil.oc.api
import li.cil.oc.api.nanomachines.Behavior
import li.cil.oc.api.nanomachines.Controller
import li.cil.oc.api.network.Packet
import li.cil.oc.api.network.WirelessEndpoint
import li.cil.oc.server.PacketSender
import li.cil.oc.util.BlockPosition
import li.cil.oc.util.ExtendedNBT._
import li.cil.oc.util.PlayerUtils
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.entity.player.EntityPlayerMP
import net.minecraft.nbt.NBTTagCompound
import net.minecraft.potion.Potion
import net.minecraft.potion.PotionEffect
import net.minecraft.util.EnumParticleTypes
import net.minecraft.world.World
import scala.collection.convert.WrapAsJava._
import scala.collection.convert.WrapAsScala._
import scala.collection.mutable
class ControllerImpl(val player: EntityPlayer) extends Controller with WirelessEndpoint {
if (isServer) api.Network.joinWirelessNetwork(this)
final val MaxSenderDistance = 2f
final val FullSyncInterval = 20 * 60
var uuid = UUID.randomUUID.toString
var responsePort = 0
var storedEnergy = Settings.get.bufferNanomachines * 0.25
var hadPower = true
val configuration = new NeuralNetwork(this)
val activeBehaviors = mutable.Set.empty[Behavior]
var activeBehaviorsDirty = true
var configCooldown = 0
var hasSentConfiguration = false
override def world: World = player.getEntityWorld
override def x: Int = BlockPosition(player).x
override def y: Int = BlockPosition(player).y
override def z: Int = BlockPosition(player).z
override def receivePacket(packet: Packet, sender: WirelessEndpoint): Unit = {
if (getLocalBuffer > 0) {
val (dx, dy, dz) = ((sender.x + 0.5) - player.posX, (sender.y + 0.5) - player.posY, (sender.z + 0.5) - player.posZ)
val dSquared = dx * dx + dy * dy + dz * dz
if (dSquared < MaxSenderDistance * MaxSenderDistance) packet.data.headOption match {
case Some(header: Array[Byte]) if new String(header, Charsets.UTF_8) == "nanomachines" =>
val command = packet.data.drop(1).map {
case value: Array[Byte] => new String(value, Charsets.UTF_8)
case value => value
}
command match {
case Array("setResponsePort", port: java.lang.Number) =>
responsePort = port.intValue max 0 min 0xFFFF
respond(sender, "responsePort", responsePort)
case Array("dispose") =>
api.Nanomachines.uninstallController(player)
respond(sender, "disposed")
case Array("reconfigure") =>
reconfigure()
respond(sender, "reconfigured")
case Array("getTotalInputCount") =>
respond(sender, "totalInputCount", getTotalInputCount)
case Array("getSafeInputCount") =>
respond(sender, "safeInputCount", getSafeInputCount)
case Array("getInput", index: java.lang.Number) =>
try {
val trigger = getInput(index.intValue - 1)
respond(sender, "input", index.intValue, trigger)
}
catch {
case _: Throwable =>
respond(sender, "input", "error")
}
case Array("setInput", index: java.lang.Number, value: java.lang.Boolean) =>
try {
setInput(index.intValue - 1, value.booleanValue)
respond(sender, "input", index.intValue, getInput(index.intValue - 1))
}
catch {
case _: Throwable =>
respond(sender, "input", "error")
}
case Array("getActiveEffects") =>
configuration.synchronized {
val names = getActiveBehaviors.map(_.getNameHint).filterNot(Strings.isNullOrEmpty)
val joined = "{" + names.map(_.replace(',', '_').replace('"', '_')).mkString(",") + "}"
respond(sender, "active", joined)
}
case Array("getPowerState") =>
respond(sender, "power", getLocalBuffer, getLocalBufferSize)
case _ => // Ignore.
}
case _ => // Not for us.
}
}
}
def respond(endpoint: WirelessEndpoint, data: Any*): Unit = {
if (responsePort > 0) {
val cost = Settings.get.wirelessCostPerRange * 10
val epsilon = 0.1
if (changeBuffer(-cost) > -epsilon) {
val packet = api.Network.newPacket(uuid, null, responsePort, (Iterable("nanomachines") ++ data.map(_.asInstanceOf[AnyRef])).toArray)
api.Network.sendWirelessPacket(this, 10, packet)
}
}
}
// ----------------------------------------------------------------------- //
override def reconfigure() = {
if (isServer && configCooldown < 1) configuration.synchronized {
configuration.reconfigure()
activeBehaviorsDirty = true
configCooldown = (Settings.get.nanomachineReconfigureTimeout * 20).toInt
player match {
case playerMP: EntityPlayerMP if playerMP.playerNetServerHandler != null =>
player.addPotionEffect(new PotionEffect(Potion.blindness.id, 100))
player.addPotionEffect(new PotionEffect(Potion.poison.id, 150))
player.addPotionEffect(new PotionEffect(Potion.moveSlowdown.id, 200))
changeBuffer(-Settings.get.nanomachineReconfigureCost)
hasSentConfiguration = false
case _ => // We're still setting up / loading.
}
}
this
}
override def getTotalInputCount: Int = configuration.synchronized(configuration.triggers.length)
override def getSafeInputCount: Int = configuration.synchronized(configuration.triggers.length * Settings.get.nanomachinesSafeInputCount).toInt
override def getInput(index: Int): Boolean = configuration.synchronized(configuration.triggers(index).isActive)
override def setInput(index: Int, value: Boolean): Unit = {
if (isServer && configCooldown < 1) configuration.synchronized {
configuration.triggers(index).isActive = value
activeBehaviorsDirty = true
}
}
override def getActiveBehaviors: lang.Iterable[Behavior] = configuration.synchronized {
cleanActiveBehaviors()
activeBehaviors
}
override def getInputCount(behavior: Behavior): Int = configuration.synchronized(configuration.inputs(behavior))
// ----------------------------------------------------------------------- //
override def getLocalBuffer: Double = storedEnergy
override def getLocalBufferSize: Double = Settings.get.bufferNanomachines
override def changeBuffer(delta: Double): Double = {
if (isClient) delta
else if (delta < 0 && (Settings.get.ignorePower || player.capabilities.isCreativeMode)) 0.0
else {
val newValue = storedEnergy + delta
storedEnergy = math.min(math.max(newValue, 0), getLocalBufferSize)
newValue - storedEnergy
}
}
// ----------------------------------------------------------------------- //
def update(): Unit = {
if (isServer) {
api.Network.updateWirelessNetwork(this)
}
if (configCooldown > 0) {
configCooldown -= 1
return
}
val hasPower = getLocalBuffer > 0 || Settings.get.ignorePower
lazy val active = getActiveBehaviors.toIterable // Wrap once.
lazy val activeInputs = configuration.triggers.count(_.isActive)
if (hasPower != hadPower) {
if (!hasPower) active.foreach(_.onDisable())
else active.foreach(_.onEnable())
}
if (hasPower) {
active.foreach(_.update())
if (isServer) {
if (player.getEntityWorld.getTotalWorldTime % Settings.get.tickFrequency == 0) {
changeBuffer(-Settings.get.nanomachineCost * Settings.get.tickFrequency * (activeInputs + 0.5))
PacketSender.sendNanomachinePower(player)
}
val overload = activeInputs - getSafeInputCount
if (!player.capabilities.isCreativeMode && overload > 0 && player.getEntityWorld.getTotalWorldTime % 20 == 0) {
player.setHealth(player.getHealth - overload)
player.performHurtAnimation()
}
}
if (isClient) {
val energyRatio = getLocalBuffer / (getLocalBufferSize + 1)
val triggerRatio = activeInputs / (configuration.triggers.length + 1)
val intensity = (energyRatio + triggerRatio) * 0.25
PlayerUtils.spawnParticleAround(player, EnumParticleTypes.PORTAL, intensity)
}
}
if (isServer) {
// Send new power state, if it changed.
if (hadPower != hasPower) {
PacketSender.sendNanomachinePower(player)
}
// Send a full sync every now and then, e.g. for other players coming
// closer that weren't there to get the initial info for an enabled
// input.
if (!hasSentConfiguration || player.getEntityWorld.getTotalWorldTime % FullSyncInterval == 0) {
hasSentConfiguration = true
PacketSender.sendNanomachineConfiguration(player)
}
}
hadPower = hasPower
}
def reset(): Unit = {
configuration.synchronized {
for (index <- 0 until getTotalInputCount) {
configuration.triggers(index).isActive = false
activeBehaviorsDirty = true
}
}
}
def dispose(): Unit = {
reset()
if (isServer) {
api.Network.leaveWirelessNetwork(this)
PacketSender.sendNanomachineConfiguration(player)
}
}
// ----------------------------------------------------------------------- //
def save(nbt: NBTTagCompound): Unit = configuration.synchronized {
nbt.setString("uuid", uuid)
nbt.setInteger("port", responsePort)
nbt.setDouble("energy", storedEnergy)
nbt.setNewCompoundTag("configuration", configuration.save)
}
def load(nbt: NBTTagCompound): Unit = configuration.synchronized {
uuid = nbt.getString("uuid")
responsePort = nbt.getInteger("port")
storedEnergy = nbt.getDouble("energy")
configuration.load(nbt.getCompoundTag("configuration"))
activeBehaviorsDirty = true
}
// ----------------------------------------------------------------------- //
private def isClient = world.isRemote
private def isServer = !isClient
private def cleanActiveBehaviors(): Unit = {
if (activeBehaviorsDirty) {
configuration.synchronized(if (activeBehaviorsDirty) {
activeBehaviors.clear()
val newBehaviors = configuration.behaviors.filter(_.isActive).map(_.behavior)
val addedBehaviors = newBehaviors -- activeBehaviors
val removedBehaviors = activeBehaviors -- newBehaviors
activeBehaviors.clear()
activeBehaviors ++= newBehaviors
activeBehaviorsDirty = false
addedBehaviors.foreach(_.onEnable())
removedBehaviors.foreach(_.onDisable())
if (isServer) {
PacketSender.sendNanomachineInputs(player)
}
})
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,133 @@
package li.cil.oc.common.nanomachines.provider
import li.cil.oc.Settings
import li.cil.oc.api
import li.cil.oc.util.BlockPosition
import li.cil.oc.util.ExtendedWorld._
import net.minecraft.block.Block
import net.minecraft.block.state.IBlockState
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.entity.player.EntityPlayerMP
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NBTTagCompound
import net.minecraft.world.World
import net.minecraftforge.event.ForgeEventFactory
import net.minecraftforge.event.entity.player.PlayerInteractEvent.Action
import net.minecraftforge.fml.common.eventhandler.Event
import scala.collection.mutable
object DisintegrationProvider extends SimpleProvider {
final val Id = "c4e7e3c2-8069-4fbb-b08e-74b1bddcdfe7"
override def doCreateBehaviors(player: EntityPlayer) = Iterable(new DisintegrationBehavior(player))
override def doReadFromNBT(player: EntityPlayer, nbt: NBTTagCompound) = new DisintegrationBehavior(player)
class DisintegrationBehavior(player: EntityPlayer) extends SimpleBehavior(player) {
var breakingMap = mutable.Map.empty[BlockPosition, SlowBreakInfo]
var breakingMapNew = mutable.Map.empty[BlockPosition, SlowBreakInfo]
// Note: intentionally not overriding getNameHint. Gotta find this one manually!
override def onDisable(): Unit = {
val world = player.getEntityWorld
for (pos <- breakingMap.keys) {
world.destroyBlockInWorldPartially(pos.hashCode(), pos, -1)
}
breakingMap.clear()
}
override def update(): Unit = {
val world = player.getEntityWorld
if (!world.isRemote) player match {
case playerMP: EntityPlayerMP =>
val now = world.getTotalWorldTime
// Check blocks in range.
val blockPos = BlockPosition(player)
val actualRange = Settings.get.nanomachineDisintegrationRange * api.Nanomachines.getController(player).getInputCount(this)
for (x <- -actualRange to actualRange; y <- 0 to actualRange * 2; z <- -actualRange to actualRange) {
val pos = BlockPosition(blockPos.offset(x, y, z))
breakingMap.get(pos) match {
case Some(info) if info.checkTool(player) =>
breakingMapNew += pos -> info
info.update(world, player, now)
case None =>
val event = ForgeEventFactory.onPlayerInteract(player, Action.LEFT_CLICK_BLOCK, world, pos.toBlockPos, null)
val allowed = !event.isCanceled && event.useBlock != Event.Result.DENY && event.useItem != Event.Result.DENY
val adventureOk = !world.getWorldInfo.getGameType.isAdventure || player.canPlayerEdit(pos.toBlockPos, null, player.getCurrentEquippedItem)
if (allowed && adventureOk && !world.isAirBlock(pos)) {
val blockState = world.getBlockState(pos.toBlockPos)
val hardness = blockState.getBlock.getPlayerRelativeBlockHardness(player, world, pos.toBlockPos)
if (hardness > 0) {
val timeToBreak = (1 / hardness).toInt
if (timeToBreak < 20 * 30) {
val info = new SlowBreakInfo(now, now + timeToBreak, pos, Option(player.getCurrentEquippedItem).map(_.copy()), blockState)
world.destroyBlockInWorldPartially(pos.hashCode(), pos, 0)
breakingMapNew += pos -> info
}
}
}
case _ => // Tool changed, pretend block doesn't exist for this tick.
}
}
// Handle completed breaks.
for ((pos, info) <- breakingMap) {
if (info.timeBroken < now) {
breakingMapNew -= pos
info.finish(world, playerMP)
}
}
// Handle aborted / incomplete breaks.
for (pos <- breakingMap.keySet -- breakingMapNew.keySet) {
world.destroyBlockInWorldPartially(pos.hashCode(), pos, -1)
}
val tmp = breakingMap
breakingMap.clear()
breakingMap = breakingMapNew
breakingMapNew = tmp
case _ => // Not available for fake players, sorry :P
}
}
}
class SlowBreakInfo(val timeStarted: Long, val timeBroken: Long, val pos: BlockPosition, val originalTool: Option[ItemStack], val blockState: IBlockState) {
var lastDamageSent = 0
def checkTool(player: EntityPlayer): Boolean = {
val currentTool = Option(player.getCurrentEquippedItem).map(_.copy())
(currentTool, originalTool) match {
case (Some(stackA), Some(stackB)) => stackA.getItem == stackB.getItem && (stackA.isItemStackDamageable || stackA.getItemDamage == stackB.getItemDamage)
case (None, None) => true
case _ => false
}
}
def update(world: World, player: EntityPlayer, now: Long): Unit = {
val timeTotal = timeBroken - timeStarted
if (timeTotal > 0) {
val timeTaken = now - timeStarted
val damage = 10 * timeTaken / timeTotal
if (damage != lastDamageSent) {
lastDamageSent = damage.toInt
world.destroyBlockInWorldPartially(pos.hashCode(), pos, lastDamageSent)
}
}
}
def finish(world: World, player: EntityPlayerMP): Unit = {
val sameBlock = world.getBlockState(pos.toBlockPos) == blockState
if (sameBlock) {
world.destroyBlockInWorldPartially(pos.hashCode(), pos, -1)
if (player.theItemInWorldManager.tryHarvestBlock(pos.toBlockPos)) {
world.playAuxSFX(2001, pos, Block.getIdFromBlock(blockState.getBlock) + (blockState.getBlock.getMetaFromState(blockState) << 12))
}
}
}
}
}

View File

@ -0,0 +1,40 @@
package li.cil.oc.common.nanomachines.provider
import li.cil.oc.Settings
import li.cil.oc.api
import net.minecraft.entity.item.EntityItem
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.nbt.NBTTagCompound
import net.minecraft.util.Vec3
import scala.collection.convert.WrapAsScala._
object MagnetProvider extends SimpleProvider {
// One-time generated UUID to identify our behaviors.
final val Id = "9324d5ec-71f1-41c2-b51c-406e527668fc"
override def doCreateBehaviors(player: EntityPlayer) = Iterable(new MagnetBehavior(player))
override def doReadFromNBT(player: EntityPlayer, nbt: NBTTagCompound) = new MagnetBehavior(player)
class MagnetBehavior(player: EntityPlayer) extends SimpleBehavior(player) {
override def getNameHint = "magnet"
override def update(): Unit = {
val world = player.getEntityWorld
if (!world.isRemote) {
val actualRange = Settings.get.nanomachineMagnetRange * api.Nanomachines.getController(player).getInputCount(this)
val items = world.getEntitiesWithinAABB(classOf[EntityItem], player.getEntityBoundingBox.expand(actualRange, actualRange, actualRange))
items.collect {
case item: EntityItem if !item.cannotPickup && item.getEntityItem != null && player.inventory.mainInventory.exists(stack => stack == null || stack.stackSize < stack.getMaxStackSize && stack.isItemEqual(item.getEntityItem)) =>
val dx = player.posX - item.posX
val dy = player.posY - item.posY
val dz = player.posZ - item.posZ
val delta = new Vec3(dx, dy, dz).normalize()
item.addVelocity(delta.xCoord * 0.1, delta.yCoord * 0.1, delta.zCoord * 0.1)
}
}
}
}
}

View File

@ -0,0 +1,55 @@
package li.cil.oc.common.nanomachines.provider
import li.cil.oc.api
import li.cil.oc.api.nanomachines.Behavior
import li.cil.oc.util.PlayerUtils
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.nbt.NBTTagCompound
import net.minecraft.util.EnumParticleTypes
object ParticleProvider extends SimpleProvider {
final val Id = "b48c4bbd-51bb-4915-9367-16cff3220e4b"
final val ParticleTypes = Array(
EnumParticleTypes.FIREWORKS_SPARK,
EnumParticleTypes.TOWN_AURA,
EnumParticleTypes.SMOKE_NORMAL,
EnumParticleTypes.SPELL_WITCH,
EnumParticleTypes.NOTE,
EnumParticleTypes.ENCHANTMENT_TABLE,
EnumParticleTypes.FLAME,
EnumParticleTypes.LAVA,
EnumParticleTypes.WATER_SPLASH,
EnumParticleTypes.REDSTONE,
EnumParticleTypes.SLIME,
EnumParticleTypes.HEART,
EnumParticleTypes.VILLAGER_HAPPY
)
override def doCreateBehaviors(player: EntityPlayer): Iterable[Behavior] = ParticleTypes.map(new ParticleBehavior(_, player))
override def doWriteToNBT(behavior: Behavior, nbt: NBTTagCompound): Unit = {
behavior match {
case particles: ParticleBehavior =>
nbt.setInteger("effectName", particles.effectType.getParticleID)
case _ => // Wat.
}
}
override def doReadFromNBT(player: EntityPlayer, nbt: NBTTagCompound): Behavior = {
val effectType = EnumParticleTypes.getParticleFromId(nbt.getInteger("effectName"))
new ParticleBehavior(effectType, player)
}
class ParticleBehavior(var effectType: EnumParticleTypes, player: EntityPlayer) extends SimpleBehavior(player) {
override def getNameHint = "particles"
override def update(): Unit = {
val world = player.getEntityWorld
if (world.isRemote) {
PlayerUtils.spawnParticleAround(player, effectType, api.Nanomachines.getController(player).getInputCount(this) * 0.25)
}
}
}
}

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@ import li.cil.oc.Localization
import li.cil.oc.Settings
import li.cil.oc.api
import li.cil.oc.api.Driver
import li.cil.oc.api.nanomachines.Controller
import li.cil.oc.api.network._
import li.cil.oc.common.Slot
import li.cil.oc.common.entity.Drone
@ -30,7 +31,7 @@ class Charger extends traits.Environment with traits.PowerAcceptor with traits.R
withConnector(Settings.get.bufferConverter).
create()
val connectors = mutable.Set.empty[(Vec3, Connector)]
val connectors = mutable.Set.empty[Chargeable]
var chargeSpeed = 0.0
@ -81,9 +82,7 @@ class Charger extends traits.Environment with traits.PowerAcceptor with traits.R
val charge = Settings.get.chargeRateExternal * chargeSpeed * Settings.get.tickFrequency
canCharge ||= charge > 0 && node.globalBuffer >= charge * 0.5
if (canCharge) {
connectors.foreach {
case (_, connector) => node.changeBuffer(connector.changeBuffer(charge + node.changeBuffer(-charge)))
}
connectors.foreach(connector => node.changeBuffer(connector.changeBuffer(charge + node.changeBuffer(-charge))))
}
}
@ -111,15 +110,15 @@ class Charger extends traits.Environment with traits.PowerAcceptor with traits.R
}
if (isClient && chargeSpeed > 0 && hasPower && world.getWorldInfo.getWorldTotalTime % 10 == 0) {
connectors.foreach {
case (position, _) =>
val theta = world.rand.nextDouble * Math.PI
val phi = world.rand.nextDouble * Math.PI * 2
val dx = 0.45 * Math.sin(theta) * Math.cos(phi)
val dy = 0.45 * Math.sin(theta) * Math.sin(phi)
val dz = 0.45 * Math.cos(theta)
world.spawnParticle(EnumParticleTypes.VILLAGER_HAPPY, position.xCoord + dx, position.yCoord + dz, position.zCoord + dy, 0, 0, 0)
}
connectors.foreach(connector => {
val position = connector.pos
val theta = world.rand.nextDouble * Math.PI
val phi = world.rand.nextDouble * Math.PI * 2
val dx = 0.45 * Math.sin(theta) * Math.cos(phi)
val dy = 0.45 * Math.sin(theta) * Math.sin(phi)
val dz = 0.45 * Math.cos(theta)
world.spawnParticle(EnumParticleTypes.VILLAGER_HAPPY, position.xCoord + dx, position.yCoord + dz, position.zCoord + dy, 0, 0, 0)
})
}
}
@ -193,23 +192,59 @@ class Charger extends traits.Environment with traits.PowerAcceptor with traits.R
}
def updateConnectors() {
val robotConnectors = EnumFacing.values.map(side => {
val robots = EnumFacing.values.map(side => {
val blockPos = BlockPosition(this).offset(side)
if (world.blockExists(blockPos)) Option(world.getTileEntity(blockPos))
else None
}).collect {
case Some(t: RobotProxy) => (BlockPosition(t).toVec3, t.robot.node.asInstanceOf[Connector])
case Some(t: RobotProxy) => new RobotChargeable(t.robot)
}
val droneConnectors = world.getEntitiesWithinAABB(classOf[Drone], BlockPosition(this).bounds.expand(1, 1, 1)).collect {
case drone: Drone => (new Vec3(drone.posX, drone.posY, drone.posZ), drone.components.node.asInstanceOf[Connector])
val bounds = BlockPosition(this).bounds.expand(1, 1, 1)
val drones = world.getEntitiesWithinAABB(classOf[Drone], bounds).collect {
case drone: Drone => new DroneChargeable(drone)
}
val players = world.getEntitiesWithinAABB(classOf[EntityPlayer], bounds).collect {
case player: EntityPlayer => new PlayerChargeable(player)
}
// Only update list when we have to, keeps pointless block updates to a minimum.
if (connectors.size != robotConnectors.length + droneConnectors.size || (connectors.size > 0 && connectors.map(_._2).diff((robotConnectors ++ droneConnectors).map(_._2).toSet).size > 0)) {
val newConnectors = robots ++ drones ++ players
if (connectors.size != newConnectors.length || (connectors.nonEmpty && (connectors -- newConnectors).nonEmpty)) {
connectors.clear()
connectors ++= robotConnectors
connectors ++= droneConnectors
connectors ++= newConnectors
world.notifyNeighborsOfStateChange(getPos, getBlockType)
}
}
trait Chargeable {
def pos: Vec3
def changeBuffer(delta: Double): Double
}
abstract class ConnectorChargeable(val connector: Connector) extends Chargeable {
override def changeBuffer(delta: Double): Double = connector.changeBuffer(delta)
}
class RobotChargeable(val robot: Robot) extends ConnectorChargeable(robot.node.asInstanceOf[Connector]) {
override def pos: Vec3 = BlockPosition(robot).toVec3
}
class DroneChargeable(val drone: Drone) extends ConnectorChargeable(drone.components.node.asInstanceOf[Connector]) {
override def pos: Vec3 = new Vec3(drone.posX, drone.posY, drone.posZ)
}
class PlayerChargeable(val player: EntityPlayer) extends Chargeable {
override def pos: Vec3 = new Vec3(player.posX, player.posY, player.posZ)
override def changeBuffer(delta: Double): Double = {
api.Nanomachines.getController(player) match {
case controller: Controller => controller.changeBuffer(delta)
case _ => delta // Cannot charge.
}
}
}
}

View File

@ -27,6 +27,10 @@ import li.cil.oc.common.item.Analyzer
import li.cil.oc.common.item.Delegator
import li.cil.oc.common.item.RedstoneCard
import li.cil.oc.common.item.Tablet
import li.cil.oc.common.nanomachines.provider.DisintegrationProvider
import li.cil.oc.common.nanomachines.provider.MagnetProvider
import li.cil.oc.common.nanomachines.provider.ParticleProvider
import li.cil.oc.common.nanomachines.provider.PotionProvider
import li.cil.oc.common.template._
import li.cil.oc.integration.ModProxy
import li.cil.oc.integration.Mods
@ -80,6 +84,7 @@ object ModOpenComputers extends ModProxy {
MinecraftForge.EVENT_BUS.register(GeolyzerHandler)
MinecraftForge.EVENT_BUS.register(HoverBootsHandler)
MinecraftForge.EVENT_BUS.register(Loot)
MinecraftForge.EVENT_BUS.register(NanomachinesEventHandler.Common)
MinecraftForge.EVENT_BUS.register(RobotCommonHandler)
MinecraftForge.EVENT_BUS.register(SaveHandler)
MinecraftForge.EVENT_BUS.register(Tablet)
@ -246,6 +251,11 @@ object ModOpenComputers extends ModProxy {
api.Manual.addTab(new TextureTabIconRenderer(Textures.GUI.ManualHome), "oc:gui.Manual.Home", "%LANGUAGE%/index.md")
api.Manual.addTab(new ItemStackTabIconRenderer(api.Items.get("case1").createItemStack(1)), "oc:gui.Manual.Blocks", "%LANGUAGE%/block/index.md")
api.Manual.addTab(new ItemStackTabIconRenderer(api.Items.get("cpu1").createItemStack(1)), "oc:gui.Manual.Items", "%LANGUAGE%/item/index.md")
api.Nanomachines.addProvider(DisintegrationProvider)
api.Nanomachines.addProvider(ParticleProvider)
api.Nanomachines.addProvider(PotionProvider)
api.Nanomachines.addProvider(MagnetProvider)
}
def useWrench(player: EntityPlayer, pos: BlockPos, changeDurability: Boolean): Boolean = {

View File

@ -1,14 +1,17 @@
package li.cil.oc.server
import li.cil.oc.api
import li.cil.oc.api.component.TextBuffer.ColorDepth
import li.cil.oc.api.driver.EnvironmentHost
import li.cil.oc.api.event.FileSystemAccessEvent
import li.cil.oc.api.network.Node
import li.cil.oc.common._
import li.cil.oc.common.nanomachines.ControllerImpl
import li.cil.oc.common.tileentity.Waypoint
import li.cil.oc.common.tileentity.traits._
import li.cil.oc.util.BlockPosition
import li.cil.oc.util.PackedColor
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.entity.player.EntityPlayerMP
import net.minecraft.inventory.Container
import net.minecraft.item.ItemStack
@ -251,6 +254,51 @@ object PacketSender {
}
}
def sendNanomachineConfiguration(player: EntityPlayer): Unit = {
val pb = new SimplePacketBuilder(PacketType.NanomachinesConfiguration)
pb.writeEntity(player)
api.Nanomachines.getController(player) match {
case controller: ControllerImpl =>
pb.writeBoolean(true)
val nbt = new NBTTagCompound()
controller.save(nbt)
pb.writeNBT(nbt)
case _ =>
pb.writeBoolean(false)
}
pb.sendToPlayersNearEntity(player)
}
def sendNanomachineInputs(player: EntityPlayer): Unit = {
api.Nanomachines.getController(player) match {
case controller: ControllerImpl =>
val pb = new SimplePacketBuilder(PacketType.NanomachinesInputs)
pb.writeEntity(player)
val inputs = controller.configuration.triggers.map(i => if (i.isActive) 1.toByte else 0.toByte).toArray
pb.writeInt(inputs.length)
pb.write(inputs)
pb.sendToPlayersNearEntity(player)
case _ => // Wat.
}
}
def sendNanomachinePower(player: EntityPlayer): Unit = {
api.Nanomachines.getController(player) match {
case controller: ControllerImpl =>
val pb = new SimplePacketBuilder(PacketType.NanomachinesPower)
pb.writeEntity(player)
pb.writeDouble(controller.getLocalBuffer)
pb.sendToPlayersNearEntity(player)
case _ => // Wat.
}
}
def sendNetSplitterState(t: tileentity.NetSplitter): Unit = {
val pb = new SimplePacketBuilder(PacketType.NetSplitterState)

View File

@ -304,7 +304,8 @@ class Player(val agent: internal.Agent) extends FakePlayer(agent.world.asInstanc
block.onBlockClicked(world, pos, this)
world.extinguishFire(this, pos, side)
val isBlockUnbreakable = block.getBlockHardness(world, pos) < 0
val hardness = block.getBlockHardness(world, pos)
val isBlockUnbreakable = hardness < 0
val canDestroyBlock = !isBlockUnbreakable && block.canEntityDestroy(world, pos, this)
if (!canDestroyBlock) {
return 0
@ -320,7 +321,6 @@ class Player(val agent: internal.Agent) extends FakePlayer(agent.world.asInstanc
return 0
}
val hardness = block.getBlockHardness(world, pos)
val strength = getBreakSpeed(state, pos)
val breakTime =
if (cobwebOverride) Settings.get.swingDelay

View File

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

View File

@ -36,6 +36,12 @@ class WirelessNetworkCard(host: EnvironmentHost) extends NetworkCard(host) with
override def world = host.world
def receivePacket(packet: Packet, source: WirelessEndpoint) {
val (dx, dy, dz) = ((source.x + 0.5) - host.xPosition, (source.y + 0.5) - host.yPosition, (source.z + 0.5) - host.zPosition)
val distance = Math.sqrt(dx * dx + dy * dy + dz * dz)
receivePacket(packet, distance)
}
// ----------------------------------------------------------------------- //
@Callback(direct = true, doc = """function():number -- Get the signal strength (range) used when sending messages.""")

View File

@ -1,5 +1,6 @@
package li.cil.oc.util
import com.google.common.hash.Hashing
import li.cil.oc.api.driver.EnvironmentHost
import net.minecraft.entity.Entity
import net.minecraft.util._
@ -34,6 +35,18 @@ class BlockPosition(val x: Int, val y: Int, val z: Int, val world: Option[World]
case position: BlockPosition => position.x == x && position.y == y && position.z == z && position.world == world
case _ => super.equals(obj)
}
override def hashCode(): Int = {
Hashing.
goodFastHash(32).
newHasher(16).
putInt(x).
putInt(y).
putInt(z).
putInt(world.hashCode()).
hash().
asInt()
}
}
object BlockPosition {
@ -45,6 +58,10 @@ object BlockPosition {
def apply(x: Double, y: Double, z: Double) = new BlockPosition(x, y, z, None)
def apply(v: Vec3) = new BlockPosition(v.xCoord, v.yCoord, v.zCoord, None)
def apply(v: Vec3, world: World) = new BlockPosition(v.xCoord, v.yCoord, v.zCoord, Option(world))
def apply(host: EnvironmentHost): BlockPosition = BlockPosition(host.xPosition, host.yPosition, host.zPosition, host.world)
def apply(entity: Entity): BlockPosition = BlockPosition(entity.posX, entity.posY, entity.posZ, entity.worldObj)

View File

@ -35,6 +35,8 @@ object ExtendedWorld {
def breakBlock(position: BlockPosition, drops: Boolean = true) = world.destroyBlock(position.toBlockPos, drops)
def destroyBlockInWorldPartially(entityId: Int, position: BlockPosition, progress: Int) = world.sendBlockBreakProgress(entityId, position.toBlockPos, progress)
def extinguishFire(player: EntityPlayer, position: BlockPosition, side: EnumFacing) = world.extinguishFire(player, position.toBlockPos, side)
def getBlockHardness(position: BlockPosition) = getBlock(position).getBlockHardness(world, position.toBlockPos)

View File

@ -2,6 +2,7 @@ package li.cil.oc.util
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.nbt.NBTTagCompound
import net.minecraft.util.EnumParticleTypes
object PlayerUtils {
def persistedData(player: EntityPlayer): NBTTagCompound = {
@ -11,4 +12,15 @@ object PlayerUtils {
}
nbt.getCompoundTag(EntityPlayer.PERSISTED_NBT_TAG)
}
def spawnParticleAround(player: EntityPlayer, effectType: EnumParticleTypes, chance: Double = 1.0): Unit = {
val rng = player.getEntityWorld.rand
if (chance >= 1 || rng.nextDouble() < chance) {
val bounds = player.getEntityBoundingBox
val x = bounds.minX + (bounds.maxX - bounds.minX) * rng.nextDouble() * 1.5
val y = bounds.minY + (bounds.maxY - bounds.minY) * rng.nextDouble() * 0.5
val z = bounds.minZ + (bounds.maxZ - bounds.minZ) * rng.nextDouble() * 1.5
player.getEntityWorld.spawnParticle(effectType, x, y, z, 0, 0, 0)
}
}
}