diff --git a/common/src/main/scala/net/psforever/objects/serverobject/painbox/Painbox.scala b/common/src/main/scala/net/psforever/objects/serverobject/painbox/Painbox.scala index 5bbe3128..3fa6d291 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/painbox/Painbox.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/painbox/Painbox.scala @@ -10,7 +10,9 @@ class Painbox(tdef : PainboxDefinition) extends Amenity { } object Painbox { + final case class Start() final case class Tick() + final case class Stop() def apply(tdef : PainboxDefinition) : Painbox = { new Painbox(tdef) diff --git a/common/src/main/scala/net/psforever/objects/serverobject/painbox/PainboxControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/painbox/PainboxControl.scala index 2a32054b..aad8f4e9 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/painbox/PainboxControl.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/painbox/PainboxControl.scala @@ -22,15 +22,19 @@ class PainboxControl(painbox: Painbox) extends Actor { nearestDoor = obj.Amenities .collect { case door : Door => door } .minBy(door => Vector3.DistanceSquared(painbox.Position, door.Position)) - painboxTick = context.system.scheduler.schedule(0 seconds,1 second, self, Painbox.Tick()) - context.become(Processing) + context.become(Stopped) case _ => ; } case _ => ; } - def Processing : Receive = { + def Running : Receive = { + case Painbox.Stop() => + context.become(Stopped) + painboxTick.cancel + painboxTick = DefaultCancellable.obj + case Painbox.Tick() => //todo: Account for overlapping pain fields //todo: Pain module @@ -49,5 +53,16 @@ class PainboxControl(painbox: Painbox) extends Actor { events ! AvatarServiceMessage(p.Name, AvatarAction.EnvironmentalDamage(p.GUID, damage)) } } + + case _ => ; + } + + def Stopped : Receive = { + case Painbox.Start() => + context.become(Running) + painboxTick.cancel + painboxTick = context.system.scheduler.schedule(0 seconds, 1 second, self, Painbox.Tick()) + + case _ => ; } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala index 6dcd285d..7fa0ab89 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala @@ -7,6 +7,7 @@ import akka.actor.{ActorContext, ActorRef} import net.psforever.objects.{GlobalDefinitions, Player} import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.serverobject.hackable.Hackable +import net.psforever.objects.serverobject.painbox.Painbox import net.psforever.objects.serverobject.resourcesilo.ResourceSilo import net.psforever.objects.serverobject.terminals.CaptureTerminal import net.psforever.objects.serverobject.tube.SpawnTube @@ -56,6 +57,18 @@ class Building(private val name: String, def PlayersInSOI : List[Player] = playersInSOI def PlayersInSOI_=(list : List[Player]) : List[Player] = { + if(playersInSOI.isEmpty && list.nonEmpty) { + Amenities.collect { + case box : Painbox => + box.Actor ! Painbox.Start() + } + } + else if(playersInSOI.nonEmpty && list.isEmpty) { + Amenities.collect { + case box : Painbox => + box.Actor ! Painbox.Stop() + } + } playersInSOI = list playersInSOI } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala index c7b2e742..34c4df24 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala @@ -6,7 +6,6 @@ import net.psforever.objects._ import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} import net.psforever.objects.serverobject.hackable.HackableBehavior -import services.Service import scala.collection.mutable import scala.concurrent.duration._ @@ -27,17 +26,7 @@ class ProximityTerminalControl(term : Terminal with ProximityUnit) extends Actor def TerminalObject : Terminal with ProximityUnit = term - def receive : Receive = Start - - def Start : Receive = checkBehavior - .orElse { - case Service.Startup() => - context.become(Run) - - case _ => ; - } - - def Run : Receive = checkBehavior + def receive : Receive = checkBehavior .orElse(hackableBehavior) .orElse { case CommonMessages.Use(_, Some(target : PlanetSideGameObject)) => diff --git a/common/src/main/scala/net/psforever/objects/zones/SphereOfInfluenceActor.scala b/common/src/main/scala/net/psforever/objects/zones/SphereOfInfluenceActor.scala index a34ed540..844c643b 100644 --- a/common/src/main/scala/net/psforever/objects/zones/SphereOfInfluenceActor.scala +++ b/common/src/main/scala/net/psforever/objects/zones/SphereOfInfluenceActor.scala @@ -1,45 +1,77 @@ -package net.psforever.objects.zones; +package net.psforever.objects.zones + import akka.actor.{Actor, Cancellable} +import net.psforever.objects.{DefaultCancellable, Player} import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.serverobject.structures.{Building, SphereOfInfluence} +import net.psforever.types.Vector3 +import scala.annotation.tailrec import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ class SphereOfInfluenceActor(zone: Zone) extends Actor { - def receive : Receive = Established + var sois : Iterable[(Building, Int)] = Nil + var populateTick : Cancellable = DefaultCancellable.obj + //private[this] val log = org.log4s.getLogger(s"${zone.Id.capitalize}-SphereOfInfluenceActor") - private var populateTick: Cancellable = context.system.scheduler.scheduleOnce(5 seconds, self, SOI.Populate()) - private[this] val log = org.log4s.getLogger(s"${zone.Id.capitalize}-SphereOfInfluenceActor") + def receive : Receive = Stopped - def Established : Receive = { + def Build : Receive = { + case SOI.Build() => + BuildSOI() + } + + def Running : Receive = Build.orElse { case SOI.Populate() => UpdateSOI() + + case SOI.StopPopulation() => + context.become(Stopped) + populateTick.cancel + sois.foreach { case (facility, _) => facility.PlayersInSOI = Nil } + + case _ => ; + } + + def Stopped : Receive = Build.orElse { + case SOI.Populate() => + context.become(Running) + UpdateSOI() + + case _ => ; + } + + def BuildSOI() : Unit = { + sois = zone.Buildings + .values + .map { facility => (facility, facility.Definition) } + .collect { case (facility, soi : ObjectDefinition with SphereOfInfluence) if soi.SOIRadius > 0 => + (facility, soi.SOIRadius * soi.SOIRadius) + } } def UpdateSOI(): Unit = { - val players = zone.LivePlayers - - zone.Buildings.foreach({ - case (_, building : Building) => - building.Definition match { - case _ : ObjectDefinition with SphereOfInfluence => - // todo: overlapping soi (e.g. tower soi in base soi) order by smallest soi first? - val playersInSoi = players.filter(p => Math.pow(p.Position.x - building.Position.x, 2) + Math.pow(p.Position.y - building.Position.y, 2) < Math.pow(300, 2) ) - if(playersInSoi.length > 0) { -// log.info(s"Building ${building.GUID} players in soi: ${playersInSoi.toString()}" ) - } - building.PlayersInSOI = playersInSoi - case _ => ; - } - - }) - + SOI.Populate(sois.iterator, zone.LivePlayers) populateTick = context.system.scheduler.scheduleOnce(5 seconds, self, SOI.Populate()) } } object SOI { + /** Rebuild the list of facility SOI data **/ + final case class Build() /** Populate the list of players within a SOI **/ final case class Populate() -} \ No newline at end of file + /** Stop sorting players into sois */ + final case class StopPopulation() + + @tailrec + def Populate(buildings : Iterator[(Building, Int)], players : List[Player]) : Unit = { + if(players.nonEmpty && buildings.hasNext) { + val (facility, radius) = buildings.next + val (tenants, remainder) = players.partition(p => Vector3.DistanceSquared(facility.Position.xy, p.Position.xy) < radius) + facility.PlayersInSOI = tenants + Populate(buildings, remainder) + } + } +} diff --git a/common/src/main/scala/net/psforever/objects/zones/Zone.scala b/common/src/main/scala/net/psforever/objects/zones/Zone.scala index 1d7eb637..d4d09130 100644 --- a/common/src/main/scala/net/psforever/objects/zones/Zone.scala +++ b/common/src/main/scala/net/psforever/objects/zones/Zone.scala @@ -13,15 +13,12 @@ import net.psforever.objects.guid.actor.UniqueNumberSystem import net.psforever.objects.guid.selector.RandomSelector import net.psforever.objects.guid.source.LimitedNumberSource import net.psforever.objects.inventory.Container -import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.painbox.{Painbox, PainboxDefinition} import net.psforever.objects.serverobject.resourcesilo.ResourceSilo import net.psforever.objects.serverobject.structures.{Amenity, Building, WarpGate} -import net.psforever.objects.serverobject.terminals.ProximityUnit import net.psforever.objects.serverobject.turret.FacilityTurret import net.psforever.packet.game.PlanetSideGUID import net.psforever.types.{PlanetSideEmpire, Vector3} -import services.Service import services.avatar.AvatarService import services.local.LocalService import services.vehicle.VehicleService @@ -30,9 +27,9 @@ import scala.collection.concurrent.TrieMap import scala.collection.mutable.ListBuffer import scala.collection.immutable.{Map => PairMap} import scala.concurrent.duration._ - import scalax.collection.Graph -import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._ +import scalax.collection.GraphPredef._ +import scalax.collection.GraphEdge._ /** * A server object representing the one-landmass planets as well as the individual subterranean caverns.
@@ -413,31 +410,27 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { case silo : ResourceSilo => silo.Actor ! "startup" } - //proximity terminals need to startup - buildings.values - .flatMap(_.Amenities.filter(_.isInstanceOf[ProximityUnit])) - .collect { - case o : PlanetSideServerObject => - o.Actor ! Service.Startup() - } + //some painfields need to look for their closest door buildings.values .flatMap(_.Amenities.filter(_.Definition.isInstanceOf[PainboxDefinition])) .collect { case painbox : Painbox => painbox.Actor ! "startup" } + //allocate soi information + soi ! SOI.Build() } private def MakeLattice(): Unit = { Map.LatticeLink.foreach({ case(source, target) => val sourceBuilding = Building(source) match { case Some(building) => building - case _ => throw new NoSuchElementException(s"Can't create lattice link between ${source} ${target}. Source is missing") + case _ => throw new NoSuchElementException(s"Can't create lattice link between $source $target. Source is missing") } val targetBuilding = Building(target) match { case Some(building) => building - case _ => throw new NoSuchElementException(s"Can't create lattice link between ${source} ${target}. Target is missing") + case _ => throw new NoSuchElementException(s"Can't create lattice link between $source $target. Target is missing") } lattice += sourceBuilding~targetBuilding @@ -477,6 +470,24 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { entry } + def StartPlayerManagementSystems() : Unit = { + println(s"start player management for $Id") + soi ! SOI.Populate() +// buildings.values.foreach { _.Amenities.collect { +// case box : Painbox => +// box.Actor ! Painbox.Start() +// } } + } + + def StopPlayerManagementSystems() : Unit = { + println(s"stop player management for $Id") + soi ! SOI.StopPopulation() +// buildings.values.foreach { _.Amenities.collect { +// case box : Painbox => +// box.Actor ! Painbox.Stop() +// } } + } + def Activity : ActorRef = projector def HotSpots : List[HotSpotInfo] = hotspots toList diff --git a/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala index f43ecb26..a77911b7 100644 --- a/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala +++ b/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala @@ -3,7 +3,6 @@ package net.psforever.objects.zones import akka.actor.{Actor, ActorRef, Props} import net.psforever.objects.avatar.PlayerControl -import net.psforever.objects.vehicles.VehicleControl import net.psforever.objects.{Avatar, Player} import scala.annotation.tailrec @@ -24,13 +23,18 @@ class ZonePopulationActor(zone : Zone, playerMap : TrieMap[Avatar, Option[Player def receive : Receive = { case Zone.Population.Join(avatar) => - PopulationJoin(avatar, playerMap) + if(PopulationJoin(avatar, playerMap) && playerMap.size == 1) { + zone.StartPlayerManagementSystems() + } case Zone.Population.Leave(avatar) => PopulationLeave(avatar, playerMap) match { case None => ; case player @ Some(_) => sender ! Zone.Population.PlayerHasLeft(zone, player) + if(playerMap.isEmpty) { + zone.StopPlayerManagementSystems() + } } case Zone.Population.Spawn(avatar, player) => @@ -42,6 +46,7 @@ class ZonePopulationActor(zone : Zone, playerMap : TrieMap[Avatar, Option[Player } else { player.Actor = context.actorOf(Props(classOf[PlayerControl], player), s"${player.Name}_${player.GUID.guid}") + player.Zone = zone } case None => sender ! Zone.Population.PlayerCanNotSpawn(zone, player)