diff --git a/src/main/java/li/cil/oc/api/API.java b/src/main/java/li/cil/oc/api/API.java index 1b0e5ad0e..733b13bb7 100644 --- a/src/main/java/li/cil/oc/api/API.java +++ b/src/main/java/li/cil/oc/api/API.java @@ -12,7 +12,7 @@ import li.cil.oc.api.detail.*; */ public class API { public static final String ID_OWNER = "OpenComputers|Core"; - public static final String VERSION = "5.5.6"; + public static final String VERSION = "5.6.0"; public static DriverAPI driver = null; public static FileSystemAPI fileSystem = null; diff --git a/src/main/java/li/cil/oc/api/nanomachines/Behavior.java b/src/main/java/li/cil/oc/api/nanomachines/Behavior.java index 38299b538..b532e253f 100644 --- a/src/main/java/li/cil/oc/api/nanomachines/Behavior.java +++ b/src/main/java/li/cil/oc/api/nanomachines/Behavior.java @@ -40,8 +40,10 @@ public interface Behavior { * Called when this behavior becomes inactive. *
* Use this to remove permanent effects. + * + * @param reason the reason the behavior is being disabled. */ - void onDisable(); + void onDisable(DisableReason reason); /** * Called each tick while this behavior is active. diff --git a/src/main/java/li/cil/oc/api/nanomachines/DisableReason.java b/src/main/java/li/cil/oc/api/nanomachines/DisableReason.java new file mode 100644 index 000000000..13d806e36 --- /dev/null +++ b/src/main/java/li/cil/oc/api/nanomachines/DisableReason.java @@ -0,0 +1,23 @@ +package li.cil.oc.api.nanomachines; + +/** + * Enum with reasons why a nanomachine behavior was disabled. + * + * This allows some more context specific behavior in a more stable fashion. + */ +public enum DisableReason { + /** + * This covers things like players logging off or the controller being reset. + */ + Default, + + /** + * Input state changed, leading to a behavior being disabled. + */ + InputChanged, + + /** + * System has run out of energy and is powering down. + */ + OutOfEnergy +} diff --git a/src/main/java/li/cil/oc/api/prefab/AbstractBehavior.java b/src/main/java/li/cil/oc/api/prefab/AbstractBehavior.java index a1d0ed199..ea1ad4bb9 100644 --- a/src/main/java/li/cil/oc/api/prefab/AbstractBehavior.java +++ b/src/main/java/li/cil/oc/api/prefab/AbstractBehavior.java @@ -1,6 +1,7 @@ package li.cil.oc.api.prefab; import li.cil.oc.api.nanomachines.Behavior; +import li.cil.oc.api.nanomachines.DisableReason; import net.minecraft.entity.player.EntityPlayer; /** @@ -42,7 +43,7 @@ public abstract class AbstractBehavior implements Behavior { } @Override - public void onDisable() { + public void onDisable(DisableReason reason) { } @Override diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index d95a00f06..ae36f0b62 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -1036,7 +1036,6 @@ opencomputers { "potion.fireResistance", "potion.waterBreathing", "potion.nightVision", - "potion.healthBoost", "potion.absorption", "potion.blindness", @@ -1049,6 +1048,14 @@ opencomputers { "potion.weakness", "potion.wither" ] + + # How much damage the hungry behavior should deal to the player when the + # nanomachine controller runs out of energy. + hungryDamage: 5 + + # How much energy the hungry behavior should restore when damaging the + # player. + hungryEnergyRestored: 50 } # 3D printer related stuff. diff --git a/src/main/resources/assets/opencomputers/lang/en_US.lang b/src/main/resources/assets/opencomputers/lang/en_US.lang index 64a6b2164..866a1153f 100644 --- a/src/main/resources/assets/opencomputers/lang/en_US.lang +++ b/src/main/resources/assets/opencomputers/lang/en_US.lang @@ -451,6 +451,15 @@ achievement.oc.transistor.desc=Create a Transistor to get started. Then listen t achievement.oc.wirelessNetworkCard=Signals achievement.oc.wirelessNetworkCard.desc=Time to go where no packet has gone before. +# Death messages. Note that the number of entries here must match the number +# set in the actual damage source in code. +death.attack.oc.nanomachinesOverload.1=%s got too greedy. +death.attack.oc.nanomachinesOverload.2=%s had a nervous breakdown. +death.attack.oc.nanomachinesOverload.3=%s's nanomachines went out of control. +death.attack.oc.nanomachinesHungry.1=%s was eaten by nanomachines. +death.attack.oc.nanomachinesHungry.2=%s didn't keep their nanomachines fed. +death.attack.oc.nanomachinesHungry.3=%s has been digested. + # NEI Integration nei.options.inventory.oredict=Show OreDictionary names nei.options.inventory.oredict.true=True diff --git a/src/main/scala/li/cil/oc/Settings.scala b/src/main/scala/li/cil/oc/Settings.scala index 648dcfaf5..84382f665 100644 --- a/src/main/scala/li/cil/oc/Settings.scala +++ b/src/main/scala/li/cil/oc/Settings.scala @@ -360,6 +360,8 @@ class Settings(val config: Config) { val nanomachineDisintegrationRange = config.getInt("nanomachines.disintegrationRange") max 0 val nanomachinePotionBlacklist = config.getAnyRefList("nanomachines.potionBlacklist") val nanomachinePotionWhitelist = config.getAnyRefList("nanomachines.potionWhitelist") + val nanomachinesHungryDamage = config.getDouble("nanomachines.hungryDamage").toFloat max 0 + val nanomachinesHungryEnergyRestored = config.getDouble("nanomachines.hungryEnergyRestored") max 0 // ----------------------------------------------------------------------- // // printer diff --git a/src/main/scala/li/cil/oc/common/nanomachines/ControllerImpl.scala b/src/main/scala/li/cil/oc/common/nanomachines/ControllerImpl.scala index 695a3ecbd..0743c05e6 100644 --- a/src/main/scala/li/cil/oc/common/nanomachines/ControllerImpl.scala +++ b/src/main/scala/li/cil/oc/common/nanomachines/ControllerImpl.scala @@ -9,8 +9,10 @@ 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.nanomachines.DisableReason import li.cil.oc.api.network.Packet import li.cil.oc.api.network.WirelessEndpoint +import li.cil.oc.integration.util.DamageSourceWithRandomCause import li.cil.oc.server.PacketSender import li.cil.oc.util.BlockPosition import li.cil.oc.util.ExtendedNBT._ @@ -32,6 +34,10 @@ class ControllerImpl(val player: EntityPlayer) extends Controller with WirelessE final val MaxSenderDistance = 2f final val FullSyncInterval = 20 * 60 + final val OverloadDamage = new DamageSourceWithRandomCause("oc.nanomachinesOverload", 3). + setDamageBypassesArmor(). + setDamageIsAbsolute() + var uuid = UUID.randomUUID.toString var responsePort = 0 var storedEnergy = Settings.get.bufferNanomachines * 0.25 @@ -154,7 +160,7 @@ class ControllerImpl(val player: EntityPlayer) extends Controller with WirelessE } override def getActiveBehaviors: lang.Iterable[Behavior] = configuration.synchronized { - cleanActiveBehaviors() + cleanActiveBehaviors(DisableReason.InputChanged) activeBehaviors } @@ -192,12 +198,15 @@ class ControllerImpl(val player: EntityPlayer) extends Controller with WirelessE return } - val hasPower = getLocalBuffer > 0 || Settings.get.ignorePower + var 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()) + if (!hasPower) { + active.foreach(_.onDisable(DisableReason.OutOfEnergy)) // This may change our energy buffer. + hasPower = getLocalBuffer > 0 || Settings.get.ignorePower + } else active.foreach(_.onEnable()) } @@ -212,8 +221,7 @@ class ControllerImpl(val player: EntityPlayer) extends Controller with WirelessE val overload = activeInputs - getSafeInputCount if (!player.capabilities.isCreativeMode && overload > 0 && player.getEntityWorld.getTotalWorldTime % 20 == 0) { - player.setHealth(player.getHealth - overload) - player.performHurtAnimation() + player.attackEntityFrom(OverloadDamage, overload) } } @@ -249,7 +257,7 @@ class ControllerImpl(val player: EntityPlayer) extends Controller with WirelessE configuration.triggers(index).isActive = false activeBehaviorsDirty = true } - cleanActiveBehaviors() + cleanActiveBehaviors(DisableReason.Default) } } @@ -283,7 +291,7 @@ class ControllerImpl(val player: EntityPlayer) extends Controller with WirelessE private def isServer = !isClient - private def cleanActiveBehaviors(): Unit = { + private def cleanActiveBehaviors(reason: DisableReason): Unit = { if (activeBehaviorsDirty) { configuration.synchronized(if (activeBehaviorsDirty) { val newBehaviors = configuration.behaviors.filter(_.isActive).map(_.behavior) @@ -293,7 +301,7 @@ class ControllerImpl(val player: EntityPlayer) extends Controller with WirelessE activeBehaviors ++= newBehaviors activeBehaviorsDirty = false addedBehaviors.foreach(_.onEnable()) - removedBehaviors.foreach(_.onDisable()) + removedBehaviors.foreach(_.onDisable(reason)) if (isServer) { PacketSender.sendNanomachineInputs(player) diff --git a/src/main/scala/li/cil/oc/common/nanomachines/NeuralNetwork.scala b/src/main/scala/li/cil/oc/common/nanomachines/NeuralNetwork.scala index 6d1503e1c..3731e418f 100644 --- a/src/main/scala/li/cil/oc/common/nanomachines/NeuralNetwork.scala +++ b/src/main/scala/li/cil/oc/common/nanomachines/NeuralNetwork.scala @@ -55,8 +55,8 @@ class NeuralNetwork(controller: ControllerImpl) extends Persistable { 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) + // Shuffle sink list to give each entry the same chance. + val sinkPool = rng.shuffle(sinks.toBuffer) for (sink <- sinkPool if sources.nonEmpty) { for (n <- 0 to rng.nextInt(Settings.get.nanomachineMaxInputs) if sources.nonEmpty) { val sourceIndex = rng.nextInt(sources.length) @@ -65,10 +65,6 @@ class NeuralNetwork(controller: ControllerImpl) extends Persistable { } } - // 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) diff --git a/src/main/scala/li/cil/oc/common/nanomachines/provider/DisintegrationProvider.scala b/src/main/scala/li/cil/oc/common/nanomachines/provider/DisintegrationProvider.scala index 31043d234..d56d5fb2b 100644 --- a/src/main/scala/li/cil/oc/common/nanomachines/provider/DisintegrationProvider.scala +++ b/src/main/scala/li/cil/oc/common/nanomachines/provider/DisintegrationProvider.scala @@ -3,6 +3,7 @@ package li.cil.oc.common.nanomachines.provider import cpw.mods.fml.common.eventhandler.Event import li.cil.oc.Settings import li.cil.oc.api +import li.cil.oc.api.nanomachines.DisableReason import li.cil.oc.api.prefab.AbstractBehavior import li.cil.oc.util.BlockPosition import li.cil.oc.util.ExtendedWorld._ @@ -28,7 +29,7 @@ object DisintegrationProvider extends ScalaProvider("c4e7e3c2-8069-4fbb-b08e-74b // Note: intentionally not overriding getNameHint. Gotta find this one manually! - override def onDisable(): Unit = { + override def onDisable(reason: DisableReason): Unit = { val world = player.getEntityWorld for (pos <- breakingMap.keys) { world.destroyBlockInWorldPartially(pos.hashCode(), pos, -1) diff --git a/src/main/scala/li/cil/oc/common/nanomachines/provider/HungryProvider.scala b/src/main/scala/li/cil/oc/common/nanomachines/provider/HungryProvider.scala new file mode 100644 index 000000000..bf7d52b01 --- /dev/null +++ b/src/main/scala/li/cil/oc/common/nanomachines/provider/HungryProvider.scala @@ -0,0 +1,32 @@ +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 li.cil.oc.api.nanomachines.DisableReason +import li.cil.oc.api.prefab.AbstractBehavior +import li.cil.oc.integration.util.DamageSourceWithRandomCause +import net.minecraft.entity.player.EntityPlayer +import net.minecraft.nbt.NBTTagCompound + +object HungryProvider extends ScalaProvider("") { + final val FillCount = 10 // Create a bunch of these to have a higher chance of one being picked / available. + + final val HungryDamage = new DamageSourceWithRandomCause("oc.nanomachinesHungry", 3). + setDamageBypassesArmor(). + setDamageIsAbsolute() + + override def createScalaBehaviors(player: EntityPlayer): Iterable[Behavior] = Iterable.fill(FillCount)(new HungryBehavior(player)) + + override protected def readBehaviorFromNBT(player: EntityPlayer, nbt: NBTTagCompound): Behavior = new HungryBehavior(player) + + class HungryBehavior(player: EntityPlayer) extends AbstractBehavior(player) { + override def onDisable(reason: DisableReason): Unit = { + if (reason == DisableReason.OutOfEnergy) { + player.attackEntityFrom(HungryDamage, Settings.get.nanomachinesHungryDamage) + api.Nanomachines.getController(player).changeBuffer(Settings.get.nanomachinesHungryEnergyRestored) + } + } + } + +} diff --git a/src/main/scala/li/cil/oc/common/nanomachines/provider/PotionProvider.scala b/src/main/scala/li/cil/oc/common/nanomachines/provider/PotionProvider.scala index 45affc130..2b1667988 100644 --- a/src/main/scala/li/cil/oc/common/nanomachines/provider/PotionProvider.scala +++ b/src/main/scala/li/cil/oc/common/nanomachines/provider/PotionProvider.scala @@ -3,6 +3,7 @@ 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 li.cil.oc.api.nanomachines.DisableReason import li.cil.oc.api.prefab.AbstractBehavior import net.minecraft.entity.player.EntityPlayer import net.minecraft.nbt.NBTTagCompound @@ -51,9 +52,7 @@ object PotionProvider extends ScalaProvider("c29e4eec-5a46-479a-9b3d-ad0f06da784 override def getNameHint: String = potion.getName.stripPrefix("potion.") - override def onEnable(): Unit = {} - - override def onDisable(): Unit = { + override def onDisable(reason: DisableReason): Unit = { player.removePotionEffect(potion.id) } diff --git a/src/main/scala/li/cil/oc/integration/opencomputers/ModOpenComputers.scala b/src/main/scala/li/cil/oc/integration/opencomputers/ModOpenComputers.scala index 6eadc5d2c..5efc6a024 100644 --- a/src/main/scala/li/cil/oc/integration/opencomputers/ModOpenComputers.scala +++ b/src/main/scala/li/cil/oc/integration/opencomputers/ModOpenComputers.scala @@ -29,6 +29,7 @@ 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.HungryProvider import li.cil.oc.common.nanomachines.provider.MagnetProvider import li.cil.oc.common.nanomachines.provider.ParticleProvider import li.cil.oc.common.nanomachines.provider.PotionProvider @@ -253,6 +254,7 @@ object ModOpenComputers extends ModProxy { 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(HungryProvider) api.Nanomachines.addProvider(ParticleProvider) api.Nanomachines.addProvider(PotionProvider) api.Nanomachines.addProvider(MagnetProvider) diff --git a/src/main/scala/li/cil/oc/integration/util/DamageSourceWithRandomCause.scala b/src/main/scala/li/cil/oc/integration/util/DamageSourceWithRandomCause.scala new file mode 100644 index 000000000..46349d70a --- /dev/null +++ b/src/main/scala/li/cil/oc/integration/util/DamageSourceWithRandomCause.scala @@ -0,0 +1,19 @@ +package li.cil.oc.integration.util + +import net.minecraft.entity.EntityLivingBase +import net.minecraft.util.ChatComponentTranslation +import net.minecraft.util.DamageSource +import net.minecraft.util.IChatComponent +import net.minecraft.util.StatCollector + +class DamageSourceWithRandomCause(name: String, numCauses: Int) extends DamageSource(name) { + override def func_151519_b(damagee: EntityLivingBase): IChatComponent = { + val damager = damagee.func_94060_bK + val format = "death.attack." + damageType + "." + (damagee.worldObj.rand.nextInt(numCauses) + 1) + val withCauseFormat = format + ".player" + if (damager != null && StatCollector.canTranslate(withCauseFormat)) + new ChatComponentTranslation(withCauseFormat, damagee.func_145748_c_, damager.func_145748_c_) + else + new ChatComponentTranslation(format, damagee.func_145748_c_) + } +}