mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-03-24 14:29:08 +00:00
moved AvatarService and LocalService into their own package
This commit is contained in:
parent
d5f40a3d5f
commit
7845508bd3
13 changed files with 144 additions and 97 deletions
|
|
@ -18,6 +18,8 @@ import net.psforever.objects.serverobject.builders.{DoorObjectBuilder, IFFLockOb
|
|||
import org.slf4j
|
||||
import org.fusesource.jansi.Ansi._
|
||||
import org.fusesource.jansi.Ansi.Color._
|
||||
import services.avatar._
|
||||
import services.local._
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.concurrent.Await
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware}
|
||||
import net.psforever.packet.{PlanetSideGamePacket, _}
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.control._
|
||||
import net.psforever.packet.game.{ObjectCreateDetailedMessage, _}
|
||||
import net.psforever.packet.game._
|
||||
import scodec.Attempt.{Failure, Successful}
|
||||
import scodec.bits._
|
||||
import org.log4s.MDC
|
||||
|
|
@ -23,6 +23,9 @@ import net.psforever.objects.serverobject.locks.IFFLock
|
|||
import net.psforever.objects.serverobject.terminals.Terminal
|
||||
import net.psforever.packet.game.objectcreate._
|
||||
import net.psforever.types._
|
||||
import services._
|
||||
import services.avatar._
|
||||
import services.local._
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.util.Success
|
||||
|
|
@ -229,13 +232,15 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectStateMsg(door_guid, 17)))
|
||||
|
||||
case LocalServiceResponse.HackClear(target_guid, unk1, unk2) =>
|
||||
log.info(s"Clear hack of $target_guid")
|
||||
sendResponse(PacketCoding.CreateGamePacket(0, HackMessage(0, target_guid, guid, 0, unk1, HackState.HackCleared, unk2)))
|
||||
|
||||
case LocalServiceResponse.HackObject(target_guid, unk1, unk2) =>
|
||||
if(player.GUID != guid) {
|
||||
sendResponse(PacketCoding.CreateGamePacket(0, HackMessage(0, target_guid, guid, 100, unk1, HackState.Hacked, unk2)))
|
||||
}
|
||||
|
||||
case LocalServiceResponse.TriggerSound(sound, pos, unk, volume) =>
|
||||
sendResponse(PacketCoding.CreateGamePacket(0, TriggerSoundMessage(sound, pos, unk, volume)))
|
||||
}
|
||||
|
||||
case Door.DoorMessage(tplayer, msg, order) =>
|
||||
|
|
@ -1631,8 +1636,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
* @see `HackMessage`
|
||||
*/
|
||||
//TODO add params here depending on which params in HackMessage are important
|
||||
//TODO sound should be centered on IFFLock, not on player
|
||||
private def FinishHackingDoor(target : IFFLock, unk : Long)() : Unit = {
|
||||
target.Actor ! CommonMessages.Hack(player)
|
||||
localService ! LocalServiceMessage(continent.Id, LocalAction.TriggerSound(player.GUID, TriggeredSound.HackDoor, player.Position, 30, 0.49803925f))
|
||||
localService ! LocalServiceMessage(continent.Id, LocalAction.HackTemporarily(player.GUID, continent, target, unk))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services
|
||||
|
||||
import akka.event.{ActorEventBus, SubchannelClassification}
|
||||
import akka.util.Subclassification
|
||||
import net.psforever.packet.game.PlanetSideGUID
|
||||
|
||||
object Service {
|
||||
final val defaultPlayerGUID : PlanetSideGUID = PlanetSideGUID(0)
|
||||
|
||||
final case class Join(channel : String)
|
||||
final case class Leave()
|
||||
final case class LeaveAll()
|
||||
28
pslogin/src/main/scala/services/avatar/AvatarAction.scala
Normal file
28
pslogin/src/main/scala/services/avatar/AvatarAction.scala
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services.avatar
|
||||
|
||||
import net.psforever.objects.equipment.Equipment
|
||||
import net.psforever.packet.game.{PlanetSideGUID, PlayerStateMessageUpstream}
|
||||
import net.psforever.packet.game.objectcreate.ConstructorData
|
||||
import net.psforever.types.{ExoSuitType, Vector3}
|
||||
|
||||
object AvatarAction {
|
||||
trait Action
|
||||
|
||||
final case class ArmorChanged(player_guid : PlanetSideGUID, suit : ExoSuitType.Value, subtype : Int) extends Action
|
||||
//final case class DropItem(pos : Vector3, orient : Vector3, item : PlanetSideGUID) extends Action
|
||||
final case class EquipmentInHand(player_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action
|
||||
final case class EquipmentOnGround(player_guid : PlanetSideGUID, pos : Vector3, orient : Vector3, item : Equipment) extends Action
|
||||
final case class LoadPlayer(player_guid : PlanetSideGUID, pdata : ConstructorData) extends Action
|
||||
// final case class LoadMap(msg : PlanetSideGUID) extends Action
|
||||
// final case class unLoadMap(msg : PlanetSideGUID) extends Action
|
||||
final case class ObjectDelete(player_guid : PlanetSideGUID, item_guid : PlanetSideGUID, unk : Int = 0) extends Action
|
||||
final case class ObjectHeld(player_guid : PlanetSideGUID, slot : Int) extends Action
|
||||
final case class PlanetsideAttribute(player_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Long) extends Action
|
||||
final case class PlayerState(player_guid : PlanetSideGUID, msg : PlayerStateMessageUpstream, spectator : Boolean, weaponInHand : Boolean) extends Action
|
||||
final case class Reload(player_guid : PlanetSideGUID, mag : Int) extends Action
|
||||
// final case class PlayerStateShift(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action
|
||||
// final case class DestroyDisplay(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action
|
||||
// final case class HitHintReturn(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action
|
||||
// final case class ChangeWeapon(unk1 : Int, sessionId : Long) extends Action
|
||||
}
|
||||
|
|
@ -1,60 +1,8 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services.avatar
|
||||
|
||||
import akka.actor.Actor
|
||||
import net.psforever.objects.equipment.Equipment
|
||||
import net.psforever.packet.game.objectcreate.ConstructorData
|
||||
import net.psforever.types.ExoSuitType
|
||||
import net.psforever.packet.game.{PlanetSideGUID, PlayerStateMessageUpstream}
|
||||
import net.psforever.types.Vector3
|
||||
|
||||
object AvatarAction {
|
||||
trait Action
|
||||
|
||||
final case class ArmorChanged(player_guid : PlanetSideGUID, suit : ExoSuitType.Value, subtype : Int) extends Action
|
||||
//final case class DropItem(pos : Vector3, orient : Vector3, item : PlanetSideGUID) extends Action
|
||||
final case class EquipmentInHand(player_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action
|
||||
final case class EquipmentOnGround(player_guid : PlanetSideGUID, pos : Vector3, orient : Vector3, item : Equipment) extends Action
|
||||
final case class LoadPlayer(player_guid : PlanetSideGUID, pdata : ConstructorData) extends Action
|
||||
// final case class LoadMap(msg : PlanetSideGUID) extends Action
|
||||
// final case class unLoadMap(msg : PlanetSideGUID) extends Action
|
||||
final case class ObjectDelete(player_guid : PlanetSideGUID, item_guid : PlanetSideGUID, unk : Int = 0) extends Action
|
||||
final case class ObjectHeld(player_guid : PlanetSideGUID, slot : Int) extends Action
|
||||
final case class PlanetsideAttribute(player_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Long) extends Action
|
||||
final case class PlayerState(player_guid : PlanetSideGUID, msg : PlayerStateMessageUpstream, spectator : Boolean, weaponInHand : Boolean) extends Action
|
||||
final case class Reload(player_guid : PlanetSideGUID, mag : Int) extends Action
|
||||
// final case class PlayerStateShift(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action
|
||||
// final case class DestroyDisplay(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action
|
||||
// final case class HitHintReturn(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action
|
||||
// final case class ChangeWeapon(unk1 : Int, sessionId : Long) extends Action
|
||||
}
|
||||
|
||||
object AvatarServiceResponse {
|
||||
trait Response
|
||||
|
||||
final case class ArmorChanged(suit : ExoSuitType.Value, subtype : Int) extends Response
|
||||
//final case class DropItem(pos : Vector3, orient : Vector3, item : PlanetSideGUID) extends Response
|
||||
final case class EquipmentInHand(slot : Int, item : Equipment) extends Response
|
||||
final case class EquipmentOnGround(pos : Vector3, orient : Vector3, item : Equipment) extends Response
|
||||
final case class LoadPlayer(pdata : ConstructorData) extends Response
|
||||
// final case class unLoadMap() extends Response
|
||||
// final case class LoadMap() extends Response
|
||||
final case class ObjectDelete(item_guid : PlanetSideGUID, unk : Int) extends Response
|
||||
final case class ObjectHeld(slot : Int) extends Response
|
||||
final case class PlanetSideAttribute(attribute_type : Int, attribute_value : Long) extends Response
|
||||
final case class PlayerState(msg : PlayerStateMessageUpstream, spectator : Boolean, weaponInHand : Boolean) extends Response
|
||||
final case class Reload(mag : Int) extends Response
|
||||
// final case class PlayerStateShift(itemID : PlanetSideGUID) extends Response
|
||||
// final case class DestroyDisplay(itemID : PlanetSideGUID) extends Response
|
||||
// final case class HitHintReturn(itemID : PlanetSideGUID) extends Response
|
||||
// final case class ChangeWeapon(facingYaw : Int) extends Response
|
||||
}
|
||||
|
||||
final case class AvatarServiceMessage(forChannel : String, actionMessage : AvatarAction.Action)
|
||||
|
||||
final case class AvatarServiceResponse(toChannel : String, avatar_guid : PlanetSideGUID, replyMessage : AvatarServiceResponse.Response) extends GenericEventBusMsg
|
||||
|
||||
/*
|
||||
/Avatar/
|
||||
*/
|
||||
import services.{GenericEventBus, Service}
|
||||
|
||||
class AvatarService extends Actor {
|
||||
//import AvatarServiceResponse._
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services.avatar
|
||||
|
||||
final case class AvatarServiceMessage(forChannel : String, actionMessage : AvatarAction.Action)
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services.avatar
|
||||
|
||||
import net.psforever.objects.equipment.Equipment
|
||||
import net.psforever.packet.game.{PlanetSideGUID, PlayerStateMessageUpstream}
|
||||
import net.psforever.packet.game.objectcreate.ConstructorData
|
||||
import net.psforever.types.{ExoSuitType, Vector3}
|
||||
import services.GenericEventBusMsg
|
||||
|
||||
final case class AvatarServiceResponse(toChannel : String,
|
||||
avatar_guid : PlanetSideGUID,
|
||||
replyMessage : AvatarServiceResponse.Response
|
||||
) extends GenericEventBusMsg
|
||||
|
||||
object AvatarServiceResponse {
|
||||
trait Response
|
||||
|
||||
final case class ArmorChanged(suit : ExoSuitType.Value, subtype : Int) extends Response
|
||||
//final case class DropItem(pos : Vector3, orient : Vector3, item : PlanetSideGUID) extends Response
|
||||
final case class EquipmentInHand(slot : Int, item : Equipment) extends Response
|
||||
final case class EquipmentOnGround(pos : Vector3, orient : Vector3, item : Equipment) extends Response
|
||||
final case class LoadPlayer(pdata : ConstructorData) extends Response
|
||||
// final case class unLoadMap() extends Response
|
||||
// final case class LoadMap() extends Response
|
||||
final case class ObjectDelete(item_guid : PlanetSideGUID, unk : Int) extends Response
|
||||
final case class ObjectHeld(slot : Int) extends Response
|
||||
final case class PlanetSideAttribute(attribute_type : Int, attribute_value : Long) extends Response
|
||||
final case class PlayerState(msg : PlayerStateMessageUpstream, spectator : Boolean, weaponInHand : Boolean) extends Response
|
||||
final case class Reload(mag : Int) extends Response
|
||||
// final case class PlayerStateShift(itemID : PlanetSideGUID) extends Response
|
||||
// final case class DestroyDisplay(itemID : PlanetSideGUID) extends Response
|
||||
// final case class HitHintReturn(itemID : PlanetSideGUID) extends Response
|
||||
// final case class ChangeWeapon(facingYaw : Int) extends Response
|
||||
}
|
||||
18
pslogin/src/main/scala/services/local/LocalAction.scala
Normal file
18
pslogin/src/main/scala/services/local/LocalAction.scala
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services.local
|
||||
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.game.{PlanetSideGUID, TriggeredSound}
|
||||
import net.psforever.types.Vector3
|
||||
|
||||
object LocalAction {
|
||||
trait Action
|
||||
|
||||
final case class DoorOpens(player_guid : PlanetSideGUID, continent : Zone, door : Door) extends Action
|
||||
final case class DoorCloses(player_guid : PlanetSideGUID, door_guid : PlanetSideGUID) extends Action
|
||||
final case class HackClear(player_guid : PlanetSideGUID, target : PlanetSideServerObject, unk1 : Long, unk2 : Long = 8L) extends Action
|
||||
final case class HackTemporarily(player_guid : PlanetSideGUID, continent : Zone, target : PlanetSideServerObject, unk1 : Long, unk2 : Long = 8L) extends Action
|
||||
final case class TriggerSound(player_guid : PlanetSideGUID, sound : TriggeredSound.Value, pos : Vector3, unk : Int, volume : Float) extends Action
|
||||
}
|
||||
|
|
@ -1,35 +1,9 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services.local
|
||||
|
||||
import akka.actor.{Actor, Props}
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
import net.psforever.objects.zones.{DoorCloseActor, HackClearActor, Zone}
|
||||
import net.psforever.packet.game.PlanetSideGUID
|
||||
|
||||
object LocalAction {
|
||||
trait Action
|
||||
|
||||
final case class DoorOpens(player_guid : PlanetSideGUID, continent : Zone, door : Door) extends Action
|
||||
final case class DoorCloses(player_guid : PlanetSideGUID, door_guid : PlanetSideGUID) extends Action
|
||||
final case class HackClear(player_guid : PlanetSideGUID, target : PlanetSideServerObject, unk1 : Long, unk2 : Long = 8L) extends Action
|
||||
final case class HackTemporarily(player_guid : PlanetSideGUID, continent : Zone, target : PlanetSideServerObject, unk1 : Long, unk2 : Long = 8L) extends Action
|
||||
}
|
||||
|
||||
object LocalServiceResponse {
|
||||
trait Response
|
||||
|
||||
final case class DoorOpens(door_guid : PlanetSideGUID) extends Response
|
||||
final case class DoorCloses(door_guid : PlanetSideGUID) extends Response
|
||||
final case class HackClear(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 LocalServiceMessage(forChannel : String, actionMessage : LocalAction.Action)
|
||||
|
||||
final case class LocalServiceResponse(toChannel : String, avatar_guid : PlanetSideGUID, replyMessage : LocalServiceResponse.Response) extends GenericEventBusMsg
|
||||
|
||||
/*
|
||||
/LocalEnvironment/
|
||||
*/
|
||||
import services.local.support.{DoorCloseActor, HackClearActor}
|
||||
import services.{GenericEventBus, Service}
|
||||
|
||||
class LocalService extends Actor {
|
||||
//import LocalService._
|
||||
|
|
@ -72,7 +46,11 @@ class LocalService extends Actor {
|
|||
case LocalAction.HackTemporarily(player_guid, zone, target, unk1, unk2) =>
|
||||
hackClearer ! HackClearActor.ObjectIsHacked(target, zone, unk1, unk2)
|
||||
LocalEvents.publish(
|
||||
LocalServiceResponse(s"/$forChannel/Avatar", player_guid, LocalServiceResponse.HackObject(target.GUID, unk1, unk2))
|
||||
LocalServiceResponse(s"/$forChannel/LocalEnvironment", player_guid, LocalServiceResponse.HackObject(target.GUID, unk1, unk2))
|
||||
)
|
||||
case LocalAction.TriggerSound(player_guid, sound, pos, unk, volume) =>
|
||||
LocalEvents.publish(
|
||||
LocalServiceResponse(s"/$forChannel/LocalEnvironment", player_guid, LocalServiceResponse.TriggerSound(sound, pos, unk, volume))
|
||||
)
|
||||
case _ => ;
|
||||
}
|
||||
|
|
@ -80,20 +58,16 @@ class LocalService extends Actor {
|
|||
//response from DoorCloseActor
|
||||
case DoorCloseActor.CloseTheDoor(door_guid, zone_id) =>
|
||||
LocalEvents.publish(
|
||||
LocalServiceResponse(s"/$zone_id/LocalEnvironment", LocalService.defaultPlayerGUID, LocalServiceResponse.DoorCloses(door_guid))
|
||||
LocalServiceResponse(s"/$zone_id/LocalEnvironment", Service.defaultPlayerGUID, LocalServiceResponse.DoorCloses(door_guid))
|
||||
)
|
||||
|
||||
//response from HackClearActor
|
||||
case HackClearActor.ClearTheHack(target_guid, zone_id, unk1, unk2) =>
|
||||
LocalEvents.publish(
|
||||
LocalServiceResponse(s"/$zone_id/LocalEnvironment", LocalService.defaultPlayerGUID, LocalServiceResponse.HackClear(target_guid, unk1, unk2))
|
||||
LocalServiceResponse(s"/$zone_id/LocalEnvironment", Service.defaultPlayerGUID, LocalServiceResponse.HackClear(target_guid, unk1, unk2))
|
||||
)
|
||||
|
||||
case msg =>
|
||||
log.info(s"Unhandled message $msg from $sender")
|
||||
}
|
||||
}
|
||||
|
||||
object LocalService {
|
||||
final val defaultPlayerGUID : PlanetSideGUID = PlanetSideGUID(0)
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services.local
|
||||
|
||||
final case class LocalServiceMessage(forChannel : String, actionMessage : LocalAction.Action)
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services.local
|
||||
|
||||
import net.psforever.packet.game.{PlanetSideGUID, TriggeredSound}
|
||||
import net.psforever.types.Vector3
|
||||
import services.GenericEventBusMsg
|
||||
|
||||
final case class LocalServiceResponse(toChannel : String,
|
||||
avatar_guid : PlanetSideGUID,
|
||||
replyMessage : LocalServiceResponse.Response
|
||||
) extends GenericEventBusMsg
|
||||
|
||||
object LocalServiceResponse {
|
||||
trait Response
|
||||
|
||||
final case class DoorOpens(door_guid : PlanetSideGUID) extends Response
|
||||
final case class DoorCloses(door_guid : PlanetSideGUID) extends Response
|
||||
final case class HackClear(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 TriggerSound(sound : TriggeredSound.Value, pos : Vector3, unk : Int, volume : Float) extends Response
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services.local.support
|
||||
|
||||
import akka.actor.{Actor, Cancellable}
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.game.PlanetSideGUID
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.concurrent.duration._
|
||||
|
||||
/**
|
||||
* Close an opened door after a certain amount of time has passed.
|
||||
* This `Actor` is intended to sit on top of the event system that handles broadcast messaging regarding doors opening.
|
||||
* @see `LocalService`
|
||||
*/
|
||||
class DoorCloseActor() extends Actor {
|
||||
/** The periodic `Executor` that checks for doors to be closed */
|
||||
private var doorCloserTrigger : Cancellable = DoorCloseActor.DefaultCloser
|
||||
/** A `List` of currently open doors */
|
||||
private var openDoors : List[DoorCloseActor.DoorEntry] = Nil
|
||||
//private[this] val log = org.log4s.getLogger
|
||||
|
||||
def receive : Receive = {
|
||||
case DoorCloseActor.DoorIsOpen(door, zone, time) =>
|
||||
openDoors = openDoors :+ DoorCloseActor.DoorEntry(door, zone, time)
|
||||
if(openDoors.size == 1) { //we were the only entry so the event must be started from scratch
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
doorCloserTrigger = context.system.scheduler.scheduleOnce(DoorCloseActor.timeout, self, DoorCloseActor.TryCloseDoors())
|
||||
}
|
||||
|
||||
case DoorCloseActor.TryCloseDoors() =>
|
||||
doorCloserTrigger.cancel
|
||||
val now : Long = System.nanoTime
|
||||
val (doorsToClose, doorsLeftOpen) = PartitionEntries(openDoors, now)
|
||||
openDoors = doorsLeftOpen
|
||||
doorsToClose.foreach(entry => {
|
||||
entry.door.Open = false //permissible break from synchronization
|
||||
context.parent ! DoorCloseActor.CloseTheDoor(entry.door.GUID, entry.zone.Id) //call up to the main event system
|
||||
})
|
||||
|
||||
if(doorsLeftOpen.nonEmpty) {
|
||||
val short_timeout : FiniteDuration = math.max(1, DoorCloseActor.timeout_time - (now - doorsLeftOpen.head.time)) nanoseconds
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
doorCloserTrigger = context.system.scheduler.scheduleOnce(short_timeout, self, DoorCloseActor.TryCloseDoors())
|
||||
}
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate over entries in a `List` until an entry that does not exceed the time limit is discovered.
|
||||
* Separate the original `List` into two:
|
||||
* a `List` of elements that have exceeded the time limit,
|
||||
* and a `List` of elements that still satisfy the time limit.
|
||||
* As newer entries to the `List` will always resolve later than old ones,
|
||||
* and newer entries are always added to the end of the main `List`,
|
||||
* processing in order is always correct.
|
||||
* @param list the `List` of entries to divide
|
||||
* @param now the time right now (in nanoseconds)
|
||||
* @see `List.partition`
|
||||
* @return a `Tuple` of two `Lists`, whose qualifications are explained above
|
||||
*/
|
||||
private def PartitionEntries(list : List[DoorCloseActor.DoorEntry], now : Long) : (List[DoorCloseActor.DoorEntry], List[DoorCloseActor.DoorEntry]) = {
|
||||
val n : Int = recursivePartitionEntries(list.iterator, now)
|
||||
(list.take(n), list.drop(n)) //take and drop so to always return new lists
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the index where the `List` of elements can be divided into two:
|
||||
* a `List` of elements that have exceeded the time limit,
|
||||
* and a `List` of elements that still satisfy the time limit.
|
||||
* @param iter the `Iterator` of entries to divide
|
||||
* @param now the time right now (in nanoseconds)
|
||||
* @param index a persistent record of the index where list division should occur;
|
||||
* defaults to 0
|
||||
* @return the index where division will occur
|
||||
*/
|
||||
@tailrec private def recursivePartitionEntries(iter : Iterator[DoorCloseActor.DoorEntry], now : Long, index : Int = 0) : Int = {
|
||||
if(!iter.hasNext) {
|
||||
index
|
||||
}
|
||||
else {
|
||||
val entry = iter.next()
|
||||
if(now - entry.time >= DoorCloseActor.timeout_time) {
|
||||
recursivePartitionEntries(iter, now, index + 1)
|
||||
}
|
||||
else {
|
||||
index
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object DoorCloseActor {
|
||||
/** The wait before an open door closes; as a Long for calculation simplicity */
|
||||
private final val timeout_time : Long = 5000000000L //nanoseconds (5s)
|
||||
/** The wait before an open door closes; as a `FiniteDuration` for `Executor` simplicity */
|
||||
private final val timeout : FiniteDuration = timeout_time nanoseconds
|
||||
|
||||
private final val DefaultCloser : Cancellable = new Cancellable() {
|
||||
override def cancel : Boolean = true
|
||||
override def isCancelled : Boolean = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Message that carries information about a door that has been opened.
|
||||
* @param door the door object
|
||||
* @param zone the zone in which the door resides
|
||||
* @param time when the door was opened
|
||||
* @see `DoorEntry`
|
||||
*/
|
||||
final case class DoorIsOpen(door : Door, zone : Zone, time : Long = System.nanoTime())
|
||||
/**
|
||||
* Message that carries information about a door that needs to close.
|
||||
* Prompting, as compared to `DoorIsOpen` which is reactionary.
|
||||
* @param door_guid the door
|
||||
* @param zone_id the zone in which the door resides
|
||||
*/
|
||||
final case class CloseTheDoor(door_guid : PlanetSideGUID, zone_id : String)
|
||||
/**
|
||||
* Internal message used to signal a test of the queued door information.
|
||||
*/
|
||||
private final case class TryCloseDoors()
|
||||
|
||||
/**
|
||||
* Entry of door information.
|
||||
* The `zone` is maintained separately to ensure that any message resulting in an attempt to close doors is targetted.
|
||||
* @param door the door object
|
||||
* @param zone the zone in which the door resides
|
||||
* @param time when the door was opened
|
||||
* @see `DoorIsOpen`
|
||||
*/
|
||||
private final case class DoorEntry(door : Door, zone : Zone, time : Long)
|
||||
}
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services.local.support
|
||||
|
||||
import akka.actor.{Actor, Cancellable}
|
||||
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.game.PlanetSideGUID
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.concurrent.duration._
|
||||
|
||||
/**
|
||||
* Restore original functionality to an object that has been hacked after a certain amount of time has passed.
|
||||
* This `Actor` is intended to sit on top of the event system that handles broadcast messaging regarding hacking events.
|
||||
* @see `LocalService`
|
||||
*/
|
||||
class HackClearActor() extends Actor {
|
||||
/** The periodic `Executor` that checks for server objects to be unhacked */
|
||||
private var clearTrigger : Cancellable = HackClearActor.DefaultClearer
|
||||
/** A `List` of currently hacked server objects */
|
||||
private var hackedObjects : List[HackClearActor.HackEntry] = Nil
|
||||
//private[this] val log = org.log4s.getLogger
|
||||
|
||||
def receive : Receive = {
|
||||
case HackClearActor.ObjectIsHacked(target, zone, unk1, unk2, time) =>
|
||||
hackedObjects = hackedObjects :+ HackClearActor.HackEntry(target, zone, unk1, unk2, time)
|
||||
if(hackedObjects.size == 1) { //we were the only entry so the event must be started from scratch
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
clearTrigger = context.system.scheduler.scheduleOnce(HackClearActor.timeout, self, HackClearActor.TryClearHacks())
|
||||
}
|
||||
|
||||
case HackClearActor.TryClearHacks() =>
|
||||
clearTrigger.cancel
|
||||
val now : Long = System.nanoTime
|
||||
//TODO we can just walk across the list of doors and extract only the first few entries
|
||||
val (unhackObjects, stillHackedObjects) = PartitionEntries(hackedObjects, now)
|
||||
hackedObjects = stillHackedObjects
|
||||
unhackObjects.foreach(entry => {
|
||||
entry.target.Actor ! CommonMessages.ClearHack()
|
||||
context.parent ! HackClearActor.ClearTheHack(entry.target.GUID, entry.zone.Id, entry.unk1, entry.unk2) //call up to the main event system
|
||||
})
|
||||
|
||||
if(stillHackedObjects.nonEmpty) {
|
||||
val short_timeout : FiniteDuration = math.max(1, HackClearActor.timeout_time - (now - stillHackedObjects.head.time)) nanoseconds
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
clearTrigger = context.system.scheduler.scheduleOnce(short_timeout, self, HackClearActor.TryClearHacks())
|
||||
}
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate over entries in a `List` until an entry that does not exceed the time limit is discovered.
|
||||
* Separate the original `List` into two:
|
||||
* a `List` of elements that have exceeded the time limit,
|
||||
* and a `List` of elements that still satisfy the time limit.
|
||||
* As newer entries to the `List` will always resolve later than old ones,
|
||||
* and newer entries are always added to the end of the main `List`,
|
||||
* processing in order is always correct.
|
||||
* @param list the `List` of entries to divide
|
||||
* @param now the time right now (in nanoseconds)
|
||||
* @see `List.partition`
|
||||
* @return a `Tuple` of two `Lists`, whose qualifications are explained above
|
||||
*/
|
||||
private def PartitionEntries(list : List[HackClearActor.HackEntry], now : Long) : (List[HackClearActor.HackEntry], List[HackClearActor.HackEntry]) = {
|
||||
val n : Int = recursivePartitionEntries(list.iterator, now)
|
||||
(list.take(n), list.drop(n)) //take and drop so to always return new lists
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the index where the `List` of elements can be divided into two:
|
||||
* a `List` of elements that have exceeded the time limit,
|
||||
* and a `List` of elements that still satisfy the time limit.
|
||||
* @param iter the `Iterator` of entries to divide
|
||||
* @param now the time right now (in nanoseconds)
|
||||
* @param index a persistent record of the index where list division should occur;
|
||||
* defaults to 0
|
||||
* @return the index where division will occur
|
||||
*/
|
||||
@tailrec private def recursivePartitionEntries(iter : Iterator[HackClearActor.HackEntry], now : Long, index : Int = 0) : Int = {
|
||||
if(!iter.hasNext) {
|
||||
index
|
||||
}
|
||||
else {
|
||||
val entry = iter.next()
|
||||
if(now - entry.time >= HackClearActor.timeout_time) {
|
||||
recursivePartitionEntries(iter, now, index + 1)
|
||||
}
|
||||
else {
|
||||
index
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object HackClearActor {
|
||||
/** The wait before a server object is to unhack; as a Long for calculation simplicity */
|
||||
private final val timeout_time : Long = 60000000000L //nanoseconds (60s)
|
||||
/** The wait before a server object is to unhack; as a `FiniteDuration` for `Executor` simplicity */
|
||||
private final val timeout : FiniteDuration = timeout_time nanoseconds
|
||||
|
||||
private final val DefaultClearer : Cancellable = new Cancellable() {
|
||||
override def cancel : Boolean = true
|
||||
override def isCancelled : Boolean = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Message that carries information about a server object that has been hacked.
|
||||
* @param target the server object
|
||||
* @param zone the zone in which the object resides
|
||||
* @param time when the object was hacked
|
||||
* @see `HackEntry`
|
||||
*/
|
||||
final case class ObjectIsHacked(target : PlanetSideServerObject, zone : Zone, unk1 : Long, unk2 : Long, time : Long = System.nanoTime())
|
||||
/**
|
||||
* Message that carries information about a server object that needs its functionality restored.
|
||||
* Prompting, as compared to `ObjectIsHacked` which is reactionary.
|
||||
* @param door_guid the server object
|
||||
* @param zone_id the zone in which the object resides
|
||||
*/
|
||||
final case class ClearTheHack(door_guid : PlanetSideGUID, zone_id : String, unk1 : Long, unk2 : Long)
|
||||
/**
|
||||
* Internal message used to signal a test of the queued door information.
|
||||
*/
|
||||
private final case class TryClearHacks()
|
||||
|
||||
/**
|
||||
* Entry of hacked server object information.
|
||||
* The `zone` is maintained separately to ensure that any message resulting in an attempt to close doors is targetted.
|
||||
* @param target the server object
|
||||
* @param zone the zone in which the object resides
|
||||
* @param time when the object was hacked
|
||||
* @see `ObjectIsHacked`
|
||||
*/
|
||||
private final case class HackEntry(target : PlanetSideServerObject, zone : Zone, unk1 : Long, unk2 : Long, time : Long)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue