Transfer base benefits via lattice (#307)

* Fix missing ObjectType on LocalBuildings, causing pain fields to stop working due to no SOI radius being set

* Fix damage logic for pain fields that don't rely on the nearest door

* Transfer base benefits via lattice

* Missed two Building.SendMapUpdate -> Building.TriggerZoneMapUpdate

* Fix Building tests
This commit is contained in:
Mazo 2019-12-27 16:50:34 +00:00 committed by Fate-JH
parent 73298a2e06
commit 4d742e9fee
7 changed files with 98 additions and 26 deletions

View file

@ -69,12 +69,13 @@ class ResourceSiloControl(resourceSilo : ResourceSilo) extends Actor with Factio
self ! ResourceSilo.LowNtuWarning(enabled = true)
}
val building = resourceSilo.Owner
val building = resourceSilo.Owner.asInstanceOf[Building]
val zone = building.Zone
if(resourceSilo.ChargeLevel == 0 && siloChargeBeforeChange > 0) {
// Oops, someone let the base run out of power. Shut it all down.
//todo: Make base neutral if silo hits zero NTU
zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttribute(building.GUID, 48, 1))
building.TriggerZoneMapUpdate()
} else if (siloChargeBeforeChange == 0 && resourceSilo.ChargeLevel > 0) {
// Power restored. Reactor Online. Sensors Online. Weapons Online. All systems nominal.
//todo: Check generator is online before starting up
@ -82,6 +83,7 @@ class ResourceSiloControl(resourceSilo : ResourceSilo) extends Actor with Factio
zone.Id,
AvatarAction.PlanetsideAttribute(building.GUID, 48, 0)
)
building.TriggerZoneMapUpdate()
}
case _ => ;
}

View file

@ -3,7 +3,7 @@ package net.psforever.objects.serverobject.structures
import java.util.concurrent.TimeUnit
import akka.actor.ActorContext
import akka.actor.{ActorContext, ActorRef}
import net.psforever.objects.{GlobalDefinitions, Player}
import net.psforever.objects.definition.ObjectDefinition
import net.psforever.objects.serverobject.hackable.Hackable
@ -13,6 +13,7 @@ import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.zones.Zone
import net.psforever.packet.game._
import net.psforever.types.{PlanetSideEmpire, Vector3}
import scalax.collection.{Graph, GraphEdge}
class Building(private val name: String,
private val building_guid : Int,
@ -40,6 +41,7 @@ class Building(private val name: String,
override def Faction_=(fac : PlanetSideEmpire.Value) : PlanetSideEmpire.Value = {
faction = fac
TriggerZoneMapUpdate()
Faction
}
@ -66,6 +68,20 @@ class Building(private val name: String,
}
}
def NtuLevel : Int = {
//if we have a silo, get the NTU level
Amenities.find(_.Definition == GlobalDefinitions.resource_silo) match {
case Some(obj: ResourceSilo) =>
obj.CapacitorDisplay.toInt
case _ => //we have no silo; we have unlimited power
10
}
}
def TriggerZoneMapUpdate(): Unit = {
if(Actor != ActorRef.noSender) Actor ! Building.TriggerZoneMapUpdate(Zone.Number)
}
// Get all lattice neighbours matching the specified faction
def Neighbours(faction: PlanetSideEmpire.Value): Option[Set[Building]] = {
this.Neighbours match {
@ -86,13 +102,7 @@ class Building(private val name: String,
Int, Option[Additional3],
Boolean, Boolean
) = {
//if we have a silo, get the NTU level
val ntuLevel : Int = Amenities.find(_.Definition == GlobalDefinitions.resource_silo) match {
case Some(obj: ResourceSilo) =>
obj.CapacitorDisplay.toInt
case _ => //we have no silo; we have unlimited power
10
}
val ntuLevel : Int = NtuLevel
//if we have a capture terminal, get the hack status & time (in milliseconds) from control console if it exists
val (hacking, hackingFaction, hackTime) : (Boolean, PlanetSideEmpire.Value, Long) = Amenities.find(_.Definition == GlobalDefinitions.capture_terminal) match {
case Some(obj: CaptureTerminal with Hackable) =>
@ -118,6 +128,46 @@ class Building(private val name: String,
(true, false)
}
}
val latticeBenefit : Int = {
if(Faction == PlanetSideEmpire.NEUTRAL) 0
else {
def FindLatticeBenefit(wantedBenefit: ObjectDefinition, subGraph: Graph[Building, GraphEdge.UnDiEdge]): Boolean = {
var found = false
subGraph find this match {
case Some(self) =>
if (this.Definition == wantedBenefit) found = true
else {
self pathUntil (_.Definition == wantedBenefit) match {
case Some(_) => found = true
case None => ;
}
}
case None => ;
}
found
}
// Check this Building is on the lattice first
zone.Lattice find this match {
case Some(_) =>
// todo: generator destruction state
val subGraph = Zone.Lattice filter ((b: Building) => b.Faction == this.Faction && !b.CaptureConsoleIsHacked && b.NtuLevel > 0)
var stackedBenefit = 0
if (FindLatticeBenefit(GlobalDefinitions.amp_station, subGraph)) stackedBenefit |= 1
if (FindLatticeBenefit(GlobalDefinitions.comm_station_dsp, subGraph)) stackedBenefit |= 2
if (FindLatticeBenefit(GlobalDefinitions.cryo_facility, subGraph)) stackedBenefit |= 4
if (FindLatticeBenefit(GlobalDefinitions.comm_station, subGraph)) stackedBenefit |= 8
if (FindLatticeBenefit(GlobalDefinitions.tech_plant, subGraph)) stackedBenefit |= 16
stackedBenefit
case None => 0;
}
}
}
//out
(
ntuLevel,
@ -130,7 +180,7 @@ class Building(private val name: String,
generatorState,
spawnTubesNormal,
false, //force_dome_active
0, //lattice_benefit
latticeBenefit,
0, //cavern_benefit; !! Field > 0 will cause malformed packet. See class def.
Nil,
0,
@ -196,4 +246,5 @@ object Building {
}
final case class SendMapUpdate(all_clients: Boolean)
final case class TriggerZoneMapUpdate(zone_num: Int)
}

View file

@ -3,6 +3,7 @@ package net.psforever.objects.serverobject.structures
import akka.actor.{Actor, ActorRef}
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.zones.InterstellarCluster
import net.psforever.packet.game.BuildingInfoUpdateMessage
import services.ServiceManager
import services.ServiceManager.Lookup
@ -11,24 +12,31 @@ import services.galaxy.{GalaxyAction, GalaxyResponse, GalaxyServiceMessage, Gala
class BuildingControl(building : Building) extends Actor with FactionAffinityBehavior.Check {
def FactionObject : FactionAffinity = building
var galaxyService : ActorRef = Actor.noSender
var interstellarCluster : ActorRef = Actor.noSender
private[this] val log = org.log4s.getLogger
override def preStart = {
log.trace(s"Starting BuildingControl for ${building.GUID} / ${building.MapId}")
ServiceManager.serviceManager ! Lookup("galaxy")
ServiceManager.serviceManager ! Lookup("cluster")
}
def receive : Receive = checkBehavior.orElse {
case ServiceManager.LookupResult("galaxy", endpoint) =>
galaxyService = endpoint
log.trace("BuildingControl: Building " + building.GUID + " Got galaxy service " + endpoint)
case ServiceManager.LookupResult("cluster", endpoint) =>
interstellarCluster = endpoint
log.trace("BuildingControl: Building " + building.GUID + " Got interstellar cluster service " + endpoint)
case FactionAffinity.ConvertFactionAffinity(faction) =>
val originalAffinity = building.Faction
if(originalAffinity != (building.Faction = faction)) {
building.Amenities.foreach(_.Actor forward FactionAffinity.ConfirmFactionAffinity())
}
sender ! FactionAffinity.AssertFactionAffinity(building, faction)
case Building.TriggerZoneMapUpdate(zone_num: Int) =>
if(interstellarCluster != ActorRef.noSender) interstellarCluster ! InterstellarCluster.ZoneMapUpdate(zone_num)
case Building.SendMapUpdate(all_clients: Boolean) =>
val zoneNumber = building.Zone.Number
val buildingNumber = building.MapId
@ -57,7 +65,7 @@ class BuildingControl(building : Building) extends Actor with FactionAffinityBeh
)
if(all_clients) {
galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(msg))
if(galaxyService != ActorRef.noSender) galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(msg))
} else {
// Fake a GalaxyServiceResponse response back to just the sender
sender ! GalaxyServiceResponse("", GalaxyResponse.MapUpdate(msg))

View file

@ -2,6 +2,7 @@
package net.psforever.objects.zones
import akka.actor.{Actor, Props}
import net.psforever.objects.serverobject.structures.Building
import scala.annotation.tailrec
@ -69,6 +70,9 @@ class InterstellarCluster(zones : List[Zone]) extends Actor {
case None => //zone_number does not exist
sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, None)
}
case InterstellarCluster.ZoneMapUpdate(zone_num: Int) =>
val zone = zones.find(x => x.Number == zone_num).get
zone.Buildings.values.foreach(b => b.Actor ! Building.SendMapUpdate(all_clients = true))
case _ =>
log.warn(s"InterstellarCluster received unknown message");
@ -122,6 +126,13 @@ object InterstellarCluster {
* @see `WorldSessionActor`
*/
final case class ClientInitializationComplete()
/**
* Requests that all buildings within a zone send a map update for the purposes of refreshing lattice benefits, such as when a base is hacked, changes faction or loses power
* @see `BuildingInfoUpdateMessage`
* @param zone_num the zone number to request building map updates for
*/
final case class ZoneMapUpdate(zone_num: Int)
}
/*

View file

@ -3,7 +3,6 @@ package services.local
import akka.actor.{Actor, ActorRef, Props}
import net.psforever.objects.ce.Deployable
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
import net.psforever.objects.serverobject.structures.{Amenity, Building}
import net.psforever.objects.serverobject.terminals.{CaptureTerminal, Terminal}
import net.psforever.objects.zones.Zone
@ -87,10 +86,12 @@ class LocalService(zone : Zone) extends Actor {
hackClearer ! HackClearActor.ObjectIsResecured(target)
case LocalAction.HackCaptureTerminal(player_guid, _, target, unk1, unk2, isResecured) =>
// When a CC is hacked (or resecured) all amenities for the base should be unhacked
val hackableAmenities = target.Owner.asInstanceOf[Building].Amenities.filter(x => x.isInstanceOf[Hackable]).map(x => x.asInstanceOf[Amenity with Hackable])
val building = target.Owner.asInstanceOf[Building]
val hackableAmenities = building.Amenities.filter(x => x.isInstanceOf[Hackable]).map(x => x.asInstanceOf[Amenity with Hackable])
hackableAmenities.foreach(amenity =>
if(amenity.HackedBy.isDefined) { hackClearer ! HackClearActor.ObjectIsResecured(amenity) }
)
if(isResecured){
hackCapturer ! HackCaptureActor.ClearHack(target, zone)
} else {
@ -103,9 +104,16 @@ class LocalService(zone : Zone) extends Actor {
hackCapturer ! HackCaptureActor.ObjectIsHacked(target, zone, unk1, unk2, duration = 1 nanosecond)
}
}
LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.HackCaptureTerminal(target.GUID, unk1, unk2, isResecured))
)
// If the owner of this capture terminal is on the lattice trigger a zone wide map update to update lattice benefits
zone.Lattice find building match {
case Some(_) => building.TriggerZoneMapUpdate()
case None => ;
}
case LocalAction.RouterTelepadTransport(player_guid, passenger_guid, src_guid, dest_guid) =>
LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.RouterTelepadTransport(passenger_guid, src_guid, dest_guid))
@ -163,16 +171,8 @@ class LocalService(zone : Zone) extends Actor {
case HackCaptureActor.HackTimeoutReached(capture_terminal_guid, _, _, _, hackedByFaction) =>
val terminal = zone.GUID(capture_terminal_guid).get.asInstanceOf[CaptureTerminal]
val building = terminal.Owner.asInstanceOf[Building]
// todo: Move this to a function for Building
var ntuLevel = building.Amenities.find(_.Definition == GlobalDefinitions.resource_silo) match {
case Some(obj: ResourceSilo) =>
obj.CapacitorDisplay.toInt
case _ =>
// Base has no NTU silo - likely a tower / cavern CC
1
}
if(ntuLevel > 0) {
if(building.NtuLevel > 0) {
log.info(s"Setting base ${building.GUID} / MapId: ${building.MapId} as owned by $hackedByFaction")
building.Faction = hackedByFaction

View file

@ -43,7 +43,7 @@ class HackCaptureActor extends Actor {
// Restart the timer, in case this is the first object in the hacked objects list or the object was removed and re-added
RestartTimer()
target.Owner.Actor ! Building.SendMapUpdate(all_clients = true)
if(target.isInstanceOf[CaptureTerminal]) { target.Owner.asInstanceOf[Building].TriggerZoneMapUpdate() }
case HackCaptureActor.ProcessCompleteHacks() =>
log.trace("Processing complete hacks")
@ -67,7 +67,7 @@ class HackCaptureActor extends Actor {
case HackCaptureActor.ClearHack(target, _) =>
hackedObjects = hackedObjects.filterNot(x => x.target == target)
if(target.isInstanceOf[CaptureTerminal]) { target.Owner.Actor ! Building.SendMapUpdate(all_clients = true) }
if(target.isInstanceOf[CaptureTerminal]) { target.Owner.asInstanceOf[Building].TriggerZoneMapUpdate() }
// Restart the timer in case the object we just removed was the next one scheduled
RestartTimer()

View file

@ -696,7 +696,7 @@ class VehicleControlShieldsNotChargingDamagedTest extends ActorTest {
val fury_dm = Vehicle(GlobalDefinitions.fury).DamageModel
val obj = ResolvedProjectile(ProjectileResolution.Hit, projectile, p_source, fury_dm, Vector3(1.2f, 3.4f, 5.6f), System.nanoTime)
"charge vehicle shields" in {
"not charge vehicle shields if recently damaged" in {
assert(vehicle.Shields == 0)
vehicle.Actor ! Vitality.Damage({case v : Vehicle => v.History(obj)})