mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-03-03 20:20:22 +00:00
* all matters related to vehicle events, including tests, adjusted so that the service actor internalizes a specific zone; major adjustments to the order fulfillment logic of vehicle spawn pads; removed a bunch of unnecessary things such as auto-drive guidance * all matters related to local events, including tests, adjusted so that the service actor internalizes a specific zone; special consideration for proximity units and tests; muted some excessive logging * all matters related to avatar events, including tests, adjusted so that the service actor internalizes a specific zone; special considerations for resource silos and painbox elements * explicit trait that acts as an owner of Amenity objects (Building and Vehicle); generalization of <obj>.Owner.Zone statement * reduced log spam from proximity terminal activity * tightened vehicle spawn pad control protocol; finally made the player re-appear if the vehicle doesn't spawn/mount correctly; pad operates independent of the person who submitted the current order so less chances for crash * adjusted workflow for vehicle spawn pad task management
337 lines
16 KiB
Scala
337 lines
16 KiB
Scala
// Copyright (c) 2017 PSForever
|
|
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
|
|
import net.psforever.objects._
|
|
import net.psforever.packet.game.{PlanetSideGUID, TriggeredEffect, TriggeredEffectLocation}
|
|
import net.psforever.objects.vital.Vitality
|
|
import net.psforever.types.Vector3
|
|
import services.local.support._
|
|
import services.vehicle.{VehicleAction, VehicleServiceMessage}
|
|
import services.{GenericEventBus, RemoverActor, Service}
|
|
|
|
import scala.concurrent.duration._
|
|
import net.psforever.objects.serverobject.hackable.Hackable
|
|
import net.psforever.objects.vehicles.{Utility, UtilityType}
|
|
import services.support.SupportActor
|
|
|
|
import scala.concurrent.duration.Duration
|
|
|
|
class LocalService(zone : Zone) extends Actor {
|
|
private val doorCloser = context.actorOf(Props[DoorCloseActor], s"${zone.Id}-local-door-closer")
|
|
private val hackClearer = context.actorOf(Props[HackClearActor], s"${zone.Id}-local-hack-clearer")
|
|
private val hackCapturer = context.actorOf(Props[HackCaptureActor], s"${zone.Id}-local-hack-capturer")
|
|
private val engineer = context.actorOf(Props[DeployableRemover], s"${zone.Id}-deployable-remover-agent")
|
|
private val teleportDeployment : ActorRef = context.actorOf(Props[RouterTelepadActivation], s"${zone.Id}-telepad-activate-agent")
|
|
private [this] val log = org.log4s.getLogger
|
|
|
|
override def preStart = {
|
|
log.trace(s"Awaiting ${zone.Id} local events ...")
|
|
}
|
|
|
|
val LocalEvents = new GenericEventBus[LocalServiceResponse]
|
|
|
|
def receive : Receive = {
|
|
case Service.Join(channel) =>
|
|
val path = s"/$channel/Local"
|
|
val who = sender()
|
|
log.info(s"$who has joined $path")
|
|
LocalEvents.subscribe(who, path)
|
|
|
|
case Service.Leave(None) =>
|
|
LocalEvents.unsubscribe(sender())
|
|
|
|
case Service.Leave(Some(channel)) =>
|
|
val path = s"/$channel/Local"
|
|
val who = sender()
|
|
log.info(s"$who has left $path")
|
|
LocalEvents.unsubscribe(who, path)
|
|
|
|
case Service.LeaveAll() =>
|
|
LocalEvents.unsubscribe(sender())
|
|
|
|
case LocalServiceMessage(forChannel, action) =>
|
|
action match {
|
|
case LocalAction.AlertDestroyDeployable(_, obj) =>
|
|
LocalEvents.publish(
|
|
LocalServiceResponse(s"/$forChannel/Local", Service.defaultPlayerGUID, LocalResponse.AlertDestroyDeployable(obj))
|
|
)
|
|
case LocalAction.DeployableMapIcon(player_guid, behavior, deployInfo) =>
|
|
LocalEvents.publish(
|
|
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.DeployableMapIcon(behavior, deployInfo))
|
|
)
|
|
case LocalAction.DoorOpens(player_guid, _, door) =>
|
|
doorCloser ! DoorCloseActor.DoorIsOpen(door, zone)
|
|
LocalEvents.publish(
|
|
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.DoorOpens(door.GUID))
|
|
)
|
|
case LocalAction.DoorCloses(player_guid, door_guid) =>
|
|
LocalEvents.publish(
|
|
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.DoorCloses(door_guid))
|
|
)
|
|
case LocalAction.HackClear(player_guid, target, unk1, unk2) =>
|
|
LocalEvents.publish(
|
|
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.HackClear(target.GUID, unk1, unk2))
|
|
)
|
|
case LocalAction.HackTemporarily(player_guid, _, target, unk1, duration, unk2) =>
|
|
hackClearer ! HackClearActor.ObjectIsHacked(target, zone, unk1, unk2, duration)
|
|
LocalEvents.publish(
|
|
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.HackObject(target.GUID, unk1, unk2))
|
|
)
|
|
case LocalAction.ClearTemporaryHack(_, target) =>
|
|
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])
|
|
hackableAmenities.foreach(amenity =>
|
|
if(amenity.HackedBy.isDefined) { hackClearer ! HackClearActor.ObjectIsResecured(amenity) }
|
|
)
|
|
if(isResecured){
|
|
hackCapturer ! HackCaptureActor.ClearHack(target, zone)
|
|
} else {
|
|
target.Definition match {
|
|
case GlobalDefinitions.capture_terminal =>
|
|
// Base CC
|
|
hackCapturer ! HackCaptureActor.ObjectIsHacked(target, zone, unk1, unk2, duration = 15 minutes)
|
|
case GlobalDefinitions.secondary_capture =>
|
|
// Tower CC
|
|
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))
|
|
)
|
|
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))
|
|
)
|
|
case LocalAction.SetEmpire(object_guid, empire) =>
|
|
LocalEvents.publish(
|
|
LocalServiceResponse(s"/$forChannel/Local", Service.defaultPlayerGUID, LocalResponse.SetEmpire(object_guid, empire))
|
|
)
|
|
case LocalAction.ToggleTeleportSystem(player_guid, router, system_plan) =>
|
|
LocalEvents.publish(
|
|
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.ToggleTeleportSystem(router, system_plan))
|
|
)
|
|
case LocalAction.TriggerEffect(player_guid, effect, target) =>
|
|
LocalEvents.publish(
|
|
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.TriggerEffect(target, effect))
|
|
)
|
|
case LocalAction.TriggerEffectLocation(player_guid, effect, pos, orient) =>
|
|
LocalEvents.publish(
|
|
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.TriggerEffect(PlanetSideGUID(0), effect, None, Some(TriggeredEffectLocation(pos, orient))))
|
|
)
|
|
case LocalAction.TriggerEffectInfo(player_guid, effect, target, unk1, unk2) =>
|
|
LocalEvents.publish(
|
|
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.TriggerEffect(target, effect, Some(TriggeredEffect(unk1, unk2))))
|
|
)
|
|
case LocalAction.TriggerSound(player_guid, sound, pos, unk, volume) =>
|
|
LocalEvents.publish(
|
|
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.TriggerSound(sound, pos, unk, volume))
|
|
)
|
|
case _ => ;
|
|
}
|
|
|
|
//response from DoorCloseActor
|
|
case DoorCloseActor.CloseTheDoor(door_guid, _) =>
|
|
LocalEvents.publish(
|
|
LocalServiceResponse(s"/${zone.Id}/Local", Service.defaultPlayerGUID, LocalResponse.DoorCloses(door_guid))
|
|
)
|
|
|
|
//response from HackClearActor
|
|
case HackClearActor.ClearTheHack(target_guid, _, unk1, unk2) =>
|
|
log.warn(s"Clearing hack for $target_guid")
|
|
LocalEvents.publish(
|
|
LocalServiceResponse(s"/${zone.Id}/Local", Service.defaultPlayerGUID, LocalResponse.HackClear(target_guid, unk1, unk2))
|
|
)
|
|
|
|
//message from ProximityTerminalControl
|
|
case Terminal.StartProximityEffect(terminal) =>
|
|
LocalEvents.publish(
|
|
LocalServiceResponse(s"/${zone.Id}/Local", PlanetSideGUID(0), LocalResponse.ProximityTerminalEffect(terminal.GUID, true))
|
|
)
|
|
case Terminal.StopProximityEffect(terminal) =>
|
|
LocalEvents.publish(
|
|
LocalServiceResponse(s"/${zone.Id}/Local", PlanetSideGUID(0), LocalResponse.ProximityTerminalEffect(terminal.GUID, false))
|
|
)
|
|
|
|
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) {
|
|
log.info(s"Setting base ${building.GUID} / MapId: ${building.MapId} as owned by $hackedByFaction")
|
|
|
|
building.Faction = hackedByFaction
|
|
self ! LocalServiceMessage(zone.Id, LocalAction.SetEmpire(building.GUID, hackedByFaction))
|
|
} else {
|
|
log.info("Base hack completed, but base was out of NTU.")
|
|
}
|
|
|
|
// Reset CC back to normal operation
|
|
self ! LocalServiceMessage(zone.Id, LocalAction.HackCaptureTerminal(PlanetSideGUID(-1), zone, terminal, 0, 8L, isResecured = true))
|
|
//todo: this appears to be the way to reset the base warning lights after the hack finishes but it doesn't seem to work. The attribute above is a workaround
|
|
self ! HackClearActor.ClearTheHack(building.GUID, zone.Id, 3212836864L, 8L)
|
|
|
|
//message to Engineer
|
|
case LocalServiceMessage.Deployables(msg) =>
|
|
engineer forward msg
|
|
|
|
//message(s) from Engineer
|
|
case msg @ DeployableRemover.EliminateDeployable(obj : TurretDeployable, guid, pos, _) =>
|
|
val seats = obj.Seats.values
|
|
if(seats.count(_.isOccupied) > 0) {
|
|
val wasKickedByDriver = false //TODO yeah, I don't know
|
|
seats.foreach(seat => {
|
|
seat.Occupant match {
|
|
case Some(tplayer) =>
|
|
seat.Occupant = None
|
|
tplayer.VehicleSeated = None
|
|
zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.KickPassenger(tplayer.GUID, 4, wasKickedByDriver, obj.GUID))
|
|
case None => ;
|
|
}
|
|
})
|
|
import scala.concurrent.ExecutionContext.Implicits.global
|
|
context.system.scheduler.scheduleOnce(Duration.create(2, "seconds"), self, msg)
|
|
}
|
|
else {
|
|
EliminateDeployable(obj, guid, pos)
|
|
}
|
|
|
|
case DeployableRemover.EliminateDeployable(obj : BoomerDeployable, guid, pos, _) =>
|
|
EliminateDeployable(obj, guid, pos)
|
|
obj.Trigger match {
|
|
case Some(trigger) =>
|
|
log.warn(s"LocalService: deconstructing boomer in ${zone.Id}, but trigger@${trigger.GUID.guid} still exists")
|
|
case _ => ;
|
|
}
|
|
|
|
case DeployableRemover.EliminateDeployable(obj : TelepadDeployable, guid, pos, _) =>
|
|
obj.Active = false
|
|
//ClearSpecific will also remove objects that do not have GUID's; we may not have a GUID at this time
|
|
teleportDeployment ! SupportActor.ClearSpecific(List(obj), zone)
|
|
EliminateDeployable(obj, guid, pos)
|
|
|
|
case DeployableRemover.EliminateDeployable(obj, guid, pos, _) =>
|
|
EliminateDeployable(obj, guid, pos)
|
|
|
|
case DeployableRemover.DeleteTrigger(trigger_guid, _) =>
|
|
LocalEvents.publish(
|
|
LocalServiceResponse(s"/${zone.Id}/Local", Service.defaultPlayerGUID, LocalResponse.ObjectDelete(trigger_guid, 0))
|
|
)
|
|
|
|
//message to RouterTelepadActivation
|
|
case LocalServiceMessage.Telepads(msg) =>
|
|
teleportDeployment forward msg
|
|
|
|
//from RouterTelepadActivation
|
|
case RouterTelepadActivation.ActivateTeleportSystem(telepad, _) =>
|
|
val remoteTelepad = telepad.asInstanceOf[TelepadDeployable]
|
|
remoteTelepad.Active = true
|
|
zone.GUID(remoteTelepad.Router) match {
|
|
case Some(router : Vehicle) =>
|
|
router.Utility(UtilityType.internal_router_telepad_deployable) match {
|
|
case Some(internalTelepad : Utility.InternalTelepad) =>
|
|
//get rid of previous linked remote telepad (if any)
|
|
zone.GUID(internalTelepad.Telepad) match {
|
|
case Some(old : TelepadDeployable) =>
|
|
log.info(s"ActivateTeleportSystem: old remote telepad@${old.GUID.guid} linked to internal@${internalTelepad.GUID.guid} will be deconstructed")
|
|
old.Active = false
|
|
engineer ! SupportActor.ClearSpecific(List(old), zone)
|
|
engineer ! RemoverActor.AddTask(old, zone, Some(0 seconds))
|
|
case _ => ;
|
|
}
|
|
internalTelepad.Telepad = remoteTelepad.GUID
|
|
if(internalTelepad.Active) {
|
|
log.info(s"ActivateTeleportSystem: fully deployed router@${router.GUID.guid} in ${zone.Id} will link internal@${internalTelepad.GUID.guid} and remote@${remoteTelepad.GUID.guid}")
|
|
LocalEvents.publish(
|
|
LocalServiceResponse(s"/${zone.Id}/Local", Service.defaultPlayerGUID, LocalResponse.ToggleTeleportSystem(router, Some((internalTelepad, remoteTelepad))))
|
|
)
|
|
}
|
|
else {
|
|
remoteTelepad.OwnerName match {
|
|
case Some(name) =>
|
|
LocalEvents.publish(
|
|
LocalServiceResponse(s"/$name/Local", Service.defaultPlayerGUID, LocalResponse.RouterTelepadMessage("@Teleport_NotDeployed"))
|
|
)
|
|
case None => ;
|
|
}
|
|
}
|
|
case _ =>
|
|
log.error(s"ActivateTeleportSystem: vehicle@${router.GUID.guid} in ${zone.Id} is not a router?")
|
|
RouterTelepadError(remoteTelepad, "@Telepad_NoDeploy_RouterLost")
|
|
}
|
|
case Some(o) =>
|
|
log.error(s"ActivateTeleportSystem: ${o.Definition.Name}@${o.GUID.guid} in ${zone.Id} is not a router")
|
|
RouterTelepadError(remoteTelepad, "@Telepad_NoDeploy_RouterLost")
|
|
case None =>
|
|
RouterTelepadError(remoteTelepad, "@Telepad_NoDeploy_RouterLost")
|
|
}
|
|
|
|
//synchronized damage calculations
|
|
case Vitality.DamageOn(target : Deployable, func) =>
|
|
func(target)
|
|
sender ! Vitality.DamageResolution(target)
|
|
|
|
case msg =>
|
|
log.warn(s"Unhandled message $msg from $sender")
|
|
}
|
|
|
|
/**
|
|
* na
|
|
* @param telepad na
|
|
* @param msg na
|
|
*/
|
|
def RouterTelepadError(telepad : TelepadDeployable, msg : String) : Unit = {
|
|
telepad.OwnerName match {
|
|
case Some(name) =>
|
|
LocalEvents.publish(
|
|
LocalServiceResponse(s"/$name/Local", Service.defaultPlayerGUID, LocalResponse.RouterTelepadMessage(msg))
|
|
)
|
|
case None => ;
|
|
}
|
|
engineer ! SupportActor.ClearSpecific(List(telepad), zone)
|
|
engineer ! RemoverActor.AddTask(telepad, zone, Some(0 seconds))
|
|
}
|
|
|
|
/**
|
|
* Common behavior for distributing information about a deployable's destruction or deconstruction.<br>
|
|
* <br>
|
|
* The primary distribution task instructs all clients to eliminate the target deployable.
|
|
* This is a cosmetic exercise as the deployable should already be unregistered from its zone and
|
|
* functionally removed from its zone's list of deployable objects by external operations.
|
|
* The other distribution is a targeted message sent to the former owner of the deployable
|
|
* if he still exists on the server
|
|
* to clean up any leftover ownership-specific knowledge about the deployable.
|
|
* @see `DeployableRemover`
|
|
* @param obj the deployable object
|
|
* @param guid the deployable objects globally unique identifier;
|
|
* may be a former identifier
|
|
* @param position the deployable's position
|
|
*/
|
|
def EliminateDeployable(obj : PlanetSideGameObject with Deployable, guid : PlanetSideGUID, position : Vector3) : Unit = {
|
|
LocalEvents.publish(
|
|
LocalServiceResponse(s"/${zone.Id}/Local", Service.defaultPlayerGUID, LocalResponse.EliminateDeployable(obj, guid, position))
|
|
)
|
|
obj.OwnerName match {
|
|
case Some(name) =>
|
|
LocalEvents.publish(
|
|
LocalServiceResponse(s"/$name/Local", Service.defaultPlayerGUID, LocalResponse.AlertDestroyDeployable(obj))
|
|
)
|
|
case None => ;
|
|
}
|
|
}
|
|
}
|