mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-03-03 20:40:24 +00:00
Timed capture consoles & towers / hacking improvements (#228)
* Add capture terminal definitions * Logging / documentation * Functionality for timed base hacks * Disable IFF locks while base is hacked * Hacking speed based on player's hacking certification level (and hack effect duration data - currently not functional) * Hack effect duration functionality * Sync hack states with clients joining the zone * Whitespace / comments * Allow IFF locks to be resecured by the owning faction if it has been hacked * Fix bases with no NTU silo failing to capture * Reset CC properly on hack that expires with no NTU in silo * Capture towers instantly and improve handling of hacked objects queue * Fix handling of neutral IFF Locks and remove unnecessary casting * Move HackCaptureActor to correct location * Re-enable Anguta door locks for air pad now hacking/resecuring IFF locks work Add capture terminals (timed CC only for now) for a few bases & a tower Add a few missed locks/doors Add a resource silo to Girru * Fix merge issues & missing documentation
This commit is contained in:
parent
8b5073dcbc
commit
abdebf09ba
23 changed files with 649 additions and 168 deletions
|
|
@ -864,6 +864,10 @@ object GlobalDefinitions {
|
|||
|
||||
val resource_silo = new ResourceSiloDefinition
|
||||
|
||||
val capture_terminal = new CaptureTerminalDefinition(158) // Base CC
|
||||
|
||||
val secondary_capture = new CaptureTerminalDefinition(751) // Tower CC
|
||||
|
||||
val manned_turret = new MannedTurretDefinition(480) {
|
||||
Name = "manned_turret"
|
||||
MaxHealth = 3600
|
||||
|
|
|
|||
|
|
@ -7,13 +7,8 @@ import net.psforever.types.Vector3
|
|||
trait Hackable {
|
||||
/** An entry that maintains a reference to the `Player`, and the player's GUID and location when the message was received. */
|
||||
private var hackedBy : Option[(Player, PlanetSideGUID, Vector3)] = None
|
||||
|
||||
private var hackSound : TriggeredSound.Value = TriggeredSound.HackDoor
|
||||
|
||||
def HackedBy : Option[(Player, PlanetSideGUID, Vector3)] = hackedBy
|
||||
|
||||
def HackedBy_=(agent : Player) : Option[(Player, PlanetSideGUID, Vector3)] = HackedBy_=(Some(agent))
|
||||
|
||||
/**
|
||||
* Set the hack state of this object by recording important information about the player that caused it.
|
||||
* Set the hack state if there is no current hack state.
|
||||
|
|
@ -41,9 +36,27 @@ trait Hackable {
|
|||
HackedBy
|
||||
}
|
||||
|
||||
/** The sound made when the object is hacked */
|
||||
private var hackSound : TriggeredSound.Value = TriggeredSound.HackDoor
|
||||
def HackSound : TriggeredSound.Value = hackSound
|
||||
def HackSound_=(sound : TriggeredSound.Value) : TriggeredSound.Value = {
|
||||
hackSound = sound
|
||||
hackSound
|
||||
}
|
||||
|
||||
/** The duration in seconds a hack lasts for, based on the hacker's certification level */
|
||||
private var hackEffectDuration = Array(0, 0, 0 , 0)
|
||||
def HackEffectDuration: Array[Int] = hackEffectDuration
|
||||
def HackEffectDuration_=(arr: Array[Int]) : Array[Int] = {
|
||||
hackEffectDuration = arr
|
||||
arr
|
||||
}
|
||||
|
||||
/** How long it takes to hack the object in seconds, based on the hacker's certification level */
|
||||
private var hackDuration = Array(0, 0, 0, 0)
|
||||
def HackDuration: Array[Int] = hackDuration
|
||||
def HackDuration_=(arr: Array[Int]) : Array[Int] = {
|
||||
hackDuration = arr
|
||||
arr
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ import net.psforever.packet.game.TriggeredSound
|
|||
class IFFLock(private val idef : IFFLockDefinition) extends Amenity with Hackable {
|
||||
def Definition : IFFLockDefinition = idef
|
||||
HackSound = TriggeredSound.HackDoor
|
||||
HackEffectDuration = Array(60, 180, 300, 360)
|
||||
HackDuration = Array(5, 3, 1, 1)
|
||||
}
|
||||
|
||||
object IFFLock {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import net.psforever.packet.game.TriggeredSound
|
|||
class Locker extends Amenity with Hackable {
|
||||
def Definition : LockerDefinition = GlobalDefinitions.mb_locker
|
||||
HackSound = TriggeredSound.HackTerminal
|
||||
HackEffectDuration = Array(0, 30, 60, 90)
|
||||
HackDuration = Array(0, 10, 5, 3)
|
||||
}
|
||||
|
||||
object Locker {
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ class ResourceSiloControl(resourceSilo : ResourceSilo) extends Actor with Factio
|
|||
if(resourceSilo.CapacitorDisplay != ntuBarLevel) {
|
||||
log.trace(s"Silo ${resourceSilo.GUID} NTU bar level has changed from ${resourceSilo.CapacitorDisplay} to ${ntuBarLevel}")
|
||||
resourceSilo.CapacitorDisplay = ntuBarLevel
|
||||
resourceSilo.Owner.Actor ! Building.SendMapUpdateToAllClients()
|
||||
resourceSilo.Owner.Actor ! Building.SendMapUpdate(all_clients = true)
|
||||
avatarService ! AvatarServiceMessage(resourceSilo.Owner.asInstanceOf[Building].Zone.Id, AvatarAction.PlanetsideAttribute(resourceSilo.GUID, 45, resourceSilo.CapacitorDisplay))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,6 @@ object Building {
|
|||
val obj = new Building(id, zone, buildingType)
|
||||
obj.Position = location
|
||||
obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$id-$buildingType-building")
|
||||
obj.Actor ! "startup"
|
||||
obj
|
||||
}
|
||||
|
||||
|
|
@ -76,9 +75,8 @@ object Building {
|
|||
import akka.actor.Props
|
||||
val obj = new Building(id, zone, buildingType)
|
||||
obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$id-$buildingType-building")
|
||||
obj.Actor ! "startup"
|
||||
obj
|
||||
}
|
||||
|
||||
final case class SendMapUpdateToAllClients()
|
||||
final case class SendMapUpdate(all_clients: Boolean)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,74 +1,114 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.serverobject.structures
|
||||
|
||||
import akka.actor.{Actor, ActorRef}
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
import akka.actor.{Actor, ActorRef, Props}
|
||||
import net.psforever.objects.GlobalDefinitions
|
||||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||
import net.psforever.objects.serverobject.hackable.Hackable
|
||||
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
|
||||
import net.psforever.objects.serverobject.terminals.CaptureTerminal
|
||||
import net.psforever.packet.game.{BuildingInfoUpdateMessage, PlanetSideGeneratorState}
|
||||
import net.psforever.types.PlanetSideEmpire
|
||||
import services.ServiceManager
|
||||
import services.ServiceManager.Lookup
|
||||
import services.galaxy.{GalaxyAction, GalaxyServiceMessage}
|
||||
import services.galaxy.{GalaxyAction, GalaxyResponse, GalaxyServiceMessage, GalaxyServiceResponse}
|
||||
import services.local.support.HackCaptureActor
|
||||
|
||||
import scala.util.Success
|
||||
import scala.concurrent.duration._
|
||||
import akka.pattern.ask
|
||||
|
||||
import scala.concurrent.{Await, Future}
|
||||
|
||||
class BuildingControl(building : Building) extends Actor with FactionAffinityBehavior.Check {
|
||||
def FactionObject : FactionAffinity = building
|
||||
var galaxyService : ActorRef = Actor.noSender
|
||||
var localService : ActorRef = Actor.noSender
|
||||
private[this] val log = org.log4s.getLogger
|
||||
|
||||
def receive : Receive = {
|
||||
case "startup" =>
|
||||
ServiceManager.serviceManager ! Lookup("galaxy") //ask for a resolver to deal with the GUID system
|
||||
|
||||
case ServiceManager.LookupResult("galaxy", endpoint) =>
|
||||
galaxyService = endpoint
|
||||
log.trace("BuildingControl: Building " + building.ModelId + " Got galaxy service " + endpoint)
|
||||
|
||||
// todo: This is just a temporary solution to drain NTU over time. When base object destruction is properly implemented NTU should be deducted when base objects repair themselves
|
||||
context.become(Processing)
|
||||
|
||||
case _ => log.warn("Message received before startup called");
|
||||
override def preStart = {
|
||||
log.info(s"Starting BuildingControl for ${building.GUID} / ${building.ModelId}")
|
||||
ServiceManager.serviceManager ! Lookup("galaxy")
|
||||
ServiceManager.serviceManager ! Lookup("local")
|
||||
}
|
||||
|
||||
def Processing : Receive = checkBehavior.orElse {
|
||||
def receive : Receive = checkBehavior.orElse {
|
||||
case ServiceManager.LookupResult("galaxy", endpoint) =>
|
||||
galaxyService = endpoint
|
||||
log.info("BuildingControl: Building " + building.ModelId + " Got galaxy service " + endpoint)
|
||||
case ServiceManager.LookupResult("local", endpoint) =>
|
||||
localService = endpoint
|
||||
log.info("BuildingControl: Building " + building.ModelId + " Got local service " + endpoint)
|
||||
case FactionAffinity.ConvertFactionAffinity(faction) =>
|
||||
val originalAffinity = building.Faction
|
||||
if(originalAffinity != (building.Faction = faction)) {
|
||||
building.Amenities.foreach(_.Actor forward FactionAffinity.ConfirmFactionAffinity())
|
||||
}
|
||||
sender ! FactionAffinity.AssertFactionAffinity(building, faction)
|
||||
case Building.SendMapUpdateToAllClients() =>
|
||||
log.info(s"Sending BuildingInfoUpdateMessage update to all clients. Zone: ${building.Zone.Number} - Building: ${building.ModelId}")
|
||||
case Building.SendMapUpdate(all_clients: Boolean) =>
|
||||
log.info(s"Sending BuildingInfoUpdateMessage update. Zone: ${building.Zone.Number} - Building: ${building.ModelId}")
|
||||
var ntuLevel = 0
|
||||
var is_hacked = false
|
||||
var hack_time_remaining_ms = 0L;
|
||||
var hacked_by_faction = PlanetSideEmpire.NEUTRAL
|
||||
|
||||
// Get Ntu level from silo if it exists
|
||||
building.Amenities.filter(x => (x.Definition == GlobalDefinitions.resource_silo)).headOption.asInstanceOf[Option[ResourceSilo]] match {
|
||||
case Some(obj: ResourceSilo) =>
|
||||
ntuLevel = obj.CapacitorDisplay.toInt
|
||||
case _ => ;
|
||||
}
|
||||
galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(
|
||||
BuildingInfoUpdateMessage(
|
||||
|
||||
// Get hack status & time from control console if it exists
|
||||
building.Amenities.filter(x => x.Definition == GlobalDefinitions.capture_terminal).headOption.asInstanceOf[Option[CaptureTerminal with Hackable]] match {
|
||||
case Some(obj: CaptureTerminal with Hackable) =>
|
||||
if(!obj.HackedBy.isEmpty) {
|
||||
is_hacked = true
|
||||
hacked_by_faction = obj.HackedBy.get._1.Faction
|
||||
}
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
val future = ask(localService, HackCaptureActor.GetHackTimeRemainingNanos(obj.GUID))(1 second)
|
||||
|
||||
//todo: this is blocking. Not so bad when we're only retrieving one piece of data but as more functionality is added we'll need to change this to be async but wait for all replies before sending BIUM to clients
|
||||
val time = Await.result(future, 1 second).asInstanceOf[Long]
|
||||
hack_time_remaining_ms = TimeUnit.MILLISECONDS.convert(time, TimeUnit.NANOSECONDS)
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
val msg = BuildingInfoUpdateMessage(
|
||||
continent_id = building.Zone.Number, //Zone
|
||||
building_id = building.Id, //Facility
|
||||
ntu_level = ntuLevel,
|
||||
is_hacked = false, //Hacked
|
||||
PlanetSideEmpire.NEUTRAL, //Base hacked by
|
||||
hack_time_remaining = 0, //Time remaining for hack (ms)
|
||||
empire_own = building.Faction, //Base owned by
|
||||
is_hacked,
|
||||
hacked_by_faction,
|
||||
hack_time_remaining_ms,
|
||||
empire_own = building.Faction,
|
||||
unk1 = 0, //!! Field != 0 will cause malformed packet. See class def.
|
||||
unk1x = None,
|
||||
generator_state = PlanetSideGeneratorState.Normal, //Generator state
|
||||
spawn_tubes_normal = true, //Respawn tubes operating state
|
||||
force_dome_active = false, //Force dome state
|
||||
lattice_benefit = 0, //Lattice benefits
|
||||
generator_state = PlanetSideGeneratorState.Normal,
|
||||
spawn_tubes_normal = true,
|
||||
force_dome_active = false,
|
||||
lattice_benefit = 0,
|
||||
cavern_benefit = 0, //!! Field > 0 will cause malformed packet. See class def.
|
||||
unk4 = Nil,
|
||||
unk5 = 0,
|
||||
unk6 = false,
|
||||
unk7 = 8, //!! Field != 8 will cause malformed packet. See class def.
|
||||
unk7x = None,
|
||||
boost_spawn_pain = false, //Boosted spawn room pain field
|
||||
boost_generator_pain = false //Boosted generator room pain field
|
||||
)))
|
||||
boost_spawn_pain = false,
|
||||
boost_generator_pain = false
|
||||
)
|
||||
|
||||
if(all_clients) {
|
||||
galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(msg))
|
||||
} else {
|
||||
// Fake a GalaxyServiceResponse response back to just the sender
|
||||
sender ! GalaxyServiceResponse("", GalaxyResponse.MapUpdate(msg))
|
||||
}
|
||||
|
||||
case default =>
|
||||
log.warn(s"BuildingControl: Unknown message ${default} received from ${sender().path}")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
package net.psforever.objects.serverobject.terminals
|
||||
|
||||
|
||||
import net.psforever.objects.serverobject.hackable.Hackable
|
||||
import net.psforever.objects.serverobject.structures.Amenity
|
||||
import net.psforever.packet.game.TriggeredSound
|
||||
|
||||
class CaptureTerminal(private val idef : CaptureTerminalDefinition) extends Amenity with Hackable {
|
||||
def Definition : CaptureTerminalDefinition = idef
|
||||
HackDuration = Array(60, 40, 20, 15)
|
||||
HackSound = TriggeredSound.HackTerminal
|
||||
}
|
||||
|
||||
object CaptureTerminal {
|
||||
/**
|
||||
* Overloaded constructor.
|
||||
* @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
|
||||
*/
|
||||
def apply(tdef : CaptureTerminalDefinition) : CaptureTerminal = {
|
||||
new CaptureTerminal(tdef)
|
||||
}
|
||||
|
||||
import akka.actor.ActorContext
|
||||
def Constructor(tdef: CaptureTerminalDefinition)(id : Int, context : ActorContext) : CaptureTerminal = {
|
||||
import akka.actor.Props
|
||||
val obj = CaptureTerminal(tdef)
|
||||
obj.Actor = context.actorOf(Props(classOf[CaptureTerminalControl], obj), s"${tdef.Name}_$id")
|
||||
obj
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.serverobject.terminals
|
||||
|
||||
import akka.actor.Actor
|
||||
import net.psforever.objects.serverobject.CommonMessages
|
||||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||
|
||||
|
||||
class CaptureTerminalControl(terminal : CaptureTerminal) extends Actor with FactionAffinityBehavior.Check {
|
||||
def FactionObject : FactionAffinity = terminal
|
||||
|
||||
def receive : Receive = checkBehavior.orElse {
|
||||
case CommonMessages.Hack(player) =>
|
||||
terminal.HackedBy = player
|
||||
sender ! true
|
||||
case CommonMessages.ClearHack() =>
|
||||
terminal.HackedBy = None
|
||||
|
||||
case _ => ; //no default message
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package net.psforever.objects.serverobject.terminals
|
||||
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
|
||||
class CaptureTerminalDefinition(objectId : Int) extends ObjectDefinition(objectId) {
|
||||
Name = if(objectId == 158) {
|
||||
"capture_terminal"
|
||||
} else if (objectId == 751) {
|
||||
"secondary_capture"
|
||||
} else {
|
||||
throw new IllegalArgumentException("Not a valid capture terminal object id")
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,8 @@ import net.psforever.types.TransactionType
|
|||
*/
|
||||
class Terminal(tdef : TerminalDefinition) extends Amenity with Hackable {
|
||||
HackSound = TriggeredSound.HackTerminal
|
||||
HackEffectDuration = Array(0, 30, 60, 90)
|
||||
HackDuration = Array(0, 10, 5, 3)
|
||||
|
||||
//the following fields and related methods are neither finalized nor integrated; GOTO Request
|
||||
private var health : Int = 100 //TODO not real health value
|
||||
|
|
|
|||
|
|
@ -61,7 +61,8 @@ class InterstellarCluster(zones : List[Zone]) extends Actor {
|
|||
sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, None)
|
||||
}
|
||||
|
||||
case _ => ;
|
||||
case _ =>
|
||||
log.warn(s"InterstellarCluster received unknown message");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ object HackState extends Enumeration {
|
|||
* 2 when performing (phalanx) upgrades;
|
||||
* 3 for building objects during login phase;
|
||||
* hack type?
|
||||
* possibly player hacking level 0-3?
|
||||
* @param target_guid the target of the hack
|
||||
* @param player_guid the player
|
||||
* @param progress the amount of progress visible;
|
||||
|
|
@ -65,6 +66,7 @@ object HackState extends Enumeration {
|
|||
* doesn't seem to be `char_id`?
|
||||
* @param hack_state hack state
|
||||
* @param unk7 na;
|
||||
* 5 - boost pain field at matrixing terminal?
|
||||
* usually, 8?
|
||||
*/
|
||||
final case class HackMessage(unk1 : Int,
|
||||
|
|
|
|||
|
|
@ -51,6 +51,15 @@ import scodec.codecs._
|
|||
* `17 - BEP. Value seems to be the same as BattleExperienceMessage`<br>
|
||||
* `18 - CEP.`<br>
|
||||
* `19 - Anchors. Value is 0 to disengage, 1 to engage.`<br>
|
||||
* `20 - Control console hacking. "The FactionName has hacked into BaseName` - also sets timer on CC and yellow base warning lights on<br>
|
||||
* <ul>
|
||||
* <li>65535 segments per faction in deciseconds (seconds * 10)</li>
|
||||
* <li>0-65535 = Neutral 0 seconds to 1h 49m 14s</li>
|
||||
* <li>65536 - 131071 - TR</li>
|
||||
* <li>131072 - 196607 - NC</li>
|
||||
* <li>196608 - 262143 - VS</li>
|
||||
* <li>17039360 - CC Resecured</li>
|
||||
* </ul>
|
||||
* `24 - Learn certifications with value :`<br>
|
||||
* 01 : Medium Assault<br>
|
||||
* 02 : Heavy Assault<br>
|
||||
|
|
@ -127,6 +136,8 @@ import scodec.codecs._
|
|||
* `116 - Apply colour to REK beam and REK icon above players (0 = yellow, 1 = red, 2 = purple, 3 = blue)`<br>
|
||||
* Client to Server : <br>
|
||||
* `106 - Custom Head`<br>
|
||||
* `224 - Player/vehicle joins black ops`<br>
|
||||
* `228 - Player/vehicle leaves black ops`<br>
|
||||
* <br>
|
||||
* `Vehicles:`<br>
|
||||
* `10 - Driver seat permissions (0 = Locked, 1 = Group, 3 = Empire)`<br>
|
||||
|
|
|
|||
|
|
@ -10,22 +10,26 @@ import scodec.codecs._
|
|||
* (Where the child object was before it was moved is not specified or important.)
|
||||
* @see `Definition.ObjectId`<br>
|
||||
* `TurretUpgrade`
|
||||
* @param avatar_guid the player
|
||||
* @param item_used_guid the item used;
|
||||
* e.g., a REK to hack, or a medkit to heal
|
||||
* @param object_guid the object affected;
|
||||
* e.g., door when opened, terminal when accessed, avatar when medkit used
|
||||
* @param unk2 na;
|
||||
* when upgrading phalanx turrets, 1 for `AVCombo` and 2 for `FlakCombo`
|
||||
* @param unk3 using tools, e.g., a REK or nano-dispenser
|
||||
* @param unk4 na
|
||||
* @param unk5 na
|
||||
* @param unk6 na
|
||||
* @param unk7 na;
|
||||
* 25 when door 223 when terminal
|
||||
* @param unk8 na;
|
||||
* 0 when door 1 when use rek (252 then equipment term)
|
||||
* @param object_id he object id for `object_guid`'s object
|
||||
* @param avatar_guid the player
|
||||
* @param item_used_guid the item used;
|
||||
* e.g. a REK to hack or a medkit to heal.
|
||||
* @param object_guid the object affected;
|
||||
* e.g., door when opened, terminal when accessed, avatar when medkit used
|
||||
* @param unk2 na;
|
||||
* when upgrading phalanx turrets, 1 for `AVCombo` and 2 for `FlakCombo`
|
||||
* @param unk3 using tools, e.g., a REK or nano-dispenser
|
||||
* @param unk4 seems to be related to T-REK viruses.
|
||||
* 0 - unlock all doors
|
||||
* 1 - disable linked benefits
|
||||
* 2 - double ntu drain
|
||||
* 3 - disable enemy radar
|
||||
* 4 - access equipment terminals
|
||||
* @param unk6 na
|
||||
* @param unk7 na;
|
||||
* 25 when door 223 when terminal
|
||||
* @param unk8 na;
|
||||
* 0 when door 1 when use rek (252 then equipment term)
|
||||
* @param object_id the object id `object_guid`'s object
|
||||
*/
|
||||
final case class UseItemMessage(avatar_guid : PlanetSideGUID,
|
||||
item_used_guid : PlanetSideGUID,
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@ package services.local
|
|||
|
||||
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.terminals.CaptureTerminal
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.game.{PlanetSideGUID, TriggeredSound}
|
||||
import net.psforever.types.Vector3
|
||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||
|
||||
object LocalAction {
|
||||
trait Action
|
||||
|
|
@ -13,7 +15,10 @@ object LocalAction {
|
|||
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 HackTemporarily(player_guid : PlanetSideGUID, continent : Zone, target : PlanetSideServerObject, unk1 : Long, duration: Int, unk2 : Long = 8L) extends Action
|
||||
final case class ClearTemporaryHack(player_guid: PlanetSideGUID, target: PlanetSideServerObject with Hackable) extends Action
|
||||
final case class HackCaptureTerminal(player_guid : PlanetSideGUID, continent : Zone, target : CaptureTerminal, unk1 : Long, unk2 : Long = 8L, isResecured : Boolean) extends Action
|
||||
final case class ProximityTerminalEffect(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, effectState : Boolean) extends Action
|
||||
final case class TriggerSound(player_guid : PlanetSideGUID, sound : TriggeredSound.Value, pos : Vector3, unk : Int, volume : Float) extends Action
|
||||
final case class SetEmpire(object_guid: PlanetSideGUID, empire: PlanetSideEmpire.Value) extends Action
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
package services.local
|
||||
|
||||
import net.psforever.packet.game.{PlanetSideGUID, TriggeredSound}
|
||||
import net.psforever.types.Vector3
|
||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||
|
||||
object LocalResponse {
|
||||
trait Response
|
||||
|
|
@ -11,6 +11,8 @@ object LocalResponse {
|
|||
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 HackCaptureTerminal(target_guid : PlanetSideGUID, unk1 : Long, unk2 : Long, isResecured: Boolean) extends Response
|
||||
final case class ProximityTerminalEffect(object_guid : PlanetSideGUID, effectState : Boolean) extends Response
|
||||
final case class TriggerSound(sound : TriggeredSound.Value, pos : Vector3, unk : Int, volume : Float) extends Response
|
||||
final case class SetEmpire(object_guid: PlanetSideGUID, empire: PlanetSideEmpire.Value) extends Response
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,44 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services.local
|
||||
|
||||
import akka.actor.{Actor, Props}
|
||||
import services.{GenericEventBus, Service}
|
||||
import akka.actor.{Actor, ActorRef, Props}
|
||||
import net.psforever.objects.serverobject.CommonMessages
|
||||
import net.psforever.objects.zones.{InterstellarCluster, Zone}
|
||||
import net.psforever.objects.zones.InterstellarCluster.GetWorld
|
||||
import services.local.support.{DoorCloseActor, HackCaptureActor, HackClearActor}
|
||||
import services.{GenericEventBus, Service, ServiceManager}
|
||||
import services.local.support.{DoorCloseActor, HackClearActor}
|
||||
|
||||
import scala.util.Success
|
||||
import scala.concurrent.duration._
|
||||
import akka.pattern.ask
|
||||
import net.psforever.objects.GlobalDefinitions
|
||||
import net.psforever.objects.serverobject.hackable.Hackable
|
||||
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
|
||||
import net.psforever.objects.serverobject.structures.{Amenity, Building}
|
||||
import net.psforever.objects.serverobject.terminals.CaptureTerminal
|
||||
import net.psforever.packet.game.PlanetSideGUID
|
||||
import services.ServiceManager.Lookup
|
||||
|
||||
class LocalService extends Actor {
|
||||
private val doorCloser = context.actorOf(Props[DoorCloseActor], "local-door-closer")
|
||||
private val hackClearer = context.actorOf(Props[HackClearActor], "local-hack-clearer")
|
||||
private val hackCapturer = context.actorOf(Props[HackCaptureActor], "local-hack-capturer")
|
||||
private [this] val log = org.log4s.getLogger
|
||||
var cluster : ActorRef = Actor.noSender
|
||||
|
||||
override def preStart = {
|
||||
log.info("Starting...")
|
||||
ServiceManager.serviceManager ! Lookup("cluster")
|
||||
}
|
||||
|
||||
val LocalEvents = new GenericEventBus[LocalServiceResponse]
|
||||
|
||||
def receive = {
|
||||
case ServiceManager.LookupResult("cluster", endpoint) =>
|
||||
cluster = endpoint
|
||||
log.trace("LocalService got cluster service " + endpoint)
|
||||
|
||||
case Service.Join(channel) =>
|
||||
val path = s"/$channel/Local"
|
||||
val who = sender()
|
||||
|
|
@ -50,11 +72,31 @@ class LocalService extends Actor {
|
|||
LocalEvents.publish(
|
||||
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.HackClear(target.GUID, unk1, unk2))
|
||||
)
|
||||
case LocalAction.HackTemporarily(player_guid, zone, target, unk1, unk2) =>
|
||||
hackClearer ! HackClearActor.ObjectIsHacked(target, zone, unk1, unk2)
|
||||
case LocalAction.HackTemporarily(player_guid, zone, 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(player_guid, target) =>
|
||||
hackClearer ! HackClearActor.ObjectIsResecured(target)
|
||||
case LocalAction.HackCaptureTerminal(player_guid, zone, target, unk1, unk2, isResecured) =>
|
||||
|
||||
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.ProximityTerminalEffect(player_guid, object_guid, effectState) =>
|
||||
LocalEvents.publish(
|
||||
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.ProximityTerminalEffect(object_guid, effectState))
|
||||
|
|
@ -63,6 +105,10 @@ class LocalService extends Actor {
|
|||
LocalEvents.publish(
|
||||
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.TriggerSound(sound, pos, unk, volume))
|
||||
)
|
||||
case LocalAction.SetEmpire(object_guid, empire) =>
|
||||
LocalEvents.publish(
|
||||
LocalServiceResponse(s"/$forChannel/Local", PlanetSideGUID(-1), LocalResponse.SetEmpire(object_guid, empire))
|
||||
)
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
|
|
@ -74,10 +120,48 @@ class LocalService extends Actor {
|
|||
|
||||
//response from HackClearActor
|
||||
case HackClearActor.ClearTheHack(target_guid, zone_id, 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))
|
||||
)
|
||||
|
||||
case HackCaptureActor.HackTimeoutReached(capture_terminal_guid, zone_id, unk1, unk2, hackedByFaction) =>
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
ask(cluster, InterstellarCluster.GetWorld(zone_id))(1 seconds).onComplete {
|
||||
case Success(InterstellarCluster.GiveWorld(zoneId, zone)) =>
|
||||
val terminal = zone.asInstanceOf[Zone].GUID(capture_terminal_guid).get.asInstanceOf[CaptureTerminal]
|
||||
val building = terminal.Owner.asInstanceOf[Building]
|
||||
|
||||
// todo: Move this to a function for Building
|
||||
var ntuLevel = 0
|
||||
building.Amenities.filter(x => (x.Definition == GlobalDefinitions.resource_silo)).headOption.asInstanceOf[Option[ResourceSilo]] match {
|
||||
case Some(obj: ResourceSilo) =>
|
||||
ntuLevel = obj.CapacitorDisplay.toInt
|
||||
case _ =>
|
||||
// Base has no NTU silo - likely a tower / cavern CC
|
||||
ntuLevel = 1
|
||||
}
|
||||
|
||||
if(ntuLevel > 0) {
|
||||
log.info(s"Setting base ${building.ModelId} as owned by ${hackedByFaction}")
|
||||
|
||||
building.Faction = hackedByFaction
|
||||
self ! LocalServiceMessage(zone.Id, LocalAction.SetEmpire(PlanetSideGUID(building.ModelId), 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(PlanetSideGUID(building.ModelId), zone.Id, 3212836864L, 8L)
|
||||
case Success(_) =>
|
||||
log.warn("Got success from InterstellarCluster.GetWorld but didn't know how to handle it")
|
||||
|
||||
case scala.util.Failure(_) => log.warn(s"LocalService Failed to get zone when hack timeout was reached")
|
||||
}
|
||||
case HackCaptureActor.GetHackTimeRemainingNanos(capture_console_guid) =>
|
||||
hackCapturer forward HackCaptureActor.GetHackTimeRemainingNanos(capture_console_guid)
|
||||
case msg =>
|
||||
log.info(s"Unhandled message $msg from $sender")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
package services.local.support
|
||||
|
||||
import akka.actor.{Actor, Cancellable}
|
||||
import net.psforever.objects.DefaultCancellable
|
||||
import net.psforever.objects.serverobject.hackable.Hackable
|
||||
import net.psforever.objects.serverobject.structures.Building
|
||||
import net.psforever.objects.serverobject.terminals.CaptureTerminal
|
||||
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.game.PlanetSideGUID
|
||||
import net.psforever.types.PlanetSideEmpire
|
||||
|
||||
import scala.concurrent.duration.{FiniteDuration, _}
|
||||
|
||||
class HackCaptureActor extends Actor {
|
||||
private [this] val log = org.log4s.getLogger
|
||||
|
||||
private var clearTrigger : Cancellable = DefaultCancellable.obj
|
||||
|
||||
/** A `List` of currently hacked server objects */
|
||||
private var hackedObjects : List[HackCaptureActor.HackEntry] = Nil
|
||||
|
||||
def receive : Receive = {
|
||||
case HackCaptureActor.ObjectIsHacked(target, zone, unk1, unk2, duration, time) =>
|
||||
log.trace(s"${target.GUID} is hacked.")
|
||||
|
||||
hackedObjects.filter(x => x.target == target).headOption match {
|
||||
case Some(x) =>
|
||||
log.trace(s"${target.GUID} was already hacked - removing it from the hacked objects list before re-adding it.")
|
||||
hackedObjects = hackedObjects.filterNot(x => x.target == target)
|
||||
log.warn(s"len: ${hackedObjects.length}")
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
hackedObjects = hackedObjects :+ HackCaptureActor.HackEntry(target, zone, unk1, unk2, duration, time)
|
||||
|
||||
// Restart the timer, in case this is the first object in the hacked objects list or the object was removed and re-added
|
||||
RestartTimer()
|
||||
|
||||
target.Owner.Actor ! Building.SendMapUpdate(all_clients = true)
|
||||
|
||||
case HackCaptureActor.ProcessCompleteHacks() =>
|
||||
log.trace("Processing complete hacks")
|
||||
clearTrigger.cancel
|
||||
val now : Long = System.nanoTime
|
||||
val stillHacked = hackedObjects.filter(x => now - x.hack_timestamp <= x.duration.toNanos)
|
||||
val unhackObjects = hackedObjects.filter(x => now - x.hack_timestamp >= x.duration.toNanos)
|
||||
hackedObjects = stillHacked
|
||||
unhackObjects.foreach(entry => {
|
||||
log.trace(s"Capture terminal hack timeout reached for terminal ${entry.target.GUID}")
|
||||
|
||||
val hackedByFaction = entry.target.HackedBy.get._1.Faction
|
||||
entry.target.Actor ! CommonMessages.ClearHack()
|
||||
|
||||
context.parent ! HackCaptureActor.HackTimeoutReached(entry.target.GUID, entry.zone.Id, entry.unk1, entry.unk2, hackedByFaction) //call up to the main event system
|
||||
})
|
||||
|
||||
// If there's hacked objects left in the list restart the timer with the shortest hack time left
|
||||
RestartTimer()
|
||||
|
||||
case HackCaptureActor.ClearHack(target, zone) =>
|
||||
hackedObjects = hackedObjects.filterNot(x => x.target == target)
|
||||
target.Owner.Actor ! Building.SendMapUpdate(all_clients = true)
|
||||
|
||||
// Restart the timer in case the object we just removed was the next one scheduled
|
||||
RestartTimer()
|
||||
|
||||
case HackCaptureActor.GetHackTimeRemainingNanos(capture_console_guid) =>
|
||||
hackedObjects.filter(x => x.target.GUID == capture_console_guid).headOption match {
|
||||
case Some(obj: HackCaptureActor.HackEntry) =>
|
||||
val time_left: Long = obj.duration.toNanos - (System.nanoTime - obj.hack_timestamp)
|
||||
sender ! time_left
|
||||
case _ =>
|
||||
log.warn(s"Couldn't find capture terminal guid ${capture_console_guid} in hackedObjects list")
|
||||
sender ! 0L
|
||||
}
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
private def RestartTimer(): Unit = {
|
||||
if(hackedObjects.length != 0) {
|
||||
val now = System.nanoTime()
|
||||
def minTimeLeft(entry1: HackCaptureActor.HackEntry, entry2: HackCaptureActor.HackEntry): HackCaptureActor.HackEntry = {
|
||||
val entry1TimeLeft = entry1.duration.toNanos - (now - entry1.hack_timestamp)
|
||||
val entry2TimeLeft = entry2.duration.toNanos - (now - entry2.hack_timestamp)
|
||||
if(entry1TimeLeft < entry2TimeLeft) entry1 else entry2
|
||||
}
|
||||
|
||||
val hackEntry = hackedObjects.reduceLeft(minTimeLeft)
|
||||
val short_timeout : FiniteDuration = math.max(1, hackEntry.duration.toNanos - (System.nanoTime - hackEntry.hack_timestamp)) nanoseconds
|
||||
|
||||
log.trace(s"Still items left in hacked objects list. Checking again in ${short_timeout}")
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
clearTrigger = context.system.scheduler.scheduleOnce(short_timeout, self, HackCaptureActor.ProcessCompleteHacks())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object HackCaptureActor {
|
||||
final case class ObjectIsHacked(target : CaptureTerminal, zone : Zone, unk1 : Long, unk2 : Long, duration: FiniteDuration, time : Long = System.nanoTime())
|
||||
|
||||
final case class HackTimeoutReached(capture_terminal_guid : PlanetSideGUID, zone_id : String, unk1 : Long, unk2 : Long, hackedByFaction : PlanetSideEmpire.Value)
|
||||
|
||||
final case class ClearHack(target : CaptureTerminal, zone : Zone)
|
||||
|
||||
final case class GetHackTimeRemainingNanos(capture_console_guid: PlanetSideGUID)
|
||||
|
||||
|
||||
private final case class ProcessCompleteHacks()
|
||||
|
||||
private final case class HackEntry(target : PlanetSideServerObject with Hackable, zone : Zone, unk1 : Long, unk2 : Long, duration: FiniteDuration, hack_timestamp : Long)
|
||||
}
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services.local.support
|
||||
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
import akka.actor.{Actor, Cancellable}
|
||||
import net.psforever.objects.DefaultCancellable
|
||||
import net.psforever.objects.serverobject.hackable.Hackable
|
||||
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.game.PlanetSideGUID
|
||||
|
|
@ -20,15 +23,15 @@ class HackClearActor() extends Actor {
|
|||
private var clearTrigger : Cancellable = DefaultCancellable.obj
|
||||
/** A `List` of currently hacked server objects */
|
||||
private var hackedObjects : List[HackClearActor.HackEntry] = Nil
|
||||
//private[this] val log = org.log4s.getLogger
|
||||
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.ObjectIsHacked(target, zone, unk1, unk2, duration, time) =>
|
||||
val durationNanos = TimeUnit.NANOSECONDS.convert(duration, TimeUnit.SECONDS)
|
||||
hackedObjects = hackedObjects :+ HackClearActor.HackEntry(target, zone, unk1, unk2, time, durationNanos)
|
||||
|
||||
// Restart the timer, in case this is the first object in the hacked objects list
|
||||
RestartTimer()
|
||||
|
||||
case HackClearActor.TryClearHacks() =>
|
||||
clearTrigger.cancel
|
||||
|
|
@ -41,15 +44,36 @@ class HackClearActor() extends Actor {
|
|||
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())
|
||||
RestartTimer()
|
||||
|
||||
case HackClearActor.ObjectIsResecured(target) =>
|
||||
val obj = hackedObjects.filter(x => x.target == target).headOption
|
||||
obj match {
|
||||
case Some(entry: HackClearActor.HackEntry) =>
|
||||
hackedObjects = hackedObjects.filterNot(x => x.target == target)
|
||||
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
|
||||
|
||||
// Restart the timer in case the object we just removed was the next one scheduled
|
||||
RestartTimer()
|
||||
case None => ;
|
||||
}
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
private def RestartTimer(): Unit = {
|
||||
if(hackedObjects.length != 0) {
|
||||
val now = System.nanoTime()
|
||||
val (unhackObjects, stillHackedObjects) = PartitionEntries(hackedObjects, now)
|
||||
val short_timeout : FiniteDuration = math.max(1, stillHackedObjects.head.duration - (now - stillHackedObjects.head.time)) nanoseconds
|
||||
|
||||
log.warn(s"Still items left in hacked objects list. Checking again in ${short_timeout}")
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
clearTrigger = context.system.scheduler.scheduleOnce(short_timeout, self, HackClearActor.TryClearHacks())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate over entries in a `List` until an entry that does not exceed the time limit is discovered.
|
||||
* Separate the original `List` into two:
|
||||
|
|
@ -84,7 +108,7 @@ class HackClearActor() extends Actor {
|
|||
}
|
||||
else {
|
||||
val entry = iter.next()
|
||||
if(now - entry.time >= HackClearActor.timeout_time) {
|
||||
if(now - entry.time >= entry.duration) {
|
||||
recursivePartitionEntries(iter, now, index + 1)
|
||||
}
|
||||
else {
|
||||
|
|
@ -95,26 +119,31 @@ class HackClearActor() extends Actor {
|
|||
}
|
||||
|
||||
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
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param duration how long the object is to stay hacked for in seconds
|
||||
* @see `HackEntry`
|
||||
*/
|
||||
final case class ObjectIsHacked(target : PlanetSideServerObject, zone : Zone, unk1 : Long, unk2 : Long, time : Long = System.nanoTime())
|
||||
final case class ObjectIsHacked(target : PlanetSideServerObject, zone : Zone, unk1 : Long, unk2 : Long, duration: Int, time : Long = System.nanoTime())
|
||||
|
||||
/**
|
||||
* Message used to request that a hack is cleared from the hacked objects list and the unhacked status returned to all clients
|
||||
*
|
||||
*/
|
||||
final case class ObjectIsResecured(target: PlanetSideServerObject with Hackable)
|
||||
|
||||
/**
|
||||
* 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 obj 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)
|
||||
final case class ClearTheHack(obj : PlanetSideGUID, zone_id : String, unk1 : Long, unk2 : Long)
|
||||
|
||||
|
||||
/**
|
||||
* Internal message used to signal a test of the queued door information.
|
||||
*/
|
||||
|
|
@ -122,11 +151,12 @@ object HackClearActor {
|
|||
|
||||
/**
|
||||
* 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.
|
||||
* The `zone` is maintained separately to ensure that any message resulting in an attempt to close doors is targeted.
|
||||
* @param target the server object
|
||||
* @param zone the zone in which the object resides
|
||||
* @param time when the object was hacked
|
||||
* @param duration The hack duration in nanoseconds
|
||||
* @see `ObjectIsHacked`
|
||||
*/
|
||||
private final case class HackEntry(target : PlanetSideServerObject, zone : Zone, unk1 : Long, unk2 : Long, time : Long)
|
||||
private final case class HackEntry(target : PlanetSideServerObject, zone : Zone, unk1 : Long, unk2 : Long, time : Long, duration: Long)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ class ResourceSiloControlUpdate1Test extends ActorTest {
|
|||
assert(reply1.asInstanceOf[AvatarServiceMessage]
|
||||
.actionMessage.asInstanceOf[AvatarAction.PlanetsideAttribute].attribute_value == 3)
|
||||
|
||||
assert(reply2.isInstanceOf[Building.SendMapUpdateToAllClients])
|
||||
assert(reply2.isInstanceOf[Building.SendMapUpdate])
|
||||
|
||||
val reply3 = probe1.receiveOne(500 milliseconds)
|
||||
assert(reply3.isInstanceOf[AvatarServiceMessage])
|
||||
|
|
@ -249,7 +249,7 @@ class ResourceSiloControlUpdate2Test extends ActorTest {
|
|||
assert(reply1.asInstanceOf[AvatarServiceMessage]
|
||||
.actionMessage.asInstanceOf[AvatarAction.PlanetsideAttribute].attribute_value == 2)
|
||||
|
||||
assert(reply2.isInstanceOf[Building.SendMapUpdateToAllClients])
|
||||
assert(reply2.isInstanceOf[Building.SendMapUpdate])
|
||||
|
||||
val reply3 = probe1.receiveOne(500 milliseconds)
|
||||
assert(obj.LowNtuWarningOn == false)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import net.psforever.objects.serverobject.mblocker.Locker
|
|||
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
|
||||
import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder, StructureType, WarpGate}
|
||||
import net.psforever.objects.serverobject.terminals.{ProximityTerminal, Terminal}
|
||||
import net.psforever.objects.serverobject.terminals.{CaptureTerminal, ProximityTerminal, Terminal}
|
||||
import net.psforever.objects.serverobject.tube.SpawnTube
|
||||
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
|
||||
import net.psforever.objects.serverobject.turret.MannedTurret
|
||||
|
|
@ -94,8 +95,8 @@ object Maps {
|
|||
DoorToLock(384, 1036)
|
||||
DoorToLock(386, 1038)
|
||||
DoorToLock(388, 1039)
|
||||
// DoorToLock(394, 1047)
|
||||
// DoorToLock(395, 1049)
|
||||
DoorToLock(394, 1047)
|
||||
DoorToLock(395, 1049)
|
||||
DoorToLock(401, 1053)
|
||||
DoorToLock(920, 968)
|
||||
|
||||
|
|
@ -173,6 +174,7 @@ object Maps {
|
|||
|
||||
def Building9() : Unit = { // Girru
|
||||
LocalBuilding(9, FoundationBuilder(Building.Structure(StructureType.Facility, Vector3(4397f, 5895f, 0)))) // Todo change pos
|
||||
LocalObject(225, CaptureTerminal.Constructor(capture_terminal))
|
||||
LocalObject(513, Door.Constructor)
|
||||
LocalObject(514, Door.Constructor)
|
||||
LocalObject(515, Door.Constructor)
|
||||
|
|
@ -225,6 +227,7 @@ object Maps {
|
|||
LocalObject(2015, Terminal.Constructor(order_terminal))
|
||||
LocalObject(2016, Terminal.Constructor(order_terminal))
|
||||
LocalObject(2017, Terminal.Constructor(order_terminal))
|
||||
LocalObject(2658, ResourceSilo.Constructor)
|
||||
LocalObject(2724, SpawnTube.Constructor(Vector3(4396.7656f, 5888.258f, 71.15625f), Vector3(0, 0, 92)))
|
||||
LocalObject(2725, SpawnTube.Constructor(Vector3(4397.211f, 5895.547f, 71.15625f), Vector3(0, 0, 92)))
|
||||
LocalObject(2726, SpawnTube.Constructor(Vector3(4397.2344f, 5902.8203f, 71.15625f), Vector3(0, 0, 92)))
|
||||
|
|
@ -244,6 +247,7 @@ object Maps {
|
|||
// LocalObject(1909, ProximityTerminal.Constructor(medical_terminal))
|
||||
// LocalObject(1910, ProximityTerminal.Constructor(medical_terminal))
|
||||
|
||||
ObjectToBuilding(225, 9)
|
||||
ObjectToBuilding(513, 9)
|
||||
ObjectToBuilding(514, 9)
|
||||
ObjectToBuilding(515, 9)
|
||||
|
|
@ -298,6 +302,7 @@ object Maps {
|
|||
ObjectToBuilding(2015, 9)
|
||||
ObjectToBuilding(2016, 9)
|
||||
ObjectToBuilding(2017, 9)
|
||||
ObjectToBuilding(2658, 9)
|
||||
ObjectToBuilding(2724, 9)
|
||||
ObjectToBuilding(2725, 9)
|
||||
ObjectToBuilding(2726, 9)
|
||||
|
|
@ -332,6 +337,7 @@ object Maps {
|
|||
|
||||
def Building10() : Unit = { // Hanish
|
||||
LocalBuilding(10, FoundationBuilder(Building.Structure(StructureType.Facility, Vector3(3749f, 5477f, 0)))) // Todo change pos
|
||||
LocalObject(223, CaptureTerminal.Constructor(capture_terminal))
|
||||
LocalObject(464, Door.Constructor)
|
||||
LocalObject(470, Door.Constructor(Vector3(3645.3984f, 5451.9688f, 88.890625f), Vector3(0, 0, 182)))
|
||||
LocalObject(471, Door.Constructor)
|
||||
|
|
@ -374,6 +380,7 @@ object Maps {
|
|||
LocalObject(971, IFFLock.Constructor)
|
||||
LocalObject(1105, IFFLock.Constructor)
|
||||
LocalObject(1106, IFFLock.Constructor)
|
||||
LocalObject(1107, IFFLock.Constructor)
|
||||
LocalObject(1108, IFFLock.Constructor)
|
||||
LocalObject(1113, IFFLock.Constructor)
|
||||
LocalObject(1114, IFFLock.Constructor)
|
||||
|
|
@ -431,6 +438,7 @@ object Maps {
|
|||
|
||||
// ObjectToBuilding(169, 10)
|
||||
// ObjectToBuilding(1906, 10)
|
||||
ObjectToBuilding(223, 10)
|
||||
ObjectToBuilding(464, 10)
|
||||
ObjectToBuilding(470, 10)
|
||||
ObjectToBuilding(471, 10)
|
||||
|
|
@ -469,10 +477,12 @@ object Maps {
|
|||
ObjectToBuilding(923, 10)
|
||||
ObjectToBuilding(932, 10)
|
||||
ObjectToBuilding(933, 10)
|
||||
ObjectToBuilding(959, 10)
|
||||
|
||||
ObjectToBuilding(971, 10)
|
||||
ObjectToBuilding(1105, 10)
|
||||
ObjectToBuilding(1106, 10)
|
||||
ObjectToBuilding(1107, 10)
|
||||
ObjectToBuilding(1108, 10)
|
||||
ObjectToBuilding(1113, 10)
|
||||
ObjectToBuilding(1114, 10)
|
||||
|
|
@ -528,6 +538,7 @@ object Maps {
|
|||
DoorToLock(475, 1108)
|
||||
DoorToLock(481, 1113)
|
||||
DoorToLock(771, 1106)
|
||||
DoorToLock(774, 1107)
|
||||
DoorToLock(779, 1114)
|
||||
DoorToLock(784, 1116)
|
||||
DoorToLock(785, 1115)
|
||||
|
|
@ -761,6 +772,7 @@ object Maps {
|
|||
}
|
||||
def Building33() : Unit = { // East Girru Gun Tower, Ishundar (ID: 62)
|
||||
LocalBuilding(33, FoundationBuilder(Building.Structure(StructureType.Tower, Vector3(4624f, 5915f, 0)))) // TODO loc
|
||||
LocalObject(2792, CaptureTerminal.Constructor(secondary_capture))
|
||||
LocalObject(2957, Door.Constructor)
|
||||
LocalObject(2958, Door.Constructor)
|
||||
LocalObject(542, Door.Constructor(Vector3(4625.9844f, 5910.211f, 55.75f), Vector3(0, 0, 180)))
|
||||
|
|
@ -777,6 +789,7 @@ object Maps {
|
|||
LocalObject(2733, SpawnTube.Constructor(respawn_tube_tower, Vector3(4624.758f, 5905.7344f, 45.984375f), Vector3(0, 0, 90)))
|
||||
LocalObject(2734, SpawnTube.Constructor(respawn_tube_tower, Vector3(4624.7266f, 5922.1484f, 45.984375f), Vector3(0, 0, 90)))
|
||||
|
||||
ObjectToBuilding(2792, 33)
|
||||
ObjectToBuilding(2957, 33)
|
||||
ObjectToBuilding(2958, 33)
|
||||
ObjectToBuilding(542, 33)
|
||||
|
|
@ -1694,6 +1707,7 @@ object Maps {
|
|||
LocalObject(396, Door.Constructor)
|
||||
LocalObject(397, Door.Constructor)
|
||||
LocalObject(398, Door.Constructor)
|
||||
LocalObject(399, Door.Constructor)
|
||||
LocalObject(462, Door.Constructor)
|
||||
LocalObject(463, Door.Constructor)
|
||||
LocalObject(522, ImplantTerminalMech.Constructor)
|
||||
|
|
@ -1741,6 +1755,7 @@ object Maps {
|
|||
ObjectToBuilding(396, 2)
|
||||
ObjectToBuilding(397, 2)
|
||||
ObjectToBuilding(398, 2)
|
||||
ObjectToBuilding(399, 2)
|
||||
ObjectToBuilding(462, 2)
|
||||
ObjectToBuilding(463, 2)
|
||||
ObjectToBuilding(522, 2)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware}
|
||||
|
|
@ -53,10 +54,11 @@ import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage,
|
|||
import scala.concurrent.duration._
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.annotation.tailrec
|
||||
import scala.concurrent.Future
|
||||
import scala.concurrent.{Await, Future}
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.Success
|
||||
import akka.pattern.ask
|
||||
import services.local.support.HackCaptureActor
|
||||
|
||||
class WorldSessionActor extends Actor with MDCContextAware {
|
||||
import WorldSessionActor._
|
||||
|
|
@ -560,10 +562,16 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
sendResponse(GenericObjectStateMsg(door_guid, 17))
|
||||
|
||||
case LocalResponse.HackClear(target_guid, unk1, unk2) =>
|
||||
log.trace(s"Clearing hack for ${target_guid}")
|
||||
// Reset hack state for all players
|
||||
sendResponse(HackMessage(0, target_guid, guid, 0, unk1, HackState.HackCleared, unk2))
|
||||
// Set the object faction displayed back to it's original owner faction
|
||||
sendResponse(SetEmpireMessage(target_guid, continent.GUID(target_guid).get.asInstanceOf[FactionAffinity].Faction))
|
||||
|
||||
continent.GUID(target_guid) match {
|
||||
case Some(obj) =>
|
||||
sendResponse(SetEmpireMessage(target_guid, obj.asInstanceOf[FactionAffinity].Faction))
|
||||
case None => ;
|
||||
}
|
||||
|
||||
case LocalResponse.HackObject(target_guid, unk1, unk2) =>
|
||||
if(tplayer_guid != guid && continent.GUID(target_guid).get.asInstanceOf[Hackable].HackedBy.get._1.Faction != player.Faction) {
|
||||
|
|
@ -576,7 +584,31 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
// Make the hacked object look like it belongs to the hacking empire, but only for that empire's players (so that infiltrators on stealth missions won't be given away to opposing factions)
|
||||
sendResponse(SetEmpireMessage(target_guid, player.Faction))
|
||||
}
|
||||
case LocalResponse.HackCaptureTerminal(target_guid, unk1, unk2, isResecured) =>
|
||||
var value = 0L
|
||||
|
||||
if(isResecured) {
|
||||
value = 17039360L
|
||||
} else {
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
val future = ask(localService, HackCaptureActor.GetHackTimeRemainingNanos(target_guid))(1 second)
|
||||
val time = Await.result(future, 1 second).asInstanceOf[Long] // todo: blocking call. Not good.
|
||||
val hack_time_remaining_ms = TimeUnit.MILLISECONDS.convert(time, TimeUnit.NANOSECONDS)
|
||||
val deciseconds_remaining = (hack_time_remaining_ms / 100)
|
||||
|
||||
val hacking_faction = continent.GUID(target_guid).get.asInstanceOf[Hackable].HackedBy.get._1.Faction
|
||||
|
||||
// See PlanetSideAttributeMessage #20 documentation for an explanation of how the timer is calculated
|
||||
val start_num = hacking_faction match {
|
||||
case PlanetSideEmpire.TR => 65536L
|
||||
case PlanetSideEmpire.NC => 131072L
|
||||
case PlanetSideEmpire.VS => 196608L
|
||||
}
|
||||
|
||||
value = start_num + deciseconds_remaining
|
||||
}
|
||||
|
||||
sendResponse(PlanetsideAttributeMessage(target_guid, 20, value))
|
||||
case LocalResponse.ProximityTerminalEffect(object_guid, effectState) =>
|
||||
if(tplayer_guid != guid) {
|
||||
sendResponse(ProximityTerminalUseMessage(PlanetSideGUID(0), object_guid, effectState))
|
||||
|
|
@ -585,6 +617,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
case LocalResponse.TriggerSound(sound, pos, unk, volume) =>
|
||||
sendResponse(TriggerSoundMessage(sound, pos, unk, volume))
|
||||
|
||||
case LocalResponse.SetEmpire(object_guid, empire) =>
|
||||
sendResponse(SetEmpireMessage(object_guid, empire))
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
|
|
@ -1451,6 +1485,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
val popNC = poplist.count(_.faction == PlanetSideEmpire.NC)
|
||||
val popVS = poplist.count(_.faction == PlanetSideEmpire.VS)
|
||||
|
||||
// StopBundlingPackets() is called on ClientInitializationComplete
|
||||
StartBundlingPackets()
|
||||
zone.Buildings.foreach({ case(id, building) => initBuilding(continentNumber, id, building) })
|
||||
sendResponse(ZonePopulationUpdateMessage(continentNumber, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO))
|
||||
|
|
@ -2764,7 +2799,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
case Some(unholsteredItem : Equipment) =>
|
||||
if(unholsteredItem.Definition == GlobalDefinitions.remote_electronics_kit) {
|
||||
// Player has ulholstered a REK - we need to set an atttribute on the REK itself to change the beam/icon colour to the correct one for the player's hack level
|
||||
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttribute(unholsteredItem.GUID, 116, GetPlayerHackData().hackLevel))
|
||||
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttribute(unholsteredItem.GUID, 116, GetPlayerHackLevel()))
|
||||
}
|
||||
case None => ;
|
||||
}
|
||||
|
|
@ -2971,9 +3006,21 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
continent.GUID(object_guid) match {
|
||||
case Some(door : Door) =>
|
||||
if(player.Faction == door.Faction || ((continent.Map.DoorToLock.get(object_guid.guid) match {
|
||||
case Some(lock_guid) => continent.GUID(lock_guid).get.asInstanceOf[IFFLock].HackedBy.isDefined
|
||||
case Some(lock_guid) =>
|
||||
val lock = continent.GUID(lock_guid).get.asInstanceOf[IFFLock]
|
||||
|
||||
var baseIsHacked = false
|
||||
lock.Owner.asInstanceOf[Building].Amenities.filter(x => x.Definition == GlobalDefinitions.capture_terminal).headOption.asInstanceOf[Option[CaptureTerminal]] match {
|
||||
case Some(obj: CaptureTerminal) =>
|
||||
baseIsHacked = obj.HackedBy.isDefined
|
||||
case None => ;
|
||||
}
|
||||
|
||||
// If the IFF lock has been hacked OR the base is neutral OR the base linked to the lock is hacked then open the door
|
||||
lock.HackedBy.isDefined || baseIsHacked || lock.Faction == PlanetSideEmpire.NEUTRAL
|
||||
case None => !door.isOpen
|
||||
}) || Vector3.ScalarProjection(door.Outwards, player.Position - door.Position) < 0f)) {
|
||||
// We're on the inside of the door - open the door
|
||||
door.Actor ! Door.Use(player, msg)
|
||||
}
|
||||
else if(door.isOpen) {
|
||||
|
|
@ -2995,13 +3042,25 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
|
||||
case Some(panel : IFFLock) =>
|
||||
if(panel.Faction != player.Faction && panel.HackedBy.isEmpty) {
|
||||
if((panel.Faction != player.Faction && panel.HackedBy.isEmpty) || (panel.Faction == player.Faction && panel.HackedBy.isDefined)) {
|
||||
player.Slot(player.DrawnSlot).Equipment match {
|
||||
case Some(tool : SimpleItem) =>
|
||||
if(tool.Definition == GlobalDefinitions.remote_electronics_kit) {
|
||||
progressBarValue = Some(-GetPlayerHackSpeed())
|
||||
self ! WorldSessionActor.HackingProgress(1, player, panel, tool.GUID, GetPlayerHackSpeed(), FinishHacking(panel, 1114636288L))
|
||||
log.info("Hacking a door~")
|
||||
val hackSpeed = GetPlayerHackSpeed(panel)
|
||||
|
||||
if(hackSpeed > 0) {
|
||||
progressBarValue = Some(-hackSpeed)
|
||||
if(panel.Faction != player.Faction) {
|
||||
// Enemy faction is hacking this IFF lock
|
||||
self ! WorldSessionActor.HackingProgress(progressType = 1, player, panel, tool.GUID, hackSpeed, FinishHacking(panel, 1114636288L))
|
||||
log.info("Hacking an IFF lock")
|
||||
} else {
|
||||
// IFF Lock is being resecured by it's owner faction
|
||||
self ! WorldSessionActor.HackingProgress(progressType = 1, player, panel, tool.GUID, hackSpeed, FinishResecuringIFFLock(panel))
|
||||
log.info("Resecuring an IFF lock")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
case _ => ;
|
||||
}
|
||||
|
|
@ -3056,18 +3115,22 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
}
|
||||
|
||||
case Some(obj : Locker) =>
|
||||
if(obj.Faction != player.Faction && obj.HackedBy.isEmpty) {
|
||||
case Some(locker : Locker) =>
|
||||
if(locker.Faction != player.Faction && locker.HackedBy.isEmpty) {
|
||||
player.Slot(player.DrawnSlot).Equipment match {
|
||||
case Some(tool: SimpleItem) =>
|
||||
if (tool.Definition == GlobalDefinitions.remote_electronics_kit) {
|
||||
progressBarValue = Some(-GetPlayerHackSpeed())
|
||||
self ! WorldSessionActor.HackingProgress(1, player, obj, tool.GUID, GetPlayerHackSpeed(), FinishHacking(obj, 3212836864L))
|
||||
log.info("Hacking a locker")
|
||||
val hackSpeed = GetPlayerHackSpeed(locker)
|
||||
|
||||
if(hackSpeed > 0) {
|
||||
progressBarValue = Some(-hackSpeed)
|
||||
self ! WorldSessionActor.HackingProgress(progressType = 1, player, locker, tool.GUID, hackSpeed, FinishHacking(locker, 3212836864L))
|
||||
log.info("Hacking a locker")
|
||||
}
|
||||
}
|
||||
case _ => ;
|
||||
}
|
||||
} else if(player.Faction == obj.Faction || !obj.HackedBy.isEmpty) {
|
||||
} else if(player.Faction == locker.Faction || !locker.HackedBy.isEmpty) {
|
||||
log.info(s"UseItem: $player accessing a locker")
|
||||
val container = player.Locker
|
||||
accessedContainer = Some(container)
|
||||
|
|
@ -3077,6 +3140,25 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
log.info(s"UseItem: not $player's locker")
|
||||
}
|
||||
|
||||
case Some(captureTerminal : CaptureTerminal) =>
|
||||
val hackedByCurrentFaction = (captureTerminal.Faction != player.Faction && !captureTerminal.HackedBy.isEmpty && captureTerminal.HackedBy.head._1.Faction == player.Faction)
|
||||
val ownedByPlayerFactionAndHackedByEnemyFaction = (captureTerminal.Faction == player.Faction && !captureTerminal.HackedBy.isEmpty)
|
||||
if(!hackedByCurrentFaction || ownedByPlayerFactionAndHackedByEnemyFaction) {
|
||||
player.Slot(player.DrawnSlot).Equipment match {
|
||||
case Some(tool: SimpleItem) =>
|
||||
if (tool.Definition == GlobalDefinitions.remote_electronics_kit) {
|
||||
val hackSpeed = GetPlayerHackSpeed(captureTerminal)
|
||||
|
||||
if(hackSpeed > 0) {
|
||||
progressBarValue = Some(-hackSpeed)
|
||||
self ! WorldSessionActor.HackingProgress(progressType = 1, player, captureTerminal, tool.GUID, hackSpeed, FinishHacking(captureTerminal, 3212836864L))
|
||||
log.info("Hacking a capture terminal")
|
||||
}
|
||||
}
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
||||
case Some(obj : MannedTurret) =>
|
||||
player.Slot(player.DrawnSlot).Equipment match {
|
||||
case Some(tool : Tool) =>
|
||||
|
|
@ -3085,11 +3167,11 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
if(ammo == Ammo.upgrade_canister && obj.Seats.values.count(_.isOccupied) == 0) {
|
||||
progressBarValue = Some(-1.25f)
|
||||
self ! WorldSessionActor.HackingProgress(
|
||||
2,
|
||||
progressType = 2,
|
||||
player,
|
||||
obj,
|
||||
tool.GUID,
|
||||
1.25f,
|
||||
delta = 1.25f,
|
||||
FinishUpgradingMannedTurret(obj, tool, TurretUpgrade(unk2.toInt))
|
||||
)
|
||||
}
|
||||
|
|
@ -3145,12 +3227,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
}
|
||||
|
||||
case Some(obj : Terminal) =>
|
||||
if(obj.Definition.isInstanceOf[MatrixTerminalDefinition]) {
|
||||
case Some(terminal : Terminal) =>
|
||||
if(terminal.Definition.isInstanceOf[MatrixTerminalDefinition]) {
|
||||
//TODO matrix spawn point; for now, just blindly bind to show work (and hope nothing breaks)
|
||||
sendResponse(BindPlayerMessage(1, "@ams", true, true, 0, 0, 0, obj.Position))
|
||||
sendResponse(BindPlayerMessage(1, "@ams", true, true, 0, 0, 0, terminal.Position))
|
||||
}
|
||||
else if(obj.Definition.isInstanceOf[RepairRearmSiloDefinition]) {
|
||||
else if(terminal.Definition.isInstanceOf[RepairRearmSiloDefinition]) {
|
||||
FindLocalVehicle match {
|
||||
case Some(vehicle) =>
|
||||
sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType))
|
||||
|
|
@ -3160,17 +3242,21 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
}
|
||||
else {
|
||||
if(obj.Faction != player.Faction && obj.HackedBy.isEmpty) {
|
||||
if(terminal.Faction != player.Faction && terminal.HackedBy.isEmpty) {
|
||||
player.Slot(player.DrawnSlot).Equipment match {
|
||||
case Some(tool: SimpleItem) =>
|
||||
if (tool.Definition == GlobalDefinitions.remote_electronics_kit) {
|
||||
progressBarValue = Some(-GetPlayerHackSpeed())
|
||||
self ! WorldSessionActor.HackingProgress(1, player, obj, tool.GUID, GetPlayerHackSpeed(), FinishHacking(obj, 3212836864L))
|
||||
log.info("Hacking a terminal")
|
||||
val hackSpeed = GetPlayerHackSpeed(terminal)
|
||||
|
||||
if(hackSpeed > 0) {
|
||||
progressBarValue = Some(-hackSpeed)
|
||||
self ! WorldSessionActor.HackingProgress(progressType = 1, player, terminal, tool.GUID, hackSpeed, FinishHacking(terminal, 3212836864L))
|
||||
log.info("Hacking a terminal")
|
||||
}
|
||||
}
|
||||
case _ => ;
|
||||
}
|
||||
} else if (obj.Faction == player.Faction || !obj.HackedBy.isEmpty) {
|
||||
} else if (terminal.Faction == player.Faction || !terminal.HackedBy.isEmpty) {
|
||||
// If hacked only allow access to the faction that hacked it
|
||||
// Otherwise allow the faction that owns the terminal to use it
|
||||
sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType))
|
||||
|
|
@ -4135,10 +4221,23 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
ask(target.Actor, CommonMessages.Hack(player))(1 second).mapTo[Boolean].onComplete {
|
||||
case Success(_) =>
|
||||
localService ! LocalServiceMessage(continent.Id, LocalAction.TriggerSound(player.GUID, target.HackSound, player.Position, 30, 0.49803925f))
|
||||
localService ! LocalServiceMessage(continent.Id, LocalAction.HackTemporarily(player.GUID, continent, target, unk))
|
||||
case scala.util.Failure(_) =>
|
||||
log.warn(s"Hack message failed on target guid: ${target.GUID}")
|
||||
}
|
||||
target match {
|
||||
case term : CaptureTerminal =>
|
||||
val isResecured = player.Faction == target.Faction
|
||||
localService ! LocalServiceMessage(continent.Id, LocalAction.HackCaptureTerminal(player.GUID, continent, term, unk, 8L, isResecured))
|
||||
case _ =>localService ! LocalServiceMessage(continent.Id, LocalAction.HackTemporarily(player.GUID, continent, target, unk, target.HackEffectDuration(GetPlayerHackLevel())))
|
||||
}
|
||||
case scala.util.Failure(_) => log.warn(s"Hack message failed on target guid: ${target.GUID}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The process of resecuring an IFF lock is finished
|
||||
* Clear the hack state and send to clients
|
||||
* @param lock the `IFFLock` object that has been resecured
|
||||
*/
|
||||
private def FinishResecuringIFFLock(lock: IFFLock)() : Unit = {
|
||||
localService ! LocalServiceMessage(continent.Id, LocalAction.ClearTemporaryHack(player.GUID, lock))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -4963,38 +5062,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
* @param building the building object
|
||||
*/
|
||||
def initFacility(continentNumber : Int, buildingNumber : Int, building : Building) : Unit = {
|
||||
var ntuLevel = 0
|
||||
building.Amenities.filter(x => (x.Definition == GlobalDefinitions.resource_silo)).headOption.asInstanceOf[Option[ResourceSilo]] match {
|
||||
case Some(obj: ResourceSilo) =>
|
||||
ntuLevel = obj.CapacitorDisplay.toInt
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
sendResponse(
|
||||
BuildingInfoUpdateMessage(
|
||||
continent_id = continentNumber,
|
||||
building_id = buildingNumber,
|
||||
ntu_level = ntuLevel,
|
||||
is_hacked = false,
|
||||
empire_hack = PlanetSideEmpire.NEUTRAL,
|
||||
hack_time_remaining = 0, // milliseconds
|
||||
empire_own = building.Faction,
|
||||
unk1 = 0, //!! Field != 0 will cause malformed packet. See class def.
|
||||
unk1x = None,
|
||||
generator_state = PlanetSideGeneratorState.Normal,
|
||||
spawn_tubes_normal = true,
|
||||
force_dome_active = false,
|
||||
lattice_benefit = 0,
|
||||
cavern_benefit = 0, //!! Field > 0 will cause malformed packet. See class def.
|
||||
unk4 = Nil,
|
||||
unk5 = 0,
|
||||
unk6 = false,
|
||||
unk7 = 8, //!! Field != 8 will cause malformed packet. See class def.
|
||||
unk7x = None,
|
||||
boost_spawn_pain = false,
|
||||
boost_generator_pain = false
|
||||
)
|
||||
)
|
||||
building.Actor ! Building.SendMapUpdate(all_clients = false)
|
||||
sendResponse(DensityLevelUpdateMessage(continentNumber, buildingNumber, List(0,0, 0,0, 0,0, 0,0)))
|
||||
}
|
||||
|
||||
|
|
@ -5058,18 +5126,35 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
amenity.Definition match {
|
||||
case GlobalDefinitions.resource_silo =>
|
||||
// Synchronise warning light & silo capacity
|
||||
var silo = amenity.asInstanceOf[ResourceSilo]
|
||||
val silo = amenity.asInstanceOf[ResourceSilo]
|
||||
sendResponse(PlanetsideAttributeMessage(amenityId, 45, silo.CapacitorDisplay))
|
||||
sendResponse(PlanetsideAttributeMessage(amenityId, 47, if(silo.LowNtuWarningOn) 1 else 0))
|
||||
|
||||
if(silo.ChargeLevel == 0) {
|
||||
// temporarily disabled until warpgates can bring ANTs from sanctuary, otherwise we'd be stuck in a situation with an unpowered base and no way to get an ANT to refill it.
|
||||
// sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(silo.Owner.asInstanceOf[Building].ModelId), 48, 1))
|
||||
//todo: temporarily disabled until warpgates can bring ANTs from sanctuary, otherwise we'd be stuck in a situation with an unpowered base and no way to get an ANT to refill it.
|
||||
//sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(silo.Owner.asInstanceOf[Building].ModelId), 48, 1))
|
||||
}
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
// Synchronise hack states to clients joining the zone.
|
||||
// We'll have to fake LocalServiceResponse messages to self, otherwise it means duplicating the same hack handling code twice
|
||||
if(amenity.isInstanceOf[Hackable]) {
|
||||
val hackable = amenity.asInstanceOf[Hackable]
|
||||
|
||||
if(hackable.HackedBy.isDefined) {
|
||||
amenity.Definition match {
|
||||
case GlobalDefinitions.capture_terminal =>
|
||||
self ! LocalServiceResponse("", PlanetSideGUID(0), LocalResponse.HackCaptureTerminal(amenity.GUID, 0L, 0L, false))
|
||||
case _ =>
|
||||
// Generic hackable object
|
||||
self ! LocalServiceResponse("", PlanetSideGUID(0), LocalResponse.HackObject(amenity.GUID, 1114636288L, 8L))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
sendResponse(HackMessage(3, PlanetSideGUID(building.ModelId), PlanetSideGUID(0), 0, 3212836864L, HackState.HackCleared, 8))
|
||||
|
||||
// sendResponse(HackMessage(3, PlanetSideGUID(building.ModelId), PlanetSideGUID(0), 0, 3212836864L, HackState.HackCleared, 8))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -5844,29 +5929,31 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
sendResponse(RawPacket(pkt))
|
||||
}
|
||||
|
||||
def GetPlayerHackSpeed(): Float = {
|
||||
if(player.Certifications.contains(CertificationType.ExpertHacking) || player.Certifications.contains(CertificationType.ElectronicsExpert)) {
|
||||
10.64f
|
||||
} else if(player.Certifications.contains(CertificationType.AdvancedHacking)) {
|
||||
5.32f
|
||||
} else {
|
||||
2.66f
|
||||
def GetPlayerHackSpeed(obj: PlanetSideServerObject with Hackable): Float = {
|
||||
val playerHackLevel = GetPlayerHackLevel()
|
||||
val timeToHack = obj.HackDuration(playerHackLevel)
|
||||
|
||||
if(timeToHack == 0) {
|
||||
log.warn(s"Player ${player.GUID} tried to hack an object ${obj.GUID} - ${obj.Definition.Name} that they don't have the correct hacking level for")
|
||||
0f
|
||||
}
|
||||
|
||||
// 250 ms per tick on the hacking progress bar
|
||||
val ticks = (timeToHack * 1000) / 250
|
||||
100f / ticks
|
||||
}
|
||||
|
||||
def GetPlayerHackData(): PlayerHackData = {
|
||||
def GetPlayerHackLevel(): Int = {
|
||||
if(player.Certifications.contains(CertificationType.ExpertHacking) || player.Certifications.contains(CertificationType.ElectronicsExpert)) {
|
||||
PlayerHackData(3, 10.64f)
|
||||
3
|
||||
} else if(player.Certifications.contains(CertificationType.AdvancedHacking)) {
|
||||
PlayerHackData(2, 7.98f)
|
||||
2
|
||||
} else if (player.Certifications.contains(CertificationType.Hacking)) {
|
||||
PlayerHackData(1, 5.32f)
|
||||
1
|
||||
} else {
|
||||
PlayerHackData(0, 2.66f)
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
case class PlayerHackData(hackLevel: Int, hackSpeed: Float)
|
||||
}
|
||||
|
||||
object WorldSessionActor {
|
||||
|
|
@ -5893,6 +5980,8 @@ object WorldSessionActor {
|
|||
* The process of "making progress" with a hack involves sending this message repeatedly until the progress is 100 or more.
|
||||
* To calculate the actual amount of change in the progress `delta`,
|
||||
* start with 100, divide by the length of time in seconds, then divide once more by 4.
|
||||
* @param progressType 1 - REK hack
|
||||
* 2 - Turret upgrade with glue gun + upgrade cannister
|
||||
* @param tplayer the player
|
||||
* @param target the object being hacked
|
||||
* @param tool_guid the REK
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue