blob: f0a4b1680bce03aec0ea6891b9fc9ee47cabf65b [file] [log] [blame] [raw]
package li.cil.oc.util
import cpw.mods.fml.common.eventhandler.SubscribeEvent
import li.cil.oc.Settings
import li.cil.oc.server.component.NetworkCard
import net.minecraft.tileentity.TileEntity
import net.minecraft.util.Vec3
import net.minecraftforge.event.world.WorldEvent
import scala.collection.mutable
object WirelessNetwork {
val dimensions = mutable.Map.empty[Int, RTree[Endpoint]]
@SubscribeEvent
def onWorldUnload(e: WorldEvent.Unload) {
if (!e.world.isRemote) {
dimensions.remove(e.world.provider.dimensionId)
}
}
@SubscribeEvent
def onWorldLoad(e: WorldEvent.Load) {
if (!e.world.isRemote) {
dimensions.remove(e.world.provider.dimensionId)
}
}
def add(endpoint: Endpoint) {
dimensions.getOrElseUpdate(dimension(endpoint), new RTree[Endpoint](Settings.get.rTreeMaxEntries)((endpoint) => (endpoint.owner.xCoord + 0.5, endpoint.owner.yCoord + 0.5, endpoint.owner.zCoord + 0.5))).add(endpoint)
}
def update(endpoint: Endpoint) {
dimensions.get(dimension(endpoint)) match {
case Some(tree) =>
tree(endpoint) match {
case Some((x, y, z)) =>
val dx = math.abs(endpoint.owner.xCoord + 0.5 - x)
val dy = math.abs(endpoint.owner.yCoord + 0.5 - y)
val dz = math.abs(endpoint.owner.zCoord + 0.5 - z)
if (dx > 0.5 || dy > 0.5 || dz > 0.5) {
tree.remove(endpoint)
tree.add(endpoint)
}
case _ =>
}
case _ =>
}
}
def remove(endpoint: Endpoint) = {
dimensions.get(dimension(endpoint)) match {
case Some(set) => set.remove(endpoint)
case _ => false
}
}
def computeReachableFrom(endpoint: Endpoint) = {
dimensions.get(dimension(endpoint)) match {
case Some(tree) if endpoint.strength > 0 =>
val range = endpoint.strength + 1
tree.query(offset(endpoint, -range), offset(endpoint, range)).
filter(_ != endpoint).
map(zipWithDistance(endpoint)).
filter(_._2 <= range * range).
map {
case (c, distance) => (c, Math.sqrt(distance))
}.
filter(isUnobstructed(endpoint))
case _ => Iterable.empty[(Endpoint, Double)] // Should not be possible.
}
}
trait Endpoint {
def owner: TileEntity
def strength: Double
def receivePacket(packet: NetworkCard.Packet, distance: Double)
}
private def dimension(endpoint: Endpoint) = endpoint.owner.getWorldObj.provider.dimensionId
private def offset(endpoint: Endpoint, value: Double) =
(endpoint.owner.xCoord + 0.5 + value, endpoint.owner.yCoord + 0.5 + value, endpoint.owner.zCoord + 0.5 + value)
private def zipWithDistance(reference: Endpoint)(endpoint: Endpoint) =
(endpoint, endpoint.owner.getDistanceFrom(
reference.owner.xCoord + 0.5,
reference.owner.yCoord + 0.5,
reference.owner.zCoord + 0.5))
private def isUnobstructed(reference: Endpoint)(info: (Endpoint, Double)): Boolean = {
val (endpoint, 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.
val world = endpoint.owner.getWorldObj
val pool = world.getWorldVec3Pool
val origin = pool.getVecFromPool(reference.owner.xCoord, reference.owner.yCoord, reference.owner.zCoord)
val target = pool.getVecFromPool(endpoint.owner.xCoord, endpoint.owner.yCoord, endpoint.owner.zCoord)
// Vector from reference endpoint (sender) to this one (receiver).
val delta = subtract(target, origin)
val v = delta.normalize()
// Get the vectors that are orthogonal to the direction vector.
val up = if (v.xCoord == 0 && v.zCoord == 0) {
assert(v.yCoord != 0)
pool.getVecFromPool(1, 0, 0)
}
else {
pool.getVecFromPool(0, 1, 0)
}
val side = crossProduct(v, up)
val top = crossProduct(v, side)
// Accumulated obstructions and number of samples.
var hardness = 0.0
val samples = math.max(1, math.sqrt(gap).toInt)
for (i <- 0 until samples) {
val rGap = world.rand.nextDouble() * gap
// Adding some jitter to avoid only tracking the perfect line between
// two endpoints when they are diagonal to each other for example.
val rSide = world.rand.nextInt(3) - 1
val rTop = world.rand.nextInt(3) - 1
val x = (origin.xCoord + v.xCoord * rGap + side.xCoord * rSide + top.xCoord * rTop).toInt
val y = (origin.yCoord + v.yCoord * rGap + side.yCoord * rSide + top.yCoord * rTop).toInt
val z = (origin.zCoord + v.zCoord * rGap + side.zCoord * rSide + top.zCoord * rTop).toInt
Option(world.getBlock(x, y, z)) match {
case Some(block) => hardness += block.getBlockHardness(world, x, y, z)
case _ =>
}
}
// Normalize and scale obstructions:
hardness *= gap / samples
// Remaining signal strength.
val strength = reference.strength - gap
// See if we have enough power to overcome the obstructions.
strength > hardness
}
else true
}
private def subtract(v1: Vec3, v2: Vec3) = v1.myVec3LocalPool.getVecFromPool(v1.xCoord - v2.xCoord, v1.yCoord - v2.yCoord, v1.zCoord - v2.zCoord)
private def crossProduct(v1: Vec3, v2: Vec3) = v1.myVec3LocalPool.getVecFromPool(v1.yCoord * v2.zCoord - v1.zCoord * v2.yCoord, v1.zCoord * v2.xCoord - v1.xCoord * v2.zCoord, v1.xCoord * v2.yCoord - v1.yCoord * v2.xCoord)
}