| package li.cil.oc.server.component |
| |
| import li.cil.oc.api.network._ |
| import li.cil.oc.{Config, api} |
| import net.minecraft.block.Block |
| import net.minecraft.nbt.NBTTagCompound |
| import net.minecraft.tileentity.TileEntity |
| import net.minecraftforge.common.MinecraftForge |
| import net.minecraftforge.event.ForgeSubscribe |
| import net.minecraftforge.event.world.WorldEvent |
| import scala.Some |
| import scala.collection.convert.WrapAsScala._ |
| import scala.collection.mutable |
| import scala.language.implicitConversions |
| |
| class WirelessNetworkCard(val owner: TileEntity) extends NetworkCard { |
| override val node = api.Network.newNode(this, Visibility.Network). |
| withComponent("modem", Visibility.Neighbors). |
| withConnector(Config.bufferComputer). |
| create() |
| |
| var strength = 64.0 |
| |
| // ----------------------------------------------------------------------- // |
| |
| @LuaCallback(value = "getStrength", direct = true) |
| def getStrength(context: Context, args: Arguments): Array[AnyRef] = result(strength) |
| |
| @LuaCallback(value = "setStrength", direct = true) |
| def setStrength(context: Context, args: Arguments): Array[AnyRef] = this.synchronized { |
| strength = args.checkDouble(0) |
| result(strength) |
| } |
| |
| override def isWireless(context: Context, args: Arguments): Array[AnyRef] = result(true) |
| |
| override def send(context: Context, args: Arguments) = this.synchronized { |
| val address = args.checkString(0) |
| val port = checkPort(args.checkInteger(1)) |
| for ((card, distance) <- WirelessNetwork.computeReachableFrom(this) |
| if card.node.address == address && card.openPorts.contains(port)) { |
| card.node.sendToReachable("computer.signal", |
| Seq("modem_message", node.address, Int.box(port), Double.box(distance)) ++ args.drop(2): _*) |
| } |
| super.send(context, args) |
| } |
| |
| override def broadcast(context: Context, args: Arguments) = this.synchronized { |
| val port = checkPort(args.checkInteger(0)) |
| for ((card, distance) <- WirelessNetwork.computeReachableFrom(this) |
| if card.openPorts.contains(port)) { |
| card.node.sendToReachable("computer.signal", |
| Seq("modem_message", node.address, Int.box(port), Double.box(distance)) ++ args.drop(2): _*) |
| } |
| super.broadcast(context, args) |
| } |
| |
| // ----------------------------------------------------------------------- // |
| |
| override def onConnect(node: Node) { |
| super.onConnect(node) |
| if (node == this.node) { |
| WirelessNetwork.add(this) |
| } |
| } |
| |
| override def onDisconnect(node: Node) { |
| super.onDisconnect(node) |
| if (node == this.node) { |
| WirelessNetwork.remove(this) |
| } |
| } |
| |
| // ----------------------------------------------------------------------- // |
| |
| override def load(nbt: NBTTagCompound) { |
| super.load(nbt) |
| if (nbt.hasKey("oc.net.strength")) { |
| strength = nbt.getDouble("oc.net.strength") |
| } |
| } |
| |
| override def save(nbt: NBTTagCompound) { |
| super.save(nbt) |
| nbt.setDouble("oc.net.strength", strength) |
| } |
| } |
| |
| object WirelessNetwork { |
| MinecraftForge.EVENT_BUS.register(this) |
| |
| val dimensions = mutable.Map.empty[Int, mutable.TreeSet[WirelessNode]] |
| |
| @ForgeSubscribe |
| def onWorldUnload(e: WorldEvent.Unload) { |
| if (!e.world.isRemote) { |
| dimensions.remove(e.world.provider.dimensionId) |
| } |
| } |
| |
| @ForgeSubscribe |
| def onWorldLoad(e: WorldEvent.Load) { |
| if (!e.world.isRemote) { |
| dimensions.remove(e.world.provider.dimensionId) |
| } |
| } |
| |
| class WirelessNode(val x: Double, val y: Double, val z: Double, val card: Option[WirelessNetworkCard]) extends Ordered[WirelessNode] { |
| // This metric allows us detecting any nodes in the cube spanned by two |
| // points - those being the box around a transmitter with all sides being |
| // away card.strength from the center (so the edge length is 2 * strength). |
| // This gives us a quick first estimate on which receivers may be in range. |
| // We then refine this with some further checks, see below. |
| def compare(that: WirelessNode) = |
| if (x < that.x && y < that.y && z < that.z) -1 |
| else if (x >= that.x && y >= that.y && z >= that.z) 1 |
| else 0 |
| } |
| |
| private implicit def toNode(card: WirelessNetworkCard) = |
| new WirelessNode(card.owner.xCoord + 0.5, card.owner.yCoord + 0.5, card.owner.zCoord + 0.5, Some(card)) |
| |
| private implicit def toNode(coordinate: (Double, Double, Double)) = |
| new WirelessNode(coordinate._1, coordinate._2, coordinate._3, None) |
| |
| def add(card: WirelessNetworkCard) { |
| dimensions.getOrElseUpdate(dimension(card), mutable.TreeSet.empty[WirelessNode]) += card |
| } |
| |
| def remove(card: WirelessNetworkCard) { |
| dimensions.get(dimension(card)) match { |
| case Some(set) => set -= card |
| case _ => |
| } |
| } |
| |
| def computeReachableFrom(card: WirelessNetworkCard) = { |
| dimensions.get(dimension(card)) match { |
| case Some(set) if card.strength >= 1 => |
| set.range(offset(card, -card.strength), offset(card, card.strength)). |
| map(_.card.get). |
| filter(_ != card). |
| map(zipWithDistance(card)). |
| filter(_._2 <= card.strength * card.strength). |
| map { case (c, distance) => (c, Math.sqrt(distance)) }. |
| filter(isUnobstructed(card)) |
| case _ => Iterable.empty[(WirelessNetworkCard, Double)] // Should not be possible. |
| } |
| } |
| |
| private def dimension(card: WirelessNetworkCard) = card.owner.worldObj.provider.dimensionId |
| |
| private def offset(card: WirelessNetworkCard, value: Double) = |
| (card.owner.xCoord + 0.5 + value, card.owner.yCoord + 0.5 + value, card.owner.zCoord + 0.5 + value) |
| |
| private def zipWithDistance(reference: WirelessNetworkCard)(card: WirelessNetworkCard) = |
| (card, card.owner.getDistanceFrom( |
| reference.owner.xCoord + 0.5, |
| reference.owner.yCoord + 0.5, |
| reference.owner.zCoord + 0.5)) |
| |
| private def isUnobstructed(reference: WirelessNetworkCard)(info: (WirelessNetworkCard, Double)): Boolean = { |
| val (card, distance) = info |
| val gap = distance - 1 |
| if (gap > 0) { |
| // If there's some space between the two wireless network cards we try to |
| // figure out if the signal might have been obstructed. We do this by |
| // taking a few samples (more the further they are apart) and check if we |
| // hit a block. For each block hit we subtract its hardness from the |
| // surplus strength left after crossing the distance between the two. If |
| // we reach a point where the surplus strength does not suffice we block |
| // the message. |
| |
| // Unit vector from reference card (sender) to this one (receiver). |
| val dx = (card.owner.xCoord - reference.owner.xCoord) / distance |
| val dy = (card.owner.yCoord - reference.owner.yCoord) / distance |
| val dz = (card.owner.zCoord - reference.owner.zCoord) / distance |
| |
| // Accumulated obstructions and number of samples. |
| var hardness = 0.0 |
| val samples = Math.sqrt(gap).toInt |
| |
| val world = card.owner.worldObj |
| val ox = reference.owner.xCoord - (if (reference.owner.xCoord < 0) 2 else 1) |
| val oy = reference.owner.yCoord - (if (reference.owner.yCoord < 0) 2 else 1) |
| val oz = reference.owner.zCoord - (if (reference.owner.zCoord < 0) 2 else 1) |
| for (i <- 0 until samples) { |
| val sample = 0.5 + world.rand.nextDouble() * gap |
| // Adding some jitter to avoid only tracking the perfect line between |
| // two modems when they are diagonal to each other. |
| val x = (ox + world.rand.nextInt(3) + 0.5 + dx * sample).toInt |
| val y = (oy + world.rand.nextInt(3) + 0.5 + dy * sample).toInt |
| val z = (oz + world.rand.nextInt(3) + 0.5 + dz * sample).toInt |
| //world.playAuxSFX(2005, x, y, z, 0) |
| Option(Block.blocksList(world.getBlockId(x, y, z))) match { |
| case Some(block) => |
| hardness += block.blockHardness |
| case _ => |
| } |
| } |
| |
| // Normalize and scale obstructions: |
| hardness *= gap.toDouble / samples.toDouble |
| |
| // Remaining signal strength. |
| val strength = reference.strength - distance |
| |
| // See if we have enough power to overcome the obstructions. |
| strength > hardness |
| } |
| else true |
| } |
| } |