diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index bd8ef92c1..9c834010e 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -807,6 +807,10 @@ opencomputers { # Energy consumed when reconfiguring nanomachines. nanomachinesReconfigure: 5000 + + # Energy consumed by a MFU per tick while connected. + # Similarly to `wirelessCostPerRange`, this is multiplied with the distance to the bound block. + mfuRelay: 1 } # The rate at which different blocks accept external power. All of these @@ -1345,6 +1349,9 @@ opencomputers { # The maximum range between the drone/robot and a villager for a trade to # be performed by the trading upgrade tradingRange: 8.0 + + # Radius the MFU is able to operate in + mfuRange: 3 } # Settings for mod integration (the mod previously known as OpenComponents). diff --git a/src/main/resources/assets/opencomputers/lang/en_US.lang b/src/main/resources/assets/opencomputers/lang/en_US.lang index 9fa64316e..2cb09e01e 100644 --- a/src/main/resources/assets/opencomputers/lang/en_US.lang +++ b/src/main/resources/assets/opencomputers/lang/en_US.lang @@ -144,6 +144,7 @@ item.oc.UpgradeHover1.name=Hover Upgrade (Tier 2) item.oc.UpgradeInventory.name=Inventory Upgrade item.oc.UpgradeInventoryController.name=Inventory Controller Upgrade item.oc.UpgradeLeash.name=Leash Upgrade +item.oc.UpgradeMF.name=MFU item.oc.UpgradeNavigation.name=Navigation Upgrade item.oc.UpgradePiston.name=Piston Upgrade item.oc.UpgradeSign.name=Sign I/O Upgrade @@ -370,6 +371,9 @@ oc:tooltip.UpgradeGenerator=Can be used to generate energy from fuel on the go. oc:tooltip.UpgradeHover=This upgrade allows robots to fly higher above the ground without having to climb walls.[nl] Maximum height: §f%s§7 oc:tooltip.UpgradeInventory=This upgrade provides inventory space to a robot or drone. Without one of these, they will not be able to store items internally. oc:tooltip.UpgradeInventoryController=This upgrade allows robots and drones more control in how it interacts with external inventories, and allows robots to swap their equipped tool with an item in their inventory. +oc:tooltip.UpgradeMF=TODO really, do this +oc:tooltip.UpgradeMF.Linked=§fConnection established§7 +oc:tooltip.UpgradeMF.Unlinked=§fNo connection§7 oc:tooltip.UpgradeLeash=Allows some devices, such as drones, to bind Isaa- excuse me... *chatter* My apologies. I'm just being told this is actually used to put animals on a leash. Multiple animals, even. Odd. oc:tooltip.UpgradeNavigation=Can be used to determine the position and orientation of a device. The position is relative to the center of the map that was used to craft this upgrade. oc:tooltip.UpgradePiston=This upgrade is very pushy. It allows moving blocks, similar to when using a piston. It does §lnot§7 move entities, however. diff --git a/src/main/resources/assets/opencomputers/recipes/default.recipes b/src/main/resources/assets/opencomputers/recipes/default.recipes index b51583a59..94966bf5f 100644 --- a/src/main/resources/assets/opencomputers/recipes/default.recipes +++ b/src/main/resources/assets/opencomputers/recipes/default.recipes @@ -295,6 +295,11 @@ inventoryControllerUpgrade { [dropper, "oc:circuitChip2", craftingPiston] [ingotGold, "oc:materialCircuitBoardPrinted", ingotGold]] } +mfu { + input: [["oc:chamelium", gemLapis, "oc:chamelium"] + ["oc:linkedCard", "oc:adapter", "oc:linkedCard"] + ["oc:chamelium", gemLapis, "oc:chamelium"]] +} leashUpgrade { input: [[ingotIron, lead, ingotIron] [lead, "oc:materialCU", lead] diff --git a/src/main/scala/li/cil/oc/Constants.scala b/src/main/scala/li/cil/oc/Constants.scala index fcb543b6a..3eee3ce77 100644 --- a/src/main/scala/li/cil/oc/Constants.scala +++ b/src/main/scala/li/cil/oc/Constants.scala @@ -118,6 +118,7 @@ object Constants { final val LinkedCard = "linkedCard" final val LootDisk = "lootDisk" final val LuaBios = "luaBios" + final val MFU = "mfu" final val Manual = "manual" final val MicrocontrollerCaseCreative = "microcontrollerCaseCreative" final val MicrocontrollerCaseTier1 = "microcontrollerCase1" @@ -174,6 +175,7 @@ object Constants { object DeviceInfo { final val DefaultVendor = "MightyPirates GmbH & Co. KG" + final val Scummtech = "Scummtech, Inc." } diff --git a/src/main/scala/li/cil/oc/Localization.scala b/src/main/scala/li/cil/oc/Localization.scala index 433e66965..9d853a65c 100644 --- a/src/main/scala/li/cil/oc/Localization.scala +++ b/src/main/scala/li/cil/oc/Localization.scala @@ -170,6 +170,8 @@ object Localization { def PrintLightValue(level: Int) = localizeImmediately("tooltip.Print.LightValue", level.toString) def PrintRedstoneLevel(level: Int) = localizeImmediately("tooltip.Print.RedstoneLevel", level.toString) + + def MFULinked(isLinked: Boolean) = localizeImmediately(if (isLinked) "tooltip.UpgradeMF.Linked" else "tooltip.UpgradeMF.Unlinked") } } diff --git a/src/main/scala/li/cil/oc/Settings.scala b/src/main/scala/li/cil/oc/Settings.scala index bd8897fdb..09db72e59 100644 --- a/src/main/scala/li/cil/oc/Settings.scala +++ b/src/main/scala/li/cil/oc/Settings.scala @@ -222,6 +222,7 @@ class Settings(val config: Config) { 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 + val mfuCost = config.getDouble("power.cost.mfuRelay") max 0 // power.rate val accessPointRate = config.getDouble("power.rate.accessPoint") max 0 @@ -357,6 +358,7 @@ 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 val tradingRange = config.getDouble("misc.tradingRange") max 0 + val mfuRange = config.getInt("misc.mfuRange") max 0 min 128 // ----------------------------------------------------------------------- // // nanomachines diff --git a/src/main/scala/li/cil/oc/common/event/BlockChangeHandler.scala b/src/main/scala/li/cil/oc/common/event/BlockChangeHandler.scala new file mode 100644 index 000000000..3830de1e7 --- /dev/null +++ b/src/main/scala/li/cil/oc/common/event/BlockChangeHandler.scala @@ -0,0 +1,71 @@ +package li.cil.oc.common.event + +import cpw.mods.fml.common.eventhandler.SubscribeEvent +import li.cil.oc.common.EventHandler +import li.cil.oc.util.BlockPosition +import net.minecraft.entity.Entity +import net.minecraft.entity.player.EntityPlayer +import net.minecraft.world.{IWorldAccess, World} +import net.minecraftforge.event.world.WorldEvent + +import scala.collection.mutable + +/** + * @author Vexatos + */ +object BlockChangeHandler { + + def addListener(listener: ChangeListener, coord: BlockPosition) = { + EventHandler.scheduleServer(() => changeListeners.put(listener, coord)) + } + + def removeListener(listener: ChangeListener) = { + EventHandler.scheduleServer(() => changeListeners.remove(listener)) + } + + private val changeListeners = mutable.WeakHashMap.empty[ChangeListener, BlockPosition] + + @SubscribeEvent + def onWorldLoad(e: WorldEvent.Load) { + e.world.addWorldAccess(new Listener(e.world)) + } + + trait ChangeListener { + def onBlockChanged() + } + + private class Listener(world: World) extends IWorldAccess { + + override def markBlockForUpdate(x: Int, y: Int, z: Int): Unit = { + val current = BlockPosition(x, y, z, world) + for ((listener, coord) <- changeListeners) if (coord.equals(current)) { + listener.onBlockChanged() + } + } + + override def playRecord(recordName: String, x: Int, y: Int, z: Int) {} + + override def playAuxSFX(player: EntityPlayer, sfxType: Int, x: Int, y: Int, z: Int, data: Int) {} + + override def onEntityDestroy(entity: Entity) {} + + override def destroyBlockPartially(breakerId: Int, x: Int, y: Int, z: Int, progress: Int) {} + + override def markBlockForRenderUpdate(x: Int, y: Int, z: Int) {} + + override def spawnParticle(particleType: String, x: Double, y: Double, z: Double, velX: Double, velY: Double, velZ: Double) {} + + override def playSound(soundName: String, x: Double, y: Double, z: Double, volume: Float, pitch: Float) {} + + override def broadcastSound(soundID: Int, x: Int, y: Int, z: Int, data: Int) {} + + override def playSoundToNearExcept(player: EntityPlayer, soundName: String, x: Double, y: Double, z: Double, volume: Float, pitch: Float) {} + + override def markBlockRangeForRenderUpdate(x1: Int, y1: Int, z1: Int, x2: Int, y2: Int, z2: Int) {} + + override def onEntityCreate(entity: Entity) {} + + override def onStaticEntitiesChanged() {} + } + +} diff --git a/src/main/scala/li/cil/oc/common/init/Items.scala b/src/main/scala/li/cil/oc/common/init/Items.scala index dfa737671..6864a77da 100644 --- a/src/main/scala/li/cil/oc/common/init/Items.scala +++ b/src/main/scala/li/cil/oc/common/init/Items.scala @@ -538,6 +538,7 @@ object Items extends ItemAPI { Recipes.addSubItem(new item.DiskDriveMountable(multi), Constants.ItemName.DiskDriveMountable, "oc:diskDriveMountable") Recipes.addSubItem(new item.UpgradeTrading(multi), Constants.ItemName.TradingUpgrade, "oc:tradingUpgrade") registerItem(new item.DiamondChip(multi), Constants.ItemName.DiamondChip) + Recipes.addSubItem(new item.UpgradeMF(multi), Constants.ItemName.MFU, "oc:mfu") // Register aliases. for ((k, v) <- aliases) { diff --git a/src/main/scala/li/cil/oc/common/item/UpgradeMF.scala b/src/main/scala/li/cil/oc/common/item/UpgradeMF.scala new file mode 100644 index 000000000..4f4b12687 --- /dev/null +++ b/src/main/scala/li/cil/oc/common/item/UpgradeMF.scala @@ -0,0 +1,31 @@ +package li.cil.oc.common.item + +import java.util + +import li.cil.oc.util.BlockPosition +import li.cil.oc.{Localization, Settings} +import net.minecraft.entity.player.EntityPlayer +import net.minecraft.item.ItemStack +import net.minecraft.nbt.NBTTagCompound + +class UpgradeMF(val parent: Delegator) extends traits.Delegate with traits.ItemTier { + + override def onItemUseFirst(stack: ItemStack, player: EntityPlayer, position: BlockPosition, side: Int, hitX: Float, hitY: Float, hitZ: Float): Boolean = { + if (!player.worldObj.isRemote && player.isSneaking) { + if (!stack.hasTagCompound) { + stack.setTagCompound(new NBTTagCompound()) + } + val data = stack.getTagCompound + data.setIntArray(Settings.namespace + "coord", Array(position.x, position.y, position.z, player.worldObj.provider.dimensionId, side)) + return true + } + super.onItemUseFirst(stack, player, position, side, hitX, hitY, hitZ) + } + + override protected def tooltipExtended(stack: ItemStack, tooltip: util.List[String]) { + tooltip.add(Localization.Tooltip.MFULinked(stack.getTagCompound match { + case data: NBTTagCompound => data.hasKey(Settings.namespace +"coord") + case _ => false + })) + } +} diff --git a/src/main/scala/li/cil/oc/integration/opencomputers/DriverUpgradeMF.scala b/src/main/scala/li/cil/oc/integration/opencomputers/DriverUpgradeMF.scala new file mode 100644 index 000000000..1c6c511c3 --- /dev/null +++ b/src/main/scala/li/cil/oc/integration/opencomputers/DriverUpgradeMF.scala @@ -0,0 +1,52 @@ +package li.cil.oc.integration.opencomputers + +import li.cil.oc.api.driver.EnvironmentProvider +import li.cil.oc.api.driver.item.HostAware +import li.cil.oc.api.network.{EnvironmentHost, ManagedEnvironment} +import li.cil.oc.common.{Slot, Tier} +import li.cil.oc.server.component +import li.cil.oc.util.BlockPosition +import li.cil.oc.{Constants, Settings, api} +import net.minecraft.item.ItemStack +import net.minecraftforge.common.DimensionManager +import net.minecraftforge.common.util.ForgeDirection + +/** + * + * @author Vexatos + */ +object DriverUpgradeMF extends Item with HostAware { + override def worksWith(stack: ItemStack): Boolean = isOneOf(stack, + api.Items.get(Constants.ItemName.MFU)) + + override def worksWith(stack: ItemStack, host: Class[_ <: EnvironmentHost]): Boolean = + worksWith(stack) && isAdapter(host) + + override def slot(stack: ItemStack): String = Slot.Upgrade + + override def tier(stack: ItemStack) = Tier.Three + + override def createEnvironment(stack: ItemStack, host: EnvironmentHost): ManagedEnvironment = { + if (host.world != null && !host.world.isRemote) { + if (stack.hasTagCompound) { + stack.getTagCompound.getIntArray(Settings.namespace + "coord") match { + case Array(x, y, z, dim, side) => + Option(DimensionManager.getWorld(dim)) match { + case Some(world) => return new component.UpgradeMF(host, BlockPosition(x, y, z, world), ForgeDirection.getOrientation(side)) + case _ => // Invalid dimension ID + } + case _ => // Invalid tag + } + } + } + null + } + + object Provider extends EnvironmentProvider { + override def getEnvironment(stack: ItemStack): Class[_] = + if (worksWith(stack)) + classOf[component.UpgradeMF] + else null + } + +} 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 164d977bc..4d0fbf052 100644 --- a/src/main/scala/li/cil/oc/integration/opencomputers/ModOpenComputers.scala +++ b/src/main/scala/li/cil/oc/integration/opencomputers/ModOpenComputers.scala @@ -98,6 +98,7 @@ object ModOpenComputers extends ModProxy { MinecraftForge.EVENT_BUS.register(Analyzer) MinecraftForge.EVENT_BUS.register(AngelUpgradeHandler) + MinecraftForge.EVENT_BUS.register(BlockChangeHandler) MinecraftForge.EVENT_BUS.register(ChunkloaderUpgradeHandler) MinecraftForge.EVENT_BUS.register(EventHandler) MinecraftForge.EVENT_BUS.register(ExperienceUpgradeHandler) @@ -166,6 +167,7 @@ object ModOpenComputers extends ModProxy { api.Driver.add(DriverUpgradeTankController) api.Driver.add(DriverUpgradeTractorBeam) api.Driver.add(DriverUpgradeTrading) + api.Driver.add(DriverUpgradeMF) api.Driver.add(DriverAPU.Provider) api.Driver.add(DriverDataCard.Provider) @@ -193,6 +195,7 @@ object ModOpenComputers extends ModProxy { api.Driver.add(DriverUpgradeSign.Provider) api.Driver.add(DriverUpgradeTankController.Provider) api.Driver.add(DriverUpgradeTractorBeam.Provider) + api.Driver.add(DriverUpgradeMF.Provider) api.Driver.add(EnvironmentProviderBlocks) diff --git a/src/main/scala/li/cil/oc/server/component/UpgradeMF.scala b/src/main/scala/li/cil/oc/server/component/UpgradeMF.scala new file mode 100644 index 000000000..a5870c618 --- /dev/null +++ b/src/main/scala/li/cil/oc/server/component/UpgradeMF.scala @@ -0,0 +1,218 @@ +package li.cil.oc.server.component + +import java.util + +import li.cil.oc.Constants +import li.cil.oc.api.driver.DeviceInfo +import li.cil.oc.api.driver.DeviceInfo.DeviceAttribute +import li.cil.oc.api.driver.DeviceInfo.DeviceClass +import li.cil.oc.api.network._ +import li.cil.oc.api.prefab +import li.cil.oc.common.event.BlockChangeHandler +import li.cil.oc.common.event.BlockChangeHandler.ChangeListener +import li.cil.oc.server.network +import li.cil.oc.util.BlockPosition +import li.cil.oc.util.ExtendedWorld._ +import li.cil.oc.Settings +import li.cil.oc.api +import net.minecraft.nbt.NBTTagCompound +import net.minecraft.tileentity.TileEntity +import net.minecraft.util.Vec3 +import net.minecraftforge.common.util.ForgeDirection + +import scala.collection.convert.WrapAsJava._ + +/** + * Mostly stolen from li.cil.oc.common.tileentity.Adapter + * + * @author Sangar, Vexatos + */ +class UpgradeMF(val host: EnvironmentHost, val coord: BlockPosition, val dir: ForgeDirection) extends prefab.ManagedEnvironment with ChangeListener with DeviceInfo { + override val node = api.Network.newNode(this, Visibility.None). + withConnector(). + create() + + private var otherEnv: Option[api.network.Environment] = None + private var otherDrv: Option[(ManagedEnvironment, api.driver.SidedBlock)] = None + private var blockData: Option[BlockData] = None + + override val canUpdate = true + + private final lazy val deviceInfo = Map( + DeviceAttribute.Class -> DeviceClass.Bus, + DeviceAttribute.Description -> "Remote Adapter", + DeviceAttribute.Vendor -> Constants.DeviceInfo.Scummtech, + DeviceAttribute.Product -> "ERR NAME NOT FOUND" + ) + + override def getDeviceInfo: util.Map[String, String] = deviceInfo + + private def otherNode(tile: TileEntity, f: (Node) => Unit) { + network.Network.getNetworkNode(tile, dir) match { + case Some(otherNode) => f(otherNode) + case _ => // Nothing to do here + } + } + + private def updateBoundState() { + if (node != null && node.network != null && coord.world.exists(_.provider.dimensionId == host.world.provider.dimensionId) + && coord.toVec3.distanceTo(Vec3.createVectorHelper(host.xPosition, host.yPosition, host.zPosition)) <= Settings.get.mfuRange) { + host.world.getTileEntity(coord) match { + case env: TileEntity with api.network.Environment => + otherEnv match { + case Some(environment: TileEntity) => + otherNode(environment, node.disconnect) + otherEnv = None + case _ => // Nothing to do here. + } + otherEnv = Some(env) + // Remove any driver that might be there. + otherDrv match { + case Some((environment, driver)) => + node.disconnect(environment.node) + environment.save(blockData.get.data) + Option(environment.node).foreach(_.remove()) + otherDrv = None + case _ => // Nothing to do here. + } + otherNode(env, node.connect) + case _ => + // Remove any environment that might have been there. + otherEnv match { + case Some(environment: TileEntity) => + otherNode(environment, node.disconnect) + otherEnv = None + case _ => // Nothing to do here. + } + val (world, x, y, z) = (coord.world.get, coord.x, coord.y, coord.z) + Option(api.Driver.driverFor(world, coord.x, coord.y, coord.z, dir)) match { + case Some(newDriver) => + otherDrv match { + case Some((oldEnvironment, driver)) => + if (newDriver != driver) { + // This is... odd. Maybe moved by some other mod? First, clean up. + otherDrv = None + blockData = None + node.disconnect(oldEnvironment.node) + + // Then rebuild - if we have something. + val environment = newDriver.createEnvironment(world, x, y, z, dir) + if (environment != null) { + otherDrv = Some((environment, newDriver)) + blockData = Some(new BlockData(environment.getClass.getName, new NBTTagCompound())) + node.connect(environment.node) + } + } // else: the more things change, the more they stay the same. + case _ => + // A challenger appears. Maybe. + val environment = newDriver.createEnvironment(world, x, y, z, dir) + if (environment != null) { + otherDrv = Some((environment, newDriver)) + blockData match { + case Some(data) if data.name == environment.getClass.getName => + environment.load(data.data) + case _ => + } + blockData = Some(new BlockData(environment.getClass.getName, new NBTTagCompound())) + node.connect(environment.node) + } + } + case _ => otherDrv match { + case Some((environment, driver)) => + // We had something there, but it's gone now... + node.disconnect(environment.node) + environment.save(blockData.get.data) + Option(environment.node).foreach(_.remove()) + otherDrv = None + case _ => // Nothing before, nothing now. + } + } + } + } + } + + private def disconnect() { + otherEnv match { + case Some(environment: TileEntity) => + otherNode(environment, node.disconnect) + otherEnv = None + case _ => // Nothing to do here. + } + otherDrv match { + case Some((environment, driver)) => + node.disconnect(environment.node) + environment.save(blockData.get.data) + Option(environment.node).foreach(_.remove()) + otherDrv = None + case _ => // Nothing to do here. + } + } + + override def onBlockChanged() = updateBoundState() + + override def update() { + super.update() + otherDrv match { + case Some((env, drv)) if env.canUpdate => env.update() + case _ => // No driver + } + if (host.world.getTotalWorldTime % Settings.get.tickFrequency == 0) { + if (!node.tryChangeBuffer(-Settings.get.mfuCost * Settings.get.tickFrequency + * coord.toVec3.distanceTo(Vec3.createVectorHelper(host.xPosition, host.yPosition, host.zPosition)))) { + disconnect() + } + } + } + + override def onConnect(node: Node) { + super.onConnect(node) + if (node == this.node) { + // Not checking for range yet because host may be a moving adapter, who knows? + BlockChangeHandler.addListener(this, coord) + + updateBoundState() + } + } + + override def onDisconnect(node: Node) { + super.onDisconnect(node) + otherEnv match { + case Some(env: TileEntity) => otherNode(env, (otherNode) => if (node == otherNode) otherEnv = None) + case _ => // No environment + } + otherDrv match { + case Some((env, drv)) if node == env.node => otherDrv = None + case _ => // No driver + } + if (node == this.node) { + BlockChangeHandler.removeListener(this) + } + } + + override def load(nbt: NBTTagCompound) { + super.load(nbt) + Option(nbt.getCompoundTag(Settings.namespace + "adapter.block")) match { + case Some(blockNbt: NBTTagCompound) => + if (blockNbt.hasKey("name") && blockNbt.hasKey("data")) { + blockData = Some(new BlockData(blockNbt.getString("name"), blockNbt.getCompoundTag("data"))) + } + case _ => // Invalid tag + } + } + + override def save(nbt: NBTTagCompound) { + super.save(nbt) + val blockNbt = new NBTTagCompound() + blockData.foreach({ data => + otherDrv.foreach(_._1.save(data.data)) + blockNbt.setString("name", data.name) + blockNbt.setTag("data", data.data) + }) + nbt.setTag(Settings.namespace + "adapter.block", blockNbt) + } + + // ----------------------------------------------------------------------- // + + private class BlockData(val name: String, val data: NBTTagCompound) + +}