CaptureFlagManager + Supporting changes

This commit is contained in:
Mazo 2021-01-27 22:11:13 +00:00
parent f7caf2f880
commit c300bce0ff
9 changed files with 644 additions and 69 deletions

View file

@ -1,23 +1,11 @@
package net.psforever.actors.session
import akka.actor.typed
import akka.actor.typed.receptionist.Receptionist
import akka.actor.typed.scaladsl.adapter._
import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware}
import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware, typed}
import akka.pattern.ask
import akka.util.Timeout
import java.util.concurrent.TimeUnit
import net.psforever.actors.net.MiddlewareActor
import net.psforever.services.ServiceManager.Lookup
import net.psforever.objects.locker.LockerContainer
import org.log4s.MDC
import scala.collection.mutable
import scala.concurrent.{Await, Future}
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.Success
import net.psforever.login.WorldSession._
import net.psforever.objects._
import net.psforever.objects.avatar._
@ -29,7 +17,7 @@ import net.psforever.objects.entity.{SimpleWorldEntity, WorldEntity}
import net.psforever.objects.equipment._
import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver}
import net.psforever.objects.inventory.{Container, InventoryItem}
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.locker.LockerContainer
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.containable.Containable
import net.psforever.objects.serverobject.damage.Damageable
@ -37,6 +25,7 @@ import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.generator.Generator
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.llu.CaptureFlag
import net.psforever.objects.serverobject.locks.IFFLock
import net.psforever.objects.serverobject.mblocker.Locker
import net.psforever.objects.serverobject.mount.Mountable
@ -44,14 +33,15 @@ import net.psforever.objects.serverobject.pad.VehicleSpawnPad
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
import net.psforever.objects.serverobject.structures.{Amenity, Building, StructureType, WarpGate}
import net.psforever.objects.serverobject.terminals._
import net.psforever.objects.serverobject.terminals.capture.{CaptureTerminal, CaptureTerminals}
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminal
import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalMech
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.serverobject.turret.{FacilityTurret, WeaponTurret}
import net.psforever.objects.serverobject.zipline.ZipLinePath
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.teamwork.Squad
import net.psforever.objects.vehicles._
import net.psforever.objects.vehicles.Utility.InternalTelepad
import net.psforever.objects.vehicles._
import net.psforever.objects.vital._
import net.psforever.objects.vital.base._
import net.psforever.objects.vital.interaction.DamageInteraction
@ -59,14 +49,14 @@ import net.psforever.objects.vital.projectile.ProjectileReason
import net.psforever.objects.zones.{Zone, ZoneHotSpotProjector, Zoning}
import net.psforever.packet._
import net.psforever.packet.game.PlanetsideAttributeEnum.PlanetsideAttributeEnum
import net.psforever.packet.game.{HotSpotInfo => PacketHotSpotInfo, _}
import net.psforever.packet.game.objectcreate._
import net.psforever.services.ServiceManager.LookupResult
import net.psforever.packet.game.{HotSpotInfo => PacketHotSpotInfo, _}
import net.psforever.services.ServiceManager.{Lookup, LookupResult}
import net.psforever.services.account.{AccountPersistenceService, PlayerToken, ReceiveAccountData, RetrieveAccountData}
import net.psforever.services.avatar.{AvatarAction, AvatarResponse, AvatarServiceMessage, AvatarServiceResponse}
import net.psforever.services.chat.ChatService
import net.psforever.services.galaxy.{GalaxyAction, GalaxyResponse, GalaxyServiceMessage, GalaxyServiceResponse}
import net.psforever.services.local.support.{HackCaptureActor, RouterTelepadActivation}
import net.psforever.services.local.support.{CaptureFlagManager, HackCaptureActor, RouterTelepadActivation}
import net.psforever.services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse}
import net.psforever.services.properties.PropertyOverrideManager
import net.psforever.services.support.SupportActor
@ -76,6 +66,13 @@ import net.psforever.services.{InterstellarClusterService, RemoverActor, Service
import net.psforever.types._
import net.psforever.util.{Config, DefinitionUtil}
import net.psforever.zones.Zones
import org.log4s.MDC
import scala.collection.mutable
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
import scala.util.Success
object SessionActor {
sealed trait Command
@ -204,6 +201,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
var setupAvatarFunc: () => Unit = AvatarCreate
var setCurrentAvatarFunc: Player => Unit = SetCurrentAvatarNormally
var persist: () => Unit = NoPersistence
var specialItemSlotGuid : Option[PlanetSideGUID] = None // If a special item (e.g. LLU) has been attached to the player the GUID should be stored here, or cleared when dropped, since the drop hotkey doesn't send the GUID of the object to be dropped.
/**
* used during zone transfers to maintain reference to seated vehicle (which does not yet exist in the new zone)
@ -556,6 +554,9 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case GalaxyResponse.MapUpdate(msg) =>
sendResponse(msg)
case GalaxyResponse.FlagMapUpdate(msg) =>
sendResponse(msg)
case GalaxyResponse.TransferPassenger(temp_channel, vehicle, vehicle_to_delete, manifest) =>
(manifest.passengers.find { case (name, _) => player.Name.equals(name) } match {
case Some((name, index)) if vehicle.Seats(index).Occupant.isEmpty =>
@ -971,6 +972,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
CancelZoningProcess()
PlayerActionsToCancel()
CancelAllProximityUnits()
DropSpecialSlotItem()
continent.Population ! Zone.Population.Release(avatar)
response match {
case Some((zone, spawnPoint)) =>
@ -1828,25 +1830,32 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
DropEquipmentFromInventory(player)(item)
case None => ;
}
DropSpecialSlotItem()
ToggleMaxSpecialState(enable = false)
keepAliveFunc = NormalKeepAlive
zoningStatus = Zoning.Status.None
deadState = DeadState.Dead
continent.GUID(mount) match {
case Some(obj: Vehicle) =>
TotalDriverVehicleControl(obj)
UnaccessContainer(obj)
case _ => ;
}
PlayerActionsToCancel()
CancelAllProximityUnits()
CancelZoningProcessWithDescriptiveReason("cancel")
if (shotsWhileDead > 0) {
log.warn(
s"KillPlayer/SHOTS_WHILE_DEAD: client of ${avatar.name} fired $shotsWhileDead rounds while character was dead on server"
)
shotsWhileDead = 0
}
reviveTimer.cancel()
if (player.death_by == 0) {
reviveTimer = context.system.scheduler.scheduleOnce(respawnTimer) {
@ -2150,6 +2159,28 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
}
}
def DropSpecialSlotItem(): Unit = {
specialItemSlotGuid match {
case Some(guid: PlanetSideGUID) =>
specialItemSlotGuid = None
continent.GUID(guid) match {
case Some(llu: CaptureFlag) =>
llu.Carrier match {
case Some(carrier: Player) if carrier.GUID == player.GUID =>
continent.LocalEvents ! CaptureFlagManager.DropFlag(llu)
case Some(carrier: Player) =>
log.warn(s"${player.toString} tried to drop LLU, but it is currently held by ${carrier.toString}")
case None =>
log.warn(s"${player.toString} tried to drop LLU, but nobody is holding it.")
}
case _ =>
log.warn(s"${player.toString} Tried to drop a special item that wasn't recognized. GUID: $guid")
}
case _ => ; // Nothing to drop, do nothing.
}
}
/**
* Enforce constraints on bulk purchases as determined by a given player's previous purchase times and hard acquisition delays.
* Intended to assist in sanitizing loadout information from the perspectvie of the player, or target owner.
@ -2298,6 +2329,40 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case LocalResponse.SendPlanetsideAttributeMessage(target_guid, attribute_number, attribute_value) =>
SendPlanetsideAttributeMessage(target_guid, attribute_number, attribute_value)
case LocalResponse.SendGenericObjectActionMessage(target_guid, action_number) =>
sendResponse(GenericObjectActionMessage(target_guid, action_number))
case LocalResponse.SendGenericActionMessage(action_number) =>
sendResponse(GenericActionMessage(action_number))
case LocalResponse.SendChatMsg(msg) =>
sendResponse(msg)
case LocalResponse.SendPacket(packet) =>
sendResponse(packet)
case LocalResponse.LluSpawned(llu) =>
// Create LLU on client
sendResponse(ObjectCreateMessage(
llu.Definition.ObjectId,
llu.GUID,
llu.Definition.Packet.ConstructorData(llu).get
))
sendResponse(TriggerSoundMessage(TriggeredSound.LLUMaterialize, llu.Position, unk = 20, 0.8000001f))
case LocalResponse.LluDespawned(llu) =>
sendResponse(TriggerSoundMessage(TriggeredSound.LLUDeconstruct, llu.Position, unk = 20, 0.8000001f))
sendResponse(ObjectDeleteMessage(llu.GUID, 0))
// If the player was holding the LLU, remove it from their tracked special item slot
specialItemSlotGuid match {
case Some(guid) =>
if (guid == llu.GUID) {
specialItemSlotGuid = None
}
case _ => ;
}
case LocalResponse.ObjectDelete(object_guid, unk) =>
if (tplayer_guid != guid) {
sendResponse(ObjectDeleteMessage(object_guid, unk))
@ -4076,7 +4141,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case None =>
log.warn(s"DropItem: ${player.Name} wanted to drop a $anItem, but it wasn't at hand")
}
case Some(obj) => //TODO LLU
case Some(obj) =>
log.warn(s"DropItem: ${player.Name} wanted to drop a $obj, but that isn't possible")
case None =>
sendResponse(ObjectDeleteMessage(item_guid, 0)) //this is fine; item doesn't exist to the server anyway
@ -4662,7 +4727,16 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case Some(item) =>
CancelZoningProcessWithDescriptiveReason("cancel_use")
captureTerminal.Actor ! CommonMessages.Use(player, Some(item))
case _ => ;
case _ if specialItemSlotGuid.nonEmpty =>
continent.GUID(specialItemSlotGuid) match {
case Some(llu: CaptureFlag) =>
if (llu.Target.GUID == captureTerminal.Owner.GUID) {
continent.LocalEvents ! LocalServiceMessage(continent.id, LocalAction.LluCaptured(llu))
} else {
log.info(s"LLU target is not this base. Target GUID: ${llu.Target.GUID} This base: ${captureTerminal.Owner.GUID}")
}
case _ => log.warn("Item in specialItemSlotGuid is not registered with continent or is not a LLU")
}
}
case Some(obj: FacilityTurret) =>
@ -4884,6 +4958,19 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case None => ;
}
case Some(obj: CaptureFlag) =>
// LLU can normally only be picked up the faction that owns it
if (specialItemSlotGuid.isEmpty) {
if(obj.Faction == player.Faction) {
specialItemSlotGuid = Some(obj.GUID)
continent.LocalEvents ! CaptureFlagManager.PickupFlag(obj, player)
} else {
log.warn(s"Player ${player.toString} tried to pick up LLU ${obj.GUID} - ${obj.Faction} that doesn't belong to their faction")
}
} else if(specialItemSlotGuid.get != obj.GUID) { // Ignore duplicate pickup requests
log.warn(s"Player ${player.toString} tried to pick up LLU ${obj.GUID} - ${obj.Faction} but their special slot already contains $specialItemSlotGuid")
}
case Some(obj) =>
CancelZoningProcessWithDescriptiveReason("cancel_use")
log.warn(s"UseItem: don't know how to handle $obj")
@ -4989,6 +5076,9 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
log.info(s"${player.Name} is back")
player.AwayFromKeyboard = false
}
if (action == GenericActionEnum.DropSpecialItem.id) {
DropSpecialSlotItem()
}
if (action == 15) { //max deployment
log.info(s"GenericObject: $player is anchored")
player.UsingSpecial = SpecialExoSuitDefinition.Mode.Anchored
@ -6657,11 +6747,13 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
//sync model access state
sendResponse(PlanetsideAttributeMessage(amenityId, 50, 0))
sendResponse(PlanetsideAttributeMessage(amenityId, 51, 0))
//sync damageable, if
val health = amenity.Health
if (amenity.Definition.Damageable && health < amenity.MaxHealth) {
sendResponse(PlanetsideAttributeMessage(amenityId, 0, health))
}
//sync special object type cases
amenity match {
case silo: ResourceSilo =>
@ -6675,19 +6767,30 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case door: Door if door.isOpen =>
sendResponse(GenericObjectStateMsg(amenityId, 16))
case _ => ;
}
//sync hack state
amenity match {
case obj: Hackable if obj.HackedBy.nonEmpty =>
//sync hack state
amenity.Definition match {
case GlobalDefinitions.capture_terminal =>
SendPlanetsideAttributeMessage(
amenity.GUID,
PlanetsideAttributeEnum.ControlConsoleHackUpdate,
HackCaptureActor.GetHackUpdateAttributeValue(amenity.asInstanceOf[CaptureTerminal], isResecured = false))
case _ =>
HackObject(amenity.GUID, 1114636288L, 8L) //generic hackable object
case GlobalDefinitions.capture_terminal =>
SendPlanetsideAttributeMessage(
amenity.GUID,
PlanetsideAttributeEnum.ControlConsoleHackUpdate,
HackCaptureActor.GetHackUpdateAttributeValue(amenity.asInstanceOf[CaptureTerminal], isResecured = false))
case _ =>
HackObject(amenity.GUID, 1114636288L, 8L) //generic hackable object
}
// sync capture flags
case llu: CaptureFlag =>
// Create LLU
sendResponse(ObjectCreateMessage(
llu.Definition.ObjectId,
llu.GUID,
llu.Definition.Packet.ConstructorData(llu).get
))
// Attach it to a player if it has a carrier
if (llu.Carrier.nonEmpty) {
continent.LocalEvents ! LocalServiceMessage(continent.id, LocalAction.SendPacket(ObjectAttachMessage(llu.Carrier.get.GUID, llu.GUID, 252)))
}
case _ => ;
}

View file

@ -3,7 +3,7 @@ package net.psforever.services.galaxy
import akka.actor.Actor
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.BuildingInfoUpdateMessage
import net.psforever.packet.game.{BuildingInfoUpdateMessage, FlagInfo}
import net.psforever.services.{GenericEventBus, Service}
class GalaxyService extends Actor {
@ -53,6 +53,11 @@ class GalaxyService extends Actor {
GalaxyServiceResponse(s"/Galaxy", GalaxyResponse.MapUpdate(msg))
)
case GalaxyAction.FlagMapUpdate(msg) =>
GalaxyEvents.publish(
GalaxyServiceResponse(s"/Galaxy", GalaxyResponse.FlagMapUpdate(msg))
)
case GalaxyAction.TransferPassenger(player_guid, temp_channel, vehicle, vehicle_to_delete, manifest) =>
GalaxyEvents.publish(
GalaxyServiceResponse(

View file

@ -3,7 +3,7 @@ package net.psforever.services.galaxy
import net.psforever.objects.Vehicle
import net.psforever.objects.vehicles.VehicleManifest
import net.psforever.packet.game.BuildingInfoUpdateMessage
import net.psforever.packet.game.{BuildingInfoUpdateMessage, CaptureFlagUpdateMessage, FlagInfo}
import net.psforever.types.PlanetSideGUID
final case class GalaxyServiceMessage(forChannel: String, actionMessage: GalaxyAction.Action)
@ -16,6 +16,7 @@ object GalaxyAction {
trait Action
final case class MapUpdate(msg: BuildingInfoUpdateMessage) extends Action
final case class FlagMapUpdate(msg: CaptureFlagUpdateMessage) extends Action
final case class TransferPassenger(
player_guid: PlanetSideGUID,

View file

@ -4,7 +4,7 @@ package net.psforever.services.galaxy
import net.psforever.objects.Vehicle
import net.psforever.objects.vehicles.VehicleManifest
import net.psforever.objects.zones.HotSpotInfo
import net.psforever.packet.game.BuildingInfoUpdateMessage
import net.psforever.packet.game.{BuildingInfoUpdateMessage, CaptureFlagUpdateMessage, FlagInfo}
import net.psforever.types.PlanetSideGUID
import net.psforever.services.GenericEventBusMsg
@ -16,6 +16,8 @@ object GalaxyResponse {
final case class HotSpotUpdate(zone_id: Int, priority: Int, host_spot_info: List[HotSpotInfo]) extends Response
final case class MapUpdate(msg: BuildingInfoUpdateMessage) extends Response
final case class FlagMapUpdate(msg: CaptureFlagUpdateMessage) extends Response
final case class TransferPassenger(
temp_channel: String,

View file

@ -2,34 +2,26 @@
package net.psforever.services.local
import akka.actor.{Actor, ActorRef, Props}
import akka.pattern.Patterns
import akka.util.Timeout
import net.psforever.actors.zone.{BuildingActor, ZoneActor}
import net.psforever.objects.ce.Deployable
import net.psforever.objects.serverobject.structures.{Amenity, Building}
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.zones.Zone
import net.psforever.objects._
import net.psforever.packet.game.{PlanetsideAttributeEnum, TriggeredEffect, TriggeredEffectLocation}
import net.psforever.objects.ce.Deployable
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.vehicles.{Utility, UtilityType}
import net.psforever.objects.vital.Vitality
import net.psforever.types.{PlanetSideGUID, Vector3}
import net.psforever.services.local.support._
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{TriggeredEffect, TriggeredEffectLocation}
import net.psforever.services.local.support.{CaptureFlagManager, _}
import net.psforever.services.support.SupportActor
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import net.psforever.services.{GenericEventBus, RemoverActor, Service}
import net.psforever.types.{PlanetSideGUID, Vector3}
import scala.concurrent.duration._
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.vehicles.{Utility, UtilityType}
import net.psforever.services.support.SupportActor
import java.util.concurrent.TimeUnit
import scala.concurrent.Await
import scala.concurrent.duration.Duration
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 hackCapturer = context.actorOf(Props(classOf[HackCaptureActor], zone.tasks), s"${zone.id}-local-hack-capturer")
private val captureFlagManager = context.actorOf(Props(classOf[CaptureFlagManager], zone.tasks, zone), s"${zone.id}-local-capture-flag-manager")
private val engineer = context.actorOf(Props(classOf[DeployableRemover], zone.tasks), s"${zone.id}-deployable-remover-agent")
private val teleportDeployment: ActorRef =
context.actorOf(Props[RouterTelepadActivation](), s"${zone.id}-telepad-activate-agent")
@ -102,10 +94,43 @@ class LocalService(zone: Zone) extends Actor {
)
case LocalAction.ClearTemporaryHack(_, target) =>
hackClearer ! HackClearActor.ObjectIsResecured(target)
case LocalAction.ResecureCaptureTerminal(target) =>
hackCapturer ! HackCaptureActor.ResecureCaptureTerminal(target, zone)
case LocalAction.StartCaptureTerminalHack(target) =>
hackCapturer ! HackCaptureActor.StartCaptureTerminalHack(target, zone, 0, 8L)
case LocalAction.LluCaptured(llu) =>
hackCapturer ! HackCaptureActor.FlagCaptured(llu)
case LocalAction.LluSpawned(player_guid, llu) =>
// Forward to all clients to create object locally
LocalEvents.publish(
LocalServiceResponse(
s"/$forChannel/Local",
player_guid,
LocalResponse.LluSpawned(llu)
)
)
case LocalAction.LluDespawned(player_guid, llu) =>
// Forward to all clients to destroy object locally
LocalEvents.publish(
LocalServiceResponse(
s"/$forChannel/Local",
player_guid,
LocalResponse.LluDespawned(llu)
)
)
case LocalAction.SendPacket(packet) =>
LocalEvents.publish(
LocalServiceResponse(
s"/$forChannel/Local",
PlanetSideGUID(-1),
LocalResponse.SendPacket(packet)
)
)
case LocalAction.SendPlanetsideAttributeMessage(player_guid, target_guid, attribute_number, attribute_value) =>
LocalEvents.publish(
LocalServiceResponse(
@ -114,6 +139,34 @@ class LocalService(zone: Zone) extends Actor {
LocalResponse.SendPlanetsideAttributeMessage(target_guid, attribute_number, attribute_value)
)
)
case LocalAction.SendGenericObjectActionMessage(player_guid, target_guid, action_number) =>
LocalEvents.publish(
LocalServiceResponse(
s"/$forChannel/Local",
player_guid,
LocalResponse.SendGenericObjectActionMessage(target_guid, action_number)
)
)
case LocalAction.SendChatMsg(player_guid, msg) =>
LocalEvents.publish(
LocalServiceResponse(
s"/$forChannel/Local",
player_guid,
LocalResponse.SendChatMsg(msg)
)
)
case LocalAction.SendGenericActionMessage(player_guid, action_number) =>
LocalEvents.publish(
LocalServiceResponse(
s"/$forChannel/Local",
player_guid,
LocalResponse.SendGenericActionMessage(action_number)
)
)
case LocalAction.RouterTelepadTransport(player_guid, passenger_guid, src_guid, dest_guid) =>
LocalEvents.publish(
LocalServiceResponse(
@ -338,6 +391,16 @@ class LocalService(zone: Zone) extends Actor {
val cause = damage_func(target)
sender() ! Vitality.DamageResolution(target, cause)
// Forward all CaptureFlagManager messages
case msg @
(CaptureFlagManager.SpawnCaptureFlag(_, _, _)
| CaptureFlagManager.PickupFlag(_, _)
| CaptureFlagManager.DropFlag(_)
| CaptureFlagManager.Captured(_)
| CaptureFlagManager.Lost(_, _)
| CaptureFlagManager) =>
captureFlagManager.forward(msg)
case msg =>
log.warn(s"Unhandled message $msg from ${sender()}")
}

View file

@ -1,16 +1,20 @@
// Copyright (c) 2017 PSForever
package net.psforever.services.local
import net.psforever.objects.{PlanetSideGameObject, TelepadDeployable, Vehicle}
import net.psforever.objects.ce.Deployable
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.llu.CaptureFlag
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminal
import net.psforever.objects.vehicles.Utility
import net.psforever.objects.zones.Zone
import net.psforever.objects.{PlanetSideGameObject, TelepadDeployable, Vehicle}
import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.GenericActionEnum.GenericActionEnum
import net.psforever.packet.game.GenericObjectActionEnum.GenericObjectActionEnum
import net.psforever.packet.game.PlanetsideAttributeEnum.PlanetsideAttributeEnum
import net.psforever.packet.game.{DeployableInfo, DeploymentAction, TriggeredSound}
import net.psforever.packet.game.{ChatMsg, DeployableInfo, DeploymentAction, TriggeredSound}
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
final case class LocalServiceMessage(forChannel: String, actionMessage: LocalAction.Action)
@ -46,14 +50,37 @@ object LocalAction {
) extends Action
final case class ClearTemporaryHack(player_guid: PlanetSideGUID, target: PlanetSideServerObject with Hackable)
extends Action
final case class ResecureCaptureTerminal(target: CaptureTerminal) extends Action
final case class StartCaptureTerminalHack(target: CaptureTerminal) extends Action
final case class LluCaptured(llu: CaptureFlag) extends Action
final case class LluSpawned(player_guid: PlanetSideGUID, llu: CaptureFlag) extends Action
final case class LluDespawned(player_guid: PlanetSideGUID, llu: CaptureFlag) extends Action
final case class SendPacket(packet: PlanetSideGamePacket) extends Action
final case class SendPlanetsideAttributeMessage(
player_guid: PlanetSideGUID,
target: PlanetSideGUID,
attribute_number: PlanetsideAttributeEnum,
attribute_value: Long
) extends Action
final case class SendGenericObjectActionMessage(
player_guid: PlanetSideGUID,
target: PlanetSideGUID,
action_number: GenericObjectActionEnum
) extends Action
final case class SendChatMsg(
player_guid: PlanetSideGUID,
msg: ChatMsg
) extends Action
final case class SendGenericActionMessage(
player_guid: PlanetSideGUID,
action_number: GenericActionEnum
) extends Action
final case class RouterTelepadTransport(
player_guid: PlanetSideGUID,
passenger_guid: PlanetSideGUID,

View file

@ -3,8 +3,13 @@ package net.psforever.services.local
import net.psforever.objects.{PlanetSideGameObject, TelepadDeployable, Vehicle}
import net.psforever.objects.ce.Deployable
import net.psforever.objects.serverobject.llu.CaptureFlag
import net.psforever.objects.serverobject.structures.{AmenityOwner, Building}
import net.psforever.objects.serverobject.terminals.{ProximityUnit, Terminal}
import net.psforever.objects.vehicles.Utility
import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.GenericActionEnum.GenericActionEnum
import net.psforever.packet.game.GenericObjectActionEnum.GenericObjectActionEnum
import net.psforever.packet.game.PlanetsideAttributeEnum.PlanetsideAttributeEnum
import net.psforever.packet.game._
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
@ -31,8 +36,18 @@ object LocalResponse {
) extends Response
final case class SendHackMessageHackCleared(target_guid: PlanetSideGUID, unk1: Long, unk2: Long) extends Response
final case class HackObject(target_guid: PlanetSideGUID, unk1: Long, unk2: Long) extends Response
final case class SendPacket(packet: PlanetSideGamePacket) extends Response
final case class SendPlanetsideAttributeMessage(target_guid: PlanetSideGUID, attribute_number: PlanetsideAttributeEnum, attribute_value: Long)
extends Response
final case class SendGenericObjectActionMessage(target_guid: PlanetSideGUID, action_number: GenericObjectActionEnum)
extends Response
final case class SendChatMsg(msg: ChatMsg) extends Response
final case class SendGenericActionMessage(action_num: GenericActionEnum) extends Response
final case class LluSpawned(llu: CaptureFlag) extends Response
final case class LluDespawned(llu: CaptureFlag) extends Response
final case class ObjectDelete(item_guid: PlanetSideGUID, unk: Int) extends Response
final case class ProximityTerminalAction(terminal: Terminal with ProximityUnit, target: PlanetSideGameObject)
extends Response

View file

@ -0,0 +1,282 @@
package net.psforever.services.local.support
import akka.actor.{Actor, ActorRef, Cancellable}
import net.psforever.objects.{Default, Player}
import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver}
import net.psforever.objects.serverobject.llu.CaptureFlag
import net.psforever.objects.serverobject.structures.Building
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminal
import net.psforever.objects.zones.Zone
import net.psforever.packet.game._
import net.psforever.services.ServiceManager
import net.psforever.services.ServiceManager.{Lookup, LookupResult}
import net.psforever.services.galaxy.{GalaxyAction, GalaxyServiceMessage}
import net.psforever.services.local.support.CaptureFlagLostReasonEnum.CaptureFlagLostReasonEnum
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import net.psforever.types.{ChatMessageType, PlanetSideEmpire, PlanetSideGUID, Vector3}
import scala.concurrent.duration.DurationInt
import scala.util.Success
/**
* Responsible for handling capture flag related lifecycles
* @param taskResolver A reference to a zone's task resolver actor
*/
class CaptureFlagManager(val taskResolver: ActorRef, zone: Zone) extends Actor{
private[this] val log = org.log4s.getLogger(self.path.name)
var galaxyService: ActorRef = ActorRef.noSender
private var mapUpdateTick: Cancellable = Default.Cancellable
/** An internally tracked list of current flags, to avoid querying AmenityOwners each second for flag lookups */
private var flags: List[CaptureFlag] = Nil
private def TrackFlag(flag: CaptureFlag): Unit = {
flag.Owner.Amenities = flag
flags = flags :+ flag
if (mapUpdateTick.isCancelled) {
// Start sending map updates periodically
import scala.concurrent.ExecutionContext.Implicits.global
mapUpdateTick = context.system.scheduler.scheduleAtFixedRate(0 seconds, 1 second, self, CaptureFlagManager.MapUpdate())
}
}
private def UntrackFlag(flag: CaptureFlag): Unit = {
flag.Owner.RemoveAmenity(flag)
flags = flags.filterNot(x => x == flag)
if (flags.isEmpty) {
mapUpdateTick.cancel()
// Send one final map update to clear the last flag from the map
self ! CaptureFlagManager.MapUpdate()
}
}
val serviceManager = ServiceManager.serviceManager
serviceManager ! Lookup("galaxy")
def receive: Receive = {
case LookupResult("galaxy", endpoint) =>
galaxyService = endpoint
case CaptureFlagManager.MapUpdate() =>
val flagInfo = flags.map(flag =>
FlagInfo(
u1 = 0,
owner_map_id = flag.Owner.asInstanceOf[Building].MapId,
target_map_id = flag.Target.MapId,
x = flag.Position.x,
y = flag.Position.y,
u6 = 0,
isMonolithUnit = false
)
)
galaxyService ! GalaxyServiceMessage(GalaxyAction.FlagMapUpdate(CaptureFlagUpdateMessage(zone.Number, flagInfo)))
case CaptureFlagManager.SpawnCaptureFlag(capture_terminal, target, hackingFaction) =>
val zone = capture_terminal.Zone
val socket = capture_terminal.Owner.asInstanceOf[Building].GetFlagSocket.get
// Override CC message when looked at
zone.LocalEvents ! LocalServiceMessage(
zone.id,
LocalAction.SendGenericObjectActionMessage(
PlanetSideGUID(-1),
capture_terminal.GUID,
GenericObjectActionEnum.FlagSpawned
)
)
// Register LLU object create task and callback to create on clients
val flag: CaptureFlag = CaptureFlag.Constructor(
Vector3(socket.Position.x, socket.Position.y, socket.Position.z - 1),
socket.Orientation,
target,
socket.Owner,
hackingFaction
)
// Add the flag as an amenity and track it internally
TrackFlag(flag)
taskResolver ! CallBackForTask(
TaskResolver.GiveTask(GUIDTask.RegisterObjectTask(flag)(socket.Zone.GUID).task),
socket.Zone.LocalEvents,
LocalServiceMessage(
socket.Zone.id,
LocalAction.LluSpawned(PlanetSideGUID(-1), flag)
)
)
// Broadcast chat message for LLU spawn
val owner = flag.Owner.asInstanceOf[Building]
ChatBroadcast(flag.Zone, CaptureFlagChatMessageStrings.CTF_FlagSpawned(owner, flag.Target))
case CaptureFlagManager.Captured(flag: CaptureFlag) =>
// Trigger Install sound
flag.Zone.LocalEvents ! LocalServiceMessage(flag.Zone.id, LocalAction.TriggerSound(PlanetSideGUID(-1), TriggeredSound.LLUInstall, flag.Target.CaptureTerminal.get.Position, 20, 0.8000001f))
// Broadcast capture chat message
ChatBroadcast(flag.Zone, CaptureFlagChatMessageStrings.CTF_Success(flag.Carrier.get, flag.Owner.asInstanceOf[Building].Name))
// Despawn flag
HandleFlagDespawn(flag)
case CaptureFlagManager.Lost(flag: CaptureFlag, reason: CaptureFlagLostReasonEnum) =>
val message = reason match {
case CaptureFlagLostReasonEnum.Resecured =>
CaptureFlagChatMessageStrings.CTF_Failed_SourceResecured(flag.Owner.asInstanceOf[Building])
case CaptureFlagLostReasonEnum.TimedOut =>
CaptureFlagChatMessageStrings.CTF_Failed_TimedOut(flag.Owner.asInstanceOf[Building].Name, flag.Target)
}
ChatBroadcast(flag.Zone, message)
HandleFlagDespawn(flag)
case CaptureFlagManager.PickupFlag(flag: CaptureFlag, player: Player) =>
val continent = flag.Zone
flag.Carrier = Some(player)
continent.LocalEvents ! LocalServiceMessage(continent.id, LocalAction.SendPacket(ObjectAttachMessage(player.GUID, flag.GUID, 252)))
continent.LocalEvents ! LocalServiceMessage(continent.id, LocalAction.TriggerSound(PlanetSideGUID(-1), TriggeredSound.LLUPickup, player.Position, 15, volume = 0.8f))
ChatBroadcast(flag.Zone, CaptureFlagChatMessageStrings.CTF_FlagPickedUp(player, flag.Owner.asInstanceOf[Building].Name), fanfare = false)
case CaptureFlagManager.DropFlag(flag: CaptureFlag) =>
flag.Carrier match {
case Some(player: Player) =>
// Set the flag position to where the player is that dropped it
flag.Position = player.Position
// Remove attached player from flag
flag.Carrier = None
// Send drop packet
flag.Zone.LocalEvents ! LocalServiceMessage(flag.Zone.id, LocalAction.SendPacket(ObjectDetachMessage(player.GUID, flag.GUID, player.Position, 0, 0, 0)))
// Send dropped chat message
ChatBroadcast(flag.Zone, CaptureFlagChatMessageStrings.CTF_FlagDropped(player, flag.Owner.asInstanceOf[Building].Name), fanfare = false)
case None =>
log.warn("Tried to drop flag but flag has no carrier")
}
case _ =>
log.warn("Received unhandled message");
}
private def HandleFlagDespawn(flag: CaptureFlag): Unit = {
// Unregister LLU from clients,
flag.Zone.LocalEvents ! LocalServiceMessage(flag.Zone.id, LocalAction.LluDespawned(PlanetSideGUID(-1), flag))
// Then unregister it from the GUID pool
taskResolver ! TaskResolver.GiveTask(GUIDTask.UnregisterObjectTask(flag)(flag.Zone.GUID).task)
// Remove the flag as an amenity
UntrackFlag(flag)
}
private def ChatBroadcast(zone: Zone, message: String, fanfare: Boolean = true): Unit = {
val messageType: ChatMessageType = if (fanfare) {
ChatMessageType.UNK_223
} else {
ChatMessageType.UNK_229
}
zone.LocalEvents ! LocalServiceMessage(
zone.id,
LocalAction.SendChatMsg(
PlanetSideGUID(-1),
ChatMsg(messageType, wideContents = false, "", message, None)
)
)
}
// Todo: Duplicate from SessionActor. Make common.
def CallBackForTask(task: TaskResolver.GiveTask, sendTo: ActorRef, pass: Any): TaskResolver.GiveTask = {
TaskResolver.GiveTask(
new Task() {
private val localDesc = task.task.Description
private val destination = sendTo
private val passMsg = pass
override def Description: String = s"callback for tasking $localDesc"
def Execute(resolver: ActorRef): Unit = {
destination ! passMsg
resolver ! Success(this)
}
},
List(task)
)
}
}
object CaptureFlagManager {
final case class SpawnCaptureFlag(capture_terminal: CaptureTerminal, target: Building, hackingFaction: PlanetSideEmpire.Value)
final case class PickupFlag(flag: CaptureFlag, player: Player)
final case class DropFlag(flag: CaptureFlag)
final case class Captured(flag: CaptureFlag)
final case class Lost(flag: CaptureFlag, reason: CaptureFlagLostReasonEnum)
final case class MapUpdate()
}
object CaptureFlagChatMessageStrings {
/*
@CTF_Failed_TargetLost=%1's LLU target facility %2 was lost!\nHack canceled!
@CTF_Failed_FlagLost=The %1 lost %2's LLU!\nHack canceled!
@CTF_Warning_Carrier=%1 of the %2 has %3's LLU.\nIt must be taken to %4 within %5 minutes!
@CTF_Warning_NoCarrier=%1's LLU is in the field.\nThe %2 must take it to %3 within %4 minutes!
@CTF_Warning_Carrier1Min=%1 of the %2 has %3's LLU.\nIt must be taken to %4 within the next minute!
@CTF_Warning_NoCarrier1Min=%1's LLU is in the field.\nThe %2 must take it to %3 within the next minute!
*/
// @CTF_Success=%1 captured %2's LLU for the %3!
/** {player.Name} captured {owner_name}'s LLU for the {player.Faction}! */
def CTF_Success(player: Player, owner_name: String): String = s"@CTF_Success^${player.Name}~^@$owner_name~^@${GetFactionString(player.Faction)}~"
// @CTF_Failed_TimedOut=The %1 failed to deliver %2's LLU to %3 in time!\nHack canceled!
/** The {target.Faction} failed to deliver {owner_name}'s LLU to {target.Name} in time!\nHack canceled! */
def CTF_Failed_TimedOut(owner_name: String, target: Building): String = s"@CTF_Failed_TimedOut^@${GetFactionString(target.Faction)}~^@$owner_name~^@${target.Name}~"
// @CTF_Failed_SourceResecured=The %1 resecured %2!\nThe LLU was lost!
/** The {owner.Faction} resecured {owner.Name}!\nThe LLU was lost! */
def CTF_Failed_SourceResecured(owner: Building): String = s"@CTF_Failed_SourceResecured^@${CaptureFlagChatMessageStrings.GetFactionString(owner.Faction)}~^@${owner.Name}~"
// @CTF_FlagSpawned=%1 %2 has spawned a LLU.\nIt must be taken to %3 %4's Control Console within %5 minutes or the hack will fail!
/** {facilityType} {facilityName} has spawned a LLU.\nIt must be taken to {targetFacilityType} {targetFacilityName}'s Control Console within 15 minutes or the hack will fail! */
def CTF_FlagSpawned(owner: Building, target: Building): String = s"@CTF_FlagSpawned^@${owner.Definition.Name}~^@${owner.Name}~^@${target.Definition.Name}~^@${target.Name}~^15~"
// @CTF_FlagPickedUp=%1 of the %2 picked up %3's LLU
/** {playerName} of the {faction} picked up {facilityName}'s LLU */
def CTF_FlagPickedUp(player: Player, owner_name: String): String = s"@CTF_FlagPickedUp^${player.Name}~^@${CaptureFlagChatMessageStrings.GetFactionString(player.Faction)}~^@$owner_name~"
// @CTF_FlagDropped=%1 of the %2 dropped %3's LLU
/** {playerName} of the {faction} dropped {facilityName}'s LLU */
def CTF_FlagDropped(player: Player, owner_name: String): String = s"@CTF_FlagDropped^${player.Name}~^@${CaptureFlagChatMessageStrings.GetFactionString(player.Faction)}~^@$owner_name~"
// todo: make private
private def GetFactionString(faction: PlanetSideEmpire.Value): String = {
faction match {
case PlanetSideEmpire.TR => "TerranRepublic"
case PlanetSideEmpire.NC => "NewConglomerate"
case PlanetSideEmpire.VS => "VanuSovereigncy" // Yes, this is wrong. It is like that in packet captures.
}
}
}
object CaptureFlagLostReasonEnum extends Enumeration {
type CaptureFlagLostReasonEnum = Value
val Resecured, TimedOut = Value
}

View file

@ -1,21 +1,26 @@
package net.psforever.services.local.support
import akka.actor.{Actor, Cancellable}
import akka.actor.{Actor, ActorRef, Cancellable}
import net.psforever.actors.zone.{BuildingActor, ZoneActor}
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.llu.CaptureFlag
import net.psforever.objects.serverobject.structures.Building
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminal
import net.psforever.objects.zones.Zone
import net.psforever.objects.{Default, GlobalDefinitions}
import net.psforever.packet.game.PlanetsideAttributeEnum
import net.psforever.packet.game.{GenericActionEnum, PlanetsideAttributeEnum}
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID}
import java.util.concurrent.TimeUnit
import scala.concurrent.duration.{FiniteDuration, _}
import scala.util.Random
class HackCaptureActor extends Actor {
/**
* Responsible for handling the aspects related to hacking control consoles and capturing bases.
*/
class HackCaptureActor(val taskResolver: ActorRef) extends Actor {
private[this] val log = org.log4s.getLogger
private var clearTrigger: Cancellable = Default.Cancellable
@ -61,6 +66,7 @@ class HackCaptureActor extends Actor {
RestartTimer()
NotifyHackStateChange(target, isResecured = false)
TrySpawnCaptureFlag(target)
case HackCaptureActor.ProcessCompleteHacks() =>
log.trace("Processing complete hacks")
@ -75,7 +81,19 @@ class HackCaptureActor extends Actor {
val hackedByFaction = entry.target.HackedBy.get.hackerFaction
entry.target.Actor ! CommonMessages.ClearHack()
HackCompleted(entry.target, hackedByFaction)
entry.target.Owner.asInstanceOf[Building].GetFlagSocket match {
case Some(socket) =>
// LLU was not delivered in time. Send resecured notifications
entry.target.Owner.asInstanceOf[Building].GetFlag match {
case Some(flag: CaptureFlag) => entry.target.Zone.LocalEvents ! CaptureFlagManager.Lost(flag, CaptureFlagLostReasonEnum.TimedOut)
case None => log.warn(s"Failed to find capture flag matching socket ${socket.GUID}")
}
NotifyHackStateChange(entry.target, isResecured = true)
case None =>
// Timed hack finished, capture the base
HackCompleted(entry.target, hackedByFaction)
}
})
// If there's hacked objects left in the list restart the timer with the shortest hack time left
@ -84,32 +102,79 @@ class HackCaptureActor extends Actor {
case HackCaptureActor.ResecureCaptureTerminal(target, _) =>
hackedObjects = hackedObjects.filterNot(x => x.target == target)
// If LLU exists it was not delivered. Send resecured notifications
target.Owner.asInstanceOf[Building].GetFlag match {
case Some(flag: CaptureFlag) => target.Zone.LocalEvents ! CaptureFlagManager.Lost(flag, CaptureFlagLostReasonEnum.Resecured)
case None => ;
}
NotifyHackStateChange(target, isResecured = true)
// Restart the timer in case the object we just removed was the next one scheduled
RestartTimer()
case HackCaptureActor.FlagCaptured(flag) =>
log.warn(hackedObjects.toString())
hackedObjects.find(_.target.GUID == flag.Owner.asInstanceOf[Building].CaptureTerminal.get.GUID) match {
case Some(entry) =>
val hackedByFaction = entry.target.HackedBy.get.hackerFaction
hackedObjects = hackedObjects.filterNot(x => x == entry)
HackCompleted(entry.target, hackedByFaction)
entry.target.Actor ! CommonMessages.ClearHack()
flag.Zone.LocalEvents ! CaptureFlagManager.Captured(flag)
// If there's hacked objects left in the list restart the timer with the shortest hack time left
RestartTimer()
case _ =>
log.error(s"Attempted LLU capture for ${flag.Owner.asInstanceOf[Building].Name} but CC GUID ${flag.Owner.asInstanceOf[Building].CaptureTerminal.get.GUID} was not in list of hacked objects")
}
case _ => ;
}
private def NotifyHackStateChange(target: CaptureTerminal, isResecured: Boolean): Unit = {
val attribute_value = HackCaptureActor.GetHackUpdateAttributeValue(target, isResecured)
private def TrySpawnCaptureFlag(terminal: CaptureTerminal): Unit = {
// Handle LLUs if the base contains a LLU socket
// If there are no neighbouring bases belonging to the hacking faction this will be handled as a regular timed hack (e.g. neutral base in enemy territory)
val owner = terminal.Owner.asInstanceOf[Building]
val hackingFaction = HackCaptureActor.GetHackingFaction(terminal).get
val hackingFactionNeighbourBases = owner.Neighbours(hackingFaction)
hackingFactionNeighbourBases match {
case Some(neighbours) =>
if(owner.IsCtfBase) {
// Find a random neighbouring base matching the hacking faction
val targetBase = neighbours.toVector((new Random).nextInt(neighbours.size))
// Request LLU is created by CaptureFlagActor via LocalService
terminal.Zone.LocalEvents ! CaptureFlagManager.SpawnCaptureFlag(terminal, targetBase, hackingFaction)
}
case None =>
log.info("Couldn't find any neighbouring bases for LLU hack.")
}
}
private def NotifyHackStateChange(terminal: CaptureTerminal, isResecured: Boolean): Unit = {
val attribute_value = HackCaptureActor.GetHackUpdateAttributeValue(terminal, isResecured)
// Notify all clients that CC has been hacked
target.Zone.LocalEvents ! LocalServiceMessage(
target.Zone.id,
terminal.Zone.LocalEvents ! LocalServiceMessage(
terminal.Zone.id,
LocalAction.SendPlanetsideAttributeMessage(
PlanetSideGUID(-1),
target.GUID,
terminal.GUID,
PlanetsideAttributeEnum.ControlConsoleHackUpdate,
attribute_value
)
)
val owner = terminal.Owner.asInstanceOf[Building]
// Notify parent building that state has changed
target.Owner.Actor ! BuildingActor.AmenityStateChange(target, Some(isResecured))
owner.Actor ! BuildingActor.AmenityStateChange(terminal, Some(isResecured))
// Push map update to clients
target.Owner.asInstanceOf[Building].Zone.actor ! ZoneActor.ZoneMapUpdate()
owner.Zone.actor ! ZoneActor.ZoneMapUpdate()
}
private def HackCompleted(terminal: CaptureTerminal with Hackable, hackedByFaction: PlanetSideEmpire.Value): Unit = {
@ -117,6 +182,9 @@ class HackCaptureActor extends Actor {
if (building.NtuLevel > 0) {
log.info(s"Setting base ${building.GUID} / MapId: ${building.MapId} as owned by $hackedByFaction")
building.Actor! BuildingActor.SetFaction(hackedByFaction)
// todo: This should probably only go to those within the captured SOI who belong to the capturing faction
building.Zone.LocalEvents ! LocalServiceMessage(building.Zone.id, LocalAction.SendGenericActionMessage(PlanetSideGUID(-1), GenericActionEnum.BaseCaptureFanfare))
} else {
log.info("Base hack completed, but base was out of NTU.")
}
@ -160,6 +228,7 @@ object HackCaptureActor {
)
final case class ResecureCaptureTerminal(target: CaptureTerminal, zone: Zone)
final case class FlagCaptured(flag: CaptureFlag)
private final case class ProcessCompleteHacks()
@ -172,11 +241,19 @@ object HackCaptureActor {
hack_timestamp: Long
)
def GetHackUpdateAttributeValue(target: CaptureTerminal, isResecured: Boolean): Long = {
def GetHackingFaction(terminal: CaptureTerminal): Option[PlanetSideEmpire.Value] = {
terminal.HackedBy match {
case Some(Hackable.HackInfo(_, _, hackingFaction, _, _, _)) =>
Some(hackingFaction)
case _ => None
}
}
def GetHackUpdateAttributeValue(terminal: CaptureTerminal, isResecured: Boolean): Long = {
if (isResecured) {
17039360L
} else {
target.HackedBy match {
terminal.HackedBy match {
case Some(Hackable.HackInfo(_, _, hackingFaction, _, start, length)) =>
// See PlanetSideAttributeMessage #20 documentation for an explanation of how the timer is calculated
val hack_time_remaining_ms =