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:
Mazo 2018-08-10 23:21:15 +01:00 committed by Fate-JH
parent 8b5073dcbc
commit abdebf09ba
23 changed files with 649 additions and 168 deletions

View file

@ -864,6 +864,10 @@ object GlobalDefinitions {
val resource_silo = new ResourceSiloDefinition 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) { val manned_turret = new MannedTurretDefinition(480) {
Name = "manned_turret" Name = "manned_turret"
MaxHealth = 3600 MaxHealth = 3600

View file

@ -7,13 +7,8 @@ import net.psforever.types.Vector3
trait Hackable { trait Hackable {
/** An entry that maintains a reference to the `Player`, and the player's GUID and location when the message was received. */ /** 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 hackedBy : Option[(Player, PlanetSideGUID, Vector3)] = None
private var hackSound : TriggeredSound.Value = TriggeredSound.HackDoor
def HackedBy : Option[(Player, PlanetSideGUID, Vector3)] = hackedBy def HackedBy : Option[(Player, PlanetSideGUID, Vector3)] = hackedBy
def HackedBy_=(agent : Player) : Option[(Player, PlanetSideGUID, Vector3)] = HackedBy_=(Some(agent)) 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 of this object by recording important information about the player that caused it.
* Set the hack state if there is no current hack state. * Set the hack state if there is no current hack state.
@ -41,9 +36,27 @@ trait Hackable {
HackedBy HackedBy
} }
/** The sound made when the object is hacked */
private var hackSound : TriggeredSound.Value = TriggeredSound.HackDoor
def HackSound : TriggeredSound.Value = hackSound def HackSound : TriggeredSound.Value = hackSound
def HackSound_=(sound : TriggeredSound.Value) : TriggeredSound.Value = { def HackSound_=(sound : TriggeredSound.Value) : TriggeredSound.Value = {
hackSound = sound hackSound = sound
hackSound 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
}
} }

View file

@ -17,6 +17,8 @@ import net.psforever.packet.game.TriggeredSound
class IFFLock(private val idef : IFFLockDefinition) extends Amenity with Hackable { class IFFLock(private val idef : IFFLockDefinition) extends Amenity with Hackable {
def Definition : IFFLockDefinition = idef def Definition : IFFLockDefinition = idef
HackSound = TriggeredSound.HackDoor HackSound = TriggeredSound.HackDoor
HackEffectDuration = Array(60, 180, 300, 360)
HackDuration = Array(5, 3, 1, 1)
} }
object IFFLock { object IFFLock {

View file

@ -10,6 +10,8 @@ import net.psforever.packet.game.TriggeredSound
class Locker extends Amenity with Hackable { class Locker extends Amenity with Hackable {
def Definition : LockerDefinition = GlobalDefinitions.mb_locker def Definition : LockerDefinition = GlobalDefinitions.mb_locker
HackSound = TriggeredSound.HackTerminal HackSound = TriggeredSound.HackTerminal
HackEffectDuration = Array(0, 30, 60, 90)
HackDuration = Array(0, 10, 5, 3)
} }
object Locker { object Locker {

View file

@ -60,7 +60,7 @@ class ResourceSiloControl(resourceSilo : ResourceSilo) extends Actor with Factio
if(resourceSilo.CapacitorDisplay != ntuBarLevel) { if(resourceSilo.CapacitorDisplay != ntuBarLevel) {
log.trace(s"Silo ${resourceSilo.GUID} NTU bar level has changed from ${resourceSilo.CapacitorDisplay} to ${ntuBarLevel}") log.trace(s"Silo ${resourceSilo.GUID} NTU bar level has changed from ${resourceSilo.CapacitorDisplay} to ${ntuBarLevel}")
resourceSilo.CapacitorDisplay = 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)) avatarService ! AvatarServiceMessage(resourceSilo.Owner.asInstanceOf[Building].Zone.Id, AvatarAction.PlanetsideAttribute(resourceSilo.GUID, 45, resourceSilo.CapacitorDisplay))
} }

View file

@ -68,7 +68,6 @@ object Building {
val obj = new Building(id, zone, buildingType) val obj = new Building(id, zone, buildingType)
obj.Position = location obj.Position = location
obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$id-$buildingType-building") obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$id-$buildingType-building")
obj.Actor ! "startup"
obj obj
} }
@ -76,9 +75,8 @@ object Building {
import akka.actor.Props import akka.actor.Props
val obj = new Building(id, zone, buildingType) val obj = new Building(id, zone, buildingType)
obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$id-$buildingType-building") obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$id-$buildingType-building")
obj.Actor ! "startup"
obj obj
} }
final case class SendMapUpdateToAllClients() final case class SendMapUpdate(all_clients: Boolean)
} }

View file

@ -1,74 +1,114 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.structures 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.GlobalDefinitions
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} 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.resourcesilo.ResourceSilo
import net.psforever.objects.serverobject.terminals.CaptureTerminal
import net.psforever.packet.game.{BuildingInfoUpdateMessage, PlanetSideGeneratorState} import net.psforever.packet.game.{BuildingInfoUpdateMessage, PlanetSideGeneratorState}
import net.psforever.types.PlanetSideEmpire import net.psforever.types.PlanetSideEmpire
import services.ServiceManager import services.ServiceManager
import services.ServiceManager.Lookup 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 { class BuildingControl(building : Building) extends Actor with FactionAffinityBehavior.Check {
def FactionObject : FactionAffinity = building def FactionObject : FactionAffinity = building
var galaxyService : ActorRef = Actor.noSender var galaxyService : ActorRef = Actor.noSender
var localService : ActorRef = Actor.noSender
private[this] val log = org.log4s.getLogger private[this] val log = org.log4s.getLogger
def receive : Receive = { override def preStart = {
case "startup" => log.info(s"Starting BuildingControl for ${building.GUID} / ${building.ModelId}")
ServiceManager.serviceManager ! Lookup("galaxy") //ask for a resolver to deal with the GUID system ServiceManager.serviceManager ! Lookup("galaxy")
ServiceManager.serviceManager ! Lookup("local")
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");
} }
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) => case FactionAffinity.ConvertFactionAffinity(faction) =>
val originalAffinity = building.Faction val originalAffinity = building.Faction
if(originalAffinity != (building.Faction = faction)) { if(originalAffinity != (building.Faction = faction)) {
building.Amenities.foreach(_.Actor forward FactionAffinity.ConfirmFactionAffinity()) building.Amenities.foreach(_.Actor forward FactionAffinity.ConfirmFactionAffinity())
} }
sender ! FactionAffinity.AssertFactionAffinity(building, faction) sender ! FactionAffinity.AssertFactionAffinity(building, faction)
case Building.SendMapUpdateToAllClients() => case Building.SendMapUpdate(all_clients: Boolean) =>
log.info(s"Sending BuildingInfoUpdateMessage update to all clients. Zone: ${building.Zone.Number} - Building: ${building.ModelId}") log.info(s"Sending BuildingInfoUpdateMessage update. Zone: ${building.Zone.Number} - Building: ${building.ModelId}")
var ntuLevel = 0 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 { building.Amenities.filter(x => (x.Definition == GlobalDefinitions.resource_silo)).headOption.asInstanceOf[Option[ResourceSilo]] match {
case Some(obj: ResourceSilo) => case Some(obj: ResourceSilo) =>
ntuLevel = obj.CapacitorDisplay.toInt ntuLevel = obj.CapacitorDisplay.toInt
case _ => ; 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 continent_id = building.Zone.Number, //Zone
building_id = building.Id, //Facility building_id = building.Id, //Facility
ntu_level = ntuLevel, ntu_level = ntuLevel,
is_hacked = false, //Hacked is_hacked,
PlanetSideEmpire.NEUTRAL, //Base hacked by hacked_by_faction,
hack_time_remaining = 0, //Time remaining for hack (ms) hack_time_remaining_ms,
empire_own = building.Faction, //Base owned by empire_own = building.Faction,
unk1 = 0, //!! Field != 0 will cause malformed packet. See class def. unk1 = 0, //!! Field != 0 will cause malformed packet. See class def.
unk1x = None, unk1x = None,
generator_state = PlanetSideGeneratorState.Normal, //Generator state generator_state = PlanetSideGeneratorState.Normal,
spawn_tubes_normal = true, //Respawn tubes operating state spawn_tubes_normal = true,
force_dome_active = false, //Force dome state force_dome_active = false,
lattice_benefit = 0, //Lattice benefits lattice_benefit = 0,
cavern_benefit = 0, //!! Field > 0 will cause malformed packet. See class def. cavern_benefit = 0, //!! Field > 0 will cause malformed packet. See class def.
unk4 = Nil, unk4 = Nil,
unk5 = 0, unk5 = 0,
unk6 = false, unk6 = false,
unk7 = 8, //!! Field != 8 will cause malformed packet. See class def. unk7 = 8, //!! Field != 8 will cause malformed packet. See class def.
unk7x = None, unk7x = None,
boost_spawn_pain = false, //Boosted spawn room pain field boost_spawn_pain = false,
boost_generator_pain = false //Boosted generator room pain field 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 => case default =>
log.warn(s"BuildingControl: Unknown message ${default} received from ${sender().path}") log.warn(s"BuildingControl: Unknown message ${default} received from ${sender().path}")
} }

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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")
}
}

View file

@ -14,6 +14,8 @@ import net.psforever.types.TransactionType
*/ */
class Terminal(tdef : TerminalDefinition) extends Amenity with Hackable { class Terminal(tdef : TerminalDefinition) extends Amenity with Hackable {
HackSound = TriggeredSound.HackTerminal 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 //the following fields and related methods are neither finalized nor integrated; GOTO Request
private var health : Int = 100 //TODO not real health value private var health : Int = 100 //TODO not real health value

View file

@ -61,7 +61,8 @@ class InterstellarCluster(zones : List[Zone]) extends Actor {
sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, None) sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, None)
} }
case _ => ; case _ =>
log.warn(s"InterstellarCluster received unknown message");
} }
/** /**

View file

@ -56,6 +56,7 @@ object HackState extends Enumeration {
* 2 when performing (phalanx) upgrades; * 2 when performing (phalanx) upgrades;
* 3 for building objects during login phase; * 3 for building objects during login phase;
* hack type? * hack type?
* possibly player hacking level 0-3?
* @param target_guid the target of the hack * @param target_guid the target of the hack
* @param player_guid the player * @param player_guid the player
* @param progress the amount of progress visible; * @param progress the amount of progress visible;
@ -65,6 +66,7 @@ object HackState extends Enumeration {
* doesn't seem to be `char_id`? * doesn't seem to be `char_id`?
* @param hack_state hack state * @param hack_state hack state
* @param unk7 na; * @param unk7 na;
* 5 - boost pain field at matrixing terminal?
* usually, 8? * usually, 8?
*/ */
final case class HackMessage(unk1 : Int, final case class HackMessage(unk1 : Int,

View file

@ -51,6 +51,15 @@ import scodec.codecs._
* `17 - BEP. Value seems to be the same as BattleExperienceMessage`<br> * `17 - BEP. Value seems to be the same as BattleExperienceMessage`<br>
* `18 - CEP.`<br> * `18 - CEP.`<br>
* `19 - Anchors. Value is 0 to disengage, 1 to engage.`<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> * `24 - Learn certifications with value :`<br>
* 01 : Medium Assault<br> * 01 : Medium Assault<br>
* 02 : Heavy 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> * `116 - Apply colour to REK beam and REK icon above players (0 = yellow, 1 = red, 2 = purple, 3 = blue)`<br>
* Client to Server : <br> * Client to Server : <br>
* `106 - Custom Head`<br> * `106 - Custom Head`<br>
* `224 - Player/vehicle joins black ops`<br>
* `228 - Player/vehicle leaves black ops`<br>
* <br> * <br>
* `Vehicles:`<br> * `Vehicles:`<br>
* `10 - Driver seat permissions (0 = Locked, 1 = Group, 3 = Empire)`<br> * `10 - Driver seat permissions (0 = Locked, 1 = Group, 3 = Empire)`<br>

View file

@ -10,22 +10,26 @@ import scodec.codecs._
* (Where the child object was before it was moved is not specified or important.) * (Where the child object was before it was moved is not specified or important.)
* @see `Definition.ObjectId`<br> * @see `Definition.ObjectId`<br>
* `TurretUpgrade` * `TurretUpgrade`
* @param avatar_guid the player * @param avatar_guid the player
* @param item_used_guid the item used; * @param item_used_guid the item used;
* e.g., a REK to hack, or a medkit to heal * e.g. a REK to hack or a medkit to heal.
* @param object_guid the object affected; * @param object_guid the object affected;
* e.g., door when opened, terminal when accessed, avatar when medkit used * e.g., door when opened, terminal when accessed, avatar when medkit used
* @param unk2 na; * @param unk2 na;
* when upgrading phalanx turrets, 1 for `AVCombo` and 2 for `FlakCombo` * when upgrading phalanx turrets, 1 for `AVCombo` and 2 for `FlakCombo`
* @param unk3 using tools, e.g., a REK or nano-dispenser * @param unk3 using tools, e.g., a REK or nano-dispenser
* @param unk4 na * @param unk4 seems to be related to T-REK viruses.
* @param unk5 na * 0 - unlock all doors
* @param unk6 na * 1 - disable linked benefits
* @param unk7 na; * 2 - double ntu drain
* 25 when door 223 when terminal * 3 - disable enemy radar
* @param unk8 na; * 4 - access equipment terminals
* 0 when door 1 when use rek (252 then equipment term) * @param unk6 na
* @param object_id he object id for `object_guid`'s object * @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, final case class UseItemMessage(avatar_guid : PlanetSideGUID,
item_used_guid : PlanetSideGUID, item_used_guid : PlanetSideGUID,

View file

@ -3,9 +3,11 @@ package services.local
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.doors.Door 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.objects.zones.Zone
import net.psforever.packet.game.{PlanetSideGUID, TriggeredSound} import net.psforever.packet.game.{PlanetSideGUID, TriggeredSound}
import net.psforever.types.Vector3 import net.psforever.types.{PlanetSideEmpire, Vector3}
object LocalAction { object LocalAction {
trait Action trait Action
@ -13,7 +15,10 @@ object LocalAction {
final case class DoorOpens(player_guid : PlanetSideGUID, continent : Zone, door : Door) extends 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 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 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 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 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
} }

View file

@ -2,7 +2,7 @@
package services.local package services.local
import net.psforever.packet.game.{PlanetSideGUID, TriggeredSound} import net.psforever.packet.game.{PlanetSideGUID, TriggeredSound}
import net.psforever.types.Vector3 import net.psforever.types.{PlanetSideEmpire, Vector3}
object LocalResponse { object LocalResponse {
trait Response trait Response
@ -11,6 +11,8 @@ object LocalResponse {
final case class DoorCloses(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 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 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 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 TriggerSound(sound : TriggeredSound.Value, pos : Vector3, unk : Int, volume : Float) extends Response
final case class SetEmpire(object_guid: PlanetSideGUID, empire: PlanetSideEmpire.Value) extends Response
} }

View file

@ -1,22 +1,44 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package services.local package services.local
import akka.actor.{Actor, Props} import akka.actor.{Actor, ActorRef, Props}
import services.{GenericEventBus, Service} 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 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 { class LocalService extends Actor {
private val doorCloser = context.actorOf(Props[DoorCloseActor], "local-door-closer") private val doorCloser = context.actorOf(Props[DoorCloseActor], "local-door-closer")
private val hackClearer = context.actorOf(Props[HackClearActor], "local-hack-clearer") 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 private [this] val log = org.log4s.getLogger
var cluster : ActorRef = Actor.noSender
override def preStart = { override def preStart = {
log.info("Starting...") log.info("Starting...")
ServiceManager.serviceManager ! Lookup("cluster")
} }
val LocalEvents = new GenericEventBus[LocalServiceResponse] val LocalEvents = new GenericEventBus[LocalServiceResponse]
def receive = { def receive = {
case ServiceManager.LookupResult("cluster", endpoint) =>
cluster = endpoint
log.trace("LocalService got cluster service " + endpoint)
case Service.Join(channel) => case Service.Join(channel) =>
val path = s"/$channel/Local" val path = s"/$channel/Local"
val who = sender() val who = sender()
@ -50,11 +72,31 @@ class LocalService extends Actor {
LocalEvents.publish( LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.HackClear(target.GUID, unk1, unk2)) LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.HackClear(target.GUID, unk1, unk2))
) )
case LocalAction.HackTemporarily(player_guid, zone, target, unk1, unk2) => case LocalAction.HackTemporarily(player_guid, zone, target, unk1, duration, unk2) =>
hackClearer ! HackClearActor.ObjectIsHacked(target, zone, unk1, unk2) hackClearer ! HackClearActor.ObjectIsHacked(target, zone, unk1, unk2, duration)
LocalEvents.publish( LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.HackObject(target.GUID, unk1, unk2)) 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) => case LocalAction.ProximityTerminalEffect(player_guid, object_guid, effectState) =>
LocalEvents.publish( LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.ProximityTerminalEffect(object_guid, effectState)) LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.ProximityTerminalEffect(object_guid, effectState))
@ -63,6 +105,10 @@ class LocalService extends Actor {
LocalEvents.publish( LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.TriggerSound(sound, pos, unk, volume)) 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 _ => ; case _ => ;
} }
@ -74,10 +120,48 @@ class LocalService extends Actor {
//response from HackClearActor //response from HackClearActor
case HackClearActor.ClearTheHack(target_guid, zone_id, unk1, unk2) => case HackClearActor.ClearTheHack(target_guid, zone_id, unk1, unk2) =>
log.warn(s"Clearing hack for ${target_guid}")
LocalEvents.publish( LocalEvents.publish(
LocalServiceResponse(s"/$zone_id/Local", Service.defaultPlayerGUID, LocalResponse.HackClear(target_guid, unk1, unk2)) 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 => case msg =>
log.info(s"Unhandled message $msg from $sender") log.info(s"Unhandled message $msg from $sender")
} }

View file

@ -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)
}

View file

@ -1,8 +1,11 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package services.local.support package services.local.support
import java.util.concurrent.TimeUnit
import akka.actor.{Actor, Cancellable} import akka.actor.{Actor, Cancellable}
import net.psforever.objects.DefaultCancellable import net.psforever.objects.DefaultCancellable
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.PlanetSideGUID
@ -20,15 +23,15 @@ class HackClearActor() extends Actor {
private var clearTrigger : Cancellable = DefaultCancellable.obj private var clearTrigger : Cancellable = DefaultCancellable.obj
/** A `List` of currently hacked server objects */ /** A `List` of currently hacked server objects */
private var hackedObjects : List[HackClearActor.HackEntry] = Nil private var hackedObjects : List[HackClearActor.HackEntry] = Nil
//private[this] val log = org.log4s.getLogger private[this] val log = org.log4s.getLogger
def receive : Receive = { def receive : Receive = {
case HackClearActor.ObjectIsHacked(target, zone, unk1, unk2, time) => case HackClearActor.ObjectIsHacked(target, zone, unk1, unk2, duration, time) =>
hackedObjects = hackedObjects :+ HackClearActor.HackEntry(target, zone, unk1, unk2, time) val durationNanos = TimeUnit.NANOSECONDS.convert(duration, TimeUnit.SECONDS)
if(hackedObjects.size == 1) { //we were the only entry so the event must be started from scratch hackedObjects = hackedObjects :+ HackClearActor.HackEntry(target, zone, unk1, unk2, time, durationNanos)
import scala.concurrent.ExecutionContext.Implicits.global
clearTrigger = context.system.scheduler.scheduleOnce(HackClearActor.timeout, self, HackClearActor.TryClearHacks()) // Restart the timer, in case this is the first object in the hacked objects list
} RestartTimer()
case HackClearActor.TryClearHacks() => case HackClearActor.TryClearHacks() =>
clearTrigger.cancel 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 context.parent ! HackClearActor.ClearTheHack(entry.target.GUID, entry.zone.Id, entry.unk1, entry.unk2) //call up to the main event system
}) })
if(stillHackedObjects.nonEmpty) { RestartTimer()
val short_timeout : FiniteDuration = math.max(1, HackClearActor.timeout_time - (now - stillHackedObjects.head.time)) nanoseconds
import scala.concurrent.ExecutionContext.Implicits.global case HackClearActor.ObjectIsResecured(target) =>
clearTrigger = context.system.scheduler.scheduleOnce(short_timeout, self, HackClearActor.TryClearHacks()) 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 _ => ; 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. * Iterate over entries in a `List` until an entry that does not exceed the time limit is discovered.
* Separate the original `List` into two: * Separate the original `List` into two:
@ -84,7 +108,7 @@ class HackClearActor() extends Actor {
} }
else { else {
val entry = iter.next() val entry = iter.next()
if(now - entry.time >= HackClearActor.timeout_time) { if(now - entry.time >= entry.duration) {
recursivePartitionEntries(iter, now, index + 1) recursivePartitionEntries(iter, now, index + 1)
} }
else { else {
@ -95,26 +119,31 @@ class HackClearActor() extends Actor {
} }
object HackClearActor { 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. * Message that carries information about a server object that has been hacked.
* @param target the server object * @param target the server object
* @param zone the zone in which the object resides * @param zone the zone in which the object resides
* @param time when the object was hacked * @param time when the object was hacked
* @param duration how long the object is to stay hacked for in seconds
* @see `HackEntry` * @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. * Message that carries information about a server object that needs its functionality restored.
* Prompting, as compared to `ObjectIsHacked` which is reactionary. * 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 * @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. * Internal message used to signal a test of the queued door information.
*/ */
@ -122,11 +151,12 @@ object HackClearActor {
/** /**
* Entry of hacked server object information. * 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 target the server object
* @param zone the zone in which the object resides * @param zone the zone in which the object resides
* @param time when the object was hacked * @param time when the object was hacked
* @param duration The hack duration in nanoseconds
* @see `ObjectIsHacked` * @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)
} }

View file

@ -176,7 +176,7 @@ class ResourceSiloControlUpdate1Test extends ActorTest {
assert(reply1.asInstanceOf[AvatarServiceMessage] assert(reply1.asInstanceOf[AvatarServiceMessage]
.actionMessage.asInstanceOf[AvatarAction.PlanetsideAttribute].attribute_value == 3) .actionMessage.asInstanceOf[AvatarAction.PlanetsideAttribute].attribute_value == 3)
assert(reply2.isInstanceOf[Building.SendMapUpdateToAllClients]) assert(reply2.isInstanceOf[Building.SendMapUpdate])
val reply3 = probe1.receiveOne(500 milliseconds) val reply3 = probe1.receiveOne(500 milliseconds)
assert(reply3.isInstanceOf[AvatarServiceMessage]) assert(reply3.isInstanceOf[AvatarServiceMessage])
@ -249,7 +249,7 @@ class ResourceSiloControlUpdate2Test extends ActorTest {
assert(reply1.asInstanceOf[AvatarServiceMessage] assert(reply1.asInstanceOf[AvatarServiceMessage]
.actionMessage.asInstanceOf[AvatarAction.PlanetsideAttribute].attribute_value == 2) .actionMessage.asInstanceOf[AvatarAction.PlanetsideAttribute].attribute_value == 2)
assert(reply2.isInstanceOf[Building.SendMapUpdateToAllClients]) assert(reply2.isInstanceOf[Building.SendMapUpdate])
val reply3 = probe1.receiveOne(500 milliseconds) val reply3 = probe1.receiveOne(500 milliseconds)
assert(obj.LowNtuWarningOn == false) assert(obj.LowNtuWarningOn == false)

View file

@ -10,6 +10,7 @@ import net.psforever.objects.serverobject.mblocker.Locker
import net.psforever.objects.serverobject.pad.VehicleSpawnPad import net.psforever.objects.serverobject.pad.VehicleSpawnPad
import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder, StructureType, WarpGate} import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder, StructureType, WarpGate}
import net.psforever.objects.serverobject.terminals.{ProximityTerminal, Terminal} 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.tube.SpawnTube
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
import net.psforever.objects.serverobject.turret.MannedTurret import net.psforever.objects.serverobject.turret.MannedTurret
@ -94,8 +95,8 @@ object Maps {
DoorToLock(384, 1036) DoorToLock(384, 1036)
DoorToLock(386, 1038) DoorToLock(386, 1038)
DoorToLock(388, 1039) DoorToLock(388, 1039)
// DoorToLock(394, 1047) DoorToLock(394, 1047)
// DoorToLock(395, 1049) DoorToLock(395, 1049)
DoorToLock(401, 1053) DoorToLock(401, 1053)
DoorToLock(920, 968) DoorToLock(920, 968)
@ -173,6 +174,7 @@ object Maps {
def Building9() : Unit = { // Girru def Building9() : Unit = { // Girru
LocalBuilding(9, FoundationBuilder(Building.Structure(StructureType.Facility, Vector3(4397f, 5895f, 0)))) // Todo change pos 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(513, Door.Constructor)
LocalObject(514, Door.Constructor) LocalObject(514, Door.Constructor)
LocalObject(515, Door.Constructor) LocalObject(515, Door.Constructor)
@ -225,6 +227,7 @@ object Maps {
LocalObject(2015, Terminal.Constructor(order_terminal)) LocalObject(2015, Terminal.Constructor(order_terminal))
LocalObject(2016, Terminal.Constructor(order_terminal)) LocalObject(2016, Terminal.Constructor(order_terminal))
LocalObject(2017, 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(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(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))) 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(1909, ProximityTerminal.Constructor(medical_terminal))
// LocalObject(1910, ProximityTerminal.Constructor(medical_terminal)) // LocalObject(1910, ProximityTerminal.Constructor(medical_terminal))
ObjectToBuilding(225, 9)
ObjectToBuilding(513, 9) ObjectToBuilding(513, 9)
ObjectToBuilding(514, 9) ObjectToBuilding(514, 9)
ObjectToBuilding(515, 9) ObjectToBuilding(515, 9)
@ -298,6 +302,7 @@ object Maps {
ObjectToBuilding(2015, 9) ObjectToBuilding(2015, 9)
ObjectToBuilding(2016, 9) ObjectToBuilding(2016, 9)
ObjectToBuilding(2017, 9) ObjectToBuilding(2017, 9)
ObjectToBuilding(2658, 9)
ObjectToBuilding(2724, 9) ObjectToBuilding(2724, 9)
ObjectToBuilding(2725, 9) ObjectToBuilding(2725, 9)
ObjectToBuilding(2726, 9) ObjectToBuilding(2726, 9)
@ -332,6 +337,7 @@ object Maps {
def Building10() : Unit = { // Hanish def Building10() : Unit = { // Hanish
LocalBuilding(10, FoundationBuilder(Building.Structure(StructureType.Facility, Vector3(3749f, 5477f, 0)))) // Todo change pos 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(464, Door.Constructor)
LocalObject(470, Door.Constructor(Vector3(3645.3984f, 5451.9688f, 88.890625f), Vector3(0, 0, 182))) LocalObject(470, Door.Constructor(Vector3(3645.3984f, 5451.9688f, 88.890625f), Vector3(0, 0, 182)))
LocalObject(471, Door.Constructor) LocalObject(471, Door.Constructor)
@ -374,6 +380,7 @@ object Maps {
LocalObject(971, IFFLock.Constructor) LocalObject(971, IFFLock.Constructor)
LocalObject(1105, IFFLock.Constructor) LocalObject(1105, IFFLock.Constructor)
LocalObject(1106, IFFLock.Constructor) LocalObject(1106, IFFLock.Constructor)
LocalObject(1107, IFFLock.Constructor)
LocalObject(1108, IFFLock.Constructor) LocalObject(1108, IFFLock.Constructor)
LocalObject(1113, IFFLock.Constructor) LocalObject(1113, IFFLock.Constructor)
LocalObject(1114, IFFLock.Constructor) LocalObject(1114, IFFLock.Constructor)
@ -431,6 +438,7 @@ object Maps {
// ObjectToBuilding(169, 10) // ObjectToBuilding(169, 10)
// ObjectToBuilding(1906, 10) // ObjectToBuilding(1906, 10)
ObjectToBuilding(223, 10)
ObjectToBuilding(464, 10) ObjectToBuilding(464, 10)
ObjectToBuilding(470, 10) ObjectToBuilding(470, 10)
ObjectToBuilding(471, 10) ObjectToBuilding(471, 10)
@ -469,10 +477,12 @@ object Maps {
ObjectToBuilding(923, 10) ObjectToBuilding(923, 10)
ObjectToBuilding(932, 10) ObjectToBuilding(932, 10)
ObjectToBuilding(933, 10) ObjectToBuilding(933, 10)
ObjectToBuilding(959, 10)
ObjectToBuilding(971, 10) ObjectToBuilding(971, 10)
ObjectToBuilding(1105, 10) ObjectToBuilding(1105, 10)
ObjectToBuilding(1106, 10) ObjectToBuilding(1106, 10)
ObjectToBuilding(1107, 10)
ObjectToBuilding(1108, 10) ObjectToBuilding(1108, 10)
ObjectToBuilding(1113, 10) ObjectToBuilding(1113, 10)
ObjectToBuilding(1114, 10) ObjectToBuilding(1114, 10)
@ -528,6 +538,7 @@ object Maps {
DoorToLock(475, 1108) DoorToLock(475, 1108)
DoorToLock(481, 1113) DoorToLock(481, 1113)
DoorToLock(771, 1106) DoorToLock(771, 1106)
DoorToLock(774, 1107)
DoorToLock(779, 1114) DoorToLock(779, 1114)
DoorToLock(784, 1116) DoorToLock(784, 1116)
DoorToLock(785, 1115) DoorToLock(785, 1115)
@ -761,6 +772,7 @@ object Maps {
} }
def Building33() : Unit = { // East Girru Gun Tower, Ishundar (ID: 62) def Building33() : Unit = { // East Girru Gun Tower, Ishundar (ID: 62)
LocalBuilding(33, FoundationBuilder(Building.Structure(StructureType.Tower, Vector3(4624f, 5915f, 0)))) // TODO loc LocalBuilding(33, FoundationBuilder(Building.Structure(StructureType.Tower, Vector3(4624f, 5915f, 0)))) // TODO loc
LocalObject(2792, CaptureTerminal.Constructor(secondary_capture))
LocalObject(2957, Door.Constructor) LocalObject(2957, Door.Constructor)
LocalObject(2958, Door.Constructor) LocalObject(2958, Door.Constructor)
LocalObject(542, Door.Constructor(Vector3(4625.9844f, 5910.211f, 55.75f), Vector3(0, 0, 180))) 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(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))) 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(2957, 33)
ObjectToBuilding(2958, 33) ObjectToBuilding(2958, 33)
ObjectToBuilding(542, 33) ObjectToBuilding(542, 33)
@ -1694,6 +1707,7 @@ object Maps {
LocalObject(396, Door.Constructor) LocalObject(396, Door.Constructor)
LocalObject(397, Door.Constructor) LocalObject(397, Door.Constructor)
LocalObject(398, Door.Constructor) LocalObject(398, Door.Constructor)
LocalObject(399, Door.Constructor)
LocalObject(462, Door.Constructor) LocalObject(462, Door.Constructor)
LocalObject(463, Door.Constructor) LocalObject(463, Door.Constructor)
LocalObject(522, ImplantTerminalMech.Constructor) LocalObject(522, ImplantTerminalMech.Constructor)
@ -1741,6 +1755,7 @@ object Maps {
ObjectToBuilding(396, 2) ObjectToBuilding(396, 2)
ObjectToBuilding(397, 2) ObjectToBuilding(397, 2)
ObjectToBuilding(398, 2) ObjectToBuilding(398, 2)
ObjectToBuilding(399, 2)
ObjectToBuilding(462, 2) ObjectToBuilding(462, 2)
ObjectToBuilding(463, 2) ObjectToBuilding(463, 2)
ObjectToBuilding(522, 2) ObjectToBuilding(522, 2)

View file

@ -1,4 +1,5 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware} import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware}
@ -53,10 +54,11 @@ import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage,
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.concurrent.Future import scala.concurrent.{Await, Future}
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.util.Success import scala.util.Success
import akka.pattern.ask import akka.pattern.ask
import services.local.support.HackCaptureActor
class WorldSessionActor extends Actor with MDCContextAware { class WorldSessionActor extends Actor with MDCContextAware {
import WorldSessionActor._ import WorldSessionActor._
@ -560,10 +562,16 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(GenericObjectStateMsg(door_guid, 17)) sendResponse(GenericObjectStateMsg(door_guid, 17))
case LocalResponse.HackClear(target_guid, unk1, unk2) => case LocalResponse.HackClear(target_guid, unk1, unk2) =>
log.trace(s"Clearing hack for ${target_guid}")
// Reset hack state for all players // Reset hack state for all players
sendResponse(HackMessage(0, target_guid, guid, 0, unk1, HackState.HackCleared, unk2)) sendResponse(HackMessage(0, target_guid, guid, 0, unk1, HackState.HackCleared, unk2))
// Set the object faction displayed back to it's original owner faction // 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) => case LocalResponse.HackObject(target_guid, unk1, unk2) =>
if(tplayer_guid != guid && continent.GUID(target_guid).get.asInstanceOf[Hackable].HackedBy.get._1.Faction != player.Faction) { 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) // 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)) 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) => case LocalResponse.ProximityTerminalEffect(object_guid, effectState) =>
if(tplayer_guid != guid) { if(tplayer_guid != guid) {
sendResponse(ProximityTerminalUseMessage(PlanetSideGUID(0), object_guid, effectState)) sendResponse(ProximityTerminalUseMessage(PlanetSideGUID(0), object_guid, effectState))
@ -585,6 +617,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
case LocalResponse.TriggerSound(sound, pos, unk, volume) => case LocalResponse.TriggerSound(sound, pos, unk, volume) =>
sendResponse(TriggerSoundMessage(sound, pos, unk, volume)) sendResponse(TriggerSoundMessage(sound, pos, unk, volume))
case LocalResponse.SetEmpire(object_guid, empire) =>
sendResponse(SetEmpireMessage(object_guid, empire))
case _ => ; case _ => ;
} }
@ -1451,6 +1485,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
val popNC = poplist.count(_.faction == PlanetSideEmpire.NC) val popNC = poplist.count(_.faction == PlanetSideEmpire.NC)
val popVS = poplist.count(_.faction == PlanetSideEmpire.VS) val popVS = poplist.count(_.faction == PlanetSideEmpire.VS)
// StopBundlingPackets() is called on ClientInitializationComplete
StartBundlingPackets() StartBundlingPackets()
zone.Buildings.foreach({ case(id, building) => initBuilding(continentNumber, id, building) }) zone.Buildings.foreach({ case(id, building) => initBuilding(continentNumber, id, building) })
sendResponse(ZonePopulationUpdateMessage(continentNumber, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO)) 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) => case Some(unholsteredItem : Equipment) =>
if(unholsteredItem.Definition == GlobalDefinitions.remote_electronics_kit) { 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 // 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 => ; case None => ;
} }
@ -2971,9 +3006,21 @@ class WorldSessionActor extends Actor with MDCContextAware {
continent.GUID(object_guid) match { continent.GUID(object_guid) match {
case Some(door : Door) => case Some(door : Door) =>
if(player.Faction == door.Faction || ((continent.Map.DoorToLock.get(object_guid.guid) match { 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 case None => !door.isOpen
}) || Vector3.ScalarProjection(door.Outwards, player.Position - door.Position) < 0f)) { }) || 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) door.Actor ! Door.Use(player, msg)
} }
else if(door.isOpen) { else if(door.isOpen) {
@ -2995,13 +3042,25 @@ class WorldSessionActor extends Actor with MDCContextAware {
} }
case Some(panel : IFFLock) => 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 { player.Slot(player.DrawnSlot).Equipment match {
case Some(tool : SimpleItem) => case Some(tool : SimpleItem) =>
if(tool.Definition == GlobalDefinitions.remote_electronics_kit) { if(tool.Definition == GlobalDefinitions.remote_electronics_kit) {
progressBarValue = Some(-GetPlayerHackSpeed()) val hackSpeed = GetPlayerHackSpeed(panel)
self ! WorldSessionActor.HackingProgress(1, player, panel, tool.GUID, GetPlayerHackSpeed(), FinishHacking(panel, 1114636288L))
log.info("Hacking a door~") 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 _ => ; case _ => ;
} }
@ -3056,18 +3115,22 @@ class WorldSessionActor extends Actor with MDCContextAware {
} }
} }
case Some(obj : Locker) => case Some(locker : Locker) =>
if(obj.Faction != player.Faction && obj.HackedBy.isEmpty) { if(locker.Faction != player.Faction && locker.HackedBy.isEmpty) {
player.Slot(player.DrawnSlot).Equipment match { player.Slot(player.DrawnSlot).Equipment match {
case Some(tool: SimpleItem) => case Some(tool: SimpleItem) =>
if (tool.Definition == GlobalDefinitions.remote_electronics_kit) { if (tool.Definition == GlobalDefinitions.remote_electronics_kit) {
progressBarValue = Some(-GetPlayerHackSpeed()) val hackSpeed = GetPlayerHackSpeed(locker)
self ! WorldSessionActor.HackingProgress(1, player, obj, tool.GUID, GetPlayerHackSpeed(), FinishHacking(obj, 3212836864L))
log.info("Hacking a 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 _ => ; 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") log.info(s"UseItem: $player accessing a locker")
val container = player.Locker val container = player.Locker
accessedContainer = Some(container) accessedContainer = Some(container)
@ -3077,6 +3140,25 @@ class WorldSessionActor extends Actor with MDCContextAware {
log.info(s"UseItem: not $player's locker") 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) => case Some(obj : MannedTurret) =>
player.Slot(player.DrawnSlot).Equipment match { player.Slot(player.DrawnSlot).Equipment match {
case Some(tool : Tool) => 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) { if(ammo == Ammo.upgrade_canister && obj.Seats.values.count(_.isOccupied) == 0) {
progressBarValue = Some(-1.25f) progressBarValue = Some(-1.25f)
self ! WorldSessionActor.HackingProgress( self ! WorldSessionActor.HackingProgress(
2, progressType = 2,
player, player,
obj, obj,
tool.GUID, tool.GUID,
1.25f, delta = 1.25f,
FinishUpgradingMannedTurret(obj, tool, TurretUpgrade(unk2.toInt)) FinishUpgradingMannedTurret(obj, tool, TurretUpgrade(unk2.toInt))
) )
} }
@ -3145,12 +3227,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
} }
} }
case Some(obj : Terminal) => case Some(terminal : Terminal) =>
if(obj.Definition.isInstanceOf[MatrixTerminalDefinition]) { if(terminal.Definition.isInstanceOf[MatrixTerminalDefinition]) {
//TODO matrix spawn point; for now, just blindly bind to show work (and hope nothing breaks) //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 { FindLocalVehicle match {
case Some(vehicle) => case Some(vehicle) =>
sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) 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 { else {
if(obj.Faction != player.Faction && obj.HackedBy.isEmpty) { if(terminal.Faction != player.Faction && terminal.HackedBy.isEmpty) {
player.Slot(player.DrawnSlot).Equipment match { player.Slot(player.DrawnSlot).Equipment match {
case Some(tool: SimpleItem) => case Some(tool: SimpleItem) =>
if (tool.Definition == GlobalDefinitions.remote_electronics_kit) { if (tool.Definition == GlobalDefinitions.remote_electronics_kit) {
progressBarValue = Some(-GetPlayerHackSpeed()) val hackSpeed = GetPlayerHackSpeed(terminal)
self ! WorldSessionActor.HackingProgress(1, player, obj, tool.GUID, GetPlayerHackSpeed(), FinishHacking(obj, 3212836864L))
log.info("Hacking a 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 _ => ; 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 // If hacked only allow access to the faction that hacked it
// Otherwise allow the faction that owns the terminal to use 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)) 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 { ask(target.Actor, CommonMessages.Hack(player))(1 second).mapTo[Boolean].onComplete {
case Success(_) => case Success(_) =>
localService ! LocalServiceMessage(continent.Id, LocalAction.TriggerSound(player.GUID, target.HackSound, player.Position, 30, 0.49803925f)) 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)) target match {
case scala.util.Failure(_) => case term : CaptureTerminal =>
log.warn(s"Hack message failed on target guid: ${target.GUID}") 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 * @param building the building object
*/ */
def initFacility(continentNumber : Int, buildingNumber : Int, building : Building) : Unit = { def initFacility(continentNumber : Int, buildingNumber : Int, building : Building) : Unit = {
var ntuLevel = 0 building.Actor ! Building.SendMapUpdate(all_clients = false)
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
)
)
sendResponse(DensityLevelUpdateMessage(continentNumber, buildingNumber, List(0,0, 0,0, 0,0, 0,0))) 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 { amenity.Definition match {
case GlobalDefinitions.resource_silo => case GlobalDefinitions.resource_silo =>
// Synchronise warning light & silo capacity // Synchronise warning light & silo capacity
var silo = amenity.asInstanceOf[ResourceSilo] val silo = amenity.asInstanceOf[ResourceSilo]
sendResponse(PlanetsideAttributeMessage(amenityId, 45, silo.CapacitorDisplay)) sendResponse(PlanetsideAttributeMessage(amenityId, 45, silo.CapacitorDisplay))
sendResponse(PlanetsideAttributeMessage(amenityId, 47, if(silo.LowNtuWarningOn) 1 else 0)) sendResponse(PlanetsideAttributeMessage(amenityId, 47, if(silo.LowNtuWarningOn) 1 else 0))
if(silo.ChargeLevel == 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. //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)) //sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(silo.Owner.asInstanceOf[Building].ModelId), 48, 1))
} }
case _ => ; 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)) sendResponse(RawPacket(pkt))
} }
def GetPlayerHackSpeed(): Float = { def GetPlayerHackSpeed(obj: PlanetSideServerObject with Hackable): Float = {
if(player.Certifications.contains(CertificationType.ExpertHacking) || player.Certifications.contains(CertificationType.ElectronicsExpert)) { val playerHackLevel = GetPlayerHackLevel()
10.64f val timeToHack = obj.HackDuration(playerHackLevel)
} else if(player.Certifications.contains(CertificationType.AdvancedHacking)) {
5.32f if(timeToHack == 0) {
} else { 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")
2.66f 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)) { if(player.Certifications.contains(CertificationType.ExpertHacking) || player.Certifications.contains(CertificationType.ElectronicsExpert)) {
PlayerHackData(3, 10.64f) 3
} else if(player.Certifications.contains(CertificationType.AdvancedHacking)) { } else if(player.Certifications.contains(CertificationType.AdvancedHacking)) {
PlayerHackData(2, 7.98f) 2
} else if (player.Certifications.contains(CertificationType.Hacking)) { } else if (player.Certifications.contains(CertificationType.Hacking)) {
PlayerHackData(1, 5.32f) 1
} else { } else {
PlayerHackData(0, 2.66f) 0
} }
} }
case class PlayerHackData(hackLevel: Int, hackSpeed: Float)
} }
object WorldSessionActor { 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. * 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`, * 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. * 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 tplayer the player
* @param target the object being hacked * @param target the object being hacked
* @param tool_guid the REK * @param tool_guid the REK